在沒有 source code 的情況下,binary-only fuzzing 是取得 coverage 的方法大概可以分成三種,並且每種做法並不完全等於下方所介紹的,根據實作還是會有些許差異:
Static binary rewriting - 靜態對 binary 做更新
{call,jmp} XXXX
跳到指定位址,而在位址 XXXX
會放蒐集 coverage 的 instruction,同時也還會執行/修補原本的程式片段,最後在跳回去
Dynamic binary instrumentation (DBI) - 透過 emulator 可以模擬程式執行的功能,特別在把 basic block 轉成 IR 之前,先多插一段蒐集 coverage 的 IR,之後動態做模擬執行時就能知道執行了哪些 basic block
Other - 使用一些較為特別的技巧做插樁
動態與靜態各自的優點 (對方的缺點) 在於:
這幾天的文章會詳細介紹各機制與其實作方法,而今天會先介紹 static binary rewriting。
Trampoline 的中文意思為彈跳床,而在資訊術語中通常是指:改變原本的程式流程,讓程式在執行到一半時先跳去做其他的 subroutine,而 subroutine 除了會執行使用者指定的程式碼外,也可能會將原程式要做的事給做完,最後 subroutine 執行完後在跳回去。
舉個簡單的例子,原本程式的執行流程為 orig
,而 1
就是使用 C
這個 trampoline 來蒐集 coverage,並在執行結束後回到原先的執行流程;2
的做法會直接替代掉原程式流程,直接在 C
當中做完 B
的行為。
如果是以程式碼為例子:
A:
mov rax, 1
mov rbx, 2
mov rcx, 3
ret
B:
mov rax, 4
mov rbx, 5
mov rcx, 6
ret
C:
mov rdx, qword ptr [rip + test_counter]
inc rdx
mov qword ptr [rip + test_counter], rdx
ret
test_counter: .long 0
在不考慮 call C
與 mov rax, {1,4}
instruction 長度,以及呼叫 function 所造成的 side-effect 的情況下,作法 1
會長得像:
A:
call C
mov rbx, 2
mov rcx, 3
ret
B:
call C
mov rbx, 5
mov rcx, 6
ret
C:
mov rdx, qword ptr [rip + test_counter]
inc rdx
mov qword ptr [rip + test_counter], rdx
ret
test_counter: .long 0
做法 2
則是:
A:
jmp C_1
B:
jmp C_2
C_1:
mov rdx, qword ptr [rip + test_counter]
inc rdx
mov qword ptr [rip + test_counter], rdx
mov rax, 1
mov rbx, 2
mov rcx, 3
ret
C_2:
mov rdx, qword ptr [rip + test_counter]
inc rdx
mov qword ptr [rip + test_counter], rdx
mov rax, 4
mov rbx, 5
mov rcx, 6
ret
test_counter: .long 0
而其中的難點也十分明顯,像是:
call
或 jmp
超過原本 function 的長度時該怎麼處理?1
當中的 mov rax, {1,4}
?諸如此類的問題還有很多,因此這部分也是有許多人在做相關的研究。
Reassemble 的核心概念在:把原本的 assembly code 做修改後,還能重新組譯回可執行檔,並且程式執行的流程仍與原本相同。
圖片取自 RetroWrite 的論文,大致代表整個 ressembly 的處理過程,下面會以 RetroWrite 的執行流程為例,並額外做一些補充:
如果想知道更詳細的 RetroWrite 實作方式,可以參考發表者在演講時所做的簡報 連結。