精易论坛
标题:
某网页的wasm逆向分析
[打印本页]
作者:
nha30
时间:
2024-5-8 20:26
标题:
某网页的wasm逆向分析
本帖最后由 nha30 于 2024-5-8 20:26 编辑
关于wasm就不多介绍了,因为wsm逆向的资料比较少,所以今天分享一下wasm逆向。本教程,仅供学习参考,
请勿非法使用
。
wasm逆向大致有两种方法。
第一种:分析wasm文件,然后翻译成JS(优点:不依赖任何的环境,执行效率高。缺点:难度大)这种方法适用于标准的算法,比如AES,或者RSA之类的。因为这类算法有开源的库,只要分析出关键参数即可使用。
第二种,也就是今天介绍的这种。通过node.js来调用wasm文件。(优点:相对简单,缺点:需要依赖node环境)适用于魔改算法。
准备工作如下:
目标网页:aHR0cHM6Ly9kYy5tYXl0ZWsuY24vIy9Mb2dpbkJveA==
环境:node.js
工具:PyCharm(不怕麻烦的话,也可以用记事本),谷歌浏览器。
第一步(定位加密函数)
逆向参数名:
secc-fetch-hash-key (请求头里面的)
通过搜索参数名,可以找到如图所示的地方
[attach]1316321[/attach]
通过跟踪这个函数,最终可以得知,这是在wasm文件里面加密出来的。
第二步(找wasm文件加载,初始的地方)
既然知道是wasm加密,所以我们现在要找wasm文件加载,初始的地方。
搜索关键字:WebAssembly.instantiate。会找到2个结果,第一个明显不是,因为没有加载wasm文件的操作。所以是第二个结果,如图所示
[attach]1316323[/attach]
因为没有混淆,所以可以很清楚的看到,通过fetch方法加载了wasm文件,然后调用了WebAssembly.instantiate方法进行初始化操作。
我们先把这段js代码复制到PyCharm上。如图所示
[attach]1316327[/attach]
箭头1所指的地方,是wasm文件的网络路径。
箭头2所指的地方,加了一句调用加密函数,并且把结果输出的一句代码。至于为什么window改成
global,那是因为在node环境里面
[size=15.0667px]global是一个全局对象,类似于浏览器里面的winow对象
[size=15.0667px]
通过阅读代码,发现,缺少了GO这个函数,所以我们要把这东西补齐。
通过断点或者搜索的方式可以在这个wasm-exec.js 文件里面找到GO的定义位置,如图所示
[attach]1316328[/attach]
我们把这个JS文件全部复制到PyCharm上去,并且执行一下。如果所示
[attach]1316334[/attach]
(这里我把代码折叠了一下,方便查看)运行结果报错
usage: go_js_wasm_exec [wasm binary] [arguments]
第三步(过node环境检测)
我们是调试工具里面搜索一下这段错误提示,看看是什么原因,如图所示,找到报错的地方
[attach]1316336[/attach]
通过阅读代码,可以发现。这里是检测了node环境。浏览器环境不会执行到这个地方
所以我们可以直接把下面这段代码删了
if
(
typeof
module !==
"undefined"
&&
global
.
require
&&
global
.
require
.main === module &&
global
.
process
&&
global
.
process
.versions &&
!
global
.
process
.versions.electron
) {
if
(
process
.
argv
.
length
<
3
) {
console
.
error
(
"usage: go_js_wasm_exec [wasm binary] [arguments]"
)
;
process
.
exit
(
1
)
;
}
const
go =
new
Go()
;
go.
argv
=
process
.
argv
.
slice
(
2
)
;
go.
env
=
Object
.
assign
({
TMPDIR
:
require
(
"os"
).tmpdir() }
,
process
.
env
)
;
go.
exit
=
process
.
exit
;
WebAssembly.
instantiate
(
fs
.readFileSync(
process
.
argv
[
2
])
,
go.
importObject
).
then
((result) => {
process
.on(
"exit"
,
(code) => {
// Node.js exits if no event handler is pending
if
(code ===
0
&& !go.
exited
) {
// deadlock, make Go print error and stack traces
go.
_pendingEvent
= {
id
:
0
}
;
go.
_resume
()
;
}
})
;
return
go.
run
(result.
instance
)
;
}).
catch
((err) => {
console
.
error
(err)
;
process
.
exit
(
1
)
;
})
;
}删除这段代码之后,执行一下发现又报错,如图所示:[attach]1316337[/attach]
[size=15.0667px]syscall/js.Value.Call(0x0, 0x0, 0x33e81, 0x7, 0x42ae70, 0x1, 0x1, 0x0, 0x456030)
[size=15.0667px]这次是在wasm文件内部报错,具体什么错误。咱也不懂。不过没关系,我们分析一下,
首先 wasm文件里面的代码肯定是不会出错的,那么唯一的可能就是,外部的JS代码有问题。我们首先考虑环境问题,先找到wasm-exec.js这文件。看看里面是不是有其他的检测
通过阅读代码,我们发现在wasm-exec.js的头部,有大量的环境判断,如图所示[attach]1316344[/attach][attach]1316345[/attach][attach]1316345[/attach]通过跟node环境下的对比,我们发现有些东西是不一样的。通过下断,比对的方式,我们只需要保留在浏览器环境里面判断为true的代码即可,最终环境检测代码如下:
if
(!
global
.
fs
) {
let
outputBuf =
""
;
global
.
fs
= {
constants
: {
O_WRONLY
: -
1
,
O_RDWR
: -
1
,
O_CREAT
: -
1
,
O_TRUNC
: -
1
,
O_APPEND
: -
1
,
O_EXCL
: -
1
}
,
// unused
writeSync
(fd
,
buf) {
outputBuf +=
decoder
.
decode
(buf)
;
const
nl = outputBuf.
lastIndexOf
(
"
\n
"
)
;
if
(nl != -
1
) {
console
.
log
(outputBuf.
substr
(
0
,
nl))
;
outputBuf = outputBuf.
substr
(nl +
1
)
;
}
return
buf.
length
;
}
,
write
(fd
,
buf
,
offset
,
length
,
position
,
callback) {
if
(offset !==
0
|| length !== buf.
length
|| position !==
null
) {
callback(
enosys
())
;
return;
}
const
n =
this
.
writeSync
(fd
,
buf)
;
callback(
null,
n)
;
}
,
chmod
(path
,
mode
,
callback) { callback(
enosys
())
;
}
,
chown
(path
,
uid
,
gid
,
callback) { callback(
enosys
())
;
}
,
close
(fd
,
callback) { callback(
enosys
())
;
}
,
fchmod
(fd
,
mode
,
callback) { callback(
enosys
())
;
}
,
fchown
(fd
,
uid
,
gid
,
callback) { callback(
enosys
())
;
}
,
fstat
(fd
,
callback) { callback(
enosys
())
;
}
,
fsync
(fd
,
callback) { callback(
null
)
;
}
,
ftruncate
(fd
,
length
,
callback) { callback(
enosys
())
;
}
,
lchown
(path
,
uid
,
gid
,
callback) { callback(
enosys
())
;
}
,
link
(path
,
link
,
callback) { callback(
enosys
())
;
}
,
lstat
(path
,
callback) { callback(
enosys
())
;
}
,
mkdir
(path
,
perm
,
callback) { callback(
enosys
())
;
}
,
open
(path
,
flags
,
mode
,
callback) { callback(
enosys
())
;
}
,
read
(fd
,
buffer
,
offset
,
length
,
position
,
callback) { callback(
enosys
())
;
}
,
readdir
(path
,
callback) { callback(
enosys
())
;
}
,
readlink
(path
,
callback) { callback(
enosys
())
;
}
,
rename
(from
,
to
,
callback) { callback(
enosys
())
;
}
,
rmdir
(path
,
callback) { callback(
enosys
())
;
}
,
stat
(path
,
callback) { callback(
enosys
())
;
}
,
symlink
(path
,
link
,
callback) { callback(
enosys
())
;
}
,
truncate
(path
,
length
,
callback) { callback(
enosys
())
;
}
,
unlink
(path
,
callback) { callback(
enosys
())
;
}
,
utimes
(path
,
atime
,
mtime
,
callback) { callback(
enosys
())
;
}
,
}
;
}
if
(
true
) {//这里原本是检测!global.process 这个的。但是在node环境 !global.process 为false ,所以这里我判断为true
global
.
process
= {
getuid
() {
return
-
1
;
}
,
getgid
() {
return
-
1
;
}
,
geteuid
() {
return
-
1
;
}
,
getegid
() {
return
-
1
;
}
,
getgroups
() {
throw
enosys
()
;
}
,
pid
: -
1
,
ppid
: -
1
,
umask
() {
throw
enosys
()
;
}
,
cwd
() {
throw
enosys
()
;
}
,
chdir
() {
throw
enosys
()
;
}
,
}
}
改完之后,执行一下。发现又报错,如图所示[attach]1316350[/attach]
[size=15.0667px]ReferenceError: crypto is not defined
[size=15.0667px]crypto 未定义,根据错误行数,我们找到报错的地方
[size=15.0667px]
[attach]1316351[/attach]我们发现这里调用了crypto对象的getRandomValues方法,这个其实就是一个取随机数据的方法,因为只调用了这一个方法,所以我们这里就不定义crypto对象了,直接定义一个getRandomValues函数function
getRandomValues
(array) {
for
(
let
i =
0
;
i < array.
length
;
i++) {
array
=
Math
.
random
()
;
}
return
array
;
}改成函数调用。然后继续执行。发现又又又报错,如图所示[attach]1316352[/attach]依旧是wasm文件内部报错,说明环境还是不对。我们在文件里面搜索一下这个错误关键字。最终我们发现了这里有一个异常捕获的模块,但是并没有抛出异常,所以我们这个异常捕获模块删了,然错误暴露出来[attach]1316354[/attach]删了异常捕获之后,执行。不出所料,还是报错。但是这次,不是wasm文件内部报错,而且外部JS文件报错,如图所示[attach]1316355[/attach]
[size=15.0667px]TypeError: Reflect.get called on non-object
[size=15.0667px]
[size=15.0667px]我们分别在浏览器的调试工具跟PyCharm的这个位置
const result = Reflect.apply(m, v, args);
下一个断点
通过对比我们发现,浏览器环境 当 sp =
[size=15.0667px]4369632
result 的结果是:'f99c121b7cc54594bbedcba7199bfe09'当sp =
[size=15.0667px]4528736 的时候,
[size=15.0667px]
result 的结果是一个随机的
Uint8Array数组。
但是在node环境里面
当 sp =
[size=15.0667px]4369632 或
sp =
[size=15.0667px]4528736 会直接报错。具体原因我没去分析,我想大概率还是环境问题。
至于这个 ”
f99c121b7cc54594bbedcba7199bfe09“ 这窜字符是window.localStorage对象里面的_pn值。这一般都是第一次访问网页返回的
所以接下来,我们只需要判断一下sp的值,然后直接给result赋值即可,代码如下:
if
(sp==
4369632
){
const
result =
'f99c121b7cc54594bbedcba7199bfe09'
;
sp =
this
.
_inst
.
exports
.
getsp
() >>>
0
;
// see comment above
storeValue
(sp +
56
,
result)
;
this
.
mem
.
setUint8
(sp +
64
,
1
)
;
}
else if
(sp==
4528736
){
const
result =
getRandomValues
(
new
Uint8Array
(
42
))
;
sp =
this
.
_inst
.
exports
.
getsp
() >>>
0
;
// see comment above
storeValue
(sp +
56
,
result)
;
this
.
mem
.
setUint8
(sp +
64
,
1
)
;
}
else
{
const
v =
loadValue
(sp +
8
)
;
const
m = Reflect.
get
(v
,
loadString
(sp +
16
))
;
const
args =
loadSliceOfValues
(sp +
32
)
;
const
result = Reflect.
apply
(m
,
v
,
args)
;
sp =
this
.
_inst
.
exports
.
getsp
() >>>
0
;
// see comment above
storeValue
(sp +
56
,
result)
;
this
.
mem
.
setUint8
(sp +
64
,
1
)
;
}
修改好之后,继续执行,这次没有报错了。正常得到结果,如图所示
[attach]1316362[/attach]
最终提交测试,参数正确。
至此,分析完成!如有不对的地方,还各位请指出来
本教程,仅供学习参考,请勿非法使用。
作者:
fire9
时间:
2024-5-8 20:30
牛的 不明觉厉
作者:
瑶总要困告了
时间:
2024-5-8 20:34
感谢分享
作者:
black0216
时间:
2024-5-8 21:00
学习了,wasm确实比较头疼
作者:
都由我来赎
时间:
2024-5-8 23:04
厉害了我的哥
作者:
shuxi8272
时间:
2024-5-9 02:03
厉害了我的哥
作者:
Mouth
时间:
2024-5-28 10:32
拿个精币
作者:
Mouth
时间:
2024-5-29 18:51
66666666666666
作者:
疯牛狂颠
时间:
2024-7-24 17:28
厉害
作者:
冰之棱
时间:
2024-8-15 09:00
看看大佬学习
作者:
小金华
时间:
2024-8-22 22:20
谢谢大佬,学习了
欢迎光临 精易论坛 (https://125.confly.eu.org/)
Powered by Discuz! X3.4