骑麦兜看落日

[Binary]HeapExploit

字数统计: 3.5k阅读时长: 16 min
2018/09/18 Share

Off-By-One

Off-By-One指单字节溢出

产生原因

通常有两种情况容易存在Off-By-One

1
2
使用循环操作对临界值检查不严
使用字符串操作对边界检查不严

利用

1
2
Off-By-One覆盖低字节
Null Byte Off-By-One

off-by-one覆盖低字节

  • 扩展被释放块

  • 拓展已分配块

  • 收缩被释放块


Unlink

利用条件

  • 可以溢出
  • 知道一个全局指针

通过堆管理机制的unlink操作达成任意写的效果

首先看unlink源码

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
/* Take a chunk off a bin list */
#define unlink(AV, P, BK, FD) { \
if (__builtin_expect (chunksize(P) != prev_size (next_chunk(P)), 0)) \
malloc_printerr ("corrupted size vs. prev_size"); \
FD = P->fd; \
BK = P->bk; \
if (__builtin_expect (FD->bk != P || BK->fd != P, 0)) \
malloc_printerr ("corrupted double-linked list"); \
else { \
FD->bk = BK; \
BK->fd = FD; \
if (!in_smallbin_range (chunksize_nomask (P)) \
&& __builtin_expect (P->fd_nextsize != NULL, 0)) { \
if (__builtin_expect (P->fd_nextsize->bk_nextsize != P, 0) \
|| __builtin_expect (P->bk_nextsize->fd_nextsize != P, 0)) \
malloc_printerr ("corrupted double-linked list (not small)"); \
if (FD->fd_nextsize == NULL) { \
if (P->fd_nextsize == P) \
FD->fd_nextsize = FD->bk_nextsize = FD; \
else { \
FD->fd_nextsize = P->fd_nextsize; \
FD->bk_nextsize = P->bk_nextsize; \
P->fd_nextsize->bk_nextsize = FD; \
P->bk_nextsize->fd_nextsize = FD; \
} \
} else { \
P->fd_nextsize->bk_nextsize = P->bk_nextsize; \
P->bk_nextsize->fd_nextsize = P->fd_nextsize; \
} \
} \
} \
}

unlink用于将一个空闲块从一个双向链表中拿出来

其存在的检查

1
2
3
chunksize(P) != prev_size (next_chunk(P)
FD->bk != P || BK->fd != P
P->fd_nextsize->bk_nextsize != P || P->bk_nextsize->fd_nextsize != P # Large bins check

第一项检查容易绕过,由于堆的机制存在空间复用,当inuse位为1时prev_size域可以被前一个chunk利用

第二项检查等同于

1
2
fakeFD->bk == P <=> *(fakeFD + 0x18) == P
fakeBK->fd == P <=> *(fakeBK + 0x10) == P

如果我们可以得到保存堆地址的地址ptr,就可以绕过这个检查

1
2
3
4
5
fakeFD = ptr-0x18
fakeBK = ptr-0x10
=>
*(ptr-0x18+0x18) == P
*(ptr-0x10+0x10) == P

最终达到劫持堆的目的

1
2
3
4
5
FD->bk = BK;
BK->fd = FD;
=>
*(ptr-0x18+0x18) = ptr-0x10
*(ptr-0x10+0x10) = ptr-0x18

再进一步,重新覆盖ptr可以实现任意地址读写


Use After Free

内存块在被释放后,其对应的指针没有被设置为NULL,并被再次引用


Overlapping

若释放一个unsorted bin,并控制其size域覆盖到下一个chunk,通过申请一个fake_size内存可以达到堆块重叠的效果

若释放前unsorted bin,并控制其size域覆盖到下一个chunk,通过申请一个fake_size的内存可以达到堆块重叠的效果


Fastbin Attack

fastbin使用单链表来维护释放的堆块,并且由fastbin管理的chunk即使被释放,其next_chunk的prev_inuse位也不会被清空

利用条件

  • 存在堆溢出、use after free等能控制chunk内容的漏洞
  • 漏洞发生于fastbin类型的chunk中

Fastbin_dup

free一个fastbin时会进行check,检查main_arena是否等于当前chunk,以此防止double free

1
2
3
4
/* Check that the top of the bin is not the record we are going to
add (i.e., double free). */
if (__builtin_expect (old == p, 0))
malloc_printerr ("double free or corruption (fasttop)");

free后将当前chunkfd指针指向之前freefastbin chunk,main_arena指向当前freechunk

1
2
p->fd = old;
*fb = p;

因此若不连续的两次释放相同的chunk则不会触发这个check,从而造成了double free

通过double free可以将多个指针指向相同的地址,可用于修改堆中数据域的数据或用于实现任意地址堆块分配

Fastbin_dup_consolidate

另一种绕过double free的check的方式是利用malloc_consolidate

当申请的size属于largebin时,会触发malloc_consolidate,malloc_consolidate执行的操作是将fastbin中的chunk合并并放入unsorted bin中

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
/*
If this is a large request, consolidate fastbins before continuing.
While it might look excessive to kill all fastbins before
even seeing if there is space available, this avoids
fragmentation problems normally associated with fastbins.
Also, in practice, programs tend to have runs of either small or
large requests, but less often mixtures, so consolidation is not
invoked all that often in most programs. And the programs that
it is called frequently in otherwise tend to fragment.
*/

else
{
idx = largebin_index (nb);
if (atomic_load_relaxed (&av->have_fastchunks))
malloc_consolidate (av);
}

由于申请的是largebin,合并操作完成后将unsorted bin中的chunk加入到fastbinlargebin

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
while ((victim = unsorted_chunks (av)->bk) != unsorted_chunks (av))
{
if (in_smallbin_range (nb) &&
bck == unsorted_chunks (av) &&
victim == av->last_remainder &&
(unsigned long) (size) > (unsigned long) (nb + MINSIZE))
{
/* 如果申请的chunk是smallbin并且匹配last_remainder则直接分配并返回 */
}
if (size == nb)
{
/* 如果申请的chunk的size与当前chunk的size相同则直接返回 */
}
/* place chunk in bin */
if (in_smallbin_range (size))
{
/* 如果当前chunk属于smallbin则添加到smallbin中 */
}
else
{
/* 如果当前chunk属于largebin则添加到largebin中 */
}
}

因此当我们free一个fastbin并申请一个largebin后,double free该chunk不再触发check,因为这个chunk已经被加入到smallbin中,若我们再申请两个相同大小的chunk将获得两个指向同一chunk的指针

House of Spirit

在指定位置伪造fastbin chunk并free,从而达到分配指定地址的chunk的目的

fake_chunk需要满足一定的条件

1
2
3
4
5
fake chunk 的 ISMMAP 位不能为 1,因为 free 时,如果是 mmap 的 chunk,会单独处理。
fake chunk 地址需要对齐, MALLOC_ALIGN_MASK
fake chunk 的 size 大小需要满足对应的 fastbin 的需求,同时也得对齐。
fake chunk 的 next chunk 的大小不能小于 2 * SIZE_SZ,同时也不能大于av->system_mem 。
fake chunk 对应的 fastbin 链表头部不能是该 fake chunk,即不能构成 double free 的情况。

通过在free之前劫持要free的指针指向fake_chunk使得我们可以控制这一区域

Arbitrary Alloca

通过劫持fastbin链表中chunk的fd指针,把fd指针指向我们想要分配的区域从而控制该段内存

fd指向的区域需要存在合法的size域

Alloc to Stack

劫持fd指针指向stack,从而实现在栈上分配fastbin chunk

Alloc to _malloc_hook

_malloc_hook附近的区域存在合法的size域0x7F,因此可以通过劫持fd指向该区域从而覆盖_malloc_hook


Unsorted Bin Attack

Unsorted Bin Attack通过控制Unstored Bin Chunk的bk指针,修改任意地址值为main_arena

Unsorted Bin的管理

产生

  • 当一个较大的chunk被分割成两半后,如果剩下的部分大于MINSIZE,就会被放到unsorted bin中
  • 释放一个不属于fastbin的chunk,并且该chunk不和top chunk紧邻时,该chunk会被首先放到unsorted bin中
  • 当进行malloc_consolidate时,可能会把合并后的chunk放到unsorted bin中,

使用

unsorted bin采用FIFO遍历顺序,插入时插入到unsorted bin的头部.取出时从链表尾部获取

利用

条件

可以控制unsorted binbk指针

原理

首先释放一个unsorted bin为p,若unsorted bin为空,则其fdbk均指向unsorted bin头部,此时修改其bk值为fake_addr-0x10,则在重新申请一个smallbin时,若smallbin中不符合要求则进入unsorted bin

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
for (;; )
{
int iters = 0;
while ((victim = unsorted_chunks (av)->bk) != unsorted_chunks (av))
{
bck = victim->bk;
if (in_smallbin_range (nb) &&
bck == unsorted_chunks (av) &&
victim == av->last_remainder &&
(unsigned long) (size) > (unsigned long) (nb + MINSIZE))
{
/* 如果申请的chunk是smallbin并且匹配last_remainder的size则直接分配并返回 */
}

/* remove from unsorted list */
unsorted_chunks (av)->bk = bck;
bck->fd = unsorted_chunks (av);

首先查找链表尾victim = unsorted_chunks(av)-bk = p

查找链表尾的下一项bck = victim->bk = fake_addr-0x10,这里是为将victim取出做准备

连接新的链表尾unsorted_chunks(av)->bk = bck = fake_addr-0x10

任意地址修改 bck->fd = unsorted_chunks(av)


House of Einherjar

条件

存在一个off-by-one

原理

1
2
3
4
5
6
7
/* consolidate backward */
if (!prev_inuse(p)) {
prevsize = prev_size (p);
size += prevsize;
p = chunk_at_offset(p, -((long) prevsize));
unlink(av, p, bck, fwd);
}

当我们控制一个堆块的prev_sizePREV_INUSE后,便可以利用后向合并操作获得一个指定位置的堆块p=chunk_at_offset(p,-((long)prevsize))

利用

堆布局大概如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
+-----------------+-----------------+
| prev_size1 | size1 |
+-----------------+-----------------+
| fd | bk |
+-----------------+-----------------+
| ... | ... |
+-----------------+-----------------+
| prev_size2 | size2 |
+-----------------+-----------------+
| ... | ... |
+-----------------+-----------------+
| prev_size3 | size3 |
+-----------------+-----------------+
| ... | ... |
+-----------------+-----------------+

为了绕过unlink检查,通常可以先释放一个unsorted bin

然后控制第三个prev_sizePREV_INUSE,prev_size3=size1+size2


House of Lore

通过控制smallbinfd实现分配任意地址

利用条件

可以控制chunk的bk指针

原理

首先释放一个unsorted bin,若unsorted bin为空,则其fdbk均指向unsorted bin头部,此时修改其bk值为fake_addr-0x10,则在重新申请一个smallbin时,若smallbin中不符合要求则进入unsorted bin

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
/*
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))
{
idx = smallbin_index (nb);
bin = bin_at (av, idx);

if ((victim = last (bin)) != bin)
{
bck = victim->bk;
if (__glibc_unlikely (bck->fd != victim))
malloc_printerr ("malloc(): smallbin double linked list corrupted");
set_inuse_bit_at_offset (victim, nb);
bin->bk = bck;
bck->fd = bin;

if (av != &main_arena)
set_non_main_arena (victim);
check_malloced_chunk (av, victim, nb);

void *p = chunk2mem (victim);
alloc_perturb (p, bytes);
return p;
}
}

首先获得文件头部bin = bin_at (av, idx);

查找链表文件尾victim = last (bin) = bin->bk

查找链表尾的下一项bck = victim->bk = fake_addr-0x10,这里是为将victim取出做准备

连接新的链表尾bin->bk = bck = fake_addr-0x10

任意地址修改bck->fd = bin


House of Forse

通过控制top chunksize域为一个特别大的整数,达到任意地址分配的目的

条件

可以控制top chunksize域为一个很大整数

原理

改写top chunksize域为一个特别大的整数,可以确保申请一个特别大的内存时使用top chunk而不是mmap分配

计算好需要申请的size,即size=target_addr-top_addr-sizeof(head),使得top chunk的地址落入.bss段或GOT

再次申请内存就可以控制区域内的值


House of Rabbit

条件

  • 可以修改fastbinfd指针或size
  • 可以触发malloc consolidate

利用

通过修改size域

释放两个fastbin,修改第一个size使chunk1的size刚好越过chunk2,再通过malloc consolidate将其放入smallbin

通过修改fd域

申请两个chunk,并在两个chunk中伪造三个新的chunk,将第一个chunk的fd指向第一个fake chunk,释放chunk

申请一个large chunk触发malloc consolidate


House of Orange

Overwrite TopChunk

首先通过堆溢出伪造top chunksize域,size域需要满足的条件

1
2
3
4
5
6
7
assert ((old_top == initial_top (av) && old_size == 0) ||
((unsigned long) (old_size) >= MINSIZE &&
prev_inuse (old_top) &&
((unsigned long) old_end & (pagesize - 1)) == 0));

/* Precondition: not enough current space to satisfy nb request */
assert ((unsigned long) (old_size) < (unsigned long) (nb + MINSIZE));

然后申请一块大于fake size,并且小于mp_.mmap_threshold的内存,这样就会通过brk扩展创建新的top chunk

1
2
3
4
5
if (av == NULL
|| ((unsigned long) (nb) >= (unsigned long) (mp_.mmap_threshold)
&& (mp_.n_mmaps < mp_.n_mmaps_max)))
{
}

原top chunk通过_int_free释放为unsorted bin,获得libc地址

1
2
3
4
5
6
7
8
9
10
if (old_size >= MINSIZE)
{
set_head (chunk_at_offset (old_top, old_size), (2 * SIZE_SZ) | PREV_INUSE);
set_foot (chunk_at_offset (old_top, old_size), (2 * SIZE_SZ));
set_head (old_top, old_size | PREV_INUSE | NON_MAIN_ARENA);
_int_free (av, old_top, 1);
}
else
{
}

Information Leak

再次申请一个largebin,unsorted bin被放入largebins中,bk_nextsizefd_nextsize指向自身,同时将该largebin取出分割一部分,剩余部分放入unsorted bin

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
/* place chunk in bin */

if (in_smallbin_range (size))
{
}
else
{
victim_index = largebin_index (size);
bck = bin_at (av, victim_index);
fwd = bck->fd;

/* maintain large bins in sorted order */
if (fwd != bck)
{
}
else
victim->fd_nextsize = victim->bk_nextsize = victim;
}

mark_bin (av, victim_index);
victim->bk = bck;
victim->fd = fwd;
fwd->bk = victim;
bck->fd = victim;

此时堆中保存了libc地址和heap的地址,首先leak出libc地址,然后覆盖一下再次leak出heap地址

Unsorted bin Attack

在上一步中,申请一个top chunk后,剩余的部分作为unsorted bin chunk被分割,因此可以通过unsorted bin attack将IO_list_all指向main_arena+88

但是由于main_arena+88的内容无法直接被我们利用,所以需要利用到_IO_FILE结构的_chain,将其指向堆上,而_chain正好位于smallbin[4]的位置,因此我们可以将unsorted bin chunksize修改为0x61

这两步操作均在一次malloc中实现

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
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
static void *
_int_malloc (mstate av, size_t bytes)
{
if (in_smallbin_range (nb))
{
idx = smallbin_index (nb);
bin = bin_at (av, idx);

if ((victim = last (bin)) != bin)
{
}
}
for (;; )
{
int iters = 0;
while ((victim = unsorted_chunks (av)->bk) != unsorted_chunks (av))
{
bck = victim->bk;
if (__builtin_expect (victim->size <= 2 * SIZE_SZ, 0)
|| __builtin_expect (victim->size > av->system_mem, 0))
malloc_printerr (check_action, "malloc(): memory corruption",
chunk2mem (victim), av);
size = chunksize (victim);

/*
If a small request, try to use last remainder if it is the
only chunk in unsorted bin. This helps promote locality for
runs of consecutive small requests. This is the only
exception to best-fit, and applies only when there is
no exact fit for a small chunk.

*/

if (in_smallbin_range (nb) &&
bck == unsorted_chunks (av) &&
victim == av->last_remainder &&
(unsigned long) (size) > (unsigned long) (nb + MINSIZE))
{
}

/* remove from unsorted list */
unsorted_chunks (av)->bk = bck;
bck->fd = unsorted_chunks (av);

/* Take now instead of binning if exact fit */

if (size == nb)
{
}

/* place chunk in bin */

if (in_smallbin_range (size))
{
victim_index = smallbin_index (size);
bck = bin_at (av, victim_index);
fwd = bck->fd;
}
else
{
}

mark_bin (av, victim_index);
victim->bk = bck;
victim->fd = fwd;
fwd->bk = victim;
bck->fd = victim;
}

在执行malloc时,由于申请的size无法被直接分配,所以将当前unsorted bin chunk取出

1
2
unsorted_chunks (av)->bk = bck;
bck->fd = unsorted_chunks (av);

bck->fd指向_IO_list_all,被覆盖为main_arena

unsorted bin chunk被修改为0x61,取出后放入smallbin[4]中,

1
2
3
4
5
mark_bin (av, victim_index);
victim->bk = bck;
victim->fd = fwd;
fwd->bk = victim;
bck->fd = victim;

Fsop

接下来是Fsop攻击,由于unsorted bin被破坏,会调用malloc_printerr进行报错

1
2
3
4
if (__builtin_expect (victim->size <= 2 * SIZE_SZ, 0)
|| __builtin_expect (victim->size > av->system_mem, 0))
malloc_printerr (check_action, "malloc(): memory corruption",
chunk2mem (victim), av);

其调用为malloc_printerr->__libc_message->__GI_abort()->_IO_flush_all_lockp

_IO_flush_all_lockp会调用_IO_OVERFLOW

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
  while (fp != NULL)
{
run_fp = fp;
if (do_lock)
_IO_flockfile (fp);

if (((fp->_mode <= 0 && fp->_IO_write_ptr > fp->_IO_write_base)
#if defined _LIBC || defined _GLIBCPP_USE_WCHAR_T
|| (_IO_vtable_offset (fp) == 0
&& fp->_mode > 0 && (fp->_wide_data->_IO_write_ptr
> fp->_wide_data->_IO_write_base))
#endif
)
&& _IO_OVERFLOW (fp, EOF) == EOF)
result = EOF;

if (last_stamp != _IO_list_all_stamp)
{
}
else
fp = fp->_chain;
}

因此我们只需要伪造vtable中的_IO_OVERFLOW即可


House of Roman

条件

  • use after free
  • 可以创建任意大小的chunk

利用

将FD指向malloc_hook

首先构造堆栈,布局如下

1
2
3
4
5
6
7
8
9
10
11
12
13
+-----------------+-----------------+
| chunk1 | 0x20 |
+-----------------+-----------------+
| ... | ... |
+-----------------+-----------------+
| chunk2 | 0xd1 |
+-----------------+-----------------+
| ... | ... |
+-----------------+-----------------+
| chunk3 | 0x71 |
+-----------------+-----------------+
| ... | ... |
+-----------------+-----------------+

在chunk2中构造fake chunk

free掉chunk2获得一个unsorted bin

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
+-----------------+-----------------+
| chunk1 | 0x20 |
+-----------------+-----------------+
| ... | ... |
+-----------------+-----------------+
| chunk2 | 0xd1 |
+-----------------+-----------------+
| main_arena+88 | main_arena+88 |
+-----------------+-----------------+
| ... | ... |
+-----------------+-----------------+
| fake_chunk | 0x61 |
+-----------------+-----------------+
| ... | ... |
+-----------------+-----------------+
| chunk3 | 0x71 |
+-----------------+-----------------+
| ... | ... |
+-----------------+-----------------+

修正 0x71 的 Freelist

利用off-by-one将chunk2的size改为0x71

修改chunk2->fd为malloc_hook-0x23

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
+-----------------+-----------------+
| chunk1 | 0x20 |
+-----------------+-----------------+
| ... | ... |
+-----------------+-----------------+
| chunk1 | 0x71 |
+-----------------+-----------------+
| malloc_hook-0x23| main_arena+88 |
+-----------------+-----------------+
| ... | ... |
+-----------------+-----------------+
| fake_chunk | 0x61 |
+-----------------+-----------------+
| ... | ... |
+-----------------+-----------------+
| chunk2 | 0x71 |
+-----------------+-----------------+
| chunk1_addr | ... |
+-----------------+-----------------+
| ... | ... |
+-----------------+-----------------+

释放chunk1和chunk3

将chunk3的fd指向chunk2

此时的bins布局为

1
2
3
4
5
6
7
8
9
10
fastbins
0x20: 0x0
0x30: 0x0
0x40: 0x0
0x50: 0x0
0x60: 0x0
0x70: chunk3 —▸ chunk2 —▸ malloc_hook-0x23 ◂—
0x80: 0x0
unsortedbin
all: malloc_hook-0x23 ◂—

重新申请三个0x70size的chunk,获得malloc_hook所在chunk

释放掉申请的第一个chunk,将其fd值指向0

往 malloc_hook 写入 one gadget

由于此时unsorted bin中海存在有最开始释放的chunk2,可以通过unsorted bin attack使得malloc_hook的值为main_arena+0x88

修改malloc_hook的低三个字节,使得malloc_hookone_gadget

通过double free触发malloc_printerr,进而触发malloc_hook


相关资料

CATALOG
  1. 1. Off-By-One
    1. 1.1. 产生原因
    2. 1.2. 利用
      1. 1.2.1. off-by-one覆盖低字节
  2. 2. Unlink
    1. 2.1. 利用条件
  3. 3. Use After Free
  4. 4. Overlapping
  5. 5. Fastbin Attack
    1. 5.1. 利用条件
    2. 5.2. Fastbin_dup
    3. 5.3. Fastbin_dup_consolidate
    4. 5.4. House of Spirit
    5. 5.5. Arbitrary Alloca
      1. 5.5.1. Alloc to Stack
      2. 5.5.2. Alloc to _malloc_hook
  6. 6. Unsorted Bin Attack
    1. 6.1. Unsorted Bin的管理
      1. 6.1.1. 产生
      2. 6.1.2. 使用
    2. 6.2. 利用
      1. 6.2.1. 条件
      2. 6.2.2. 原理
  7. 7. House of Einherjar
    1. 7.1. 条件
    2. 7.2. 原理
    3. 7.3. 利用
  8. 8. House of Lore
    1. 8.1. 利用条件
    2. 8.2. 原理
  9. 9. House of Forse
    1. 9.1. 条件
    2. 9.2. 原理
  10. 10. House of Rabbit
    1. 10.1. 条件
    2. 10.2. 利用
      1. 10.2.1. 通过修改size域
      2. 10.2.2. 通过修改fd域
  11. 11. House of Orange
    1. 11.0.1. Overwrite TopChunk
    2. 11.0.2. Information Leak
    3. 11.0.3. Unsorted bin Attack
    4. 11.0.4. Fsop
  • 12. House of Roman
    1. 12.1. 条件
    2. 12.2. 利用
      1. 12.2.1. 将FD指向malloc_hook
      2. 12.2.2. 修正 0x71 的 Freelist
      3. 12.2.3. 往 malloc_hook 写入 one gadget
  • 13. 相关资料