开启辅助访问 切换到宽版

精易论坛

 找回密码
 注册

QQ登录

只需一步,快速开始

用微信号发送消息登录论坛

新人指南 邀请好友注册 - 我关注人的新帖 教你赚取精币 - 每日签到


求职/招聘- 论坛接单- 开发者大厅

论坛版规 总版规 - 建议/投诉 - 应聘版主 - 精华帖总集 积分说明 - 禁言标准 - 有奖举报

查看: 3880|回复: 10
收起左侧

[技术文章] 某网页的wasm逆向分析

[复制链接]
结帖率:100% (10/10)
发表于 2024-5-8 20:26:03 | 显示全部楼层 |阅读模式   湖南省永州市
本帖最后由 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
发表于 2024-8-22 22:20:14 | 显示全部楼层   广西壮族自治区玉林市
谢谢大佬,学习了
回复 支持 反对

使用道具 举报

结帖率:40% (2/5)
发表于 2024-8-15 09:00:53 | 显示全部楼层   江西省南昌市
看看大佬学习
回复 支持 反对

使用道具 举报

结帖率:97% (37/38)

签到天数: 13 天

发表于 2024-7-24 17:28:31 | 显示全部楼层   广西壮族自治区梧州市
   厉害
回复 支持 反对

使用道具 举报

签到天数: 12 天

发表于 2024-5-29 18:51:29 | 显示全部楼层   湖北省武汉市
66666666666666
回复 支持 反对

使用道具 举报

签到天数: 12 天

发表于 2024-5-28 10:32:54 | 显示全部楼层   湖北省武汉市
拿个精币
回复 支持 反对

使用道具 举报

签到天数: 1 天

发表于 2024-5-9 02:03:42 | 显示全部楼层   湖南省岳阳市
厉害了我的哥
回复 支持 反对

使用道具 举报

签到天数: 2 天

发表于 2024-5-8 23:04:56 | 显示全部楼层   黑龙江省哈尔滨市
厉害了我的哥
回复 支持 反对

使用道具 举报

签到天数: 21 天

发表于 2024-5-8 21:00:57 | 显示全部楼层   浙江省杭州市
学习了,wasm确实比较头疼
回复 支持 反对

使用道具 举报

结帖率:92% (12/13)

签到天数: 10 天

发表于 2024-5-8 20:34:46 | 显示全部楼层   湖北省咸宁市
感谢分享
回复 支持 反对

使用道具 举报

您需要登录后才可以回帖 登录 | 注册

本版积分规则 致发广告者

发布主题 收藏帖子 返回列表

sitemap| 易语言源码| 易语言教程| 易语言论坛| 易语言模块| 手机版| 广告投放| 精易论坛
拒绝任何人以任何形式在本论坛发表与中华人民共和国法律相抵触的言论,本站内容均为会员发表,并不代表精易立场!
论坛帖子内容仅用于技术交流学习和研究的目的,严禁用于非法目的,否则造成一切后果自负!如帖子内容侵害到你的权益,请联系我们!
防范网络诈骗,远离网络犯罪 违法和不良信息举报电话0663-3422125,QQ: 793400750,邮箱:wp@125.la
Powered by Discuz! X3.4 揭阳市揭东区精易科技有限公司 ( 粤ICP备12094385号-1) 粤公网安备 44522102000125 增值电信业务经营许可证 粤B2-20192173

快速回复 返回顶部 返回列表