Javascript 靜態分析工具 AST (上)

反爬蟲技術須要,最近調研了關於 Javascript 靜態分析相關知識,主要是協助進行 Javascript 的反混淆工做。javascript

本身自己對於 Javascript 的技術掌握接近於 0,在網上也找到很多的反混淆的工具,這裏放出來幾個比較好用的:java

  • prepack 代碼簡化在線工具node

  • jstillery   javascripts 代碼反混淆在線工具git

經過 https://obfuscator.io/ 生成的代碼放到上面的兩個工具中,基本上均可以反混淆出東西來,可是本身遇到的場景與上述仍是有差別的,須要本身定製化一些東西,也就須要對 AST 這個工具比較熟悉,而且可以靈活使用起來。github

AST 是什麼

不管是 javascripts 仍是 Python 的 AST 他們的做用都是類似的,就是把原代碼轉換成 token,這個過程是程序編譯過程當中一個重要的步驟。AST 全稱是抽象語法樹,簡單點理解就是有了這個樹你能夠更加方便的遍歷和修改以前的邏輯了,使用程序修改源代碼遠比使用編輯器更加的快速準確,自動化程度更高,使用抽象語法樹還能夠自動生成源代碼。express

初識 Javascript AST

Javascript AST 有不少的工具,並且不少都很成熟,能夠把源代碼快速成 JSON 對象,一個樣例以下:npm

源代碼:微信

console.log("hello, world")

解析事後的 AST ,使用 https://astexplorer.net/ 生成:編輯器

{
"type": "Program",
"start": 0,
"end": 27,
"body": [
{
"type": "ExpressionStatement",
"start": 0,
"end": 27,
"expression": {
"type": "CallExpression",
"start": 0,
"end": 27,
"callee": {
"type": "MemberExpression",
"start": 0,
"end": 11,
"object": {
"type": "Identifier",
"start": 0,
"end": 7,
"name": "console"
},
"property": {
"type": "Identifier",
"start": 8,
"end": 11,
"name": "log"
},
"computed": false
},
"arguments": [
{
"type": "Literal",
"start": 12,
"end": 26,
"value": "hello, world",
"raw": "\"hello, world\""
}
]
}
}
],
"sourceType": "module"}

其實看起來也不是很難,先是一個表達式,裏面再是函數調用,而後是函數的來源,參數等。若是有興趣能夠本身去在線的站點輸入本身 熟悉的語句,看看各個語句有什麼不一樣的地方。關於抽象語法樹的標準文檔看這裏。函數

遍歷 AST

解析 javascript 抽象語法書並不難,我以爲困難的地方是怎麼遍歷的問題,這就是很關鍵的一個部分了,有些庫給的文檔很全,有些庫雖然 不少 star,可是文檔太少了,對於初學者來講也不是好庫。通常遍歷一個樹有深度優先和廣度優先兩個方式,這兩個方式取到的東西的順序也就不一樣。

這裏我使用的一個庫是 https://github.com/estools/estraverse,從源碼考證是一個深度優先遍歷工具,具體的咱們能夠寫個 demo 測試一下:

function level1() {
function level2() {
;
}
}
function level1_1() {
;
}

若是是深度優先的話,首先讀取到的應該是 leve1 -> level2 -> level1_1。

測試代碼以下:

var esprima = require("esprima");
var estraverse = require("estraverse");

var ast = esprima.parse(`
function level1() {
function level2() {
;
}
}
function level1_1() {
;
}
`);

estraverse.traverse(ast, {
enter: enter,
leave: leave
});

function enter(node) {
if (node.type === "FunctionDeclaration") {
console.log("enter function: ", node.id.name);
}
}

function leave(node) {
if (node.type === "FunctionDeclaration") {
console.log("leave function: ", node.id.name);
}
}

安裝代碼須要的依賴:

npm install esprima
npm install estraverse

代碼執行結果以下:

enter function:  level1
enter function: level2
leave function: level2
leave function: level1
enter function: level1_1
leave function: level1_1

瞭解遍歷順序對於後面的實戰更加劇要,反混淆過程當中一般會涉及到做用域的問題,若是自身對於遍歷順序不清楚的話,那麼很難操做函數和變量的做用域。

實戰獲取函數的全部參數

接下來思考如何利用 AST 獲取函數的全部入參,好比下面的示例代碼:

var esprima = require("esprima");
var estraverse = require("estraverse");

var ast = esprima.parse(`
function level1(a, b) {
function level2(c, d) {
;
}
}
function level1_1(e, f) {
;
}
`);

var scopeList = [];

estraverse.traverse(ast, {
enter: enter,
leave: leave
});

function enter(node) {
createNewScope(node);

if (node.type === "FunctionDeclaration") {
let scope = scopeList[scopeList.length - 1];
for (const item of node.params) {
scope.push(item.name);
}
}
}

function leave(node) {
if (node.type === "FunctionDeclaration") {
const a = scopeList.pop();
console.log("params defined in ", node.id.name, ", params list: ", a);
}
}

function createNewScope(node) {
if (node.type === "FunctionDeclaration") {
scopeList.push([])
}
}

如何獲取函數體內定義的全部變量

如何利用 AST 獲取某個做用域裏面的全部變量?如何檢查某個變量在某個做用域下面是否已經定義?下節再續~


本文分享自微信公衆號 - 茶歇小棧(smilehackerboy)。
若有侵權,請聯繫 support@oschina.cn 刪除。
本文參與「OSC源創計劃」,歡迎正在閱讀的你也加入,一塊兒分享。

相關文章
相關標籤/搜索