詳解升訊威在線客服系統前端 JavaScript 腳本加密技術(1)

我在業餘時間開發維護了一款免費開源的升訊威在線客服系統,也收穫了許多用戶。對我來說,只要能獲得用戶的認可,就是我最大的動力。

這段時間有幾個技術小夥伴問了我一個有意思的問題:“你的前端腳本是怎麼加密的?”

我決定寫帖子來分享這個問題的答案。

在線客服系統訪客端:

在線客服系統客服端:


免費在線使用 & 免費私有化部署:https://kf.shengxunwei.com


視頻實拍:演示升訊威在線客服系統在網絡中斷,直接禁用網卡,拔掉網線的情況下,也不丟消息,不出異常。
https://blog.shengxunwei.com/Home/Post/fe432a51-337c-4558-b9e8-347b58cbcd53


首先打開客服系統官方網站:https://kf.shengxunwei.com 然後查看嵌入的客服系統 JavaScript 文件,接着,用瀏覽器訪問嵌入站點的 JavaScript 文件。可以看到,文件是加密和壓縮過的:

然後我們通過格式化工具,格式化看看:

所有的變量名都經過了混淆:

所有的函數名都經過了混淆:

那麼這是怎麼做到的呢?我打算分成幾篇不同的博文,循序漸進的解答這個問題,如何混淆前端 JavaScript 代碼。

JavaScript 語法解析

JavaScript 是如何執行的

對於常見編譯型語言(例如:Java)來說,編譯步驟分爲:詞法分析->語法分析->語義檢查->代碼優化和字節碼生成。

對於解釋型語言(例如 JavaScript)來說,通過詞法分析 -> 語法分析 -> 語法樹,就可以開始解釋執行了。

具體過程是這樣的:

1.詞法分析是將字符流(char stream)轉換爲記號流(token stream)

NAME "AST"  
EQUALS  
NAME "is Tree"  
SEMICOLON

2.語法分析成 AST (Abstract Syntax Tree)。

3.預編譯,當JavaScript引擎解析腳本時,它會在預編譯期對所有聲明的變量和函數進行處理!並且是先預聲明變量,再預定義函數!

4.解釋執行,在執行過程中,JavaScript 引擎是嚴格按着作用域機制(scope)來執行的,並且 JavaScript 的變量和函數作用域是在定義時決定的,而不是執行時決定的。JavaScript 中的變量作用域在函數體內有效,無塊作用域;

function func(){
    for(var i = 0; i < array.length; i++){  
      //do something here.  
    }
    //此時 i 仍然有值,及 i == array.length  
    console.log(i); // 但在 java 語言中,則無效
}

JavaScript 引擎通過作用域鏈(scope chain)把多個嵌套的作用域串連在一起,並藉助這個鏈條幫助 JavaScript 解釋器檢索變量的值。這個作用域鏈相當於一個索引表,並通過編號來存儲它們的嵌套關係。當 JavaScript 解釋器檢索變量的值,會按着這個索引編號進行快速查找,直到找到全局對象(global object)爲止,如果沒有找到值,則傳遞一個特殊的 undefined 值。

var scope = "global";
scopeTest();
function scopeTest(){  
    console.log(scope);  
    var scope = "local";  
    console.log(scope); 
}
打印結果:undefined,local;

我們常說的 V8 是 Google 發佈的開源 JavaScript 引擎,採用 C++ 編寫。SpiderMonkey(Mozilla,基於 C)、Rhino(Mozilla,基於 Java),而 Nodejs 依賴於 V8 引擎開發,接下來的內容是 JavaScript 在 V8 引擎中的運行狀態,而類似的 JavaScript 現代引擎對於這些實現大同小異。

在本文的開頭提到了編譯型語言,解釋型語言。JavaScript 是解釋型語言且弱類型,在生成 AST 之後,就開始一邊解釋,一邊執行,但是有個弊端,當某段代碼被多次執行時,它就有了可優化的空間(比如類型判斷優化),而不用一次次的去重複之前的解釋執行。 編譯型語言如 JAVA,可以在執行前就進行優化編譯,但是這會耗費大量的時間,顯然不適用於 Web 交互。

於是就有了,JIT(Just-in-time),JIT 是兩種模式的混合。

它是如何工作的呢:

1.在 JavaScript 引擎中增加一個監視器(也叫分析器)。監視器監控着代碼的運行情況,記錄代碼一共運行了多少次、如何運行的等信息,如果同一行代碼運行了幾次,這個代碼段就被標記成了 “warm”,如果運行了很多次,則被標記成 “hot”。

2.(基線編譯器)如果一段代碼變成了 “warm”,那麼 JIT 就把它送到基線編譯器去編譯,並且把編譯結果存儲起來。比如,監視器監視到了,某行、某個變量執行同樣的代碼、使用了同樣的變量類型,那麼就會把編譯後的版本,替換這一行代碼的執行,並且存儲。

3.(優化編譯器)如果一個代碼段變得 “hot”,監視器會把它發送到優化編譯器中。生成一個更快速和高效的代碼版本出來,並且存儲。例如:循環加一個對象屬性時,假設它是 INT 類型,優先做 INT 類型的判斷

4.(去優化)可是對於 JavaScript 從來就沒有確定這麼一說,前 99 個對象屬性保持着 INT 類型,可能第 100 個就沒有這個屬性了,那麼這時候 JIT 會認爲做了一個錯誤的假設,並且把優化代碼丟掉,執行過程將會回到解釋器或者基線編譯器,這一過程叫做去優化。

總結

明白了基本原理之後,接下來就是如何執行混淆的過程了,

未完待續。


免費在線使用 & 免費私有化部署:https://kf.shengxunwei.com


視頻實拍:演示升訊威在線客服系統在網絡中斷,直接禁用網卡,拔掉網線的情況下,也不丟消息,不出異常。
https://blog.shengxunwei.com/Home/Post/fe432a51-337c-4558-b9e8-347b58cbcd53


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章