# [SFuzz: Slice-based Fuzzing for Real-Time Operating Systems](https://huhong789.github.io/papers/chen:sfuzz.pdf) ## Abstract * RTOS 是現在嵌入式系統的主要類別之一 * 缺乏安全機制,設備容易受攻擊 * RTOS 常把任務與服務整合成一個 binary file,造成程式分析的困難 * 提出 SFuzz,基於切片的模糊器 * 將複雜的 Binary file 切成許多任務 * SFuzz 先辨識 user input 的 function,建構出 call graphs,再利用前向切片修剪路徑 * 接著會做 coverage-guided fuzzing * 最後利用前向和後向切片驗證路徑,確定漏洞是否誤報 * 在 35 個 RTOS 樣本上發現 77 個 zero-day 漏洞,67 個被分配 CVE 或 CNVD (中國的 CVE) ## Introduction * 為了應付 Real-time tasks,RTOS 捨棄 kernel space 和 user space 的隔離,帶來更多潛在威脅 * 在 local network 沒差,但很多設備直接連到 Internet * RTOS 常以 blob-firmware 格式呈現,無法在一般 CPU 上執行,使大部分錯誤檢測無法正常運作 * 靜態分析部分,因為 binary 檔很大,常遇到路徑爆炸等問題 * 動態分析部分,需要實際設備或是依賴穩定的模擬 * 皆缺乏可擴展性 * RTOS 可以劃分成許多獨立任務,且任務如果是同一類型,他們的 data flow 可能也是相似的 * 搜尋從外部進入點到 sink function(如 memcpy 等) 的 data flow,將任務切分 * 只要切的夠小,就可以降低 Fuzzing 的模擬難度和分析複雜度 --- * SFuzz 利用前向切片建構一個 code space,接著跑 Fuzzing * 結合反向切片來執行 concolic,驗證崩潰輸入 * 四個主要元件 * Forward Slicer * 識別 user input function * 修改與輸入無關的條件分支,確保控制流可以到這些地方 * 透過 DMA 覆蓋 data-sharing paradigm,前向切片器可以擴展到不同的任務 * 不太懂 * Control Flow Node Handler * 如果直接 Fuzz 片段的 code,會因為缺少上下文和執行狀態而失敗 * 處理與 user input 無關的部分提高 Fuzz 穩定性 * Micro Fuzzing * 透過指令級模擬執行上下文 (user mode) * Concolic Analyzer * 符號化被忽略 function 的上下文,利用 concolice 提供具體值 * 基於 Ghidra, UnicornAFL 寫了 6200 行 Python、4300 行 C 和 5100 行 Java 實現 * 為了驗證有效性,應用於 11 個供應商的 35 個韌體 * 發現 77 個未知漏洞 * 優於其他工具 --- * 提出基於切片的方式測試 RTOS * 透過模擬執行 Fuzz,有效檢測 RTOS 韌體漏洞 * 從 35 個真實世界的韌體發現 77 個未知漏洞 * 已分配 86 個 CVE/CNVD ID ## Problem and Approach Overview ### RTOS and Embedded Devices * 因為空間限制和要求快速回應,將所有功能編譯成單個 binary * strip 減少大小 * 造成分析困難 ### Motivation Example ```c= void devDiscoverHandle (int sockfd) { int len , ret; struct sockaddr_in src_addr; int addrlen = sizeof(struct sockaddr_in ); memset ((uint8 *)&src_addr , 0, 0x10); memset(Global_addr , 0, 0x5C0); len = recvfrom(sockfd , Global_addr +0x1c , 0x5a4 , 0, (struct sockaddr *)&src_addr , ( socklen_t *)&addrlen); if ( len != ERROR ) ret = protocol_handler (( packet *)( Global_addr +0 x1c)); if (ret == ERROR) logOutput(" devDiscoverHandle Error!"); } int protocol_handler (packet *data) { bytes [4] = {0xe1 , 0x2b , 0x83 , 0xc7 }; if ( header_check (data)) if ( magic_check (data ->magic_bytes , bytes , 4)) if (checksum(data)) return msg_handler (data); return ERROR; } int msg_handler (packet *data) { int ret = ERROR; if (data ->version == 0x01) ret= parse_advertisement (data ->payload , data ->payloadLen ); return ret; } int parse_advertisement (uint8 *payload , int payloadLen) { char* dst; char* var_addr; char buffer [64]; int index; var_addr = DAT_404d33a8 ; msg_element *element; msg_element_header * element_header ; element = parse_msg_element (payload , payloadLen ); element_header = element ->header; if ( element_header ) { index = (int)*( var_addr +4)); dst = buffer+index; if ( copy_msg_element ((char *) element ->data , dst , ment_header ->len)) == 0) // Stack Overflow return SUCCESS; } return ERROR; } ``` * 第 40 行有 BOF * 利用 SFuzz 發現 TP-Link WDR7660 的這個錯誤 * CVE-2020-28877 * 第 7 行接收外部數據,9~44 行處理 * 18 行根據版本去 call 對應的處理程式 * 35~36 分析標頭,並在 40 行複製到內存空間 * 建構 package 的 len 值,即可造成 BOF * 現有工作難以分析這種 case ### Necessity and Reasonability of SFuzz * 因為 code 邏輯太複雜,動態分析會比靜態適合 * 在沒有人工分析和穩健的全系統方法下,使用的方法是基於功能獨立的做 Fuzz * slice-based fuzzing * 如果收集了一個程式切片,包含接收和處理的完整 function,配合 Fuzzing 和指令級模擬就能有效找出漏洞 * 能忽略模擬時的困難 * 因為執行時缺少完整的上下文,需要處理與外部無關的控制流節點 (引導程式走到想要的位址) * 發現漏洞後就根據 input 進行 concolic,幫助判斷是否為誤報 * 靜態分析的部分只有切片找出風險 function,減少路徑爆炸的問題 --- * 分析了四個設備,搜尋所有數據 input 點 (如 recvfrom()),並將他們的調用 function 當作 root,搜尋下面所有子 function * ![](https://i.imgur.com/UVPhbKd.png) * 發現 function 的重複率很低,表示 function 之間很獨立 * 全域變數也是 ### Challenges of Slice-based Fuzzing * 三個主要挑戰 1. 如何確定切片範圍 2. 如何處理切片中控制流的點 3. 如何有效進行模糊測試和驗證 PoC ## Design ### Approach Overview * ![](https://i.imgur.com/viLXjgG.png) * 輸入是 RTOS 的 Firmware,輸出是 code 中的漏洞 ### Forward Slicer * Sensitive Call Graph Constructor 檢測輸入獲取的 function 和全域數據讀取點,將這些 function 的 caller 作為 root 建構圖 * Call Graph Pruning 修剪路徑 * 不易受攻擊的分支會被修剪 * Call Graph Stitching 拼接不同圖之間的邊 #### Call Graph Pruning * 利用汙點分析過濾路徑 #### Call Graph Stitching * set_env 和 get_env 等等的外部輸入可能會被 data sharing paradigms 中斷 * 是否跳轉到這種讀取點根據隨機機率 * 不太懂 ### Control Flow Nodes Handler * 經過 Forward Slicer 可以得到 call graph tree * 但為了 Fuzzing 順利,需要處理不必要的控制流 * 缺少 RTOS 上下文,需要指導 fuzzer 走到正確的分支 #### Call Instruction * 如果發現路徑上有 function 的參數和回傳值都不會影響 user input,fuzzing 時就跳過他 #### Conditional Branch * 如果期間發生分支,且只有一個分支能到達 recv * 若條件受到 input 影響,盡量避免 fuzzer 到這裡 * 否則讓分支無條件跳轉至目標分支 * 如果期間發生分支,且兩個分支都能到達 recv * 隨機跳轉 * 如果期間發生分支,且兩個分支都無法到達 recv * 直接退出當前路徑探索 ### Micro Fuzzing * 本篇基於切片的 Fuzzing 技術取名為 Micro Fuzzing #### Image Loader * 讀取完韌體後,經由前面的元件得到切片過的程式碼區段,還有對應要跳過或是另外處理的 Function or Address * call function 改為 NOP * Address 根據目標設為 Avoid Explore 或是退出、跳轉等操作 #### Fuzzing Engine * 基於 UnicornAFL * 卡住時會呼叫 concolic,將輸入符號化,求解強制走之前沒走過的地方 * 時間內沒找到新路徑就退出 * 不太確定為何是 concolic 不是 symbolic * 跳過硬體模擬相關的指令,這些通常與中斷相關,不影響數據流 #### Memory Safety Policies * memory 區塊可分為兩種 * 可利用靜態分析得到大小 * 執行到 sink function 時判斷是否溢出 * 不可利用... * concolic 階段確認大小 ### Concolic Analyzer * Fuzzer 退出後,Concolic Analyzer 會檢查所有崩潰輸入 * 給定具體輸入與目標路徑,求解 * 因為修剪了 function 和對分支做過處理,才需要重新檢查是否誤報 ![](https://i.imgur.com/agaYUfZ.png) * recv 是第 8 行,sink 是 24 行 * stitched by data sharing paradigms,同一個 function 在第 17 行 * 利用 concolic 找出 9、10 的回傳值,符合 11 13 16 的分支條件 * 9 10 可能受到其他輸入影響造成假陰性 * sink 的大小會可也造成假陽性或假陰性 * 利用反向追蹤,會定位出 9 10 14 為 source * 如果依然可以到達 recv 表示 11 行是否進入根本不重要 ## Implementation * 使用 6200 行 Python、4300 行 C 與 5100 行 Java 完成原型 * 汙點分析和語意恢復利用 Ghidra 完成 * Fuzzing 基於 UnicornAFL 與 Driller、concolic 分析器基於 Angr * 擴展 Driller 使其適用 RTOS ### Image Extraction * BinWalk 提取 ### Base Address Recognition * 需要知道 Base Address 才能正確做跳轉 * 從系統中的 data reference pointers 提取 * 基於 PCode 完成,這是 Ghidra 的中間語言,因此可以支援更多架構 ### Function Semantic Reconstruction * 希望恢復三種類型的 Funciton * 接收外部輸入 * sink function (memcpy 等) * set or get 全域資料 * Symbol File & Log Function * 一些廠商會發布 symbol file 可以幫助恢復 * Virtual Execution * 比較 function 的參數和 return 的數量,和標準函式庫比對 * 找出 strcpy 等等 * 也會比較對記憶體的影響 * 找出 memcpy 或 printf 等等 * Web Service Semantic * 利用前端和後端的標記 user input 來匹配 web 相關的 function * SaTC * Open source firmware * 直接用開源的 ## Evaluation * RQ * RQ1: SFuzz 是否能發現 RTOS 真實漏洞? * RQ2: SFuzz 每個部分都對發現漏洞是必要的?與最先進的工具比較表現如何? * RQ3: SFuzz 每一步發現漏洞都準確嗎? * ![](https://i.imgur.com/XWZxl1Q.png) * 11 個供應商 35 個韌體 * 包含 23 台路由器、7 台印表機、2 台防火牆和一台 Brain-Computer Interface(人機介面) #### Environment Setup * Ubuntu 18.04 host with a RAM of 256 GB and a 32-core Intel Xeon Processor of 2.4 GHz * 每個 Fuzz 跑六小時,一個 CPU core 去跑 * 過了這個時間後就都沒找到新路徑或崩潰 #### Experiments Design * ![](https://i.imgur.com/FGoGNZK.png) * SFuzz-Handler: 不處理控制流 * SFuzz-FHandler: 只處理條件分支 * SFuzz-CHandler: 只處理 Call Function 刪減 #### Bug Confirmation * 手動驗證每個警報是否為真的錯誤 ### Real-world Vulnerabilities * 發現 77 個錯誤 * ![](https://i.imgur.com/a4pgQ42.png) * 左邊是在哪些 input * 右邊是在哪些任務 * ![](https://i.imgur.com/tY4vCNB.png) * 其實所有 Fuzzer 都有發現漏洞 * UnicornAFL 和 SFuzz-Handler 能探索的路徑很少 * SFuzz-FHandler 加上分支判斷後,探索路徑加了很多,但 bug 沒變 * SFuzz-CHandler 加上 Function 剪枝後路徑只加一點,但能發現的 bug 變為 17 個 * SFuzz 優於所有工具 #### Stability * ![](https://raw.githubusercontent.com/NSSL-SJTU/SFuzz/main/figs/success_rate.png) * Fuzzing 時的模擬成功率 * 分支處理會造成成功率下降 * Function 處理會讓成功率大幅上升 #### Efficiency * ![](https://raw.githubusercontent.com/NSSL-SJTU/SFuzz/main/figs/avg_time.jpeg) * 越複雜需要跑越久 * 不知道什麼時候算結束 ### Comparison with Symbolic Execution * ![](https://i.imgur.com/GiDK6Ti.png) * 修改 angr 讓他可以跑 RTOS * SFuzz 速度最快、路徑看到最多 * 有些比較少是因為太快找到漏洞了 * 加上 Control Flow Nodes Handler 可以讓一般的 SE 提高性能 * 不清楚這邊到底是給定什麼 * source and sink 然後看什麼時候找到路徑? ### Accuracy and Efficiency #### Semantic Reconstruction * 31 個樣本中可以恢復 25 個 base address * 剩下要手動修復 #### Forward Slicer * 每個切片程式平均占全部的 1.52% * 有效節省分析工作 #### Micro Fuzzing * 平均一棵樹分析 7~30 分鐘 * 一整個模型總分析從 20 分鐘 ~ 21 小時不等,取決於樹的複雜度 #### Concolic Analyzer * 在 Fuzzer 找到的 302 個 Crash 中,115 個發出警報 * 其中有 99 個真的是 bug * GitHub 上介紹成因和如何確認 ## Related Work #### RTOS Security #### Symbolic Execution #### Greybox Fuzzing & Dynamic testing #### Code Fragment Execution ## Conclusion * 提出了 SFuzz,基於切片的 Fuzzing 方法,用於檢測 RTOS 漏洞 * 發現 20 個設備中的 77 個 zero-day,其中 67 個被分配 CVE/CNVD ID * 實驗證明優於最先進的工具 ###### tags: `paper`