JavaScript 混淆與逆向必讀之 AST 節點類型名詞基礎

我在《Python3 反爬蟲原理與繞過實戰》一書中給出了「爬蟲與反爬蟲都是綜合技術的應用」、「技術在對抗中進步」這樣的觀點。隨着時間的推移、技術的普及和進步,Web 應用方給爬蟲增長了愈來愈多的限制,其中效果最顯著的就是代碼混淆。html

單純的加密算法或者自定義的字符處理函數已經沒法知足防護需求了, Web 應用方將目光轉移到了代碼混淆技術。代碼混淆有幾個優勢:前端

  • 操做門檻低,有現成可用且免費的混淆產品;
  • 混淆效果好,混淆後真的是連親媽都不認識;
  • 瀏覽器可以正常解析混淆後的代碼,一萬行之內的小規模混淆對性能影響不大;
  • 混淆帶來的性能影響能夠經過其餘優化下降,不慌;

加密算法和字符串處理函數配合代碼混淆,防護力直線上升。舉個簡單例子,一個簡單的字符處理函數以下:git

這裏有三個函數,stringArray 返回一個包含字符的數組對象、mergeArray 將數組對象裏的元素拼接成爲一個字符串並返回、main 調用 stringArray 函數和 mergeArray 函數並打印獲得的字符串,下方的 output 註釋即運行結果。github

這麼清晰明瞭的函數調用,爬蟲工程師能看不懂嗎?算法

咱們看看上面三個函數混淆後的樣子:編程

同樣的功能、同樣的輸出,可是代碼卻徹底不同了,變得不可讀。若是把下面的註釋去掉,那你根本就不知道發生了什麼,也不知道會輸出什麼,這就是代碼混淆給 Web 應用方帶來的防護力。數組

做爲一名爬蟲工程師,你如今有兩個選擇:瀏覽器

  1. 經過一個入口函數強行找出關聯的函數調用,直到鋪滿調用鏈後拿到正確的輸出;
  2. 還原部分混淆,從這堆雜亂無章的代碼中捋清邏輯,再根據複雜度選擇用其餘語言實現或回到第一步;

第一種方法,就是平時爬蟲工程師說的「硬扣」,若是有跨文件的函數調用和冗長複雜的調用鏈,那「硬扣」真的是會掉頭髮的。babel

第二種方法的技術門檻稍微高一些,須要爬蟲工程師懂得 AST 理論,並學會編寫還原代碼,將雜亂無章的代碼閱讀難度下降,從而下降本身閱讀代碼邏輯或者整理調用鏈的難度與成本。編程語言

什麼是 AST?

這裏引用百度百科對 AST 的解釋:

在計算機科學中, 抽象語法樹Abstract Syntax Tree,AST),或簡稱 語法樹(Syntax tree),是源代碼語法結構的一種抽象表示。它以樹狀的形式表現編程語言的語法結構,樹上的每一個節點都表示源代碼中的一種結構。之因此說語法是「抽象」的,是由於這裏的語法並不會表示出真實語法中出現的每一個細節。好比,嵌套括號被隱含在樹的結構中,並無以節點的形式呈現;而相似於 if-condition-then 這樣的條件跳轉語句,可使用帶有兩個分支的節點來表示。

嗯,這看起來有點繞,我打算用一個例子來表述。JavaScript 變量聲明和賦值的代碼示例以下:

var nick = "vansenb";

這一行代碼會被解析成很長的語法樹,具體解析可經過 AST Explorer 查看。如下是 JavaScript 語句和語法樹的對應關係:

圖有點模糊,想看清晰結構的請移步 AST Explorer。

AST 有什麼用?

上圖的語法樹中代表了程序主體、聲明類型、標識符、字面量等信息,由此咱們能夠得出:

  • var - VariableDeclarator 變量聲明;
  • nick - Identifier 標識符;
  • vansenb - Literal 字面量;

從人類閱讀的角度來看,這行代碼:聲明瞭一個名爲 nick 、值爲 vansenb 的變量。

若是你想改變這行代碼,將它變成:

var nick = "James";

只須要改變語法樹中 type 爲 Literal 下的 value 屬性對應的值便可,那麼代碼的語義就變成了:聲明瞭一個名爲 nick 、值爲 James 的變量。瞭解到這一點以後,咱們就能夠思路放在代碼的混淆和還原上面了。

你想一想,當你使用那些一鍵混淆/還原工具的時候,是否是隻須要將代碼粘貼到輸入框並點擊「混淆」按鈕便可獲得混淆後的代碼?並且相同結構的代碼混淆後的結構也是相同的?

這說明一鍵混淆/還原工具經過改變原代碼的抽象語法樹實現混淆/還原的效果,例如在樹的某個節點先後增長或刪除節點,亦或在混淆時將本來直接能夠輸出結果的單個函數轉換爲相互調用的多個函數。

經常使用的 JavaScript AST 解析庫

語法樹並非 JavaScript 獨有的,幾乎全部編程語言都有語法樹,例如 Golang、Python 和 Java。JavaScript 的語法樹出現頻次較高,這是由於 JavaScript 隔代語法的差別和不得不考慮的兼容性形成的,ES5 和 ES6 語法隔代,在實際應用中會須要進行語法的轉換,這就使得語法樹可以在實際場景中發揮做用。

語法樹的做用就像是一個轉接頭,把代碼的表現形式 A 轉換爲表現形式 B

JavaScript 領域經常使用的 AST 解析庫有 babel、esprima、espree 和 acorn 等,各位工程師可根據本身的喜愛和風格選擇趁手的庫。

這些庫經常被前端開發工程師用來編寫代碼轉換的工具或者代碼混淆工具,甚至是將 React 和 Vue 的工程代碼編譯爲瀏覽器能運行的 JavaScript 代碼,而在爬蟲工程師這裏,大機率會用來輔助本身逆向 JavaScript 代碼。

AST 節點類型名詞基礎

語法樹相關的知識和技巧須要必定的時間學習(大概一兩個月),對此感興趣的你能夠經過如下幾篇實戰型文章瞭解它的具體應用:

AST 還原 obfuscator 混淆

操做AST還原混淆代碼基礎系列課程三:十六進制字符串還原

操做AST還原混淆代碼:讓代碼分析變得如此簡單

AST實戰:全自動解密經obfuscator混淆的加密字符串

操做AST還原混淆代碼課程九:還原簡單的CallExpression 類型

上面列舉了經常使用的幾個 AST 解析庫,雖然各個庫解析同一份代碼獲得的結構不徹底一致,但用於表示節點類型的名詞幾乎都是一致的,例如 VariableDeclaration 表明這是變量聲明語句、CallExpression 表明這是調用表達式。

掌握節點類型的名詞,有助於咱們在閱讀語法樹結構時更清晰地瞭解節點的做用和意圖,也能夠說節點名詞是咱們成爲代碼混淆大師或代碼逆向大師的必經之路,很是重要

咱們如下圖的代碼爲例,看看 AST 中經常使用的節點類型名詞有哪些。

上圖代碼包含了 JavaScript 語法中經常使用的語句,例如變量聲明、函數聲明、三元表達式、if 控制流語句、switch 控制流、函數調用、賦值語句、數組聲明、for 循環等。

將上面的代碼複製到 AST Explorer 即可以獲得語法樹,根據左側的代碼和右側的語法樹,咱們能夠統計語法樹節點名詞和具體描述,以下表:

序號 類型原名稱 中文名稱 描述
1 Program 程序主體 整段代碼的主體
2 VariableDeclaration 變量聲明 聲明一個變量,例如 var let const
3 FunctionDeclaration 函數聲明 聲明一個函數,例如 function
4 ExpressionStatement 表達式語句 一般是調用一個函數,例如 console.log()
5 BlockStatement 塊語句 包裹在 {} 塊內的代碼,例如 if (condition){var a = 1;}
6 BreakStatement 中斷語句 一般指 break
7 ContinueStatement 持續語句 一般指 continue
8 ReturnStatement 返回語句 一般指 return
9 SwitchStatement Switch 語句 一般指 Switch Case 語句中的 Switch
10 IfStatement If 控制流語句 控制流語句,一般指 if(condition){}else{}
11 Identifier 標識符 標識,例如聲明變量時 var identi = 5 中的 identi
12 CallExpression 調用表達式 一般指調用一個函數,例如 console.log()
13 BinaryExpression 二進制表達式 一般指運算,例如 1+2
14 MemberExpression 成員表達式 一般指調用對象的成員,例如 console 對象的 log 成員
15 ArrayExpression 數組表達式 一般指一個數組,例如 [1, 3, 5]
16 NewExpression New 表達式 一般指使用 New 關鍵詞
17 AssignmentExpression 賦值表達式 一般指將函數的返回值賦值給變量
18 UpdateExpression 更新表達式 一般指更新成員值,例如 i++
19 Literal 字面量 字面量
20 BooleanLiteral 布爾型字面量 布爾值,例如 true false
21 NumericLiteral 數字型字面量 數字,例如 100
22 StringLiteral 字符型字面量 字符串,例如 vansenb
23 SwitchCase Case 語句 一般指 Switch 語句中的 Case
這只是經常使用的那部分,更多節點類型名詞在你須要用到時再補充便可。我會持續更新相關資料,感興趣的朋友能夠到夜幕團隊的 GitHub 倉庫 https://github.com/NightTeam/... 查看 。

有了這些名詞對照關係以後,咱們閱讀語法樹結構就變得簡單了。當你看到節點 tpye 爲 IfStatement 的時候,你知道後面一定會有至少一個 BlockStatement,即 if (condition){}。

更多關於 AST 理論和實戰的內容請關注夜幕團隊公衆號 NightTeam 或團隊倉庫 https://github.com/NightTeam

相關文章
相關標籤/搜索