一、背景:

  对于物联网设备,比如家用路由器模块,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绘制:

BooFuzz模糊测试_数据

在上图中,节点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请求