概览

我们知道,前端 Web 网页编程采用的是 HTML + CSS + JS 这样的组合,其中 HTML 是用来描页面的结构,CSS 用来描述页面的样子,JS 通常用来处理页面逻辑和用户的交互。类似地,在小程序中也有同样的角色,一个小程序工程主要包括如下几类文件:

  • .json 后缀的 JSON 配置文件
  • .wxml 后缀的 WXML 模板文件
  • .wxss 后缀的 WXSS 样式文件
  • .js 后缀的 JavaScript 脚本逻辑文件

例如“知识小集”的小程序源码工程结构如下:

然而,根据上一篇文章介绍,对“知识小集”小程序的 .wxapkg 解包后得到如下文件:

主要包括 app-config.jsonapp-service.jspage-frame.html*.html资源文件 等,但这些文件已经被“编译混淆”并重新整合压缩,微信开发者工具并不能识别它们,我们无法直接对它们进行调试/编译运行。

所以,我们先尝试分析一下从 .wxapkg 提取出来的各个文件内容的结构及其用途,然后介绍如何用脚本工具把它们一键还原为“编译”前的源码,并在微信开发者工具中跑起来。

文件分析

本节主要以“知识小集”小程序的 .wxapkg 解包后的源码文件为例,进行分析。

你也可以跳过本节的分析,直接看下一节介绍用脚本“反编译”还原源码。

app-config.json

小程序工程主要包括工具配置 project.config.json,全局配置 app.json 以及页面配置 page.json 三类 JSON 配置文件。其中:

project.config.json 主要用于对开发者工具进行个性化配置以及包括小程序项目工程的一些基础配置,所以它不会被“编译”到 .wxapkg 包中;

app.json 是对当前小程序的全局配置,包括了小程序的所有页面路径、界面表现、网络超时时间、底部 tab 等;

page.json 用于对每一个页面的窗口表现进行配置,页面中配置项会覆盖 app.json 的 window 中相同的配置项。

因此“编译”后的文件 app-config.json 其实就是 app.json 和各个页面的配置文件的汇总,它的内容大致如下:

  1. {

  2. "page": { // 各页面配置

  3. "pages/index/index.html": { // 某一页面地址

  4. "window": { // 某一页面具体配置

  5. "navigationBarTitleText": "知识小集",

  6. "enablePullDownRefresh": true

  7. }

  8. },

  9. // 此处省略...

  10. },

  11. "entryPagePath": "pages/index/index.html", // 小程序入口地址

  12. "pages": ["pages/index/index", "pages/detail/detail", "pages/search/search"], // 页面列表

  13. "global": { // 全局页面配置

  14. "window": {

  15. "navigationBarTextStyle": "black",

  16. "navigationBarTitleText": "知识小集",

  17. "navigationBarBackgroundColor": "#F8F8F8",

  18. "backgroundColor": "#F8F8F8"

  19. }

  20. }

  21. }

通过与原工程 app.json 和各页面配置 page.json 内容的对比,我们可以得出 app-config.json 汇总文件的简单整合规律,很容易把它拆分成“编译”前对应的各 json 文件。

app-service.js

在小程序项目中 JS 文件负责交互逻辑,主要包括 app.js,每个页面的 page.js,开发者自定义的 JS 文件和引入的第三方 JS 文件,在“编译”后所有这些 JS 文件都会被汇总到 app-service.js 文件中,它的结构如下:

  1. // 一些全局变量的声明

  2. var __wxAppData = {};

  3. var __wxRoute;

  4. var __wxRouteBegin;

  5. var __wxAppCode__ = {};

  6. var global = {};

  7. var __wxAppCurrentFile__;

  8. var Component = Component || function(){};

  9. var definePlugin = definePlugin || function(){};

  10. var requirePlugin = requirePlugin || function(){};

  11. var Behavior = Behavior || function(){};

  12. // 小程序编译基础库版本

  13. /*v0.6vv_20180125_fbi*/

  14. global.__wcc_version__='v0.6vv_20180125_fbi';

  15. global.__wcc_version_info__={"customComponents":true,"fixZeroRpx":true,"propValueDeepCopy":false};

  16. // 工程中第三方或者自定义的一些 JS 源码

  17. define("utils/util.js", function(require, module, exports, window,document,frames,self,location,navigator,localStorage,history,Caches,screen,alert,confirm,prompt,XMLHttpRequest,WebSocket,Reporter,webkit,WeixinJSCore) {

  18. "use strict";

  19. // ... 具体源码内容

  20. });

  21. // ...

  22. // app.js 源码定义

  23. define("app.js", function(...) {

  24. "use strict";

  25. // ... app.js 源码内容

  26. });

  27. require("app.js");

  28. // 每个页面对应的 JS 源码定义

  29. __wxRoute = 'pages/index/index'; // 页面路由地址

  30. __wxRouteBegin = true;

  31. define("pages/index/index.js", function(...){

  32. "use strict";

  33. // ... page.js 源码内容

  34. });

  35. require("pages/index/index.js");

在这个文件中,原有小程序工程中的每个 JS 文件都被 define 方法定义声明,定义中包含 JS 文件的路径和内容,如下:

  1. define("path/to/xxx.js", function(...){

  2. "use strict";

  3. // ... xxx.js 源码内容

  4. });

因此,我们同样很容易提取这些 JS 文件源码,并恢复至相应的路径位置中。当然,这些 JS 文件中的内容经过混淆压缩,我们可以使用 UglifyJS 这样的工具进行美化,但仍很难还原一些原始变量名,不过基本不影响正常阅读和使用。

page-frame.html

在小程序中使用 WXML 文件描述页面的结构,WXSS 文件描述页面的样式。工程中有一个 app.wxss 文件用于定义一些全局的样式,会自动被 import 到各个页面中;另外每个页面也都分别包含 page.wxml 和 page.wxss 用于描述其页面的结构和样式;同时,我们也会自定义一些公共的 xxxCommon.wxss 样式文件和公共的 xxxTemplate.wxml 模板文件供一些页面复用,一般在各自页面的 page.wxss 和 page.wxml 中去 import

当“编译”小程序后,所有的 .wxml 文件和 app.wxss 及公共 xxxCommon.wxss 样式文件的将被整合到 page-frame.html 文件中,而每个页面的 page.wxss 样式文件,将分别单独在各自的路径下生成一个 page.html 文件。

page-frame.html 文件的内容结构如下:

  1. <!DOCTYPE html>

  2. <html lang="zh-CN">

  3. <head>

  4. <meta charset="UTF-8" />

  5. <meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0" />

  6. <meta http-equiv="Content-Security-Policy" content="script-src 'self' 'unsafe-inline'">

  7. <link rel="icon" href="data:image/ico;base64,aWNv">

  8. <script>

  9. // 一些全局变量的声明

  10. var __pageFrameStartTime__ = Date.now();

  11. var __webviewId__;

  12. var __wxAppCode__ = {};

  13. var __WXML_GLOBAL__ = {

  14. entrys: {},

  15. defines: {},

  16. modules: {},

  17. ops: [],

  18. wxs_nf_init: undefined,

  19. total_ops: 0

  20. };

  21. // 小程序编译基础库版本

  22. /*v0.6vv_20180125_fbi*/

  23. window.__wcc_version__ = 'v0.6vv_20180125_fbi';

  24. window.__wcc_version_info__ = {

  25. "customComponents": true,

  26. "fixZeroRpx": true,

  27. "propValueDeepCopy": false

  28. };

  29. var $gwxc

  30. var $gaic = {}

  31. $gwx = function(path, global) {

  32. // $gwx 方法定义(最核心)

  33. }

  34. var BASE_DEVICE_WIDTH = 750;

  35. var isIOS = navigator.userAgent.match("iPhone");

  36. var deviceWidth = window.screen.width || 375;

  37. var deviceDPR = window.devicePixelRatio || 2;

  38. function checkDeviceWidth() {

  39. // checkDeviceWidth 方法定义

  40. }

  41. checkDeviceWidth()

  42. var eps = 1e-4;

  43. function transformRPX(number, newDeviceWidth) {

  44. // transformRPX 方法定义

  45. }

  46. var setCssToHead = function(file, _xcInvalid) {

  47. // setCssToHead 方法定义

  48. }

  49. setCssToHead([])(); // 先清空 Head 中的 CSS

  50. setCssToHead([...]); // 设置 app.wxss 的内容到 Head 中,其中 ... 为小程序工程中 app.wxss 的内容

  51. var __pageFrameEndTime__ = Date.now()

  52. </script>

  53. </head>

  54. <body>

  55. <div></div>

  56. </body>

  57. </html>

相比其他文件,page-frame.html 比较复杂,微信把 .wxml 和部分 .wxss 直接“编译”并混淆成 JS 代码放入上述文件中,然后通过调用这些 JS 代码来构造 Virtual-Dom,进而渲染页面。

其中最核心的是 $gwx 和 setCssToHead 这两个方法。

$gwx 用于通过 JS 代码生成所有 .wxml 文件,其中每个 .wxml 文件的内容结构都在 $gwx 方法中被定义好并混淆了,我们只要传给它页面的 .wxml 路径参数,即可获取到每个 .wxml 的内容,再简单加工一下即可还原成“编译”前的内容。

在 $gwx 中有一个 x 数组用于存储当前小程序都有哪些 .wxml 文件,例如,“知识小集”小程序的 x 值如下:

var x = ['./pages/detail/detail.wxml', '/towxml/entry.wxml', './pages/index/index.wxml', './pages/search/search.wxml', './towxml/entry.wxml', '/towxml/renderTemplate.wxml', './towxml/renderTemplate.wxml'];

此时我们可以在 Chrome 中打开 page-frame.html 文件,然后在 Console 中输入如下命令,即可得到 index.wxml 的内容(输出一个 JS 对象,通过遍历这个对象即可还原出 .wxml 的内容)

$gwx("./pages/index/index.wxml")

setCssToHead 方法用于根据几段被拆分的样式字符串数组生成 .wxss 代码并设置到 HTML 的 Head 中,同时,它还将所有被 import 引用的 .wxss 文件(公共 xxxCommon.wxss样式文件)所对应的样式数组内嵌在该方法中的 _C 变量中,并标记哪些文件引用了 _C 中数据。另外在 page-frame.html 文件的末尾,调用了该方法生成全局 app.wxss 的内容设置到 Head中。

因此,我们可以在每个调用 setCssToHead 方法的地方提取相应 .wxss 的内容并还原。

对于 page-frame.html 文件中 $gwx 和 setCssToHead 这两个方法更详细的分析,可以参考这篇文章。

此外,checkDeviceWidth 方法顾明思议,用于检测屏幕的宽度,其检测结果将用于 transformRPX 方法中将 rpx 单位转换为 px 像素。

rpx 的全称是 responsive pixel,它是小程序自己定义的一个尺寸单位,可以根据当前设备屏幕宽度进行自适应。小程序中规定,所有的设备屏幕宽度都为 750rpx,根据设备屏幕实际宽度的不同,1rpx所代表的实际像素值也不一样。

*.html

上面提到,每个页面的 page.wxss 样式文件,“编译”后将分别在各自的所在路径下生成一个 page.html 文件,每个 page.html 的结构如下:

  1. <style></style>

  2. <page></page>

  3. <script>

  4. var __setCssStartTime__ = Date.now();

  5. setCssToHead([...])() // 设置 search.wxss 的内容

  6. var __setCssEndTime__ = Date.now();

  7. document.dispatchEvent(new CustomEvent("generateFuncReady", {

  8. detail: {

  9. generateFunc: $gwx('./pages/search/search.wxml')

  10. }

  11. }))

  12. </script>

在该文件中通过调用 setCssToHead 方法将 .wxss 样式内容设置到 Head 中,所以同样地,我们可以根据 setCssToHead 的调用参数提取每个页面的 page.wxss

资源文件

小程序工程中的图片、音频等资源文件在“编译”后将直接被拷贝到 .wxapkg 包中,其原始的路径也保留不变,因此我们可以直接使用。

“反编译”

在上一节,我们完成了 .wxapkg 包几乎所有文件内容的简要分析。现在我们介绍一下如何通过 node.js 脚本帮我们还原出小程序的源码。

在这里需要再次感谢 wxappUnpacker 作者提供的还原工具,让我们可以“站在巨人的肩膀上”轻松地去完成“反编译”。它的使用如下:

  • node wuConfig.js <path/to/app-config.json> : 将 app-config.json 中的内容拆分成各个页面所对应的 page.json和 app.json

  • node wuJs.js <path/to/app-service.js> : 将 app-service.js 拆分成一系列原先独立的 JS 文件,并使用 Uglify-ES美化工具尽可能将代码还原为“编译”前的内容;

  • node wuWxml.js [-m] <path/to/page-frame.html> : 从 page-frame.html 中提取并还原各页面的 .wxml 和 app.wxss及公共 .wxss 样式文件;

  • node wuWxss.js <path/to/unpack_dir> : 该命令参数为 .wxapkg 解包后目录,它将分析并从各个 page.html 中提取还原各页面的 page.wxss 样式文件;

同时,作者还提供了一键解包并还原的脚本,你只需要提供一个小程序的 .wxapkg 文件,然后执行如下命令:

node wuWxapkg.js [-d] <path/to/.wxapkg>

此脚本就会自动将 .wxapkg 文件解包,并将包中相关的已被“编译/混淆”的文件自动地恢复原状(包括目录结构)。

PS: 此工具依赖 uglify-esvm2esprimacssbeautifycss-tree 等 node.js 包,所以你可能需要 npm install xxx 安装这些依赖包才能正确执行。

更详细的用法及相关问题请查阅该开源项目的 GitHub repo。

最后,我们在 微信开发者工具 中新建一个空小程序工程,并将上述还原后的相关目录文件导入工程,即可编译运行起来,如下图为“知识小集”小程序的 .wxapkg 包还原后的代码工程:

以上,大功告成!

总结

本文详细分析了 .wxapkg 解包后的各文件结构,并介绍了如何通过脚本“一键还原”得到任意小程序的源码。

对于一些简单的,且使用微信官方介绍的原生开发方式开发的小程序,用上述工具基本可以直接还原得到可运行的源码,但是对于一些逻辑复杂,或者使用 WePYVue 等一些框架开发的小程序,还原后的源码可能会有一些小问题,需要我们人肉去分析解决。

后续

本文对小程序源码“编译”后的各文件内容结构及用途的分析相对比较零散,而且没有对各文件的依赖关系及加载逻辑进行研究,后续我们再写一些文章讲解微信客户端是如何解析加载小程序 .wxapkg 包并运行起来。

微信反编译(二)源码还原相关推荐

  1. 关于微信小程序反编译获取源码

    记录一下微信小程序如果反编译获取源码 需要用到的工具: 1.获取root权限的安卓手机,或者直接用模拟器(推荐用网易mumu模拟器,不推荐用夜神) mumu模拟器下载地址:   http://mumu ...

  2. vue代码可以反编译吗_避免小程序被反编译获取源码(转载)

    原地址:https://www.cnblogs.com/taltao/p/10082084.html 众所周知,微信小程序的代码安全性很弱,很容易被别人反编译获取源码.我自己的小程序也被别人反编译拿到 ...

  3. 微信小程序反编译-获取源码

    1.使用PC端微信找到微信小程序的存储位置 找到文件位置删除applet文件夹下所有文件,方便后期定位自己需要编译的小程序 2. 点击自己需要反编译的小程序 3. 查看微信小程序生成的文件 wx开头的 ...

  4. 爬去微信小程序服务器代码,微信小程序反编译及源码抓取(2021最新)

    网易 Mumu 模拟器安装 下载完安装包后直接安装 打开模拟器 安装 微信.RE文件管理器 设置Root 权限 打开 RE文件管理器,提示请求超级用户访问权限,选择允许 到此准备工作完成. 打开微信, ...

  5. 微信小程序反编译及源码抓取(2021最新)

    网易 Mumu 模拟器安装 下载地址: http://mumu.163.com/360/ 下载完安装包后直接安装 打开模拟器 安装 微信.RE文件管理器 设置Root 权限 打开 RE文件管理器,提示 ...

  6. 工具(一):微信小程序反编译获取源码 NoxAppPlayer + wxappUnpacker、ADB + tesseract OCR 读取

    NoxAppPlayer wxappUnpacker ADB tesseract OCR NoxAppPlayer + wxappUnpacker NoxAppPlayer 下载 NoxAppPlay ...

  7. android apk反编译(获取源码,资源文件等)

    android apk反编译,是让你可以去借鉴别人的应用是怎么开发的,那些漂亮的动画和精致的布局是怎么实现的 一.当然我们也需要借助工具: apktool (资源文件获取,可以提取出图片文件和布局文件 ...

  8. 如何使用ILSpy 把发布版本反编译成源码

    有时候,看法别人写的代码比较好,想看看他们的代码到底是如何写的,于是就找方法,看看能否把发布版本变成源码.后来终于发现一个词"反编译",我终于知道怎么办了. 工具:ILSpy   ...

  9. Ubuntu下反编译apk源码查看

    1.下载dex2jar和jd-gui工具 下载dex2jar与jd-gui工具 2.解压dex2jar和jd-gui工具   # ./dex2jar.sh ~/xxx.apk //会反编译出class ...

  10. 反编译查看源码dex2jar

    为什么80%的码农都做不了架构师?>>>    上次说到了用apktool反编译,这次我们来用dex2jar 把apk解压得到文件夹 文件夹打开看到这些文件 其中这个classes. ...

最新文章

  1. OpenCV——图像的平移旋转
  2. redis bind多个ip_Spring Boot 中 Redis 的使用
  3. CVPR2020 | 参数量减半,北大开源全新高效空域转换模块,还原图像逼真细节
  4. linux 4.1.16 ftrace 进程调度,Linux内核进程调度overview(1)
  5. j2ee核心模式_Operator和Sidecar正在成为软件交付新模式
  6. Hadoop 系列之 Hive
  7. 2019-12-02 调用C++高精度时钟 std::chrono::high_resolution_clock的方法
  8. 17、关于hibernate的N+1问题
  9. tensorflow学习笔记1:batch normalization 用法
  10. 虚拟机中centos7找不到vim命令的解决方法
  11. 单因素方差分析 OR 重复测量方差分析
  12. 即时聊天通讯软件安卓+ios双端原生源码
  13. 【CodeWars】Path Finder #2: shortest path
  14. Unity 3D委托entrust
  15. 和平精英服务器维护怎么办,和平精英登陆不了怎么办 和平精英无法登陆解决方法...
  16. android 软件备份工具,android备份软件 知乎 备份软件 知乎
  17. 浙江小学python教材_PPT、H5、Python、大数据……浙江中小学新教材9月投用!
  18. [转帖]我的2006
  19. 大学英语四级考试试题及参考答案
  20. HDU 4986 Little Pony and Alohomora Part I(递推+犹拉常数)

热门文章

  1. 计算机处理器的hz越大越好,cpu主频越高越好吗,教您CPU处理器主频率越高越好吗...
  2. hdwiki的php架构,关于HDWiki的安装踩坑
  3. 12V高耐压带ovp保护1A线性锂电池充电芯片DFN封装4056芯片
  4. 美国不道德的人体实验
  5. 计算机run常用命令,Run-Command
  6. bigdecimal 平均数_用Java计算平均值
  7. 变量、函数、流程控制与游标
  8. 同城信息发布小程序开发制作
  9. 阿里小程序亮相2019上海云峰会:大生态促成许多“小而美”
  10. 工测中坐标方位角的计算c语言,工程测量中坐标方位角是怎么进行推算的?