免费国产欧美国日产_少妇AV一区二区三区无码_蜜桃精品av无码喷奶水小说_jk18禁网站视频_精产国品一二三级产品区别_被夫の上司に犯波多野结衣_78m成人手机免费看_最爽最刺激18禁视频_偷偷色噜狠狠狠狠的777米奇

易優(yōu)GEO 重磅上線 ~ 一站式GEO優(yōu)化工具,讓豆包、文心一言、DeepSeek 在回答中主動推薦你的品牌,搶占AI流量入口!  點擊查看

小程序模板網(wǎng)

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

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

前言

眾所周知,“跳一跳”在前幾個月很火,并且出現(xiàn)了包括通過規(guī)則匹配/機器學習得到關(guān)鍵點坐標后模擬點擊和通過源碼獲知加密方式偽造請求等方法。后者提到了如何獲取含有源碼的程序包 wxapkg ,以及使其能夠在微信開發(fā)者工具中具體步驟(見參考鏈接1)。

當時我在對其他微信小程序應用進行嘗試的時候發(fā)現(xiàn),他們不同于小游戲,解包后的文件并不能通過簡單增改就直接在微信開發(fā)者工具中運行,于是對小程序源代碼=>wxapkg包內(nèi)文件的具體轉(zhuǎn)換關(guān)系進行了一定研究。

正文

由前文知,我們可以通過查看 Android 手機中的 /data/data/com.tencent.mm/MicroMsg/{User}/appbrand/pkg({User} 為當前用戶的用戶名,類似于2bc**************b65)文件夾,獲取最近使用過的微信小程序所對應的 wxapkg 包文件。

通過簡單分析知,這個包由文件名+文件內(nèi)容起始地址及長度信息開頭,且各個文件明文存放在包內(nèi),通過類似于 https://gist.github.com/feix/32ab8f0dfe99aa8efa84f81ed68a0f3e 的腳本(這一個腳本處理包內(nèi)二進制文件時有個小 bug ,將第78行的 w 改成 wb 即可),我們可以輕易獲取包內(nèi)文件。(具體解包細節(jié)可見于參考鏈接3)

但是這個包中的文件內(nèi)容主要如下:

app-config.json

app-service.js

page-frame.html

其他一堆放在各文件夾中的.html文件

和源碼包內(nèi)位置和內(nèi)容相同的圖片等資源文件

微信開發(fā)者工具并不能識別這些文件,它要求我們提供由wxml/wxss/js/wxs/json組成的源碼才能進行模擬/調(diào)試。

js

注意到app-service.js中的內(nèi)容由

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函數(shù)就可以將這些 js 文件恢復到源碼中所對應的位置。當然,這些 js 文件中的內(nèi)容經(jīng)過壓縮,即使使用 UglifyJS 這樣的工具進行美化,也無法還原一些原始變量名。

wxss

所有在 wxapkg 包中的 html 文件都調(diào)用了setCssToHead函數(shù),其代碼如下

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 代碼拆分成幾段數(shù)組,數(shù)組中的內(nèi)容可以是一段將要作為 css 文件的字符串,也可以是一個表示 這里要添加一個公共后綴 或 這里要包含另一段代碼 或 要將以 wxss 專供的 rpx 單位表達的數(shù)字換算成能由瀏覽器渲染的 px 單位所對應的數(shù)字 的數(shù)組。

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

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

json

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

wxs

在 page-frame.html 中,我們找到了這樣的內(nèi)容

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']();

可以看出微信將內(nèi)嵌和外置的 wxs 都轉(zhuǎn)譯成np_%d函數(shù),并由f_數(shù)組來描述他們。轉(zhuǎn)譯的主要變換是調(diào)用的函數(shù)名稱都加上了nv_前綴。在不嚴謹?shù)膱龊?,我們可以直接通過文本替換去除這些前綴。

wxml

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

首先,微信將所有要動態(tài)計算的變量放在了一個由函數(shù)構(gòu)造的z數(shù)組中,構(gòu)造部分代碼如下:

(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]看作由指令與操作數(shù)的組合。注意每個這樣的數(shù)組作為指令所產(chǎn)生的結(jié)果會作為外層數(shù)組中的操作數(shù),這樣可以構(gòu)成一個樹形結(jié)構(gòu)。通過將遞歸計算的過程改成拼接源代碼字符串的過程,我們可以還原出每個數(shù)組所對應的實際內(nèi)容。下文中,將這個數(shù)組中記為z。

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

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

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

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

  • var {name}=_o({id},..,..,..)  創(chuàng)建名稱為{name},內(nèi)容為z[{id}]的文本節(jié)點。

  • var {name}=_v() 創(chuàng)建名稱為{name}的虛節(jié)點( wxml 里恰好提供了功能相當?shù)奶摻Y(jié)點block, 這句話相當于var {name}=_n('block'))。

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

  • return {name}  名稱為{name}的節(jié)點設為主節(jié)點。

  • cs.*** 調(diào)試用語句,無視之。

此外wx:if結(jié)構(gòu)和wx:for可做遞歸處理。例如,對于如下wx:if結(jié)構(gòu):

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
}

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

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

具體實現(xiàn)中可以將遞歸時創(chuàng)建好多個block,調(diào)用子函數(shù)時指明將放入{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數(shù)組中的值):

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

調(diào)用子函數(shù)時指明將放入 {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}下添加如下節(jié)點:

<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 中的字符串數(shù)組):

<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 大概結(jié)構(gòu)還原后,可能相比編譯前的 wxml 顯得臃腫,可以考慮自動簡化,例如:

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

可簡化為:

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

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

工具

 

參考鏈接

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

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

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

本文由看雪論壇 qwertyaa 原創(chuàng) 

轉(zhuǎn)載請注明來自看雪社區(qū)

往期熱門閱讀:

  • 看雪學院招募看雪講師

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

  • 使用Unicorn Engine繞過混淆完成算法的調(diào)用

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

  • 記一次linux(被)入侵

點擊閱讀原文/read,

更多干貨等著你~



易優(yōu)小程序(企業(yè)版)+靈活api+前后代碼開源 碼云倉庫:starfork
本文地址:http://www.szcjxy.com/wxmini/doc/course/24336.html 復制鏈接 如需定制請聯(lián)系易優(yōu)客服咨詢: 點擊咨詢
在線客服