ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

微信小程序反解析工具wxappUnpacker:从解密到还原代码

微信小程序反解析工具wxappUnpacker:从解密到还原代码 简介wxappUnpacker是一套面向微信小程序开发者和安全研究人员的反解析工具包。它能够解析微信小程序编译后的.wxapkg产物还原WXML标记语言、WXSS样式语言以及JavaScript业务逻辑便于进行调试、原理剖析和合规的逆向研究。压缩包共收录1842个文件大小约2.17MB主体为1446个JS脚本、115个JSON配置、68个TypeScript类型定义和84个Markdown文档同时附带若干cmd/ps1辅助命令目录结构一目了然Node.js环境下即可直接运行。借助wuWxml、wuWxapkg、wuRestoreZ等核心模块可以掌握小程序打包文件的组织方式与资源提取方法wuConfig与wuLib等基础模块则负责配置加载和公共函数帮助读者系统梳理从编译产物回到接近源码状态的完整流程。资源适合已有前端或小程序基础的中高级开发者用于代码还原、安全审计和内部机制学习。目前已有836人学习下载具备较高的参考与实战价值。1. wxappUnpacker 到底是什么一次说清它的用途和边界做微信小程序开发的迟早会遇到一个尴尬时刻手头只有设备里缓存下来的.wxapkg包没有源码却要搞明白某个功能是怎么实现的或者要对比线上版本和上个版本改了哪些东西。wxappUnpacker 就是围绕这个需求被反复捞出来用的微信小程序反解析工具包它能解密、拆包、还原小程序包里的 JS、WXML、WXSS 和 JSON把黑匣子式的产物变成一份能继续读、继续查、继续对比的工程目录。它适合前端、客户端和安全测试的同学配合模拟器和本地缓存就能跑通不用专门去问别人要源码。2. 先把原理讲透wxapkg 里装了什么反解析为什么能还原代码2.1 wxapkg 的包结构不是 ZIP而是一张“文件索引表”微信小程序发布时开发者工具会把 JS、JSON、WXML、WXSS 还有各色静态资源打成一个自定义二进制容器扩展名就叫.wxapkg。它不是一个标准 ZIP也不是简单的 JSON而是一套带文件索引的私有格式。拿到一个包之后第一步永远不是急着解密而是先看文件头确认这个包是明文包还是加密包属于哪个微信版本。# 拿到一个 wxapkg 样本后先看文件头再决定走哪条解析路径 file ./samples/target.wxapkg xxd -l 64 ./samples/target.wxapkgfile命令会通过魔数告诉你这个包大概是什么类型xxd -l 64可以打印前 64 字节的十六进制视图。常见明文 wxapkg 的头几个字节有固定魔数后面跟着的信息区里有应用配置再往后就是索引区和内容区。索引区逐条记录了文件名、文件路径、偏移和长度解析脚本只要把这个索引读对就能把包里的文件一个个抠出来。这里要提醒一下微信客户端版本不同包头的字段顺序和长度也不完全一样。有的版本前 4 字节是小端序版本号有的版本中间会夹一个信息长度字段所以解析脚本必须按版本走。这也是为什么老 fork 解不开新版包报错往往出在读文件头这一步。2.2 反解析的三层动作解密锁壳、拆包、还原代码wxappUnpacker 这类工具包做的事可以拆成三层第一层是解密。你从网络接口抓回来的包或者部分设备上直接拿到的缓存包可能是密文包头部是乱掉的直接解包只会得到空目录和报错。这一层工具包里有专门的解密脚本它输出一个新的明文 wxapkg 交给下一步处理。本机缓存里的包很多时候已经是明文不一定每次都走解密。第二层是拆包。拆包就是顺着索引表把包内的app-config.json、app-service.js、app-wxss.js、每个页面目录下的 JS 和 JSON以及各类图片资源导出来。这一步完成后你会得到一份“还没有可读性”的压缩目录。第三层是还原代码。发布时的小程序 JS 不是源码形态而是被压缩、混淆、模块化改造过的产物WXML 也可能被编译成模板函数。还原这层通常需要先做格式化再借助 AST 重写把模块路径、字符串编码、条件片段恢复出来。下面这段示例就是先用js-beautify把压缩 JS 展开方便后面人工阅读// 用 js-beautify 快速把压缩壳展开方便后面人工读 const beautify require(js-beautify).js; const fs require(fs); const src fs.readFileSync(app-service.js, utf8); fs.writeFileSync( app-service.beauty.js, beautify(src, { indent_size: 2 }) );逻辑说明这段脚本只做格式化不做混淆还原所以indent_size设成 2 保证缩进可读即可。真正还原混淆变量和字符串还需要工具包里的 esprima/uglify 相关模块配合不是一段代码能覆盖的。格式化后的文件是后续搜索接口、查看页面逻辑的基础。2.3 还原的边界为什么输出不完全是源码把 wxappUnpacker 的产出当成“源代码”是不准确的。它还原出来的是一份“接近源码的可读工程”但和原始工程有差异。差异来自微信小程序构建过程中的几个不可逆动作作用域被合并进一个大的运行时模块 require 路径被改写页面 WXML 可能被预编译成 render 函数CSS 类名也可能被压缩重排。所以你在解包产物里看到t、e、n这类变量名是正常的看到某个 WXML 是一个render: function()也不奇怪。工具包能做的是把这些东西尽量还原成结构清晰的代码而不是变魔术一样变回开发者的原稿。理解这一点后续排查问题就能少走很多弯路。3. 实操跑通从拉包、解密、解包到还原文件一条命令走完3.1 环境准备Node 版本和依赖安装wxappUnpacker 主流的发版形态是 Node.js 脚本仓库先确认本机有 Node 环境版本不要太老Linux、macOS、Windows 都能跑。进入仓库目录后先装依赖再跑自带 help确认当前 fork 的参数有没有变化。# 把工具包克隆到本地后进入目录 cd wxappUnpacker # 安装依赖核心一般包括 esprima、js-beautify、uglify 等 npm install # 查看当前版本支持的参数每个 fork 的命令可能会略有差异 node wuWxapkg.js -h参数说明npm install会根据仓库里的package.json拉齐解析、美化、AST 还原要用到的库。node wuWxapkg.js -h这一步非常关键因为 wxappUnpacker 在社区里被改过很多版有的 fork 用-o指定输出目录有的直接用第二个参数指定输出目录不先看 help 容易把命令跑错。3.2 素材获取从缓存目录拿包比抓包更稳反解析的第一步是拿到 wxapkg。最常见的路径是 Android 模拟器或已 root 设备里的微信缓存。微信会把用过的每个小程序缓存成独立的 wxapkg 文件目录通常长这样# 微信 Android 端小程序缓存根目录hash 目录名不定 adb shell find /data/data/com.tencent.mm/MicroMsg -path *appbrand/pkg/*.wxapkg 2/dev/null # 把第一个命中路径拖回本地 adb pull /data/data/com.tencent.mm/MicroMsg/xxx/appbrand/pkg/aaa.wxapkg ./target.wxapkg逻辑说明find的查找范围内包含appbrand/pkg是当前版本比较稳定的路径特征不同版本可能有别的中间目录找不到时把find的范围放宽到整个MicroMsg目录再碰。adb pull是安卓调试桥的标准文件传输命令注意本地路径不能和模拟器路径混在一起。如果手边只有 PC 端微信也可以去微信小程序缓存目录里找同名的.wxapkg文件。抓包抓出来的包其实也可以但你得先处理证书校验而且抓出来的通常是密文得再走一层解密所以我在实际操作里更推荐先找本地缓存。3.3 解密与解包wxappUnpacker 的最小命令拿到文件后先用主脚本试着解包。如果文件头不对或输出目录为空再走解密脚本。下面这套命令可以处理大多数情况# 直接尝试解包明文包会正常输出密文包会报错或产出空目录 node wuWxapkg.js ./target.wxapkg -o ./out/ # 如果是加密包先走解密再解包 node wuDecrypt.js ./target.wxapkg -o ./target.decrypted.wxapkg node wuWxapkg.js ./target.decrypted.wxapkg -o ./out/参数说明-o指定输出目录最好每次解包都用独立目录避免新旧产物混在一起。wuDecrypt.js是工具包里提供的额外脚本有的 fork 会把解密能力和主脚本合在一起看到-s之类的开关时可以先看 help 确认用途。执行过程中脚本会打印每个被解出的文件路径看到app-config.json和app-service.js出现基本说明拆包成功。如果只出现.js而没有页面文件大概率是主脚本版本和包版本不匹配需要换新版或者同步更新解析配置。3.4 解包产物结构和命名规则out/ ├── app-config.json ├── app-service.js ├── app-wxss.js └── pages/ ├── index/index.wxml ├── index/index.js ├── index/index.json └── index/index.wxss这是比较理想的还原结果。实际使用里pages/*.wxml可能被还原成.js或.template要看包的基础库版本。app-config.json是所有页面和分包的地图里面能看到页面注册表、tabBar 配置、网络超时时间等后续对页面、找分包都要从这里出发。拿到这个文件后建议先看它再去看 JS。4. 还原结果怎么落地抽接口、对页面、找回资源和合并分包4.1 先从 app-service.js 里抽出接口域名一份反解析产物里最有价值的就是app-service.js所有请求发送逻辑都集中在这里。用正则把字符串里的http开头的链接全捞出来几秒钟就能得到一份接口清单。// 提取 app-service.js 中所有出现在字符串里的 http 域名和路径 const fs require(fs); const code fs.readFileSync(app-service.js, utf8); const set new Set(); for (const m of code.matchAll(/https?:\/\/[A-Za-z0-9.\-/_?%]/g)) { set.add(m[0]); } console.log([...set].sort().join(\n));逻辑说明matchAll是字符串全局匹配的现代写法结果里的set会自动去重同一接口被多次调用也只会输出一次。如果你想只看域名可以把m[0]换成new URL(m[0]).hostname如果只想保留wx.request里的请求还得往上找调用栈这就要靠 AST 解析不是一个正则能搞定的。拿到接口清单后配合域名备案信息和使用场景基本能判断出小程序的服务端架构。这一步对做接缝测试、写接口文档和做版本对比都很有用。4.2 把 WXML 和 JS 的 data 字段对应起来页面目录里同时有 JS 和 WXML 时最直接的阅读方式是先在 JS 里找到页面初始数据再回 WXML 看绑定关系。小程序页面通常用setData去驱动视图所以反向解析时JS 里data块的字段名就是 WXML 里绑定的变量名。# 看页面初始数据块字段名会直接暴露业务模型 grep -n data: out/pages/index/index.js | head -20逻辑说明grep -n直接定位data:所在行head -20限制输出条数避免数据块过长刷屏。很多小程序把图片、文案、状态值都放在data里看这一块能快速还原页面的默认状态。再看 WXML 里对应绑定的地方就能知道哪些字段被哪些组件消费。如果解出来的 WXML 是 render 函数而不是静态标签这一步会吃力很多。这种情况下只能靠函数名和字符串直出推断业务结构建议优先用新版本 fork 再跑一次。4.3 资源文件落地为什么比 dat 查看器更好用反解析时经常有人问要不要专门去找微信 dat 文件查看器其实不用。wxappUnpacker 解包成功后图片、图标、字体这些资源都会按原始扩展名落到输出目录里不需要再猜 dat 文件格式也不存在文件头被改写的问题。本地缓存里的 dat 文件之所以让很多人头疼是因为微信为了缓存效率会把图片做二次编码文件名也打散。而 wxapkg 里的资源还保留着相对完整的路径和名称解包出来直接就能打开。比如 tabBar 图标通常就在assets或icons目录下按钮图片在页面目录附近按路径找就可以。4.4 主包与分包合并还原完整工程的关键一步只解一个 wxapkg 往往拿不到全部页面。小程序发布时经常拆成“主包 分包”多个文件每个.wxapkg只包含一部分页面。合并前先看主包里的app-config.json# 查看分包配置里面会列出 subPackage 的 root 路径 grep -E subPackages|root out/app-config.json逻辑说明subPackages是分包配置root是分包根路径。拿到分包列表后把每个分包解包再按 root 路径覆盖到主包目录里。合并不只是简单cp -r因为分包的内部路径是从自身 root 开始计的必须先建好 root 目录再放文件否则页面路径会错位。5. 避坑现场wxappUnpacker 反解析常见的 5 个翻车点5.1 现象解出来是空目录或者直接报 header check failed原因拿到的包是加密 wxapkg工具没有先走解密也可能是工具版本太老读不懂新版包文件头头部结构。解决先跑解密脚本node wuDecrypt.js再解包。如果解密后依旧是空目录那就去确认微信版本和工具分支是否同步。社区里经常被问“微信小程序逆向最新支持哪个版本”答案不是某个固定版本号而是看你拿到的包头是否被当前 fork 兼容。我的习惯是手边常备两个不同时期的 fork一个管老包一个管新包。5.2 现象WXML 还原出来是一堆 render 函数或 template 模板原因新版微信基础库在发布阶段就把 WXML 编译成了渲染函数包内不再保留静态标签结构。旧版工具默认按静态 WXML 解析自然一无所获。解决换用支持新格式的 fork或者接受 render 函数通过函数内字符串还原页面结构。不要花大量时间手抄结构先把 JS 里的数据字段和接口清单理顺页面呈现反而更容易推断。5.3 现象JS 里全是 t、e、n字符串变成十六进制拼接原因构建脚本做了变量短名混淆和字符串编码为了压缩体积和增加阅读成本。这不是工具解坏了而是发布包本来就是这个样子。解决先跑一遍js-beautify还原缩进和换行再用工具包里的字符串还原模块把\x和\u开头的编码片段解回来。变量短名还原需要作用域分析工作量很大除非定位关键业务逻辑否则不建议逐个字段手工还原。5.4 现象分包解完了页面找不到路径全部错位原因分包包体在解析时是从自己的 root 开始计路径的解包脚本不会自动帮你把分包文件填回主包的subPackages路径下。解决读app-config.json里的subPackages配置拿到每个分包的root字段手动对应到输出目录里。正确合包后的页面路径应该和真实小程序运行时的页面 URL 保持一致。5.5 现象解包过程中断TypeError 抛到一半原因包内文件数异常或文件名包含中文、特殊字符时某些解析脚本的字符串切割逻辑会直接崩。解决先用file命令确认包体完整再检查磁盘还剩多少空间最后换一个 fork 重跑。同一个包在不同版本工具下表现差异很大遇到中断不要反复用同一版本重试先换工具分支。6. 反解析后的进阶玩法版本对比与接口审计技巧6.1 用不同版本 wxapkg 做 diff快速定位变更点手上留着两个版本的解包产物后最值得做的事是目录级对比。微信小程序迭代不会改动所有文件diff -r可以直接列出哪些页面 JS 和配置发生了变化。# 对比新旧两个解包目录输出所有差异文件的路径 diff -r out_old out_new diff.txt grep -E ^diff diff.txt | head -20逻辑说明diff -r递归对比目录grep ^diff只过滤“哪些文件不同”的行避免直接淹没在具体行变更里。看到变化文件清单后再单独diff对应文件就能快速判断这次迭代是加接口还是调样式。6.2 把接口清单导出成接口文档雏形反解析出一份接口清单后直接用脚本把它们整理成 CSV后续做回归测试或者交接后端都方便。// 从接口清单生成 CSV方便导入表格工具 const fs require(fs); const urls fs.readFileSync(urls.txt, utf8) .trim() .split(\n); const rows urls.map((url, i) { let method GET; if (url.includes(POST)) method POST; return ${i 1},${method},${url}; }); fs.writeFileSync(api-list.csv, rows.join(\n));这段脚本只是雏形实际可以把wx.request({ method: ... })附近的代码片段也抓出来但我在实操里发现正则够用就好方法识别靠盲猜不如去看页面功能。6.3 我的验收习惯三条规则先检查再深入反解析做完不是“命令跑完就成功”。我一般先检查三件事第一app-config.json能不能正常打开页面列表是否完整第二主要页面 JS 格式化后是否有可读的函数名和字符串不是一屏乱码第三关键资源文件尤其是 tabBar 图标能不能直接预览。这三条都过了我才会相信这份产物可以用来继续做分析。早年间我拿旧版工具去解新版包解出来的页面 JS 全是转义字符我还以为是包被加密了换了三个脚本才意识到是版本不匹配。从那以后我先看包头再看工具版本省下不少时间。希望这份经验也能帮到你。本文还有配套的精品资源点击获取
返回列表