自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

小程序不讓用 JS 解釋器?那我再杠一次鵝廠

開(kāi)發(fā) 前端
為什么我們要將 JavaScript 編譯成字節(jié)碼呢?我們的目的是為了繞過(guò)微信小程序的代碼審核限制,所以我們要想盡辦法隱藏兩樣?xùn)|西。

前言

6月23號(hào)的時(shí)候,微信團(tuán)隊(duì)發(fā)了如下通知將禁止小程序使用 JavaScript 解釋來(lái)動(dòng)態(tài)更新代碼。消息一出,小程序開(kāi)發(fā)者們哀嚎哀嚎遍野,更有人聲稱(chēng)要開(kāi)始加班改代碼了。

自 2018年1月,我寫(xiě)下 「brambles:微信小程序也要強(qiáng)行熱更代碼」 (https://zhuanlan.zhihu.com/p/34191831) 這篇文章開(kāi)始,就帶起來(lái)在小程序里面用 JavaScript 解釋器的潮流。然而四年過(guò)去了,微信小程序終于明文規(guī)定不在讓用 JavaScript 解釋器了,那小程序熱更的時(shí)代是不是就過(guò)去了?

圖片

當(dāng)然不是,如果就這樣過(guò)去了也就沒(méi)我這篇文章了,其實(shí)早在四年前寫(xiě)前一篇文章的時(shí)候我就已經(jīng)想好解決方案,只是我是沒(méi)想到的一年以后微信小程序才開(kāi)始封 JavaScript 解釋器讓我之前設(shè)想的方案一直拖到四年后的今天。n那么今天我們這篇文章主要就討論兩個(gè)點(diǎn):

如何突破微信小程序限制 JavaScript 解釋器使用進(jìn)行熱更代碼。

為什么從理論上無(wú)法從根本上禁止小程序代碼的熱更。

基本步驟 & 最終效果

示例代碼 Github 倉(cāng)庫(kù):https://github.com/bramblex/jsjs-vm-demo

我們首先要寫(xiě)一個(gè) JavaScript 的編譯器,將 JavaScript 代碼編譯成二進(jìn)制的字節(jié)碼。

找一張圖片,將字節(jié)碼編碼并隱藏進(jìn)圖片中。

在小程序中引入藏有 JavaScript 字節(jié)碼的圖片,并且解碼出字節(jié)碼。

寫(xiě)一個(gè)對(duì)應(yīng)的字節(jié)碼虛擬機(jī),并且執(zhí)行從圖片中解出的字節(jié)碼。

圖片

實(shí)現(xiàn)一個(gè)字節(jié)碼虛擬機(jī)

為什么我們要將 JavaScript 編譯成字節(jié)碼呢?我們的目的是為了繞過(guò)微信小程序的代碼審核限制,所以我們要想盡辦法隱藏兩樣?xùn)|西。第一個(gè)是要想辦法隱藏解釋器,因?yàn)橐粋€(gè)完整的 JavaScript 解釋器代碼量非常龐大,并且往往都需要引入別人寫(xiě)的庫(kù)沒(méi)辦法自己維護(hù),這樣的解釋往都不需要用什么高深的技術(shù)手段,字符串一匹配就能查出來(lái)個(gè)七七八八。

比如在小程序一個(gè)完整可用的 JavaScript 解釋器引入代碼,起碼需要引入一個(gè)至少 100k 以上的代碼,這個(gè)目標(biāo)實(shí)在是太大了,幾乎很難隱藏。但是在小程序里面引入一個(gè)可以執(zhí)行字節(jié)碼的虛擬機(jī)實(shí)現(xiàn),可以做到只引入 10k 左右,壓縮前總代碼量不超過(guò)千行的代碼,這樣就更容易隱藏動(dòng)態(tài)代碼的實(shí)現(xiàn)。比如我目前實(shí)現(xiàn)的字節(jié)碼虛擬機(jī),除了 try-catch 和 with 以外,能實(shí)現(xiàn) ES5 所有能力的虛擬機(jī)總共才 7k 大小,就這都是還可以再壓縮的。

第二點(diǎn)是我們需要隱藏?zé)岣?JavaScript 代碼不被微信發(fā)現(xiàn),比如你把熱更的大量 JavaScript 代碼通過(guò)接口明文傳輸,只要微信稍微攔截一下你的網(wǎng)絡(luò),這不就全露餡了嗎?所以將代碼編譯成了二進(jìn)制的字節(jié)碼以后,微信就沒(méi)有辦法通過(guò)簡(jiǎn)單的攔截你的接口請(qǐng)求來(lái)確定里面有沒(méi)有 JavaScript 代碼來(lái)判斷你是是否熱更代碼了。二進(jìn)制的文件在你能夠明確清楚它的個(gè)格式之前是沒(méi)辦法準(zhǔn)確接出來(lái)他到底是個(gè)什么東西的1,更何況二進(jìn)制的加密混淆的算法滿大街都是,而且還都沒(méi)有幾行……下面是我實(shí)現(xiàn)字節(jié)碼的指令集,總共只有 50 多個(gè)指令:

export enum OpCode {
NOP = 0x00,
UNDEF = 0x01, NULL = 0x02, OBJ = 0x03, ARR = 0x04, TRUE = 0x05,
FALSE = 0x06, NUM = 0x07, ADDR = 0x08, STR = 0x09, POP = 0x0A,
TOP = 0x0D, TOP2 = 0x0E, VAR = 0x10, LOAD = 0x11, OUT = 0x12,
JUMP = 0x20, JUMPIF = 0x21, JUMPNOT = 0x22, FUNC = 0x30, CALL = 0x31,
NEW = 0x32, RET = 0x33, GET = 0x40, SET = 0x41, IN = 0x43,
DELETE = 0x44, EQ = 0x50, NEQ = 0x51, SEQ = 0x52, SNEQ = 0x53,
LT = 0x54, LTE = 0x55, GT = 0x56, GTE = 0x57, ADD = 0x60,
SUB = 0x61, MUL = 0x62, EXP = 0x63, DIV = 0x64, MOD = 0x65,
BNOT = 0x70, BOR = 0x71, BXOR = 0x72, BAND = 0x73, LSHIFT = 0x73,
RSHIFT = 0x75, URSHIFT = 0x76, OR = 0x80, AND = 0x81, NOT = 0x82,
INSOF = 0x90, TYPEOF = 0x91,
}

以下是將一段示例代碼以及其編譯后的字節(jié)碼:

圖片

JavaScript 代碼與編譯后的字節(jié)碼,這個(gè)字節(jié)碼中還能看到 wx showModal 等字樣

上面字節(jié)碼是以下指令(節(jié)選)的二進(jìn)制表示:

.main_1:
STR(09)
"wx" (00 77 00 78 00 00)
LOAD(11)
TOP(0d)
STR(09)
"showModal" (00 73 00 68 00 6f 00 77 00 4d 00 6f 00 64 00 61 00 6c 00 00)
GET(40)
ARR(04)
TOP(0d)
NUM(07)
0 (00 00 00 00 00 00 00 00)
OBJ(03)
TOP(0d)
STR(09)
"title" (00 74 00 69 00 74 00 6c 00 65 00 00)
STR(09)
"這是一段隱藏在圖片中的代碼" (8f d9 66 2f 4e 00 6b b5 96 90 85 cf 57 28 56 fe 72 47 4e 2d 76 84 4e e3 78 01 00 00)
SET(41)
POP(0a)
TOP(0d)
STR(09)
"content" (00 63 00 6f 00 6e 00 74 00 65 00 6e 00 74 00 00)
STR(09)
"這是一段隱藏在圖片中的代碼" (8f d9 66 2f 4e 00 6b b5 96 90 85 cf 57 28 56 fe 72 47 4e 2d 76 84 4e e3 78 01 00 00)
SET(41)
POP(0a)
TOP(0d)
STR(09)
"success" (00 73 00 75 00 63 00 63 00 65 00 73 00 73 00 00)
NULL(02)
NUM(07)
1 (3f f0 00 00 00 00 00 00)
ADDR(08)
.anonymous_2
FUNC(30)
SET(41)
POP(0a)
SET(41)
POP(0a)
CALL(31)
POP(0a)
RET(33)

畢竟是做個(gè) Demo,如果真的需要實(shí)用的話,還有大量的優(yōu)化空間。比如字節(jié)碼字面量現(xiàn)在都是非常簡(jiǎn)單粗暴直接內(nèi)聯(lián),如果將數(shù)據(jù)和代碼部分區(qū)分可以得到一個(gè)更好的性能。比如字符串的編碼使用的是 utf16 編碼,如果轉(zhuǎn)換成 utf8 編碼可以節(jié)省空間占用等等,這些以后有心情再做。

將字節(jié)碼藏在圖片里

我們說(shuō)需要隱藏虛擬機(jī)和熱更的代碼,但是我們思考一下,一個(gè)普通的小程序整天需要加在二進(jìn)制文件,這一個(gè)行為是不是非常的怪異?沒(méi)錯(cuò),這件事情非常非常的奇怪,因?yàn)橐粋€(gè)正常小程序根本沒(méi)有什么讀寫(xiě)二進(jìn)制文件的需求。但是如果我告訴一個(gè)小程序,需要做一張有小程序二維碼的分享圖給用戶(hù)保存,而且這張分享圖還經(jīng)常需要更新,這不是就非常符合邏輯了?所以我們要將熱更的字節(jié)碼藏在圖片里面,偽裝成一個(gè)正常小程序的行為,并且要保證這場(chǎng)圖片看起來(lái)也是正常的。以下就是我們開(kāi)頭示例中圖片,左圖是原圖片,而右圖是藏了我們上面示例代碼的圖,只有非常仔細(xì)看才能看到細(xì)微的差別。

圖片

仔細(xì)看隱藏了字節(jié)碼的區(qū)域,跟原圖片有細(xì)微的差別

圖片一個(gè)像素點(diǎn)有 RGBA 一共四個(gè) byte,為了最少影響圖片看上去的效果,我們選擇只將字節(jié)碼編碼隱藏在圖片的 Alpha 通道,這里用了最簡(jiǎn)單的編碼方式,將 RGBA 中的 A 當(dāng)成一個(gè) bit 來(lái)進(jìn)行編碼。A 高于 0xF8 則為 1,否則則為 0。編碼和解碼算法如下:

圖片

在編譯器中的編碼算法(左)在小程序中執(zhí)行的解碼算法(右)

在小程序中只需要把圖片畫(huà)在 Canvas 上面,并且逐個(gè)讀取 Alpha 通道上的數(shù)據(jù)就能隱藏在圖片中的字節(jié)碼接解碼出來(lái)。最后通過(guò)我們上一小節(jié)實(shí)現(xiàn)的字節(jié)碼虛擬機(jī),就能執(zhí)行我們想要熱更的代碼了。

為什么無(wú)法從根本上禁止小程序代碼的熱更

先說(shuō)結(jié)論,只要滿足以下兩個(gè)條件,那么從根本上禁止熱更都是無(wú)稽之談:

  • 宿主語(yǔ)言圖靈完備
  • 允許通過(guò)網(wǎng)絡(luò)讀取數(shù)據(jù)

第一,宿主語(yǔ)言如果圖靈完備的話,那么宿主語(yǔ)言就可以實(shí)現(xiàn)任何其他圖靈完備的編程語(yǔ)言。比如 JavaScript 圖靈完備,那么你就能用 JavaScript 實(shí)現(xiàn) JavaScript 解釋器、Python 解釋器、PHP 解釋器等等只要你能想得到的編程語(yǔ)言解釋器,甚至你還可以設(shè)計(jì)一個(gè)自己的比如本文的字節(jié)碼虛擬機(jī)。所以當(dāng)公告一出來(lái)的時(shí)候,樓底下第一個(gè)回復(fù)的朋友就一語(yǔ)道破封 JavaScript 解釋器是一件多么可笑的事情。

圖片

公告發(fā)出來(lái)的第一天,就有朋友在評(píng)論區(qū)中抖機(jī)靈

第二,你可以把一切能夠從得到不同輸入,并且產(chǎn)生不同結(jié)果的程序都稱(chēng)之為解釋器,無(wú)非就是它表達(dá)能力的強(qiáng)與弱、是通用的還是專(zhuān)用的區(qū)別而已,所以這個(gè)界限是非常模糊的。比如我們業(yè)務(wù)中,可能需要程序去服務(wù)器上拉一份配置,這份配置可能是某些功能的開(kāi)關(guān)顯示與否等等,那么這時(shí)候我拉的一份配置文件和拉了一份 JavaScript 代碼動(dòng)態(tài)執(zhí)行有本質(zhì)上的區(qū)別嗎?其實(shí)你也可以理解代碼不過(guò)是一份解釋器/編譯器的配置文件而已,沒(méi)有那么特殊,唯一的區(qū)別僅僅是代碼設(shè)計(jì)通用且復(fù)雜。所以才有那么一句話,代碼既數(shù)據(jù),數(shù)據(jù)既代碼。

寫(xiě)在最后

在文章的最后,要向兩位科學(xué)家致敬。第一位是艾倫·圖靈,提出了圖靈機(jī)奠定了計(jì)算理論的基礎(chǔ)。第二位是香農(nóng),奠定了現(xiàn)代信息論的基礎(chǔ)。感謝巨人們給我們提供的肩膀。

圖片

艾倫·圖靈(左) 克勞德·香農(nóng)(右)

責(zé)任編輯:武曉燕 來(lái)源: Tecvan
相關(guān)推薦

2020-01-08 15:11:28

Python編輯器程序

2020-11-17 06:57:15

存儲(chǔ)互聯(lián)網(wǎng)用戶(hù)

2016-11-28 15:52:09

微信小程序開(kāi)發(fā)

2018-12-04 13:30:28

Javascript編譯原理前端

2021-03-10 08:05:10

Nginx面試并發(fā)

2012-02-01 16:48:54

后門(mén)Putty

2018-09-06 08:46:15

騰訊秋招跳槽

2025-02-28 09:00:00

DeepSeek人工智能AI

2020-12-11 08:42:06

Kona 開(kāi)源JDK

2024-03-27 10:21:47

字符串棧內(nèi)存V8

2022-07-28 10:39:50

OpenApiSwaggerSpringDoc

2011-06-28 10:41:50

DBA

2010-04-28 17:14:52

Google服務(wù)器

2020-11-02 11:23:14

騰訊跳槽百度

2021-12-27 10:08:16

Python編程語(yǔ)言庫(kù)

2020-10-24 13:50:59

Python庫(kù)編程語(yǔ)言

2024-07-09 10:20:05

VueJSX函數(shù)

2018-03-02 10:42:44

服務(wù)器數(shù)據(jù)備份
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)