关于 DreamCat

主题名称:DreamCat | 版本:3.0.240224

主题开发:HanFengA7 | CornWorld

Designed by HanFengA7 Power by Typecho

Copyright © 2015-2024 by LychApe All rights reserved!

menu
refresh

逆向一个微信小程序

作者: ciaoℒy

时间:

新版小程序包样品 | 旧版小程序包样品

使用工具wxappUnpacker对该小程序解包时会抛出错误: SyntaxError: Unexpected token '}'

image-20230309165938992

异常发生在wuWxml.js, 这个文件的功能是将js文件逆向成wxml文件. 使用命令node wuWxapkg.js -o xxx.wxpkg只解包不反编译, 可跳过执行该文件以避免发生该错误.

发生错误的原因应该是微信小程序的编译方法变化了, 使得原来的解包工具的反编译程序无法正常工作了. 接下来就对照wxappUnpacker的源码和一个可以正常逆向解包的小程序, 尝试修改wuWxml.js的代码以使其能够顺利反编译新的小程序包.

TL;DR

  1. 生成z数组的gz$gwx函数的命名变了。以前是gz$gwx1,现在是gz$gwx1_XC_1_0_1这样子
  2. 处理z数组的m函数的命名和组织形式变了。以前是所有的m函数都放在了一起、依次声明为m0, m1, m2这样子;现在是每一个Z数组后面都紧跟着一个m0函数。

简单修改一下 wuWxml.jswuRestoreZ.js 文件,即可成功反编译出相关的Page代码。


为了更好地阅读wxappUnpacker的源码和了解小程序的反编译原理, 可对照这篇文章: 将微信小程序(.wxapkg)解包及将包内内容还原为"编译"前的内容的"反编译"器-Android安全-看雪论坛-安全社区

处理Z数组

根据上述文章中所述, 微信将所有要动态计算的变量放在了一个由函数构造的z数组中. 对照一个可以正常解包的小程序可以发现, 两个版本在生成z数组时的函数基本上是不变的, 但是函数名发生了变化:

// 新版的小程序
function gz$gwx1_XC_5_1(){
if( __WXML_GLOBAL__.ops_cached.$gwx1_XC_5_1)return __WXML_GLOBAL__.ops_cached.$gwx1_XC_5_1
__WXML_GLOBAL__.ops_cached.$gwx1_XC_5_1=[];
(function(z){var a=11;function Z(ops){z.push(ops)}
Z([3,'list'])

// 旧版的小程序
function gz$gwx_15(){
if( __WXML_GLOBAL__.ops_cached.$gwx_15)return __WXML_GLOBAL__.ops_cached.$gwx_15
__WXML_GLOBAL__.ops_cached.$gwx_15=[];
(function(z){var a=11;function Z(ops){z.push(ops)}
Z([a,[3,'custom-class '],[[12],[[6],[[7],[3,'utils']],[3,'bem']],[[5],[[5],[1,'loading']],[[8],

修改wuRestoreZ.js以提取z数组:

  1. 找到函数catchZ, 修改其中匹配函数名的正则表达式为: /function gz\$gwx(\d*[\_XC]*[\d\_]*)\(\)\{\s*if\( __WXML_GLOBAL__\.ops_cached\.\$gwx\d*[\_\dXC]+\)/

  2. 找到函数catchZGroup, 修改其中提取函数编号的正则表达式为: /function gz\$gwx(\d*[\_XC]*[\d\_]+)/

image-20230309172342176

如图中第19/60行所示. (图中catchM0Group是我自己新增的, 原版程序里没有这个函数)

之后便可以正常的提取z数组了.

处理M函数

在提取z数组后, 小程序使用了一系列的m函数来处理这些z数组, 以实现动态的渲染. 文章中提到了一种将每种可能的 js 语句拆分成"指令"的分析方法. 相应的程序在文件wuWxml.jsanalyze函数中. 对照文章中列举的部分"指令"和代码, 可以看出新版小程序的编译方法与旧版几乎无差异. 因此analyze函数大概率是依然可以使用的.

继续分析wuWxml.js源码. 在getZ函数的回调中, 执行了以下程序以获取m系列函数:

        getZ(code, z => {
            const before = "\nvar nv_require=function(){var nnm=";
            code = code.slice(code.lastIndexOf(before) + before.length, code.lastIndexOf("if(path&&e_[path]){"));
            json = code.slice(0, code.indexOf("};") + 1);
            let endOfRequire = code.indexOf("()\r\n") + 4;
            if (endOfRequire == 4 - 1) endOfRequire = code.indexOf("()\n") + 3;
            code = code.slice(endOfRequire);
            let rD = {}, rE = {}, rF = {}, requireInfo = {}, x, vm = new VM({
                sandbox: {
                    d_: rD, e_: rE, f_: rF, _vmRev_(data) {
                        [x, requireInfo] = data;
                    }, nv_require(path) {
                        return () => path;
                    }
                }
            });
            let vmCode = code + "\n_vmRev_([x," + json + "])";
            vm.run(vmCode);

上述代码也是文章开头提到的抛出异常的代码. 对照旧版小程序, 在代码var nv_require=function(){var nnm=if(path&&e_[path]){之间是所有的m系列函数.

image-20230309174238265

此外旧版小程序声明了一个x数组, 用以保存所有的.wxml文件位置.

image-20230309174744850

但是在新的小程序中, m系列函数不再具有连贯的编号, 而且其位置也不再集中放置, x数组也不再具有保存所有.wxml的作用:

image-20230309175226351

m0函数会紧跟每一个生成Z数组的gw$gwx函数之后, x数组仅保存当前z数组对应的文件路径. 此外, 后面还会紧跟该页面的wxss样式代码

经过上述分析, 需要编写一个catchM0函数, 用以将所有的m0函数提取出来.

/**
 * 获取所有的m0函数
 * @param {String} code 代码
 * @param {Object} z z数组
 * @param {Function} cb 回调函数
 * @returns {name: {code: string}}
 */
function catchM0(code, z, cb) {
    let groupTest = code.match(/var x=\['.*\.wxml'];d_\[x\[0\]\]=\{\}/g);
    if (groupTest !== null) return catchM0Group(code, groupTest, z, cb);
}

function catchM0Group(code, groupPreStr, z, cb) {
    let m0Arr = {};
    let i = 0;
    for (let preStr of groupPreStr) {
        i++;
        let name = preStr.replace(/.*var x=\['(.*\.wxml)'\].*/g, "$1");
        let content = code.slice(code.indexOf(preStr));
        content = content.slice(0, content.indexOf("if(path&&e_[path]){"));
        let zFuncName = ""
        content.replace(/var z=gz\$gwx([_XC\d]+)\(\)/g, (match, p1, p2, p3, offset, string) => {
            zFuncName = p1;
        });
        // TODO: d_数组不知道是什么意思, 暂时不考虑了
        let rD = {}, rF = {}, rZ = z[zFuncName] || [], x = [];
        let vm = new VM({
            sandbox: {
                x: x, z: rZ, d_: rD, e_: m0Arr, f_: rF, debugInfo: []
            }
        });
        vm.run(content);
    }
    cb(m0Arr);
}

虽然x数组不再存储所有的.wxml文件清单, 上述函数的执行结果m0ArrKeys实际上就等价于x数组. 再之后将m0Arr数组/z数组/x数组交给analyze函数执行:

catchM0(code, z, m0 => {
    for (let name in m0) tryWxml(dir, name, m0[name].f.toString(), z, Object.keys(m0), [], [], moreInfo);
    cb({[name]: 4});
});

可以正确地反编译出wxml文件了.

image-20230309180523469

修改后的文件:

wuWxml.js | wuRestoreZ.js


#本文链接:https://blog.chaol.top/archives/71.html
#本文采用 CC BY-NC-SA 4.0 协议进行许可
#如无特别声明,该文章均为 ciaoℒy 原创,转载请遵循 署名-非商业性使用 4.0 国际(CC BY-NC 4.0)协议,即转载请注明文章来源。
#最后编辑时间为: 2023 年 03 月 09 日
none

create 添加新评论


account_circle
email
language
textsms


assessment 已有 6 条评论
  1. gq
    2023-06-02 16:50

    😄,感谢大牛,解决我多年来的困惑


  2. ZG
    2023-07-30 14:08

    大佬,wuWxss.js也同样有如上报错,应该如何解决呢


    1. 2023-09-11 20:58

      wxss文件就是变体的css文件, 应该不需要反编译, 直接复制出来改改就能用了吧


  3. 2023-07-30 21:49

    大佬wuWxss.js、wuLib.js也有报错,怎么解决呀


    1. 2023-09-11 21:02

      我也不清楚哈, 具体情况得具体分析.
      我贴一个仓库的地址, 本来这个仓库是我提了issue后作者重构了的开源项目, 现在好像半商业化运营了, 无意推广. 如介意请忽略.

      https://github.com/r3x5ur/unveilr
      https://u.openal.lat/#document


    2. 2023-09-11 21:06

      哦, 我补一个之前下载的unveilr的源码

      下载链接: https://cloud.chaol.top/s/Z6id
      密码: j19b0z




加我的QQ
加我的微博
加我的支付宝
加我的微信