众所周知,“跳一跳”在前几个月很火,并且出现了包括通过规则匹配/机器学习得到关键点坐标后模拟点击和通过源码获知加密方式伪造请求等方法。后者提到了如何获取含有源码的程序包 wxapkg ,以及使其能够在微信开发者工具中具体步骤(见参考链接1)。
当时我在对其他微信小程序应用进行尝试的时候发现,他们不同于小游戏,解包后的文件并不能通过简单增改就直接在微信开发者工具中运行,于是对小程序源代码=>wxapkg包内文件的具体转换关系进行了一定研究。
正文 包由前文知,我们可以通过查看 Android 手机中的 /data/data/com.tencent.mm/MicroMsg/{User}/appbrand/pkg({User} 为当前用户的用户名,类似于2bc**************b65)文件夹,获取最近使用过的微信小程序所对应的 wxapkg 包文件。
通过简单分析知,这个包由文件名+文件内容起始地址及长度信息开头,且各个文件明文存放在包内,通过类似于 https://gist.github.com/feix/32ab8f0dfe99aa8efa84f81ed68a0f3e 的脚本(这一个脚本处理包内二进制文件时有个小 bug ,将第78行的 w 改成 wb 即可),我们可以轻易获取包内文件。(具体解包细节可见于参考链接3)
但是这个包中的文件内容主要如下:
app-config.json
app-service.js
page-frame.html
其他一堆放在各文件夹中的.html文件
和源码包内位置和内容相同的图片等资源文件
微信开发者工具并不能识别这些文件,它要求我们提供由wxml/wxss/js/wxs/json组成的源码才能进行模拟/调试。
js注意到app-service.js中的内容由
define('xxx.js',function(...){ //The content of xxx.js });require('xxx.js'); define('yyy.js',function(...){ //The content of xxx.js });require('yyy.js'); ....
组成,很显然,我们只要定义自己的define函数就可以将这些 js 文件恢复到源码中所对应的位置。当然,这些 js 文件中的内容经过压缩,即使使用 UglifyJS 这样的工具进行美化,也无法还原一些原始变量名。
所有在 wxapkg 包中的 html 文件都调用了setCssToHead函数,其代码如下
var setCssToHead = function(file, _xcInvalid) { var Ca = {}; var _C = [...arrays...]; function makeup(file, suffix) { var _n = typeof file === "number"; if (_n && Ca.hasOwnProperty(file)) return ""; if (_n) Ca[file] = 1; var ex = _n ? _C[file] : file; var res = ""; for (var i = ex.length - 1; i >= 0; i--) { var content = ex[i]; if (typeof content === "object") { var op = content[0]; if (op == 0) res = transformRPX(content[1]) + "px" + res; else if (op == 1) res = suffix + res; else if (op == 2) res =makeup(content[1], suffix) + res; } else res = content + res; } return res; } return function(suffix, opt) { if (typeof suffix === "undefined") suffix = ""; if (opt && opt.allowIllegalSelector != undefined && _xcInvalid != undefined) { if (opt.allowIllegalSelector) console.warn("For developer:" + _xcInvalid); else { console.error(_xcInvalid + "This wxss file is ignored."); return; } } Ca = {}; css = makeup(file, suffix); var style = document.createElement("style"); var head = document.head || document.getElementsByTagName("head")[0]; style.type = "text/css"; if (style.styleSheet) { style.styleSheet.cssText = css; } else { style.appendChild(document.createTextNode(css)); } head.appendChild(style); }; };