Fuzzm: 針對WebAssembly內存錯誤的模糊測試
WebAssembly程序在JavaScript圈非常的火,可以大大加快瀏覽器的加載速度。但是WebAssembly的二進制程序通常由內存不安全的語言編譯而成,例如C和C++,而且由于WebAssembly屬于線性內存和缺乏保護的功能。
一些源碼級別的內存漏洞可以在編譯的WebAssembly二進制文件里邊利用。而Fuzzm是第一個針對WebAssembly的模糊測試工具。
作者在github上開源了這款工具,本文詳細介紹Fuzzm的配置流程。
背景介紹
看到了一篇對Web的WebAssembly程序的進行fuzzing的論文。
相信有許多的大佬已經十分熟悉模糊測試和WebAssembly了,這里還是簡單介紹一下背景知識,這樣會更有連貫性。
WebAssembly程序
webAssembly是一種新型的能夠運行在現代web瀏覽器中運行的代碼。這是一種低級的壓縮的二進制格式的類匯編語言,能以近乎native的性能運行并且提供將c/c++、c#、Rust等高級語言編譯為可以運行在web端的目標版本;它也被設計為允許與JavaScript一起運行。
WebAssembly已經被收錄為W3C WebAssembly Community Group的開放標準,使用WebAssembly JavaScript API,你可以通過它們加載WebAssembly模塊到一個Web App(node和JavaScript)中并且在兩者間共享功能。

需要注意的是WebAssembly并不是真正的匯編語言。所以不能像真正的匯編語言一樣在真實的物理機上運行,總而言之,它是一個概念機上的機器語言。
正因如此,WebAssembly 指令有時候被稱為虛擬指令。它比 JavaScript 代碼更快更直接的轉換成機器代碼,但它們不直接和特定硬件的特定機器代碼對應。
在瀏覽器下載WebAssembly后,使 WebAssembly 的迅速轉換成目標機器的匯編代碼。

如圖所示,如果想在頁面上添加WebAssembly,需要將代碼編譯成.wasm文件。
使用WebAssembly,可以更快地在 web 應用上運行代碼。這里有 幾個 WebAssembly 代碼運行速度比 JavaScript 高效的原因:
文件加載 - WebAssembly 文件體積更小,所以下載速度更快;
解析 - 解碼 WebAssembly 比解析 JavaScript 要快;
編譯和優化 - 編譯和優化所需的時間較少,因為在將文件推送到服務器之前已經進行了更多優化,JavaScript 需要為動態類型多次編譯代碼;
重新優化 - WebAssembly 代碼不需要重新優化,因為編譯器有足夠的信息可以在第一次運行時獲得正確的代碼;
執行 - 執行可以更快,WebAssembly 指令更接近機器碼;
垃圾回收 - 目前 WebAssembly 不直接支持垃圾回收,垃圾回收都是手動控制的,所以比自動垃圾回收效率更高。
模糊測試AFL
模糊測試(fuzz testing, fuzzing)是一種軟件測試技術。
其核心思想是將自動或半自動生成的隨機數據輸入到一個程序中,并監視程序異常,如崩潰,斷言(assertion)失敗,以發現可能的程序錯誤,比如內存泄漏。模糊測試常常用于檢測軟件或計算機系統的安全漏洞。
模糊測試誕生于1988年秋季的一個黑暗暴風雨之夜 [Takanen et al, 2008.]。巴頓·米勒教授坐在麥迪遜威斯康星州的公寓里,通過一條1200波特的電話線連接到他所屬大學的計算機。
陣陣的雷暴在線路上造成噪音,這些噪音又導致兩端的UNIX命令獲得錯誤的輸入,并導致崩潰。頻繁的崩潰使他感到驚訝—我們編寫的程序不是應該十分強大嗎?作為一名科學家,他想探究該問題的嚴重程度及其原因。
因此,他為威斯康星大學麥迪遜分校的學生編寫了一個編程練習,而該練習將使他的學生創建第一個模糊測試器。
這項作業的原文描述是這樣的:
The goal of this project is to evaluate the robustness of various UNIX utility programs, given an unpredictable input stream. […] First, you will build a fuzz generator. This is a program that will output a random character stream. Second, you will take the fuzz generator and use it to attack as many UNIX utilities as possible, with the goal of trying to break them.
該項目的目標是在給定不可預測的輸入流的情況下評估各種UNIX實用程序的健壯性。[…]首先,您將構建一個模糊發生器。這是一個將輸出隨機字符流的程序。其次,您將使用模糊發生器,并使用它來攻擊盡可能多的UNIX實用程序,以試圖破壞它們。
這個作業在不經意間抓住了模糊測試的本質:創建隨機的輸入,并持續性觀察它是否會破壞目標應用程序,理論上只要運行足夠長的時間,我們就會看到錯誤的發生。
AFL(american fuzzy lop)最初由Micha? Zalewski開發,和libFuzzer等一樣是基于覆蓋引導(Coverage-guided)的模糊測試工具,它通過記錄輸入樣本的代碼覆蓋率,從而調整輸入樣本以提高覆蓋率,增加發現漏洞的概率。其工作流程大致如下:
1 從源碼編譯程序時進行插樁,以記錄代碼覆蓋率(Code Coverage)
2 選擇一些輸入文件,作為初始測試集加入輸入隊列(queue)
3 將隊列中的文件按一定的策略進行“突變”
4 如果經過變異文件更新了覆蓋范圍,則將其保留添加到隊列中
5 上述過程會一直循環進行,期間觸發了crash的文件會被記錄下來

Fuzzm
基于AFL模糊測試的原理,作者提出了首款針對WebAssembly的程序。測試原理如下圖所示:

首先,針對原始的.wasm二進制文(Original Binary)件,由于其為線性內存,, 我們可以在WebAssembly程序中添加Stack & Heap Canaries(堆棧檢測點),用于檢測WebAssembly程序是否發生內存溢出錯誤, wasm二進制程序也變成了Hardened Binary。
根據之前AFL的知識,我們知道灰盒測試(Greybox Fuzzing)之所以有效是因為它依賴于程序執行期間的輕量級反饋來引導AFL的執行, 為了收集該反饋,AFL會進行編譯插裝來跟蹤路徑覆蓋的的近似形式(approximate form of path coverage), 然后存儲在跟蹤位數組(trace bits array)中。
由于WebAssembly屬于二進制程序,無法訪問源碼, 所以Fuzzm通過在所有的程序分支中插入代碼來提取與AFL兼容的覆蓋信息, 即instrument Approx Coverage。
最后Fuzzm對AFL進行了高度優化,因此可以在wasm虛擬機上對程序進行模糊測試。
開始運行
說了這么多前導知識,那么我們現在就開始Fuzzm的配置安裝吧,首先我們從github網站上把這個項目拉下來。https://github.com/fuzzm/fuzzm-project
由于要從外網下載各種配置資源,所以這里建議大家kexueshangwang。
5.1 安裝各種環境
- 下載 Rust 和 cargo
curl https://sh.rustup.rs -sSf | sh

默認選1即可:

下載完成后,輸入以下命令:
source $HOME/.cargo/env

下載 wasmtime:
curl https://wasmtime.dev/install.sh -sSf | bash

構建 wasmtime:
git clone --recurse-submodules \ https://github.com/bytecodealliance/wasmtime.git

之后開始構建wasmtime:
cd wasmtimecargo build --release

經過漫長的等待后構建完成,當然,,網速快的除外。:

將程序連接到全局:
sudo ln -s /home/mzs/Desktop/fuzzm-project/wasmtime/target/release/wasmtime /usr/bin/wasmtime
從Ubuntu軟件源安裝Node.js 和 npm
sudo apt updatesudo apt install nodejs npm

配置完成,輸入nodejs --version 和 npm --version查看版本:

下載 wasi sdk:
wget https://github.com/WebAssembly/wasi-sdk/releases/download/wasi-sdk-11/wasi-sdk-11.0-linux.tar.gztar xvf wasi-sdk-11.0-linux.tar.gz
在fuzzm-project目錄下 執行npm install。
5.2 開始配置
進入 AFL-wasm文件夾:
make & make install
配置完成后, 使用afl-fuzz發現有以下錯誤:

這是因為系統沒有找到對應的庫文件. 我們使用以下命令鏈接即可:
sudo ln /path/to/fuzzm-project/AFL-wasm/wasmtime-v0.20.0-x86_64-linux-c-api/lib/libwasmtime.so /usr/lib/libwasmtime.so
注意,下載路徑要配套, 庫文件的地址是你下載項目的位置。

然后我們在運行afl-fuzz,情況已經正常:

進入wasm_instrumenter文件夾:
cargo build --release
這個文件夾可以對wasm進行insert canaries 和 instrument coverage操作, 耐心等上一段時間, 配置完成后:

開始鏈接到全局:
sudo ln -s /home/mzs/Desktop/fuzzm-project/wasm_instrumenter/target/release/afl_branch /usr/bin/afl_branch sudo ln -s /home/mzs/Desktop/fuzzm-project/wasm_instrumenter/target/release/canaries /usr/bin/canaries
5.3 開始fuzz
a. 進入 motivating-example 文件夾
我們可以看到這里有一段實例代碼:

我們先對現成的wasm程序進行模糊測試。
chmod +x vuln-cov-canaries.wasm WASM_MODE=1 afl-fuzz -i testcases/ -o output_cov_canaries ./vuln-cov-canaries.wasm

b. 編譯項目,進入 benchmarks/openjpeg文件夾:
./wasm-compile-and-instrument.shcd bin/WASM_MODE=1 afl-fuzz -i ../tests/ -o output_canaries ./opj_compress.instr.wasm

c. 將c文件轉化為wasm,并進行fuzz。
我們以vuln.c 文件為例,首先我們從motivating-example文件夾中,,拷貝testcases 和 vuln.c,然后輸入以下命令::
../wasi-sdk-11.0/bin/clang --sysroot=../wasi-sdk-11.0/share/wasi-sysroot -O2 -D USE_JPIP=1 vuln.c -o vuln.wasm
將c文件轉化為wasm文件。

之后進行insert canaries 和 instrument coverage操作, 注意生成文件后使用chmod +x xxx.wasm授權。
afl_branch vuln.wasm vuln.instr.wasm canaries vuln.instr.wasm vuln-cov-canaries.wasm

然后開始fuzzing:
WASM_MODE=1 afl-fuzz -i testcases/ -o outputs ./vuln-cov-canaries.wasm

如何將項目轉化為wasm?
這里我們以benchmarks/openjpeg為例。進入文件夾,,打開wasm-compile.sh文件。

可以看到:想要轉化為wasm文件,需要將所有的依賴文件轉化過去。