<span id="7ztzv"></span>
<sub id="7ztzv"></sub>

<span id="7ztzv"></span><form id="7ztzv"></form>

<span id="7ztzv"></span>

        <address id="7ztzv"></address>

            原文地址:http://drops.wooyun.org/tips/7713

            0x00 前言


            Javascript 作為一種運行在客戶端的腳本語言,其源代碼對用戶來說是完全可見的。但不是每一個 js 開發者都希望自己的代碼能被直接閱讀,比如惡意軟件的制造者們。為了增加代碼分析的難度,混淆(obfuscate)工具被應用到了許多惡意軟件(如 0day 掛馬、跨站攻擊等)當中。分析人員為了掀開惡意軟件的面紗,首先就得對腳本進行反混淆(deobfuscate)處理。

            本文將介紹一些常見的混淆手段和 estools 進行靜態代碼分析的入門。

            0x01 常見混淆手段


            加密

            這類混淆的關鍵思想在于將需要執行的代碼進行一次編碼,在執行的時候還原出瀏覽器可執行的合法的腳本,然后執行之。看上去和可執行文件的加殼有那么點類似。Javascript 提供了將字符串當做代碼執行(evaluate)的能力,可以通過Function 構造器evalsetTimeoutsetInterval將字符串傳遞給 js 引擎進行解析執行。最常見的是base62 編碼——其最明顯的特征是生成的代碼以eval(function(p,a,c,k,e,r))開頭。

            base62 編碼的 Javascript

            無論代碼如何進行變形,其最終都要調用一次 eval 等函數。解密的方法不需要對其算法做任何分析,只需要簡單地找到這個最終的調用,改為 console.log 或者其他方式,將程序解碼后的結果按照字符串輸出即可。自動化的實現方式已經有許多文章介紹過,此處就不再贅述。

            隱寫術

            嚴格說這不能稱之為混淆,只是將 js 代碼隱藏到了特定的介質當中。如通過最低有效位(LSB)算法嵌入到圖片的 RGB 通道、隱藏在圖片 EXIF 元數據、隱藏在 HTML 空白字符等。

            比如這個聳人聽聞的議題:[一張圖片黑掉你:在圖片中嵌入惡意程序]PPT放出來一看,正是使用了最低有效位平面算法。結合 HTML5 的 canvas 或者處理二進制數據的 TypeArray,腳本可以抽取出載體中隱藏的數據(如代碼)。

            最低有效位

            隱寫的方式同樣需要解碼程序和動態執行,所以破解的方式和前者相同,在瀏覽器上下文中劫持替換關鍵函數調用的行為,改為文本輸出即可得到載體中隱藏的代碼。

            復雜化表達式

            代碼混淆不一定會調用 eval,也可以通過在代碼中填充無效的指令來增加代碼復雜度,極大地降低可讀性。Javascript 中存在許多稱得上喪心病狂的特性,這些特性組合起來,可以把原本簡單的字面量(Literal)、成員訪問(MemberExpression)、函數調用(CallExpression)等代碼片段變得難以閱讀。

            Js 中的字面量有字符串、數字、正則表達式

            下面簡單舉一個例子。

            在 js 中可以找到許多這樣互逆的運算,通過使用隨機生成的方式將其組合使用,可以把簡單的表達式無限復雜化。

            0x02 靜態分析實現


            解析和變換代碼

            本文對 Javascript 實現反混淆的思路是模擬執行代碼中可預測結果的部分,編寫一個簡單的腳本執行引擎,只執行符合某些預定規則的代碼塊,最后將計算結果替換掉原本冗長的代碼,實現表達式的簡化。

            如果對腳本引擎解釋器的原理有初步了解的話,可以知道解釋器在為了“讀懂”代碼,會對源代碼進行詞法分析、語法分析,將代碼的字符串轉換為抽象語法樹(Abstract Syntax Tree, AST)的數據形式。

            如這段代碼:

            var a = 42; var b = 5; function addA(d) { return a + d; } var c = addA(2) + b;

            對應的語法樹如圖:

            抽象語法樹

            (由 JointJS的在線工具生成)

            不考慮 JIT 技術,解釋器可以從語法樹的根節點開始,使用深度優先遍歷整棵樹的所有節點,根據節點上分析出來的指令逐個執行,直到腳本結束返回結果。

            通過 js 代碼生成抽象語法樹的工具很多,如壓縮器 UglifyJS 帶的 parser,還有本文使用的 esprima

            esprima 提供的接口很簡單:

            ? var ast = require('esprima').parse(code)
            

            另外 Esprima 提供了一個在線工具,可以把任意(合法的)Javascript 代碼解析成為 AST 并輸出: http://esprima.org/demo/parse.html

            再結合 estools 的幾個輔助庫即可對 js 進行靜態代碼分析:

            項目中使用的遍歷工具是 estraverse。其提供了兩個靜態方法,estraverse.traverseestraverse.replace。前者單純遍歷 AST 的節點,通過返回值控制是否繼續遍歷到葉子節點;而 replace 方法則可以在遍歷的過程中直接修改 AST,實現代碼重構功能。具體的用法可以參考其官方文檔,或者本文附帶的示例代碼。

            規則設計

            從實際遇到的代碼入手。最近在研究一些 XSS 蠕蟲的時候遇到了類似如下代碼混淆:

            代碼樣本

            觀察其代碼風格,發現這個混淆器做了這幾件事:

            經過搜索,這樣的代碼很有可能是通過 javascriptobfuscator.com的免費版生成的。其中免費版可以使用的三個選項(Encode Strings / Strings / Replace Names)也印證了前面觀察到的現象。

            這些變換中,變量名混淆是不可逆的。要是可以智能給變量命名的工具也不錯,比如這個 jsnice 網站提供了一個在線工具,可以分析變量具體作用自動重命名。就算不能做到十全十美,實在不行就用人工的方式,使用 IDE(如 WebStorm)的代碼重構功能,結合代碼行為分析進行手工重命名還原。

            再看字符串的處理。由于字符串將會被提取到一個全局的數組,在語法樹中可以觀察到這樣的特征: 在全局作用域下,出現一個 VariableDeclarator,其 init 屬性為 ArrayExpression,而且所有元素都是 Literal ——這說明這個數組所有元素都是常量。簡單地將其求值,與變量名(標識符)關聯起來。注意,此處為了簡化處理,并沒有考慮變量名作用域鏈的問題。在 js 中,作用域鏈上存在變量名的優先級,比如全局上的變量名是可以被局部變量重新定義的。如果混淆器再變態一點,在不同的作用域上使用相同的變量名,反混淆器又沒有處理作用域的情況,將會導致解出來的代碼出錯。

            在測試程序中我設置了如下的替換規則:

            至于縮進的還原,這是 escodegen 自帶的功能。在調用 escodegen.generate 方法生成代碼的時候使用默認的配置(忽略第二個參數)即可。

            DEMO 程序

            這個反混淆器的原型放在 GitHub 上:https://github.com/ChiChou/etacsufbo

            運行環境和使用方法參考倉庫的 README。

            從 ?YOU MIGHT NOT NEED JQUERY上摘抄了一段代碼,放入 javascriptobfuscator.com 測試混淆:

            jsobfuscate.com 混淆樣例

            將混淆結果https://github.com/ChiChou/etacsufbo/blob/master/tests/cases/jsobfuscator.com.js進行解開,結果如下:

            6-deobfuscated

            雖然變量名可讀性依舊很差,但已經可以大體看出代碼的行為了。

            演示程序目前存在大量局限性,只能算一個半自動的輔助工具,還有許多沒有實現的功能。

            一些混淆器會對字符串字面量進行更復雜的保護,將字符串轉換為 f(x) 的形式,其中 f 函數為一個解密函數,參數 x 為密文的字符串。也有原地生成一個匿名函數,返回值為字符串的。這種方式通常使用的函數表達式具有上下文無關的特性——其返回值只與函數的輸入有關,與當前代碼所處的上下文(比如類的成員、DOM 中取到的值)無關。如以下代碼片段中的 xor 函數:

            var xor = function(str, a, b) {
            

            return String.fromCharCode.apply(null, str.split('').map(function(c, i) { var ascii = c.charCodeAt(0); return ascii ^ (i % 2 ? a : b); })); };

            如何判斷某個函數是否具有這樣的特性呢?首先一些庫函數可以確定符合,如 btoa,escape,String.fromCharCode 等,只要輸入值是常量,返回值就是固定的。建立一個這樣的內置函數白名單,接著遍歷函數表達式的 AST,若該函數參與計算的參數均沒有來自外部上下文,且其所有 CallExpression 的 callee 在函數白名單內,那么通過遞歸的方式可以確認一個函數是否滿足條件。

            還有的混淆器會給變量創建大量的引用實例,也就是給同一個對象使用了多個別名,閱讀起來非常具有干擾性。可以派出 escope 工具對變量標識符進行數據流分析,替換為所指向的正確值。還有利用數學的恒等式進行混淆的。如聲明一個變量 a,若 a 為 Number,則表達式 a-aa * 0 均恒為 0。但如果 a 滿足 isNaN(a),則表達式返回 NaN。要清理這類代碼,同樣需要借助數據流分析的方法。

            目前還沒有見到使用扁平化流程跳轉實現的 js 混淆樣本,筆者認為可能跟 js 語言本身的使用場景和特點有關。一般 js 的代都是偏業務型的,不會有太復雜的流程控制或者算法,混淆起來效果不一定理想。

            0x03 結束語


            Javascript 的確是一門神奇的語言,經常可以遇到一些讓人驚訝的奇技淫巧。解密保護過的代碼也是有趣的事情。據說幾大科技巨頭在醞釀給瀏覽器應用設計一款通用的字節碼標準——WebAssembly。一旦這個設想得以實現,代碼保護將可以引入真正意義上的“加殼”或者虛擬機保護,對抗技術又將提升到一個新的臺階。

            演示項目代碼托管在 GitHub:https://github.com/ChiChou/etacsufbo

            0x04 參考資料


            1. http://tobyho.com/2013/12/02/fun-with-esprima/
            2. https://github.com/estree/estree/blob/master/spec.md
            3. https://developer.mozilla.org/en-US/docs/Mozilla/Projects/SpiderMonkey/Parser_API
            4. http://jointjs.com/demos/javascript-ast

            <span id="7ztzv"></span>
            <sub id="7ztzv"></sub>

            <span id="7ztzv"></span><form id="7ztzv"></form>

            <span id="7ztzv"></span>

                  <address id="7ztzv"></address>

                      亚洲欧美在线