當前位置:
首頁 > 新聞 > 如何逆向分析WebAssembly二進位代碼

如何逆向分析WebAssembly二進位代碼

最近,我們發表過一篇關於WebAssembly(Wasm)基本概念及其安全問題的文章。作為後續內容,本文將為讀者介紹Wasm應用程序的逆向工程方法。當我們遇到一個未知源碼的Wasm應用程序,並且想要弄清楚其內部機制的時候,該如何進行分析呢?關於這個主題,目前幾乎找不到任何有用的文檔,所以,我們決定拋磚引玉。

對於Wasm應用程序,我們可以使用不同的方式進行分析。在本文中,我們將通過一個非常簡單的應用程序,來為讀者詳細介紹Chrome內置的Wasm調試功能。在此過程中,我們將因地制宜地引入一些相關概念。

對於急著了解相關技術內容的讀者,如果按耐不住的話,可以先從附錄部分獲取HTML文件test.html,然後直接跳轉到「調試示例應用程序」部分。

為什麼要逆向分析Wasm呢?

我們為什麼對分析Wasm應用程序如此饒有興趣呢?在深入學習逆向分析的細節知識之前,讓我們先來回答這個問題。

對於安全分析人員來說,我們最感興趣的就是了解惡意軟體作者是如何利用新興技術的。每當出現新威脅時,例如新的勒索軟體家族、物聯網蠕蟲或更不尋常的東西,安全研究人員都希望深入分析該惡意代碼的所有功能。當我們知道了惡意軟體是如何工作的,而且了解了它們的特性後,我們就可以編寫簽名來提供相應的安全保護了。

在分析傳統惡意軟體的時候,有許多分析工具可選,無論對於混淆過的JavaScript、惡意Flash對象、可移植可執行文件(PE)還是其他軟體,都是如此。並且,在分析這些惡意軟體的時候,總能找到一種行之有效的方法。

正如我們在本系列的第一篇文章中提到的,在安全分析工具與分析方法上面,Wasm的情況有所不同。關於如何分析Wasm應用程序方面,幾乎沒有任何文檔可用,而且大多數常見的逆向工程工具,當前也不適用於Wasm。因此,我們才決心撰寫本文,為讀者深入揭示如何逆向分析Wasm二進位文件。

創建Wasm示例應用程序:「Hello World」

首先,讓我們來創建一個簡單的Wasm應用程序,以便稍後對其進行逆向分析。我們將在瀏覽器中運行該應用程序,並使用Chrome的開發人員工具對其進行逆向分析。

要在瀏覽器中運行Wasm應用程序,我們需要使用一個HTML文件來載入和執行Wasm二進位文件。下面,我們開始介紹如何創建這個HTML文件。(如前所述,完整的文件可以在附錄中找到。)

首先,建立一個框架(我們將進一步對其進行修改),並將其保存到test.html文件:

為了便於配置並避免安裝任何工具,我們這裡使用名為WasmFiddler的在線Web應用程序來生成Wasm。在WasmFiddler中,輸入以下內容:

void hello() {

printf("Hello World
");

}

然後點擊「Build」按鈕:

圖1:使用WasmFiddler編譯Wasm應用程序

在上圖的右側,我們可以看到一個名為utf8ToString()的函數。我們需要將該函數複製並粘貼到HTML頁面的JavaScript部分,並將其放在test()函數的上方。

在截圖的右側,我們可以在函數utf8ToString()後面看到如下所示的幾行JavaScript代碼:

複製這些代碼,並將其粘貼到test()函數中。這些代碼的作用,就是根據定義在buffer數組中的代碼來實例化我們的Wasm,然後執行hello()函數。

那麼我們如何定義這個緩衝區的內容(Wasm代碼)呢? 在WasmFiddler中,單擊源代碼下面的下拉菜單(圖1中的「Text Format」),然後選擇「Code Buffer」即可。

圖2:在WasmFiddler中查看代碼緩衝區。

這樣,WasmFiddler就會生成二進位Wasm代碼,並將其放入JavaScript緩衝區中。這時,我們會看到下列內容(有所刪減):

var wasmCode = new Uint8Array([0,97,115,109,1,0,0,0,...,108,100,0]);

注意:如果得到的只是一個空數組(「var wasmCode = new Uint8Array([null]);」),說明忘了先編譯源代碼。在這種情況下,可以點擊Build按鈕,然後重試。

複製這個緩衝區的內容,並將其粘貼到test()函數的開頭部分。然後,將數組從wasmCode重命名為buffer,以便與WasmFiddler生成的其他代碼的命名相匹配。

如果您讀過本系列的第一篇文章的話,就知道Wasm應用程序本身無法將文本列印到屏幕上。所以,需要定義一個JavaScript函數,在我們的Wasm代碼中調用printf()函數。在WasmFiddler中,選擇下拉菜單中的Text Format選項,以查看編譯後的Wasm應用程序的文本表示形式:

圖4:puts()函數的Imports模板。

複製上面wasmImports的定義,並將其粘貼到test()函數的開頭部分。然後,我們還需要將Imports的定義提供給Wasm的實例,具體如下所示:

var m = new WebAssembly.Instance(new WebAssembly.Module(buffer),wasmImports);

最後,讓我們來定義puts()函數在被調用時應該做些什麼。具體來說,就是將其改為下面的樣子:

puts: function puts (index) {

alert(utf8ToString(h, index));

}

現在,我們已經完成了構建演示程序所需的全部步驟。接下來,請在Chrome中載入test.html文件,這時會看到:

圖5:Chrome中的通知。

我們可以看到,Wasm代碼成功的調用了我們的外部函數。

注意:如果您沒有看到彈出窗口,可能是您的瀏覽器不支持Wasm所致。在這種情況下,請嘗試更新瀏覽器,因為目前所有主流瀏覽器的最新版本都支持Wasm。

調試示例應用程序

現在,我們終於可以通過Chrome開發者工具來調試示例應用程序了。

利用Chrome打開test.html文件後,啟動Chrome開發人員工具(按F12鍵),並選擇頂部的Sources選項卡。然後,按Ctrl+R組合鍵重新載入頁面。現在,應該出現一個帶有文字「wasm」的小雲圖標。接下來,請展開它及其下面的項目,選擇wasm子樹下的葉子項目,具體如下圖所示:

圖6:Chrome開發人員工具

讓我們單步執行這個函數,以便更好的理解其功能。為此,請點擊左邊以「i32」開頭的那行代碼,為其設置斷點。這時會顯示一個藍條,表明已設置好斷點。接下來,按Ctrl+R組合鍵重新載入頁面。現在,將在斷點處停下來。這時候,Wasm堆棧是空的。然後,單擊調試器中的Step Over按鈕(或點擊F10鍵或帶有彎曲箭頭的圖標)以執行指令「i32.const 16」,該指令會將16的值壓入堆棧:

圖7:將值16壓入堆棧。

Wasm中的所有函數都具有對應的編號,編號為0的函數對應於Wasm從JavaScript導入的puts函數(函數編號1對應於hello函數)。因此,下一條調用0的指令實際上就是調用printf/puts函數,並且堆棧中的值「16」是其參數。

那麼,值「16」是如何與字元串「Hello World」對應起來的呢? 實際上,該值是指向Wasm應用程序內存空間中的地址的指針。利用Chrome的調試器展開全局樹,我們就可以查看Wasm應用程序的內存了:

圖8:考察Wasm應用程序的內存。

下面讓我們來看看內存中位置編號為16處的內容:

圖9:Wasm應用程序內存中的「Hello World」。

運行狀態下的Wasm應用程序的內存空間實際上是作為JavaScript數組實現的。該數組的定義位於負責載入Wasm應用程序的HTML文件中。在上面的例子中,變數「h」的定義如下所示;該變數用於保存應用程序的內存空間:

現在,請重新點擊Step Over按鈕來執行該調用。這樣,就能看到相應的JavaScript警報了。

結束語

現在,我們已經對一個簡單的Wasm程序成功地進行了逆向分析。雖然這個例子非常簡單,但請不要忘記,千里之行始於足下。

在逆向過程中,我們是通過調用JavaScript聲明的導入函數來了解Wasm是如何與外部環境進行交互的。此外,我們還介紹了如何在JavaScript和Wasm之間共享內存。

參考文獻

WasmFiddle,在線編譯Wasm: https://wasdk.github.io/WasmFiddle/?wvzhb

關於如何在瀏覽器調試器中調試Wasm的視頻: https://www.youtube.com/watch?v=R1WtBkMeGds

在JavaScript和Wasm之間傳遞值: https://hacks.mozilla.org/2017/07/memory-in-webassembly-and-why-its-safer-than-you-think/

附錄: test.html

為了便於參考,以下是我們創建並分析的整個test.html文件:

喜歡這篇文章嗎?立刻分享出去讓更多人知道吧!

本站內容充實豐富,博大精深,小編精選每日熱門資訊,隨時更新,點擊「搶先收到最新資訊」瀏覽吧!


請您繼續閱讀更多來自 嘶吼RoarTalk 的精彩文章:

Check Point安全研究人員針對UPAS Kit與Kronos銀行木馬的分析(一)
Facebook又被曝出數據泄露事件,牽涉用戶多達1.2億

TAG:嘶吼RoarTalk |