小程序逆向分析这件事乍一听有点像地下实验室里才发生的事实际上它跟拆一台旧家电、顺着电路板看元器件布局没什么两样。前阵子因为一直在研究某家电品牌的官方小程序我完整走了一遍“拿到小程序包、还原逻辑代码、拆解请求签名、反馈安全问题”的流程。为了避免牵扯到具体品牌下面统一用“目标品牌”代指全程基于我自己账号、自己设备上的授权测试目的不是薅羊毛也不是搞破坏而是理解这套小程序的整体架构与数据流。这个项目做完以后最大的感受是小程序逆向的门槛比想象中低很多但可挖的深度一点都不比原生App少而且做安全反馈时的分寸感特别重要。1. 为什么会对一个小程序做逆向分析1.1 这次分析的目标与边界起因是我家里有一批目标品牌的智能家电但官方App实在太重我想确认能否通过小程序完成设备配网和远程控制顺便评估第三方设备接入自家智能家居平台的可行性。直接看黑盒是看不出门道的于是决定做一次有针对性的逆向分析重点回答三个问题小程序在本地到底缓存了什么数据请求接口的签名是怎么生成的服务端有没有明显的越权或数据泄露点这里必须先说清楚边界分析范围严格限制在我自己登录的账号、我自己绑定的设备、我手机上下载的小程序包缓存不访问任何他人的数据不修改任何线上请求也不做批量拉取。整个过程更接近一次小规模安全评估而不是为了绕过限制去刷优惠券或者获取权限。做完之后如果能发现真问题走正常反馈渠道提交这才是这类分析最有价值的落地方式。1.2 前置知识小程序到底是什么小程序虽然是跑在超级App里的但它并不是一个普通的网页而是“逻辑层视图层”分离的特殊应用。视图层负责渲染页面逻辑层用JavaScript运行两者通过底层桥接接口通信。代码和资源会打包成一个后缀类似wxapkg的文件在用户打开小程序时下载到手机本地这就给静态分析提供了很大的便利。和原生App相比小程序包的逆向门槛低不少。原因是它没有编译成机器码核心逻辑仍是JavaScript顶多做了一些变量名混淆和字符串加密。只要能把包解出来配合一款前端格式化工具基本就能还原出可读的业务代码。很多原生App开发者在转向小程序开发时往往会低估这个风险把密钥和签名逻辑直接塞进前端代码里这正是后续几类安全问题的根源。1.3 整体的分析路线我这次没有采用“从页面点一遍再看流量”的野路子而是先静态后动态先解包拿到代码分析请求封装和参数生成逻辑再通过抓包工具验证请求头里的签名是否和本地计算一致。静态分析能精确定位加密算法的位置动态抓包则能验证还原结果是否正确两者互相印证效率比纯黑盒高很多。下面就从最枯燥的“解包”开始讲。2. 从拿到小程序包到还原业务代码2.1 获取目标小程序包的几种稳妥办法想分析小程序第一步是拿到它的包。常见路径有三种我实际用过前两种。第一种是在已开启调试能力的测试机上从宿主App的缓存目录里找。小程序被打开后包一般会存放在宿主App对应的目录下路径里通常包含mini、appbrand之类的关键词。我在测试机上用文件管理器定位到缓存目录后按时间排序能轻松找到刚打开过的小程序包。文件大小一般从几百KB到几MB不等目标品牌这个包大约1.4MB已经能看出业务比较庞杂。第二种是通过网络代理抓包下载。在小程序启动时宿主App会从CDN下载最新包在代理工具里能看到包含包文件后缀的请求保存响应体即可拿到完整包。这个方式的好处是不依赖手机是否越狱缺点是如果小程序配置了包内容加密抓到的包可能还需要额外处理。目标品牌这个包下载后直接可见明文索引省了不少事。第三种是使用开发者工具本地缓存。某些开发者工具支持导入小程序包进行预览调试在调试过程中会生成临时缓存目录找起来也不难。但这个方法需要处理代码签名等问题对于只想快速看逻辑的人来说前两种更直接。2.2 解包后的目录观察拿到的包本质上是自定义打包格式需要先解包还原出原始目录结构。目前社区常见的解包工具都是基于文件头里的索引信息来还原的我在终端执行了解包命令python unpack.py target.wxapkg -o src/命令跑完后src/目录下会出现一大片文件典型的骨架长这样src/ ├── app.js ├── app.json ├── app.wxss ├── pages/ │ ├── index/ │ ├── device/ │ └── user/ ├── components/ ├── utils/ │ ├── request.js │ ├── crypto.js │ └── config.js └── vendor/第一次打开这套目录时我先没有急着去看代码而是从app.json入手。它记录了小程序的所有页面路由、窗口样式和底部Tab栏配置等于给了你一张完整的功能地图。哪些页面需要登录、哪些是设备控制核心、哪些是商城入口看一遍就清楚了。从功能地图再往回找代码比一个文件一个文件硬读高效得多。2.3 从混淆代码里捞关键逻辑目标品牌的代码做了一定混淆变量名变成了a、b、c这种短名函数名部分被重写但最关键的两类信息几乎都以明文存在接口路径和固定字符串。这是小程序逆向里最容易切入的地方。我用格式化工具把压缩过的JS文件重新排版然后直接搜索关键字比如/api/、sign、token、secret、encrypt等。第一次搜sign就发现了十几个命中集中在utils/request.js和utils/crypto.js这两个文件。顺着这些命中继续看很快能拼出一张请求封装模块的调用关系图页面调用某个业务函数业务函数调用request()request()内部再拼装公共参数、计算签名、附加请求头。这一步的经验是不要在“把所有代码读明白”上较劲。小程序逆向的正确姿势是打点式分析先找到几个关键入口函数比如登录、设备列表、远程控制然后沿着调用链往下挖。等理解了主链路之后如果还有余力再去补外围逻辑否则很容易陷入上万个函数名中出不来。顺便说一句很多博主会给出“一键还原源码”的工具实际上工具只能美化排版和恢复部分字符串真正的业务逻辑分析还是得靠人这个活没有捷径。3. 请求层与数据加密的拆解过程3.1 找到请求封装模块小程序的所有接口请求最终都会走向统一的wx.request调用所以第一步就是全局搜索这个API。目标品牌的utils/request.js里封装了一层request(options)外部传递业务路径和参数它负责做三件事补公共参数、计算签名、设置请求头。实际的请求头结构大概是这样的已做脱敏处理X-App-Id: wx_app_home X-Timestamp: 1720000000 X-Nonce: 6f8f2d1c3a9b4e5d X-Sign: 0ad3f3a6d7a5a3c1...其中Timestamp是毫秒级时间戳Nonce是随机字符串Sign看起来是一串32位的十六进制字符串极可能是MD5系列算法。到这里只能猜要看Sign的具体生成规则还需要定位到计算签名的那段代码。3.2 识别签名算法的生成规则在utils/crypto.js里我找到了一个名为generateSign的函数。还原后的核心逻辑如下这是我把混淆变量重命名后的版本function generateSign(params, timestamp, nonce) { const keys Object.keys(params).sort(); let raw keys.map(key ${key}${params[key]}).join(); raw raw timestamp timestamp nonce nonce salt SALT; return md5(raw); }逻辑本身并不复杂先把请求参数按key排序拼成k1v1k2v2形式的字符串再拼接上时间戳、随机数和一个写死在代码里的salt常量最后做一次MD5。这是非常典型的“只防君子不防小人”的设计它能让普通用户不易篡改参数却防不住能拿到代码的人。这里有一个值得展开的点为什么服务端要搞这么一层签名主要防止两类问题——接口被恶意刷量和请求参数被批量篡改。但MD5加静态salt的方案在拿到代码后基本等于裸奔因为攻击者只需要把salt提取出来就能任意伪造合法签名。真正的加固方式应该是每次从服务端动态获取salt或使用非对称加密保证客户端本地没有完整的签名材料。这次的发现也印证了一个老观点前端里的静态密钥迟早会变成安全报告里的“高危漏洞”。3.3 动态验证与抓包对照静态分析还原出签名逻辑后我并没有直接信任它而是用抓包工具做了一次动态验证。在测试手机上安装代理证书把流量指向本地代理然后重新打开小程序触发一次设备状态查询请求。抓到的请求体里果然有排序后的设备ID、时间戳和nonce本地用同样的字符串计算出来的MD5和请求头里的签名完全一致说明还原是准确的。动态验证过程中也踩了两个坑。一是小程序请求走的是HTTPS部分版本会做证书固定不信任用户自行安装的证书这时需要在小程序逻辑层或底层做一次绕过操作属于动态调试的常见套路。我这次没有选择绕过而是用了一个更简单的方法先开启代理抓包再在代码层面临时打日志输出签名参数通过控制台观察。虽然绕了一点但省去了处理证书固定的麻烦。另一个坑是时间戳的精度。服务端会把签名时间差控制在一定范围内用来防重放。我在本地验证时一开始用了当前系统时间没问题但如果想写脚本模拟请求必须保证模拟环境和抓包环境的时间一致否则签名校验会直接失败。大多数开发者在联调时都会栽在这个“时间差”上而不是算法本身。4. 逆向分析暴露出的常见安全弱点4.1 硬编码密钥与隐藏接口这次逆向分析最大的“收获”是在config.js里看到了一个写着secretKey的字段初始值就是一串看起来像Base64的字符串。虽然它被注释成“测试密钥”但这个文件却被打进了正式发布的小程序包。更麻烦的是代码里还有一个隐藏的动态接口只有当请求头里带上这个secretKey时才返回完整数据否则只返回脱敏数据。如果攻击者拿到这个密钥加上已经还原的签名算法就能在本地伪造一个全功能客户端甚至可以绕过App内的风控限制。这让我意识到“前端代码里不留任何秘密”这条安全铁律在小程序开发里常常被忽略。原因也很现实小程序开发门槛低很多团队把业务逻辑全堆在前端又缺少严格的前端安全审计于是密钥、盐值、内部接口就这么暴露了出来。4.2 业务权限绕过风险如果说硬编码密钥是单纯的开发失误那业务权限问题就更致命。在逆向分析设备管理页面时我发现一个“获取设备详细信息”的接口请求参数里直接带着deviceId而且代码里没有任何判断当前用户是否是该设备主人的逻辑。也就是说只要知道设备的唯一标识任意登录用户都可能拉到设备的详情数据。这类漏洞属于典型的水平越权也是小程序攻击中成本最低的一类。攻击者不需要逆向签名算法只需要在正常请求里把deviceId替换成别人的设备编号然后再配合签名算法或直接重放抓到的流量就能拉到不该看到的数据。我在这里停下了测试没有继续深入但已经足够写出一条高危漏洞描述服务端未校验资源属主导致越权访问。这给我一个很直观的启发逆向分析的价值不只是“把代码看明白”更是站在攻击者视角重新审视接口设计。很多开发者认为只要前端隐藏入口别人就发现不了接口但小程序包一经分发前端的一切防线都要假设为零。真正的安全边界必须建立在服务端参数校验上。4.3 明文缓存与日志泄露除了接口层面的问题本地存储也是一种常见信息泄露场景。在解包后的代码里我搜索了wx.setStorageSync和wx.getStorageSync在用户页面中发现登录后会把完整的access_token和部分用户信息明文写入本地缓存。这本身不算严重漏洞因为移动应用本地存储本来就是可读的但如果小程序运行环境本身就是多用户共用的设备那泄露风险就会被放大。另一个被我注意到的问题来自日志输出。调试模式下代码会把完整响应数据console.log出来包括接口返回的敏感字段。工作日里这种日志无所谓但正式包忘了关一旦有人在调试工具里打开控制台就能轻松看到所有网络交互数据。4.4 从漏洞反馈到加固建议把问题整理清楚之后我按“问题描述、复现路径、影响范围、修复建议”的格式写了一份反馈文档提交给目标品牌的开发者反馈渠道。建议部分针对性地给出四类修复思路这里也分享给同样在做安全评估的人问题风险修复建议前端静态salt签名可伪造改为动态下发或使用非对称签名硬编码secretKey接口被伪客户端利用密钥全部移到服务端前端只放临时凭证未校验设备归属水平越权获取他人设备信息服务端统一校验资源与当前用户绑定关系明文token存本地共用设备上账号信息泄露使用安全存储缩短token有效期增加刷新机制反馈这件事其实不难难的是别抱着“秀技术”的心态去沟通。措辞里不要踩对方尽量写“建议在服务端增加校验”而不是“你们漏洞太多”。最终对方能不能及时修复我控制不了但把问题完整交给他们这件事就算闭环了。5. 复盘哪些动作值得做哪些雷不要踩5.1 工具比想象中成熟门槛比想象中低做完整个流程后我最强烈的感受是小程序逆向在今天几乎是一个“搭积木”的过程。解包有现成工具、格式化有现成插件、抓包也有成熟的本地代理方案真正需要思考的部分集中在两件事——读代码的耐心和安全边界的判断力。这种成熟度带来的隐性风险是任何人都能轻松撕开一个小程序的前端保护小程序上线前的安全检查就显得更加重要。对于初学者我建议先从一个小工具或简单页面开始练习比如找一个天气类小程序解包之后尝试还原它获取地理位置和请求天气接口的完整链路。这个小目标啃下来你就掌握了小程序逆向的全部核心方法。再往后可以尝试分析登录态、研究复杂加密方案但每一次都要守住“只在自己授权范围内分析”的底线。5.2 踩过的坑与经验清单我把这次过程里踩过的坑和最终沉淀的经验整理成一份清单供后来人参考。第一别一上来就全量解包分析。目标品牌这个1.4MB的包里有接近400个JS片段如果没有重点几个小时就耗在无关功能里。要先从app.json确定功能地图再搜索接口路径和关键词找到主干再分叉。第二签名算法还原后一定用动态抓包做交叉验证。只看代码推导容易漏掉一些隐式拼接逻辑比如公共参数补齐的位置、URL encoding的时机、空值过滤规则这些细节不看真实请求根本对不上。第三遇到加密算法不要慌先判断是标准算法还是自定义算法。标准算法可以从特征值如MD5的32位长度、AES的IV长度直接推断自定义算法才需要逐行跟踪。大多数小程序的“加密”都只是MD5/SHA加盐定位到salt基本就结束了。第四安全评估要有“点到为止”的分寸感。发现越权接口后不需要真的去拉别人的数据来证明危害只要确认代码里缺少校验就可以写报告了。再往下走一步事情的性质就变了。这是我这次最谨慎也最坚持的一点。5.3 后续还能怎么扩展这次分析只是拆开了第一层后续如果想继续深入可以沿着两条线走。一条是往“防”的方向走研究小程序代码加固和混淆对抗比如字符串加密、控制流平坦化、反调试之类的技术另一条是往“管”的方向走把逆向分析时还原出来的接口和参数整理成文档做成一个针对自家设备数据的自动化监测脚本在设备状态异常时主动告警。我个人其实更推荐第二条线。因为逆向分析的技术乐趣是短暂的真正有长期价值的东西是“你比厂商更清楚自己的数据在怎么流动”。有了这套理解后续不管是想接第三方平台还是做设备本地化控制都相当于手上拿了一张内部地图。最后再分享一个小技巧也是我这次最常用的一招在代码里搜索所有中文提示语。小程序的错误提示、弹窗文案基本都是中文原串把这些字符串当成锚点可以非常快地跳到相关业务逻辑所在的位置比顺着函数调用栈找省力得多。以后无论你分析什么小程序这一招大概率都能派上用场。