|
本帖最后由 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 (请求头里面的)
通过搜索参数名,可以找到如图所示的地方
通过跟踪这个函数,最终可以得知,这是在wasm文件里面加密出来的。
第二步(找wasm文件加载,初始的地方)
既然知道是wasm加密,所以我们现在要找wasm文件加载,初始的地方。
搜索关键字:WebAssembly.instantiate。会找到2个结果,第一个明显不是,因为没有加载wasm文件的操作。所以是第二个结果,如图所示
因为没有混淆,所以可以很清楚的看到,通过fetch方法加载了wasm文件,然后调用了WebAssembly.instantiate方法进行初始化操作。
我们先把这段js代码复制到PyCharm上。如图所示
箭头1所指的地方,是wasm文件的网络路径。
箭头2所指的地方,加了一句调用加密函数,并且把结果输出的一句代码。至于为什么window改成global,那是因为在node环境里面[size=15.0667px]global是一个全局对象,类似于浏览器里面的winow对象
[size=15.0667px]
通过阅读代码,发现,缺少了GO这个函数,所以我们要把这东西补齐。
通过断点或者搜索的方式可以在这个wasm-exec.js 文件里面找到GO的定义位置,如图所示
我们把这个JS文件全部复制到PyCharm上去,并且执行一下。如果所示
(这里我把代码折叠了一下,方便查看)运行结果报错
usage: go_js_wasm_exec [wasm binary] [arguments]
第三步(过node环境检测)
我们是调试工具里面搜索一下这段错误提示,看看是什么原因,如图所示,找到报错的地方
通过阅读代码,可以发现。这里是检测了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);
});
}删除这段代码之后,执行一下发现又报错,如图所示:
[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的头部,有大量的环境判断,如图所示通过跟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(); },
}
}
改完之后,执行一下。发现又报错,如图所示[size=15.0667px]ReferenceError: crypto is not defined [size=15.0667px]crypto 未定义,根据错误行数,我们找到报错的地方[size=15.0667px]
我们发现这里调用了crypto对象的getRandomValues方法,这个其实就是一个取随机数据的方法,因为只调用了这一个方法,所以我们这里就不定义crypto对象了,直接定义一个getRandomValues函数function getRandomValues(array) { for (let i = 0; i < array.length; i++) {
array = Math.random();
}
return array;
}改成函数调用。然后继续执行。发现又又又报错,如图所示依旧是wasm文件内部报错,说明环境还是不对。我们在文件里面搜索一下这个错误关键字。最终我们发现了这里有一个异常捕获的模块,但是并没有抛出异常,所以我们这个异常捕获模块删了,然错误暴露出来删了异常捕获之后,执行。不出所料,还是报错。但是这次,不是wasm文件内部报错,而且外部JS文件报错,如图所示[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);
}
修改好之后,继续执行,这次没有报错了。正常得到结果,如图所示
最终提交测试,参数正确。
至此,分析完成!如有不对的地方,还各位请指出来本教程,仅供学习参考,请勿非法使用。
|
本帖子中包含更多资源
您需要 登录 才可以下载或查看,没有帐号?注册
x
|