一、背景:
对于物联网设备,比如家用路由器模块,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请求