网友真实露脸自拍10p,成人国产精品秘?久久久按摩,国产精品久久久久久无码不卡,成人免费区一区二区三区

小程序模板網

教你如何反編譯微信小程序

發布時間:2018-05-08 14:15 所屬欄目:小程序開發教程

前言

眾所周知,“跳一跳”在前幾個月很火,并且出現了包括通過規則匹配/機器學習得到關鍵點坐標后模擬點擊和通過源碼獲知加密方式偽造請求等方法。后者提到了如何獲取含有源碼的程序包 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 這樣的工具進行美化,也無法還原一些原始變量名。

wxss

所有在 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);
    };
};

閱讀這段代碼可知,它把 wxss 代碼拆分成幾段數組,數組中的內容可以是一段將要作為 css 文件的字符串,也可以是一個表示 這里要添加一個公共后綴 或 這里要包含另一段代碼 或 要將以 wxss 專供的 rpx 單位表達的數字換算成能由瀏覽器渲染的 px 單位所對應的數字 的數組。

同時,它還將所有被 import 引用的 wxss 文件所對應的數組內嵌在該函數中的 _C 變量中。

我們可以修改setCssToHead,然后執行所有的setCssToHead,第一遍先判斷出 _C 變量中所有的內容是哪個要被引用的 wxss 提供的,第二遍還原所有的 wxss。值得注意的是,可能出于兼容性原因,微信為很多屬性自動補上含有-webkit-開頭的版本,另外幾乎所有的 tag 都加上了wx-前綴,并將page變成了body。通過一些 CSS 的 AST ,例如 CSSTree,我們可以去掉這些東西。

json

app-config.json 中的page對象內就是其他各頁面所對應的 json , 直接還原即可,余下的內容便是 app.json 中的內容了,除了格式上要作相應轉換外,微信還將iconPath的內容由原先指向圖片文件的地址轉換成iconData中圖片內容的 base64 編碼,所幸原來的圖片文件仍然保留在包內,通過比較iconData中的內容和其他包內文件,我們找到原始的iconPath。

wxs

在 page-frame.html 中,我們找到了這樣的內容

f_['a/comm.wxs'] = nv_require("p_a/comm.wxs");
function np_0(){var nv_module={nv_exports:{}};nv_module.nv_exports = ({nv_bar:nv_some_msg,});return nv_module.nv_exports;}
 
f_['b/comm.wxs'] = nv_require("p_b/comm.wxs");
function np_1(){var nv_module={nv_exports:{}};nv_module.nv_exports = ({nv_bar:nv_some_msg,});return nv_module.nv_exports;}
 
f_['b/index.wxml']={};
f_['b/index.wxml']['foo'] =nv_require("m_b/index.wxml:foo");
function np_2(){var nv_module={nv_exports:{}};var nv_some_msg = "hello world";nv_module.nv_exports = ({nv_msg:nv_some_msg,});returnnv_module.nv_exports;}
f_['b/index.wxml']['some_comms'] =f_['b/comm.wxs'] || nv_require("p_b/comm.wxs");
f_['b/index.wxml']['some_comms']();
f_['b/index.wxml']['some_commsb'] =f_['a/comm.wxs'] || nv_require("p_a/comm.wxs");
f_['b/index.wxml']['some_commsb']();

可以看出微信將內嵌和外置的 wxs 都轉譯成np_%d函數,并由f_數組來描述他們。轉譯的主要變換是調用的函數名稱都加上了nv_前綴。在不嚴謹的場合,我們可以直接通過文本替換去除這些前綴。

wxml

相比其他內容,這一段比較復雜,因為微信將原本 類 xml 格式的 wxml 文件直接編譯成了 js 代碼放入 page-frame.html 中,之后通過調用這些代碼來構造 virtual-dom,進而渲染網頁。

首先,微信將所有要動態計算的變量放在了一個由函數構造的z數組中,構造部分代碼如下:

(function(z){var a=11;function Z(ops){z.push(ops)}
Z([3,'index']);
Z([[8],'text',[[4],[[5],[[5],[[5],[1,1]],[1,2]],[1,3]]]]);
})(z);

其實可以將[[id],xxx,yyy]看作由指令與操作數的組合。注意每個這樣的數組作為指令所產生的結果會作為外層數組中的操作數,這樣可以構成一個樹形結構。通過將遞歸計算的過程改成拼接源代碼字符串的過程,我們可以還原出每個數組所對應的實際內容。下文中,將這個數組中記為z。

然后,對于 wxml 文件的結構,可以將每種可能的 js 語句拆分成 指令 來分析,這里可以用到 Esprima 這樣的 js 的 AST 來簡化識別操作,可以很容易分析出以下內容,例如:

  • var {name}=_n('{tag}') 創建名稱為{name}, tag 為{tag}的節點。

  • _r({name},'{attrName}',{id},e,s,gg)  將{name}的{attrName}屬性修改為z[{id}]的值。

  • _({parName},{name})  將{name}作為{parName}的子節點。

  • var {name}=_o({id},..,..,..)  創建名稱為{name},內容為z[{id}]的文本節點。

  • var {name}=_v() 創建名稱為{name}的虛節點( wxml 里恰好提供了功能相當的虛結點block, 這句話相當于var {name}=_n('block'))。

  • var {name}=_m('{tag}',['{attrName1}',{id1},'{attrName2}',{id2},...],[],..,..,..)  創建名稱為{name}, tag 為{tag}的節點,同時將{attrNameX}屬性修改為z[f({idX})]的值(f定義為{idX}與{base}的和;{base}初始為0,f返回的第一個正值后{base}即改為該返回值;若返回負值,表示該屬性無值)。

  • return {name}  名稱為{name}的節點設為主節點。

  • cs.*** 調試用語句,無視之。

此外wx:if結構和wx:for可做遞歸處理。例如,對于如下wx:if結構:

var {name}=_v()
_({parName},{name})
if(_o({id1},e,s,gg)){oD.wxVkey=1
//content1
}
else if(_o({id2},e,s,gg)){oD.wxVkey=2
//content2
}
else{oD.wxVkey=3
//content3
}

相當于將以下節點放入{parName}節點下(z[{id1}]應替換為對應的z數組中的值):

<block wx:if="z[{id1}]">
    <!--content1-->
</block>
<block wx:elif="z[{id2}]">
    <!--content2-->
</block>
<block wx:else>
    <!--content3-->
</block>

具體實現中可以將遞歸時創建好多個block,調用子函數時指明將放入{name}下(_({name},{son}))識別為放入對應{block}下。wx:for也可類似處理,例如:

var {name}=_v()
_({parName},{name})
var {funcName}=function(..,..,{fakeRoot},..){
//content
return {fakeRoot}
}
aDB.wxXCkey=2
_2({id},{funcName},..,..,..,..,'{item}','{index}','{key}')

對應(z[{id1}]應替換為對應的z數組中的值):

<view wx:for="{z[{id}]}" wx:for-item="{item}" wx:for-index="{index}" wx:key="{key}">
    <!--content-->
</view>

調用子函數時指明將放入 {fakeRoot}下(_({fakeRoot},{son})) 識別為放入{name}下。

除此之外,有時我們還要將一組代碼標記為一個指令,例如下面:

var lK=_v()
_({parName},lK)
var aL=_o({isId},e,s,gg)
var tM=_gd(x[0],aL,e_,d_)
if(tM){
var eN=_1({dataId},e,s,gg) || {}
var cur_globalf=gg.f
lK.wxXCkey=3
tM(eN,eN,lK,gg)
gg.f=cur_globalf
}
else _w(aL,x[0],11,26)

對應于{parName}下添加如下節點:

<template is="z[{isId}]" data="z[{dataId}]"></template>

還有import和include的代碼比較分散,但其實只要抓住重點的一句話就可以了,例如:

var {name}=e_[x[{to}]].i
//Other code
_ai({name},x[{from}],e_,x[{to}],..,..)
//Other code
{name}.pop()

對應與(其中的x是直接定義在 page-frame.html 中的字符串數組):

<import src="x[{from}]" />

而include類似:

var {name}=e_[x[0]].j
//Other code
_ic(x[{from}],e_,x[{to}],..,..,..,..);
//Other code
{name}.pop()

對應與:

<include src="x[{from}]" />

可以看到我們可以在處理時忽略前后兩句話,把中間的_ic和_ai處理好就行了。

通過解析 js 把 wxml 大概結構還原后,可能相比編譯前的 wxml 顯得臃腫,可以考慮自動簡化,例如:

<block wx:if="xxx">
    <view>
        <!--content-->
    </view>
</block>

可簡化為:

<view wx:if="xxx">
    <!--content-->
</view>

這樣,我們完成了幾乎所有 wxapkg包 內容的還原。

工具

 

參考鏈接

  1. 如何獲得微信小游戲跳一跳源碼

  2. 微信小程序(.wxapkg)解包心得以及未開放API猜測

  3. 微信小程序源碼閱讀筆記1

本文由看雪論壇 qwertyaa 原創 

轉載請注明來自看雪社區

往期熱門閱讀:

  • 看雪學院招募看雪講師

  • Windows是如何將64位Ntdll映射到32位進程的

  • 使用Unicorn Engine繞過混淆完成算法的調用

  • 使用 Frida 逆向分析 Android 應用與 BLE 設備的通信

  • 記一次linux(被)入侵

點擊閱讀原文/read,

更多干貨等著你~



易優小程序(企業版)+靈活api+前后代碼開源 碼云倉庫:starfork
本文地址:http://www.xiuhaier.com/wxmini/doc/course/24336.html 復制鏈接 如需定制請聯系易優客服咨詢:800182392 點擊咨詢
QQ在線咨詢
AI智能客服 ×