简介:wxappUnpacker 是一款面向微信小程序开发者与安全研究人员的反编译工具,可将小程序二进制包还原为可读的 WXML、WXSS、JS 与 JSON 源码,支持主包与分包分别处理,便于分析业务逻辑、优化代码结构或开展安全审计。资源包共 780 个文件,以 374 个 js 脚本、83 个 json 配置、71 个 md 文档、70 个 ts 源码及 61 个 map 映射文件为主,另含 license、ps1、cmd 等辅助文件,整体约 1.97MB,目录结构完整,覆盖反编译核心逻辑与依赖模块。目前已有 268 人学习下载。借助该工具,读者可深入理解小程序的架构设计、组件使用、事件处理与网络请求机制,排查数据泄露、权限滥用等潜在隐患,同时学习代码美化、依赖解析与工程化脚本的实践思路,适合具备一定前端基础、希望进阶小程序逆向与安全检测的技术人员参考使用。
1. 拿到一个 .wxapkg 文件之后:wxappUnpacker 能拆出什么,拆不出什么
做小程序安全评估或者接手别人遗留项目时,经常会遇到一个尴尬局面:手上只有一个.wxapkg包,没有源码,没有工程配置,连页面结构都得靠猜。这个包本质上是一个二进制归档,里面按目录结构打包了编译后的 JS、WXML、WXSS 和 JSON 配置。wxappUnpacker 就是针对这种归档格式做逆向还原的一套脚本工具,核心目标是把.wxapkg拆回可读的目录树,并把部分编译产物还原成接近源码的形态。
它适合两类人:一类是做安全审计,需要看某个小程序到底请求了哪些接口、有没有硬编码密钥;另一类是做技术考古,想研究某个已下线小程序的实现思路。但先说清楚边界——wxappUnpacker 不是万能的,它还原出来的是“可读的编译产物”,不是原始工程。变量名可能被压缩成单字母,WXML 里的表达式可能被转成运行时代码,WXSS 里的 rpx 也可能被处理过。抱着“一键还原完整源码”的预期去用,大概率会翻车。下面按实际拆包流程,把每一步的参数、坑和验证方法讲透。
2. 拆包前的环境准备与 .wxapkg 文件定位
2.1 运行环境与依赖安装
wxappUnpacker 是基于 Node.js 的脚本集合,不是单个可执行文件。常见做法是先把仓库克隆到本地,然后安装依赖。它对 Node 版本有一定要求,太新的版本可能因为依赖包 API 变动导致报错,太老的版本又可能不支持某些语法。我一般会选 Node 14 或 16 这类长期支持版,避免在依赖上浪费时间。
# 克隆工具仓库到本地目录 git clone https://github.com/example/wxappUnpacker.git cd wxappUnpacker # 安装项目依赖,package.json 里通常包含 esprima、css-tree 等解析库 npm install # 验证主脚本能否正常输出帮助信息 node wuWxapkg.js --help这里的关键点是npm install必须成功,因为后续拆包依赖esprima做 JS 语法解析、依赖css-tree做样式还原。如果安装过程中出现node-gyp编译错误,通常是本机缺少 Python 或 C++ 构建工具,按提示补齐即可。--help能正常输出说明主入口脚本没有语法问题,可以进入下一步。
2.2 找到 .wxapkg 文件的实际路径
.wxapkg文件不会凭空出现,它通常藏在设备或模拟器的应用数据目录里。不同平台路径差异很大,而且目录名往往是哈希值,需要按修改时间排序来找。常见做法是先定位到小程序缓存根目录,再按时间倒序排列,最近使用过的包排在最前面。
# 以某类设备环境为例,先进入小程序缓存根目录 cd /path/to/miniprogram/cache # 按修改时间倒序列出所有 .wxapkg 文件,最近使用的在最上面 ls -lt --time-style=long-iso | grep '\.wxapkg$' # 如果目录层级较深,用 find 按时间筛选最近修改的包 find . -name '*.wxapkg' -mmin -60 -type fls -lt里的-t是按时间排序,-l是长格式显示,能同时看到文件大小和修改时间。find的-mmin -60表示最近 60 分钟内修改过的文件,适合刚打开过小程序后快速定位。找到目标包之后,建议先复制一份到工作目录再操作,避免原文件被误改。
提示:有些包是主包和分包分开存放的,主包通常体积较大,分包体积较小。拆包时要先拆主包,再根据主包里的配置去拆对应分包,顺序反了会导致页面路径对不上。
3. 执行拆包:wuWxapkg.js 的参数含义与输出结构
3.1 基础拆包命令与参数说明
主拆包脚本是wuWxapkg.js,调用方式不复杂,但参数顺序和可选参数需要留意。最简用法是直接跟一个.wxapkg文件路径,工具会在同目录下生成一个以包名命名的文件夹。
# 最基础的拆包命令,输出到当前目录下的同名文件夹 node wuWxapkg.js ./target.wxapkg # 指定输出目录,避免污染当前工作区 node wuWxapkg.js ./target.wxapkg -o ./output_dir # 如果包是分包,需要额外指定主包目录,让工具能找到公共依赖 node wuWxapkg.js ./subpackage.wxapkg -o ./output_dir -m ./main_package_dir-o参数控制输出路径,建议每次都显式指定,方便后续对比不同版本的拆包结果。-m参数只在拆分包时使用,它告诉工具主包在哪里,这样分包里的页面才能正确引用主包的公共模块。如果拆分包时不加-m,常见现象是页面能拆出来但运行时报模块找不到,因为公共依赖没有被关联上。
3.2 输出目录里到底有什么
拆包完成后,输出目录通常包含以下几类内容,理解它们的来源和局限很重要:
| 输出内容 | 来源 | 可读性 | 备注 |
|---|---|---|---|
.js文件 | 编译后的 JS 代码 | 中等 | 变量名可能被压缩,逻辑需手动梳理 |
.wxml文件 | 编译后的模板 | 较高 | 部分表达式被转成运行时代码 |
.wxss文件 | 编译后的样式 | 较高 | rpx 可能被转换,需还原 |
.json文件 | 页面/应用配置 | 高 | 基本可直接阅读 |
app-service.js | 逻辑层入口 | 低 | 通常是合并后的压缩代码 |
.json配置文件的还原度最高,页面路径、窗口样式、tabBar 配置基本都能直接看懂。.wxml和.wxss还原度也不错,但要注意编译过程中一些简写会被展开,一些条件渲染会被转成wx:if的等价形式。.js文件是最需要耐心的部分,尤其是app-service.js,它往往是所有逻辑模块合并后的产物,变量名被压缩成a、b、c,需要结合调用关系反推含义。
注意:拆包结果里如果出现大量
undefined或空文件,先检查原始.wxapkg是否完整。有些包在传输过程中被截断,或者本身就是加密过的,这种情况下拆包工具只能还原出部分内容。
4. 还原后的代码怎么读:JS 反混淆与 WXML/WXSS 修复
4.1 JS 压缩代码的阅读策略
拆出来的 JS 大部分是经过压缩的,直接读会非常痛苦。常见做法是先做一次格式化,把单行压缩代码展开成带缩进的多行,然后再配合搜索关键字符串来定位逻辑。格式化可以用prettier或js-beautify,不需要理解代码含义,只是让结构可见。
# 用 js-beautify 格式化单个 JS 文件,输出到新文件 js-beautify ./output_dir/app-service.js -o ./output_dir/app-service.pretty.js # 或者用 prettier 批量格式化整个目录 npx prettier --write "./output_dir/**/*.js"格式化之后,不要从头到尾读,而是先搜关键字符串。比如搜request、url、token、key这类词,快速定位网络请求和鉴权逻辑。搜navigateTo、switchTab可以梳理页面跳转关系。搜setData可以找到数据绑定的入口。这种“按关键词定点突破”的方式,比逐行阅读效率高得多。
4.2 WXML 和 WXSS 的常见修复点
WXML 还原后经常遇到两个问题:一是wx:for的item和index变量名被改成了无意义的名字,二是模板里的表达式被转成了{{}}里嵌函数调用的形式。前者需要结合上下文重命名,后者需要手动拆回可读的表达式。WXSS 的问题主要是 rpx 单位,有些拆包结果会把 rpx 转成 px 或保留原样,需要根据设计稿宽度反推。
# 搜索 WXML 里所有 wx:for 的使用,检查变量名是否可读 grep -rn "wx:for" ./output_dir --include="*.wxml" # 搜索 WXSS 里的 rpx,确认单位是否被转换 grep -rn "rpx" ./output_dir --include="*.wxss" | head -20如果发现 rpx 被转成了固定 px,而你又需要还原成响应式布局,可以按 750 设计稿宽度做换算:1rpx = 屏幕宽度 / 750。常见做法是写一个简单的替换脚本,把 px 按比例换回 rpx,但要注意区分哪些 px 是原始设计值,哪些是运行时计算值,后者不能盲目替换。
提示:WXML 里如果出现
{{}}包裹的复杂表达式,先不要急着改,把它复制到小程序开发者工具的 WXML 面板里试运行,能跑通就说明逻辑没问题,只是可读性差。
5. 避坑与排查:拆包过程中最容易翻车的五个点
5.1 拆包后页面空白,控制台报模块找不到
现象:拆包成功,目录结构完整,但用开发者工具打开后页面空白,控制台提示module 'xxx' is not defined。
原因:分包拆包时没有指定主包路径,导致分包里的页面引用了主包的公共模块,但工具不知道主包在哪里,没有建立关联。
解决:重新执行拆包命令,加上-m参数指向主包输出目录。如果主包和分包是分开拆的,确保先拆主包,再拆分包,并且分包输出目录和主包输出目录在同一层级下。
5.2 JS 文件格式化后报语法错误
现象:用js-beautify格式化后,文件开头出现#!/usr/bin/env node或类似内容,导致解析失败。
原因:拆包工具可能把一些非 JS 内容也写进了.js文件,或者原始包里的 JS 本身就不是标准语法。
解决:先检查文件头部几行,如果是无关内容,手动删掉再格式化。如果格式化工具报错,换用prettier并加上--parser babel参数,它对非标准语法的容忍度更高。
5.3 WXSS 里出现大量重复选择器
现象:还原后的 WXSS 文件里,同一个选择器出现多次,样式互相覆盖,页面显示异常。
原因:编译过程中,多个页面的样式被合并到了同一个文件里,拆包时没有按页面拆分,导致选择器冲突。
解决:按页面路径拆分 WXSS,每个页面的样式单独存放。常见做法是根据app.json里的pages列表,逐个页面提取对应的样式块,再分别写入独立文件。
5.4 拆包结果里缺少 app.json
现象:输出目录里没有app.json,只有app-service.js和app-config.json。
原因:不同版本的小程序编译格式有差异,有些版本把配置信息合并进了app-config.json,而不是独立的app.json。
解决:检查app-config.json的内容,它通常包含页面列表和窗口配置。手动把它重命名为app.json,或者根据它的内容补全app.json的字段。注意字段名可能略有不同,需要对照小程序官方配置文档做映射。
5.5 拆包后代码能读但跑不起来
现象:代码结构完整,逻辑也能看懂,但放到开发者工具里就是跑不起来,报各种运行时错误。
原因:拆包还原的是编译产物,不是原始工程。编译过程中一些全局注入、运行时补丁、环境变量被剥离了,直接运行会缺少这些依赖。
解决:不要指望拆包结果能直接运行。把它当作“可读的参考代码”来用,重点看逻辑和接口调用,而不是追求可运行。如果确实需要运行,手动补全缺失的全局变量和运行时依赖,但工作量可能很大,要有心理准备。
注意:拆包工具本身也在不断更新,不同版本对同一份
.wxapkg的还原效果可能不同。如果某个版本拆出来的结果不理想,换一个版本再试,有时候会有惊喜。
6. 进阶技巧:用 diff 对比两个版本的拆包结果定位变更
拆包最实用的进阶场景不是单次还原,而是对比。比如你手上有同一个小程序的两个版本包,想知道新版本改了什么,这时候 diff 就是最高效的工具。我一般会先把两个版本的包分别拆到不同目录,然后用diff -r做递归对比,重点关注.js和.json的变化。
# 分别拆两个版本的包到不同目录 node wuWxapkg.js ./v1.wxapkg -o ./output_v1 node wuWxapkg.js ./v2.wxapkg -o ./output_v2 # 递归对比两个目录,只输出有差异的文件名 diff -rq ./output_v1 ./output_v2 # 对具体有差异的 JS 文件做逐行对比,忽略空白变化 diff -u -w ./output_v1/app-service.js ./output_v2/app-service.js | head -100diff -rq里的-r是递归,-q是只报告差异不显示具体内容,适合先快速定位哪些文件变了。diff -u -w里的-u是统一格式输出,-w是忽略空白字符差异,避免因为格式化导致的误报。拿到差异行之后,结合前后文判断是功能新增、接口变更还是纯样式调整。
还有一个技巧是配合grep做定向对比。比如你只关心网络请求的变化,可以分别搜两个版本里的url字段,然后对比结果集。
# 提取 v1 里所有疑似接口地址的字符串 grep -oE 'https?://[^"'\'' ]+' ./output_v1/app-service.js | sort -u > ./v1_urls.txt # 提取 v2 里的接口地址并对比 grep -oE 'https?://[^"'\'' ]+' ./output_v2/app-service.js | sort -u > ./v2_urls.txt diff ./v1_urls.txt ./v2_urls.txt这个方法的精度取决于压缩代码里 URL 是否以明文字符串存在。如果 URL 是拼接出来的,grep 可能抓不全,这时候就需要回到格式化后的代码里手动搜索域名关键词。我自己的习惯是每次拆完包先跑一遍 URL 提取,把结果存成文本,后续对比时直接 diff 文本文件,比在几万行 JS 里翻找快得多。
从那以后我每次拆包都会先做两件事:一是把原始.wxapkg备份到独立目录,二是在输出目录里放一个README记录拆包命令和参数。这样过几个月再回头看,能立刻复现当时的操作,不用靠记忆去猜。希望帮到你。
本文还有配套的精品资源,点击获取