ARTICLE DETAIL

资讯详情

深耕商务建站与企业官网运营的一线实战洞察。

小程序反编译实战:wxappUnpacker 解包与源码还原

小程序反编译实战:wxappUnpacker 解包与源码还原 简介面向微信小程序逆向分析与安全审计场景wxappUnpacker 是一套可将小程序包还原为可读源码的工具合集适用于希望研究主包与分包结构、排查逻辑或开展合规安全检测的开发者和安全研究人员。压缩包共780个文件以374个 JavaScript 源码、83个 JSON 配置、70个 TypeScript 文件及71个 Markdown 文档为主另含依赖库、命令行脚本和许可说明整体约1.97MB便于在本地按模块查阅与运行。已有269人学习说明其在相关学习群体中具备一定参考价值。借助这套工具读者可围绕 WXML、WXSS、JS 与 JSON 的还原结果理解页面结构、样式组织、业务逻辑和配置关系并进一步分析组件使用、事件处理与网络请求等实现细节配套文档与脚本也有助于复现处理流程、梳理依赖和定位常见问题适合需要做代码阅读、性能优化或安全审计的中高级开发者参考。1. 从一堆 .wxapkg 说起为什么我要把反编译流程跑通去年做一次内部代码审计对方给过来的东西很尴尬一个跑了三年的小程序源码仓库因为人员流动丢了分支线上还在正常跑本地只剩几个 .wxapkg 缓存包。最坏打算是重写但真重写一个三年迭代的业务成本比想象中高得多。抱着试试看的心态用 wxappUnpacker 折腾了一晚上小程序反编译出来的 wxml、wxss、js 拼回了七八成配合日志和接口文档业务逻辑基本能梳理清楚。这篇就把这条链路完整摊开.wxapkg 里面到底装了什么、四个脚本谁管哪一段、命令行参数怎么给、哪一步最容易翻车。写给两类人——手里有授权资产要做审计或找回源码的以及想搞懂小程序编译产物到底长什么样的。2. 看懂 .wxapkg 再动手容器结构、版本差异与授权边界反编译这件事九成失败不是因为工具不行是因为根本没搞清楚手里这个东西是什么。我见过太多人上来就node wuWxapkg.js xxx.wxapkg报一堆错才发现拿到的是个分包、是个没下载完的残包、或者根本就是加过壳的产物。2.1 一个 .wxapkg 里到底装了什么.wxapkg 不是 zip也不是 tar它是一套自定义的线性容器格式。整体结构大致分三段区段内容说明文件头固定首字节 4 字节大端序索引长度用来判断这到底是不是合法包索引段文件数量、每个文件的名称长度、名称、数据偏移、数据大小相当于一张目录表数据段按索引顺序紧密排列的原始文件字节真正的 wxml、wxss、js、json、图片索引段里的偏移量是相对数据段起点的所以只要索引能正确读出来理论上每个文件都能原样切出来。这部分是纯字节操作不涉及任何破解它就是一个自定义打包格式的逆运算。真正麻烦的在后面切出来的 js 不是你能直接读的 jswxml 不是手写的 wxmlwxss 也被压过。容器解包只是第一步格式还原才是主线。一个判断技巧解开后如果app-service.js只有几 KB而包里索引显示有几十个页面那基本可以确定这是个分包主包的逻辑不在这里。分包单独解出来的 js 是残缺的缺主包的模块注册表跑起来会一路报模块未定义。2.2 编译产物的代际差异决定你能还原到几成不同时期、不同基础库版本编译出来的产物可还原程度差别很大。我一般按三个维度去比第一个维度是模块化写法。早期编译产物是自定义的define/require加模块 ID 映射模块之间的依赖关系写在数组里还原后能看出清晰的文件边界。近一些的产物更接近 CommonJS 风格甚至带上了打包器的运行时。前者还原出来可读性更好后者需要把运行时那段壳剥掉才看得清业务代码。第二个维度是模板压缩程度。wxml 编译后属性会被压短、注释被清空、部分标签做名称映射。压缩得越狠还原出来的结构越需要人工对回原语义。有些产物里wx:if被改成了短属性名映射表不在包里只能靠上下文推断。第三个维度是样式处理。wxss 编译时会把import内联展开单位处理也可能被重写。内联展开是不可逆的你只能还原出合并后的结果还原不出原本拆成了哪几个文件。这一点必须先接受别指望完美。所以我在动手前会先做一次抽样判断随便切两三个文件出来看看头部特征估一下还原上限。如果目标是找回业务逻辑六七成就够用如果目标是逐字节还原原始仓库那大概率要失望。这个预期差如果一开始不摆正后面每一步都会觉得工具有 bug。2.3 先把授权边界划出来哪些包能碰哪些碰都别碰这段必须放在前面讲不是什么免责套话。能碰的自己公司或自己开发的、有明确书面授权的资产自己设备上自己账号产生的小程序缓存包教学或研究场景下官方提供的样例包。这几类的共同点是——你对这份代码有处置权。不能碰的任何第三方的小程序包。哪怕它就在你手机缓存目录里躺着哪怕它没有任何加密。反编译他人代码并用于分析接口、复制逻辑、寻找漏洞在很多地方的司法实践里都是有明确判例的不要拿我只是学习当挡箭牌。我的习惯是每次开工前先写一行记录——包来自哪台设备、哪个账号、谁授权的、用途是什么。听着很形式主义但真出事的时候这一行记录比什么都管用。血泪经验谈不上算是给自己留的后悔药。3. wxappUnpacker 的四个脚本谁解容器谁还原代码这套工具的设计思路很朴素一个主入口负责解容器和调度三个子脚本各管一种文件类型的还原。理解了分工报错信息基本就能自己对上号。3.1 环境这一关Node 版本与依赖别在这翻车仓库 clone 下来第一件事不是跑是看package.json。这类工具大多写得比较早依赖的解析库、字符串处理库版本偏旧直接npm install在新版 Node 上经常出问题。我一般的处理顺序是先用node -v看当前版本如果报错里有Cannot find module之外的语法错误大概率是 Node 太新。装一个 LTS 版本用版本管理器切过去跑一次。npm install如果卡在某个包编译上先看是不是需要本地编译工具链能跳过的可选依赖就跳过。依赖装完目录里应该能看到这几个文件文件职责wuWxapkg.js主入口解容器、建目录、按类型调用子脚本wuJs.jsjs 还原处理模块包装与依赖映射wuWxml.jswxml 还原处理标签与属性压缩wuWxss.jswxss 还原处理 import 内联与格式化wuConfig.js映射表与配置很多还原细节靠它wuConfig.js经常被忽略但它决定了标签名映射、属性名映射这些细节。如果还原出来的 wxml 里出现一堆看不懂的短标签先去这个文件里对一下映射表是不是缺项。3.2 wuWxapkg.js 主干流程解容器、建目录、分发任务主脚本干的事可以拆成四步我在调试时习惯按这四步去定位问题第一步读文件头做合法性校验。首字节不对直接退出。这一步失败通常意味着文件被截断或者根本不是这个格式。第二步读索引段把文件表解析成内存里的数组。每项含名称、偏移、大小。这一步失败一般是索引长度字段被读错常见于下载不完整的包。第三步按偏移切数据段把文件写到输出目录。文件名的路径分隔符在包里可能是平台相关的写盘前要做一次规范化否则在类 Unix 系统上会出现文件名里带反斜杠的情况。第四步遍历输出目录按扩展名分发.js交给 js 脚本.wxml交给 wxml 脚本.wxss交给 wxss 脚本。分发的顺序有讲究——js 先还原因为它产出的模块映射会影响 wxml 里表达式的可读性。想加日志的话最省事的位置是第三步和第四步之间把切出来的文件按大小排个序打出来。哪一类文件数量明显偏少就说明那一路还原会出问题。3.3 wuJs.js / wuWxml.js / wuWxss.js三路还原与各自的失败特征三个脚本的失败表现完全不同记住特征能省很多时间。js 这一路的典型失败是文件生成了但是空的或者只还原出壳。原因是模块格式识别失败——脚本按预期格式去找注册表找不到就跳过了。这种情况下打开原始切出来的app-service.js看头部如果开头不是预期的模块定义结构说明产物代际和工具预期不一致需要手动改一下匹配规则。wxml 这一路失败通常是结构对但属性全丢。这不是 bug是映射表缺项需要往wuConfig.js里补。另一种失败是嵌套层级错乱多半出在自闭合标签的处理上。wxss 这一路最轻多数情况只是格式没排好内容基本完整。真出问题一般是 import 展开时路径解析失败看日志里的路径拼接结果就能定位。4. 一次完整复现从包文件到可读源码的实操路径前三章讲的都是判断依据这一章走一遍完整流程。我用一个虚构的内部审计场景来演示——某公司自家小程序的历史版本源码丢失只剩测试机上的一份缓存包。4.1 定位你自己的包主包、分包与缓存目录包一般在自己设备的宿主 App 缓存目录下按小程序的 AppID 分文件夹存放。目录里通常能看到两类文件一类是主包文件名带__APP__之类的标识另一类是分包按分包名命名。找到之后先把整个目录拷出来一份别在原位置操作。拷出来之后先做两件事第一用文件大小排个序。主包通常最大几十 KB 到几 MB 不等。有几个包大小接近说明是多分包结构。第二逐个看文件头。首字节应该是统一的固定值不是的话这个包大概率没下载完整需要重新触发一次加载。分包的处理顺序是先解主包得到输出目录再用-d参数解各个分包输出到主包目录下。顺序反了会缺模块注册表。4.2 参数怎么给-o、-s、-d 的作用与组合主入口的参数不多但每个都有明确的用途# -o 指定输出目录建议每个包单独一个目录方便对比 # -s 传入 AppID部分版本靠它定位分包归属路径 # -d 标记当前是分包不再重复解主包直接复用已有输出目录 node wuWxapkg.js -o./out/main ./pkg/__APP__.wxapkg node wuWxapkg.js -d -swx0000000000000000 \ -o./out/main ./pkg/sub_home.wxapkg-o是最常用的一定要显式给默认输出到当前目录会把工作区搞乱。-s在主包解完之后可以省略但解分包时建议带上能避免路径推断出错。-d是分包专用开关漏了它会导致重复解主包、输出目录被覆盖。参数给错的典型症状输出目录里只有一层空文件夹或者解出来的文件数明显少于预期。这时候别急着怀疑工具先把命令回显打出来核对一遍。4.3 跑通之后先看哪几个文件解包成功不等于还原成功先扫一遍输出目录再决定要不要深挖。我的顺序是先看根目录的app.json之类的配置确认页面列表、分包配置、tabBar 结构。这份文件通常还原得最完整能直接告诉你这个应用有多少个页面、路由怎么组织的。再看app-service.js还原后的主逻辑文件确认模块注册表在不在。如果模块列表是完整的说明 js 这一路基本成功了后面逐个页面读就行。如果只有一层空壳说明格式不匹配得回头改匹配规则。最后抽查两三个页面的 wxml看标签和属性是不是人能读的。这一步是判断 wxml 还原质量最直接的办法抽到看不懂的短标签就说明映射表有缺项。4.4 还原不完整时的三级降级策略理想情况是一把过实际很少。我准备好三级降级第一级补映射表。针对 wxml 的短标签、短属性往配置文件里补映射重跑 wxml 那一路。这一步成本最低能解决大部分能读但读不顺的问题。第二级手工修 js 模块格式。把原始的app-service.js切出来对照工具预期的格式改匹配规则或者干脆自己写个小脚本按模块 ID 切分。这一步需要一点正则功底但对新版产物的效果最好。第三级放弃全量还原转成按需还原。只解自己关心的那几个页面其余不管。审计场景下这往往是最理性的选择——你要的是逻辑链不是一份能编译的工程。降级不是失败是控制成本。我见过有人为了把一张图的 base64 还原出来耗了两天最后发现那张图跟审计目标毫无关系。5. 避坑与排查五条现场踩出来的记录这一章全是真出过的问题按现象、原因、解决三段写。遇到卡住的时候直接对一遍。5.1 第一坑解出来的 js 全是空的现象命令跑完没有报错输出目录结构也对但每个 js 文件都是 0 字节或者只有几十个字节的壳。原因模块格式识别失败。工具按固定的模块定义结构去匹配产物代际不同注册表长得不一样匹配不上就直接跳过写空文件了。解决先把原始app-service.js单独切出来用任何十六进制工具按索引偏移切看它的头部结构。把工具里负责匹配的那段正则对照着改或者写个小脚本按模块 ID 手动切分。改完只跑 js 这一路别每次都全量重跑。5.2 第二坑分包解出来缺一半页面现象主包解得挺完整分包解完发现页面数量对不上路由里有但目录里没有。原因多半是解分包时没带-d工具以为你在解主包把输出目录重新初始化了一遍把之前的结果覆盖掉了。解决确认命令里带了-d并且-o指向的是主包输出目录而不是新建目录。如果不放心先把主包输出目录备份一份再解分包出问题还能回滚。5.3 第三坑wxml 里全是看不懂的短标签现象结构能看出来但标签名是_c、_d这类短标识属性名也对不上读起来像乱码。原因标签和属性在编译期被做了名称映射映射表不在包里工具的配置文件中缺项。解决对着上下文猜。列表类结构大概率是view加wx:for可点击的容器大概率是view加bindtap。把这些推断补进配置文件重跑 wxml 那一路能解决大部分。实在猜不出的先放着优先保证业务逻辑可读。5.4 第四坑wxss 还原后样式全乱现象文件切出来了内容也有但import全被展开成一大坨原本分文件的样式混在一起改都没法改。原因import内联是编译期行为不可逆。工具只能还原合并后的结果还原不出原本拆成了哪几个文件。解决接受这个限制别想着完美还原。真要拆就按选择器和注释痕迹去人工分组。或者干脆只关注关键页面的样式其余不动。这个坑没有技术解法只有预期管理。5.5 第五坑跑一半进程被杀输出目录半残现象解一个比较大的包跑到一半进程没了输出目录里有一半文件再跑一次又从头覆盖。原因大包解包时内存占用高加上写盘是逐个文件进行的中断后状态不一致。解决分两步走。先只解容器不还原把分发那一步注释掉确认所有文件都切出来了再单独跑还原。这样中断了也不用重来切出来的原始文件一直都在。6. 把流程固化成脚本一条命令跑完解包与校验手动跑几次之后必然会烦——每次都要记参数、分辨主包分包、解完还要数文件。我的做法是把它固化成一个小脚本输入包目录输出解包结果和一份完整性报告。#!/usr/bin/env bash # batch-unpack.sh —— 批量解包并对结果做一次完整性打分 # 用法: ./batch-unpack.sh 包目录 输出根目录 set -uo pipefail PKG_DIR${1:?请给出 .wxapkg 所在目录} OUT_ROOT${2:?请给出输出根目录} TOOL./wuWxapkg.js mkdir -p $OUT_ROOT for pkg in $PKG_DIR/*.wxapkg; do name$(basename $pkg .wxapkg) out$OUT_ROOT/$name mkdir -p $out # 主包先解-o 显式指定输出避免污染工作区 if ! node $TOOL -o$out $pkg; then echo [FAIL] $name 解包异常跳过 continue fi # 三类产物的数量就是最直观的完整性指标 js$(find $out -name *.js | wc -l) wxml$(find $out -name *.wxml | wc -l) wxss$(find $out -name *.wxss | wc -l) # 空文件也算失败单独统计一次 empty$(find $out -type f -size 0 | wc -l) echo [OK] $name js$js wxml$wxml wxss$wxss empty$empty done脚本逻辑就三件事遍历、解包、计数。empty那一项是我后来加的因为空文件是最容易被忽略的失败信号——目录结构齐全、命令没有报错但内容全是空的光看文件数量根本发现不了。参数上有一点要注意set -uo pipefail里我没有加-e。原因是解包过程中单个文件失败很常见加了-e会让整个批次中断反而不好排查。改成用if !单独捕获每次调用的退出码失败了就跳过继续下一个日志里留痕。跑完对照一下app.json里声明的页面数量和实际还原出的 wxml 数量做比值。低于六成说明这一路有明显问题得回去看 js 或 wxml 的失败特征八成以上基本可以直接进入人工阅读阶段了。从那以后我每次做这类审计第一步都不是打开工具而是先跑一遍这个脚本拿到完整性报告再决定这次要投多少时间进去。工具给的是上限脚本告诉你实际能拿到多少。希望帮到你。本文还有配套的精品资源点击获取
返回列表
PREV
查看更多资讯
NEXT
返回资讯列表