一、背景:
對於物聯網設備,比如家用路由器模塊,UPNP、HTTP server、Telnet都是經常接觸的模塊,通常也能夠與外界交互,從而提供了入口。如果這些模塊或者使用的協議存在漏洞,往往能夠直接利用,到達遠程攻擊的效果。針對IoT設備的模糊測試,本文介紹BooFuzz。
對物聯網設備的協議fuzz測試,不可丟失的一環是監控器,能夠發現bug是監控器作用所在。一般來說,大多數針對協議的fuzz測試,在每次發送變異報文后,通過ping或者其他協議檢測對方是否在線從而判斷設備是否出現異常。這種方法較為簡單,但是粗粒度的監控器帶來的后果是比較高的誤報率;另一種方式是ssh或者telnet登錄設備,實時觀察當前設備的狀態,這種方式優點是隨時捕捉異常信號,缺點是,針對不同設備可能要開發不同的監控API。所以當前諸多的協議Fuzz工具,大部分使用第一種,即通過目標設備是否可達來判斷崩潰情況。
二、BooFuzz框架介紹:
1、四大組件:
a) Data Generation 數據生成
b) Session 會話管理
c) Agents代理
d) Utilities獨立單元工具
其中,數據生成和會話管理是比較重要的兩個模塊。數據生成方式是基於generation-based的方式,需要對協議或者文件進行建模,數據生成的特點:
== 一個數據報文由基元(Primitives)、塊(Blocks)組成
== 多個基於可以組成塊,塊可以相互嵌套
== 在基元的基礎上我們可以創建自定義的特殊的負責基元(Legos,數據積木),例如Email的地址,IP地址等。
== 最后還有一些有用的工具,例如算length長度、校驗和、加密模塊等
會話模塊,根據已經構建好的Request,利用pgraph繪制:
在上圖中,節點ehlo、helo、mail from、rcpt to、data表示5個請求,路徑'ehlo'->'mail form'->'rcpt to'->'data'和'helo'->'mail from'->'rcpt to'->'data'體現了請求之間的先后順序關系。callback_one()和callback_two()表示回調函數,當從節點echo移動到節點mail from時會觸發該回調函數,利用這一機制,節點mail from可以獲取節點ehlo中的一些信息。而pre_send()和post_send()則負責測試前的一些預處理工具和測試后的一些清理工作。每個節點連接起來,組成狀態圖,可以對圖中的每個節點進行操作,也可以自定義callback回調函數。
2、BooFuzz API
Session對象是Fuzz測試的核心,當你創建Session的時候,你會傳遞一個Target對象,該對象本身接收一個Connection對象
session = Session( target = Target( connection=SocketConnection("127.0.0.1", 8021, proto='tcp')))
准備好會話對象后,接下來需要在協議中定義消息。細節請參照靜態協議定義功能定義協議:https://boofuzz.readthedocs.io/en/stable/user/static-protocol-definition.html#static-primitives 每一條消息均以一個s_initialize函數開頭,比如fuzz FTP協議中的幾個消息定義:
s_initialize("user") s_string("USER") s_delim(" ") s_string("anonymous") s_static("\r\n") s_initialize("pass") s_string("PASS") s_delim(" ") s_string("james") s_static("\r\n") s_initialize("stor") s_string("STOR") s_delim(" ") s_string("AAAA") s_static("\r\n") s_initialize("retr") s_string("RETR") s_delim(" ") s_string("AAAA") s_static("\r\n")
定義消息后,將使用剛剛創建的Session對象將它們連接到圖中:
session.connect(s_get("user")) session.connect(s_get("user"), s_get("pass")) session.connect(s_get("pass"), s_get("stor")) session.connect(s_get("pass"), s_get("retr"))
之后,就可以進行fuzz測試了
session.fuzz()
每次運行的日志數據將保存在當前工作目錄下boofuzz-results目錄中的SQLite數據庫中。可以隨時通過以下任一方式在任何這些數據可上重新打開Web界面
boo open <run-*.db>
3、Static Protocol Definition(靜態協議定義)
Request是信息,Blocks是消息中的塊,而Primitives是構成塊/請求的元素(字節,字符串,數字,檢驗和等)。
Blocks將獨立的primitives組建成有序的塊。Groups中包含了一些特定的primitives,一個Group和一個Block結合后,每次fuzzer調用Block的時候,都會將Group中的數據循環的取出,組成不同的Block。
Group允許你連接一個塊到指定的group原語,和一個組關聯的block必須為每個組中的值循環窮盡該block的所有空間。組原語是非常有用的,s_group定義一個組,並接受兩個必須的參數,第一個參數指定組名稱,第二個參數指定一個需要迭代的原始值列表。
a) 請求操作
s_initialize(名稱)
---初始化一個新的塊請求。此調用后生成的所有塊/原語都適用於指定的請求。使用s_switch()在工廠之間跳轉。
參數:name(str) - 請求名稱
s_get(名稱=無)
---如果未指定名稱,則返回具有指定名稱的請求或當前請求。使用它可以從全局函數樣式請求操作切換到直接對象操作。
s_num_mutations()
---確定我們將要進行的重復次數
返回類型:整數
s_switch(名稱)
---將當前請求更改為"name"指定的請求。
參數: name(str) - 請求名稱
b) 塊操作
s_block ( name = None , group = None , encoder = None , dep = None , dep_value = None , dep_values = None , dep_compare = '==' )
在當前請求下打開一個新塊。返回的實例支持"with"接口,因此它會自動為您關閉:
with s_block("header"): s_static("\x00\x01") if s_block_start("body"): ...
參數:
-
name ( str , optional ) – 正在打開的塊的名稱
-
group ( str , optional ) – (Optional, def=None) 與這個塊相關聯的組名
-
編碼器(函數指針,可選)–(可選,def=None)指向函數的可選指針,在返回之前將呈現的數據傳遞給
-
dep ( str , optional ) – (Optional, def=None) 可選原語,它的具體值取決於這個塊
-
dep_value ( Mixed , optional ) – (Optional, def=None) 字段“dep”必須包含的值才能渲染塊
-
dep_values(混合類型列表,可選)–(可選,def=None)字段“dep”可能包含用於渲染塊的值
-
dep_compare ( str , optional ) – (Optional, def="==") 依賴的比較方法 (==, !=, >, >=, <, <=)
s_block_start ( name = None , * args , ** kwargs )
在當前請求下打開一個新塊。此例程始終返回一個實例,因此您可以通過縮進使模糊器變得漂亮:(更喜歡直接使用 s_block 而不是這個函數)
s_block_end (名稱=無)
關閉最后打開的塊。可選地指定要關閉的塊的名稱(純粹出於美觀目的)
參數:name(str) - (可選,def-None)要關閉的塊的名稱。
c) 原始定義:
s_binary ( value , name = None )
將可變格式的二進制字符串解析為靜態值並將其壓入當前塊堆棧。
參數:
-
value ( str ) – 可變格式二進制字符串
-
name ( str ) –(可選,def=None)指定名稱可以讓您直接訪問原語
s_delim ( value = ' ' , fuzzable = True , name = None )
將分隔符推送到當前堆棧上。
參數:
-
value ( Character ) – (可選,def="")原始值
-
fuzzable ( bool ) –(可選,def=True)啟用/禁用此原語的模糊測試
-
name ( str ) –(可選,def=None)指定名稱可以讓您直接訪問原語
s_group ( name = None , values = None , default_value = None )
這個原語代表一個靜態值列表,在突變時逐步遍歷每個值。您可以將一個塊綁定到一個組原語,以指定該塊應該循環遍歷組內每個值的所有可能的突變。例如,組原語對於表示有效操作碼列表很有用。
參數:
-
name ( str ) –(可選,def=None)組名
-
值(列表或原始數據)–(可選,def=None)該組可以采用的可能原始值的列表。
-
default_value ( str or bytes ) – (可選,def=None) fuzzing() 完成時指定一個值
s_static(值=無,名稱=無)
將靜態值推送到當前塊堆棧上。
- 參數:
-
value ( Raw ) – 原始靜態數據
-
name ( str ) –(可選,def=None)指定名稱可以讓您直接訪問原語
s_string ( value = '' , size = None , padding = b'\x00' , encoding = 'ascii' , fuzzable = True , max_len = None , name = None )
將字符串壓入當前堆棧。
參數:
-
值(str)–(可選,def =””)默認字符串值
-
size ( int ) –(可選,def=None)此字段的靜態大小,為動態保留 None 。
-
填充(字符)-(可選,def=”x00”)用作填充以填充靜態字段大小的值。
-
encoding ( str ) –(可選,def=”ascii”)字符串編碼,例如: utf_16_le 用於 Microsoft Unicode。
-
fuzzable ( bool ) –(可選,def=True)啟用/禁用此原語的模糊測試
-
max_len ( int ) –(可選,def=None)最大字符串長度
-
name ( str ) –(可選,def=None)指定名稱可以讓您直接訪問原語
例如下面是官網給出的http.py中的代碼:
s_initialize("HTTP VERBS") s_group("verbs", values=["GET", "HEAD", "POST", "TRACE", "PUT", "DELETE"]) if s_block_start("body", group="verbs"): s_delim(" ") s_delim("/") s_string("index.html ") s_delim(" ") s_string("HTTP") s_delim("/") s_string("1") s_delim(".") s_string("1") s_block_end()
模塊常用語法:
s_initialize('grammar') # 初始化塊請求並命名 s_static("HELLO\r\n") # 始終發送此消息 s_static("PROCESS") # 在HELLO\r\n之后立即發送 s_delim("") # 使用s_delim()原語代替分隔符 s_string("AAAA") # 這是我們的fuzz字符串 s_static("\r\n") # 告訴服務器"done"
Connections
target = sessions.target("10.0.0.1", 5168) target.netmon = pedrpc.client("10.0.0.1", 26001) target.procmon = pedrpc.client("10.0.0.1", 26002) target.vmcontrol = pedrpc.client("127.0.0.1", 26003) target.procmon_options = \ { "proc_name" : "SpntSvc.exe", "stop_commands" : ['net stop "trend serverprotect"'], "start_commands" : ['net start "trend serverprotect"'], } sess.add_target(target) sess.fuzz()
下面的netmon(網絡監控代理) 、procmon(進程監控代理)、vmcontrol(VMware控制代理)為3個agent的子模塊,用來監測程序:
- netmon子模塊:netmon子模塊主要負責捕捉網絡的雙向流量,並保存。 在向target發送數據之前,agent向target發送請求並記錄流量,數據傳送成功后,該代理子模塊將記錄的流量存入磁盤。
- procmon子模塊:procmon子模塊主要負責檢測fuzz過程中發生的故障。 在向target發送數據之后,boofuzz聯系該代理以確定是否觸發了故障,如果產生故障,關於故障性質的hgih level信息將被傳送回session。錯誤信息會儲存在名為"crash bin"的文件中,我們也可以在web監控服務中看到發生crash時加載詳細的crash信息。
- vmcontrol子模塊:vmcontrol子模塊主要用來控制虛擬機。一種常見的用法是把Target目標放在虛擬機中,使用這個子模塊來控制虛擬機的啟動、關閉、創建快照等,最重要的功能是能在目標出現崩潰的時候恢復主機的狀態。
下面簡單的介紹一個請求樣例:
session.connect(s_get("user")) session.connect(s_get("user"), s_get("pass")) session.connect(s_get("pass"), s_get("stor")) session.connect(s_get("pass"), s_get("retr"))
連接后,我們發送用戶名請求
在發送用戶名后,我們發送密碼
只有在發送密碼后我們才能發送stor或retr請求