百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 编程字典 > 正文

持续更新 | 模糊测试工具合集,附贴心教程(二)

toyiye 2024-06-22 20:20 13 浏览 0 评论

模糊测试

模糊测试是一种暴力可靠性测试技术。它采用黑盒测试的思想,通过自动或半自动的生成大量畸形的随机数据来作为应用程序的输入,并监视程序异常,以发现应用程序中可能存在的漏洞或安全缺陷。


模糊测试是一个很重要的流程,有助于发现影响当今复杂应用程序的未知关键错误,有时候甚至能暴露出一些让人“脑洞大开”或者“七窍生烟”的奇葩问题。


本文分为上中下三篇文章,介绍一些现有的运用在不同方向上的优秀fuzzing开源项目,包括针对通信协议Fuzzers、和文件Fuzzers


通信协议Fuzzers


针对通信协议的开源的Fuzz工具——BooFuzz


1、什么是BooFuzz


BooFuzz是一个开源的网络协议模糊测试框架,它是由Python语言编写的,可以部署在Windows、Linux和Mac平台。BooFuzz继承自Sulley,它提供了对于网络协议进行模糊测试的规范和功能函数,您可以在此基础上编写针对特定目标的Python脚本,对目标进行量身定制的Fuzz,但这需要开发者对所测试协议的格式有一定了解。BooFuzz并不是直接对服务器程序或客户端程序进行Fuzz的,为了更好地模拟服务器或客户端在真实情境下的工作状态,其模糊测试的对象是会话。


BooFuzz可用于IoT设备通信协议的测试。想路由器、HTTP server和Telnet这些模块通常能够与外界进行交互,从而为攻击者提供了入口。如果这些模块或使用的协议存在漏洞,其很有可能会被直接利用来进行远程攻击,从而带来严重的安全隐患。BooFuzz根据测试人员编写的脚本生成大量变异报文,并发送给目标设备,实时监控目标设备,通过设备服务的可用性来判断设备崩溃情况。


与Sulley不同,BooFuzz还具有以下特点:更轻松的安装体验;支持多种协议,比较灵活;内置支持串行模糊测试、以太网和IP层、UDP广播;能够更好地记录测试数据;测试结果CSV导出等。


由于BooFuzz继承了Sulley框架,若想了解BooFuzz的框架,应当先了解Sulley的整体架构。Sulley框架如下(来源: Fuzzing Sucks! Introducing the sulley fuzzing framework. Pedram Amini & Aaron Portnoy. Black Hat US 2007):



由图基本可以看到Sulley主要包括四大组件,分别为Data Generation(数据生成)、Session(会话管理)、Agents(代理)和Utilities(独立单元工具)。其整体的架构和peach、sfuzz相似,是一种比较好的商业化架构。


目前BooFuzz主要支持两种底层的通信方式:socket和串口serial。这两种通信方式又可以延申出TCPSocketConnection、UDPSocketConnection、SSLSocketConnection、RawL2SocketConnection、RawL3SocketConnection、SocketConnection和SerialConnection几种通信方式。


BooFuzz可以从socket底层开始封装测试,比如可以测试tcp、udp、ftp和http等协议。这类fuzz工具还有一个比较典型的特点,就是必须存在对崩溃进行监控的监控器。BooFuzz本身提供了三种监控器:networkmonitor、processmonitor和callbackmonitor。BooFuzz支持的编译算法比较简单,仅仅支持对于string、num和字典数据的变异。


2、BooFuzz 脚本编写


2.1 创建session对象

所有的Fuzz都是有针对的对象的,也就是说,在开始Fuzz前,我们首先要确定对哪个程序进行Fuzz。BooFuzz是针对Session进行模糊测试的,因此我们首先要定义一个Session对象。当您创建Session时,您将传递一个Target对象,该对象本身接收一个Connection对象(可用选项包括SocketConnection和SerialConnection)。例如:

session = Session(
    target=Target(
        connection=SocketConnection("127.0.0.1", 8021, proto='tcp')))


2.2 定义消息

准备好会话对象后,需要在协议中定义消息,细节可参照静态协议定义:https://boofuzz.readthedocs.io/en/stable/user/static-protocol-definition.html#static-primitives


每一个消息都是以一个s_initialize函数开头的,模块的常用语法如下:

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"


下面是fuzz FTP协议中的几个消息定义:


2.3 连接请求

如果需要fuzz多个请求,则需要将定义的请求按照一定的先后顺序连接起来。在前面的fuzz FTP协议中定义了user、pass、stor和retr这几个消息,在定义消息后,要将之前定义的请求按一定的先后顺序连接,比如:

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"))

其意味着pass请求要在user请求之后发生,stor和retr要在pass之后发生,而stor和retr这两个请求之间则没有明确的先后关系。上述代码等同于下图:



BooFuzz的快速入门指南参见官方文档:

https://boofuzz.readthedocs.io/en/stable/user/quickstart.html


3、BooFuzz实战


3.1BooFuzz安装

这里仅介绍在kali linux的安装,其他系统的安装方法请参考官方手册:

https://boofuzz.readthedocs.io/en/stable/user/install.html


首先下载源码:
git clone https://github.com/jtpereyda/boofuzz.git


若出现报错:
fatal: unable to access 'https://github.com/jtpereyda/boofuzz.git/': gnutls_handshake() failed: Error in the pull function.
则将命令中的https换成git即可:git clone git://github.com/jtpereyda/boofuzz.git

BooFuzz要求Python版本不低于3.5,且使用pip安装,因此要确保有最新版的pip和setuptools,可执行命令pip3 install -U pip setuptools来更新pip和setuptools版本,然后执行pip install boofuzz命令进行安装即可。


3.2实战思路


3.3开始Fuzz

Step1 根据网络数据包构造请求

比如您要对路由器的登录接口进行fuzz测试,则先访问路由器的登录界面,使用BurpSuite设置代理,并进行抓包。注意,在开始之前要尽可能多地与设备进行交互。BooFuzz方法不仅局限于IoT设备,也可用于对常见的服务程序进行测试。

在此,我们将对bWapp的登录接口进行fuzz测试。先使用bWapp登录界面,并使用BurpSuite进行抓包。BurpSuite抓取到的报文如下:


根据数据包报文,利用BooFuzz框架对HTTP请求进行定义:


Step2 设置会话信息

定义一个Session对象,其中参数包括TCP连接、每一次fuzz的时间间隔以及每一次fuzz日志的输出。格式如下:


在此给出一个最简单的会话设置,包括IP地址和端口:

完成后要将我们构造的请求包加入到我们的会话图(Session)中,作为唯一的一个节点:

session.connect(s_get("Login"))


Step3 添加监视器

如前面所说,判断目标设备是否崩溃的方法之一,就是判断设备是否在线。结合Step2中引用的函数Remote_NetworkMonitor(),该函数并非官方API,也可以不添加,其核心代码如下:

注意:完成各部分设置后,需要调用session.fuzz()才能进行测试。


Step4 编写完整测试脚本

进入boofuzz目录,创建测试脚本bfuzztest.py,并编写脚本,完成脚本内容如下(脚本代码附在文末):


Step5 开始Fuzz

执行python3 [脚本文件名]来运行编写好的脚本


每次测试的日志数据将保存到当前目录下boofuzz-results目录中的SQLite数据库中,运行boo open,将在26000端口开启一个Web服务器,用于控制和查看测试进度。(在某些版本中运行fuzz脚本会自动打开26000端口)在浏览器访问localhost:26000即可。


对于测试出来的漏洞,我们需要根据测试结果来分析其产生的原因。在Kali中自带DB Browser for SQLite工具,用它打开boofuzz-result目录下的相关文件即可查看fuzz的日志。在其他系统中可以使用数据库查看软件来查看日志。

从数据库中可以看到,构造的数据包按照我们的代码对相关字段进行了变异,从而达到了fuzz的效果。


4、相关博客

https://blog.csdn.net/song_lee/article/details/104334096

https://jackfromeast.site/2021-03/boofuzz-1.html

https://cloud.tencent.com/developer/article/1418469

https://www.cnblogs.com/yuzhouliu/p/15180591.html


附:bfuzztest.py脚本

#!/usr/bin/env python
# Designed for use with boofuzz v0.0.9
# coding=utf-8
from boofuzz import *




def main():
    session = Session(
        target=Target(
            connection=SocketConnection("192.168.153.143", 80, proto='tcp')
        ),
    )




    s_initialize(name="Login")
    with s_block("Login-Line"):
        # LINE 1
        s_static("POST", name="Method")
        s_delim(" ", name='space-1')
        s_string("/login.php", name='Login-URI')  # variation
        s_delim(" ", name='space-2')
        s_static('HTTP/1.1', name='HTTP-Version')  
        s_static("\r\n")




        # LINE 2
        s_static("Host", name="Host")
        s_static(": ")
        s_static("192.168.153.143", name="ip")
        s_static("\r\n")




        # LINE 3  对应 Content-Length: 52
        s_static('Content-Length')
        s_static(': ')
        s_size('data', output_format='ascii', fuzzable=True)    
# size的值根据data部分的长度自动进行计算,同时对该字段进行fuzz
       s_static('\r\n')




        # ...
        s_static('\r\n')




    # 对应http请求数据
    with s_block('data'):
        s_static(' login=bee&password=')
        s_string('1231', max_len=1024)   # 需要变异,且最大长度为1024
        s_static('&security_level=0&form=submit')




    session.connect(s_get("Login"))
    session.fuzz()




if __name__ == "__main__":
    main()



文件Fuzzers


模糊测试应当用于测试每个需要接受某种形式输入的接口,起码要测试每个从潜在恶意来源获取输入的接口。除了前面提到的通信协议,文件也同样可能存在安全风险,尤其是我们熟知的二进制文件。


对于程序来说,少数字节错位都有可能使得整个应用程序毁于一旦。这些使程序崩溃的畸形文件通过损坏程序的字节,而使其破坏自己的栈、堆。在旧式、无内存的操作系统中,畸形文件通常能将整个计算机宕掉。此外,还有JS文件和HTML等Web相关的文件,这些文件都有可能引发安全问题。


文件类型模糊测试的通常做法是:先准备一份插入程序中的正确的文件,然后用随机数据替换该文件的某些部分,最后用程序打开经模糊后的文件,观察程序是否出现异常或出现了什么异常。


下面将介绍两款工具:DrillerDomato


针对文件的开源的Fuzz工具——Driller


1、什么是Driller

Driller依赖于模糊测试工具AFL和二进制分析工具angr,Driller是在AFL的基础上开发的crash模糊测试工具。Driller在AFL的基础上加入了动态符号执行引擎,当模糊测试发生stuck时,使用动态符号执行去突破这些限制,生成满足fuzz需求的新输入,使得fuzz能够继续执行。具体实现是,通过监测AFL的执行,我们可以决定什么时候开始符号执行以探索新路径。如果AFL执行了x轮后,bitmap上显示没有发现新的状态转换(也即新的代码块转移),说明AFL卡住了,这时候调用angr进行符号执行。每个具体输入对应于PathGroup中的单个路径, 在PathGroup的每一步中,检查每个分支以确保最新的跳转指令引入先前AFL未知的路径。当发现这样的跳转时,SMT求解器被查询以创建一个输入来驱动执行到新的跳转。这个输入反馈给AFL,AFL在未来的模糊步骤中进行变异。这个反馈循环使我们能够将昂贵的符号执行时间与廉价的模糊时间进行平衡,并且减轻了模糊对程序操作的低语义洞察力。


Driller结合了AFL的高效、低消耗、快速的优点和动态符号执行探索能力强的优点,又避免了AFL较难突破特殊的边界和动态符号执行路径爆炸的问题。不过基于符号执行增强的模糊测试技术仍然会受限于符号执行中的约束求解问题,符号执行的引入可能会弱化模糊测试本身的可扩展性。


2、Driller的安装

此安装过程使用的是ubuntu16.04。


2.1、环境准备

(1)安装一些必需的软件包

apt-get install build-essential gcc-multilib libtool automake autoconf bison debootstrap debian-archive-keyring libtool-bin python3-dev libffi-dev virtualenvwrapper git wget flex python libglib2.0-dev qemu-system libacl1-dev python3 python3-pip libssl-dev



(2)安装在编译qemu时所需的环境

apt-get build-dep qemu



2.2、安装AFL

(1)新建一个文件夹来保存所有的过程文件

mkdir driller && cd driller


(2)从GitHub上可以下载AFL源码

git clone git://github.com/google/AFL.git



(3)进入源码文件夹,执行编译命令

cd AFL
make -j4



(4)编译qemu

cd qemu_mode
./build_qemu_support.sh



2.3、安装Driller

在安装与使用Driller时,最好在一个单独的Python虚拟环境中进行,这里使用的是Aconda环境。


Part1:安装Anaconda

(1)下载Anaconda的安装脚本

wget https://repo.anaconda.com/archive/Anaconda3-2020.11-Linux-x86_64.sh



(2)赋予执行权限

chmod +x Anaconda3-2020.11-Linux-x86_64.sh



(3)运行脚本

./Anaconda3-2020.11-Linux-x86_64.sh




(4)修改shell的环境配置


vim ~/.bashrc
在其中添加实际安装路径
export PATH="/root/anaconda3/bin:$PATH"


(5)刷新重新运行

source ~/.bashrc


Part2:建立Driller虚拟环境

(1)使用Aconda建立一个名字叫做driller的虚拟环境

conda create -n driiler python=3.8


(2)进入虚拟环境中

conda activate driiler


Part3:安装Driller

(1)安装angr

pip install angr


(2)安装cle

pip install cle


(3)安装tracer

pip install git+git://github.com/angr/tracer


(4)安装driller

pip install git+git://github.com/shellphish/driller


可以导入driller即为安装成功。


3、Driller的使用


这种安装方法是采用一种Driller和AFL并行运行的过程,将Driller中求解输入的部分和AFL fuzz的部分分别放在两个terminal运行。

(1)创建Fuzz的程序源码为buggy.c,其内容如下:

#include
#include




int main(int argc, char *argv[]) {
  char buffer[6] = {0};
  int i;
  int *null = 0;




  read(0, buffer, 6);
  if (buffer[0] == '7' && buffer[1] == '/' && buffer[2] == '4'
      && buffer[3] == '2' && buffer[4] == 'a' && buffer[5] == '8') {
    i = *null;
  }




  puts("No problem");
}



(2)编译buggy.c

由于使用AFL QEMU模式,因此不需要对源码进行插桩

gcc -o buggy buggy.c -g


(3)打开一个terminal, 用AFL进行FUZZ

mkdir -p workdir/input
echo 'init' > workdir/input/seed1   # 提供初始化种子输入
echo core | sudo tee /proc/sys/kernel/core_pattern
AFL/afl-fuzz -M fuzzer-master -i workdir/input/ -o workdir/output/ -Q ./buggy


接下来就是开始AFL的Fuzz过程(窗口较小时不会显示界面,调整窗口大小即可):


当输入为7/42a8就会发生crash。

process timing:显示的是fuzzer的运行时间,最近一次发现新路径的时间,最近一次崩溃的时间和最近一次挂起的时间。


overall results:显示的是运行的总周期数,总路径数,崩溃次数和挂起次数。


cycle progress:显示的是正在处理的测试样例的编号和由于超时放弃的的输入数量。


map coverage:第一行显示的是已经命中了多少分支元组与位图可以容纳的数量的比例。左边的是当前输入,右边的是整个输入语料库的值。第二行显示的是二进制文件中元组命中计数的变化。


stage progress:显示的是正在测试的策略,进度,总执行次数和执行速度。一般正常的执行速度应该在500以上。


findings in depth:显示的是基于应用到代码的最小化算法获得的fuzzer最喜欢的路径数和真正获取更好的边缘覆盖率的测试用例数,还有崩溃和超时的数量,注意这里的tmout和hang有所不同,tmout数量包括了所有超过超时的测试用例,即使它们没有超过超时足够的幅度而分类为hangs(挂起)


fuzzing strategy yields:用于追踪各种fuzzing策略所获得的路径和尝试执行的次数的比例,用于验证各种方法的有效性。


path geometry:第一个数据指的是fuzzing过程中达到的路径深度,pending指的是还有多少输入数据没有经过任何测试。pend fav时指fuzzer在这个队列中真正想到达的条目。接下来的是这个fuzzing部分找到的新路径数量和在进行并行化fuzzing时从其他fuzzer实例导入的路径数量 。最后一个数据可以衡量观测到痕迹的一致性,如果程序对相同的输入始终表现相同,则为100%。


(4)创建一个run_driller.py脚本来运行driller,其内容如下:

#!/usr/bin/env python




import errno
import os
import os.path
import sys
import time




from driller import Driller








def save_input(content, dest_dir, count):
    """Saves a new input to a file where AFL can find it.




    File will be named id:XXXXXX,driller (where XXXXXX is the current value of
    count) and placed in dest_dir.
    """
    name = 'id:%06d,driller' % count
    with open(os.path.join(dest_dir, name), 'wb') as destfile:
        destfile.write(content)








def main():
    if len(sys.argv) != 3:
        print('Usage: %s' % sys.argv[0])
        sys.exit(1)




    _, binary, fuzzer_dir = sys.argv




    # Figure out directories and inputs
    with open(os.path.join(fuzzer_dir, 'fuzz_bitmap'), 'rb') as bitmap_file:
        fuzzer_bitmap = bitmap_file.read()
    source_dir = os.path.join(fuzzer_dir, 'queue')
    dest_dir = os.path.join(fuzzer_dir, '..', 'driller', 'queue')




    # Make sure destination exists
    try:
        os.makedirs(dest_dir)
    except os.error as e:
        if e.errno != errno.EEXIST:
            raise




    seen = set()  # Keeps track of source files already drilled
    count = len(os.listdir(dest_dir))  # Helps us name outputs correctly




    # Repeat forever in case AFL finds something new
    while True:
        # Go through all of the files AFL has generated, but only once each
        for source_name in os.listdir(source_dir):
            if source_name in seen or not source_name.startswith('id:'):
                continue
            seen.add(source_name)
            with open(os.path.join(source_dir, source_name), 'rb') as seedfile:
                seed = seedfile.read()




            print('Drilling input: %s' % seed)
            for _, new_input in Driller(binary, seed, fuzzer_bitmap).drill_generator():
                save_input(new_input, dest_dir, count)
                count += 1




            # Try a larger input too because Driller won't do it for you
            seed = seed + b'0000'
            print('Drilling input: %s' % seed)
            for _, new_input in Driller(binary, seed, fuzzer_bitmap).drill_generator():
                save_input(new_input, dest_dir, count)
                count += 1
        time.sleep(10)




if __name__ == '__main__':
    main()



(5)打开另一个terminal运行driller部分

source ~/.bashrc
conda activate driiler
python run_driller.py ./buggy workdir/output/fuzzer-master

启动成功后显示如下内容。

测试一段时间后我们可以打开输出文件夹里的fuzzer-master,就可以看到有crashes,hangs,queue三个文件夹存放着生成的对应的样例,以及fuzz_bitmap, fuzzer_stats, plot_data三个文件。


crash文件夹包含导致测试程序接收致命信号的测试用例,进入crashes可以看到触发得到的crash,得到的crash可以用xxd查看。


hang文件夹包含导致测试程序超时的测试用例,本次为0。

queue文件夹包含每个独特执行路径的测试用例,以及用户给出的起始文件。


查看里面的文件,afl逐渐从初始输入的“init”接近“7/42a8”,这将使我们的程序崩溃。


3、相关文献及博客:

[1] Stephens N , Grosen J , Salls C , et al. Driller: Augmenting Fuzzing Through Selective Symbolic Execution[C]// Network and Distributed System Security Symposium. 2016.

[2] 博客Driller工具分析:https://blog.csdn.net/Chen_zju/article/details/80791281


静候下文,仍有更多工具汇总

相关推荐

为何越来越多的编程语言使用JSON(为什么编程)

JSON是JavascriptObjectNotation的缩写,意思是Javascript对象表示法,是一种易于人类阅读和对编程友好的文本数据传递方法,是JavaScript语言规范定义的一个子...

何时在数据库中使用 JSON(数据库用json格式存储)

在本文中,您将了解何时应考虑将JSON数据类型添加到表中以及何时应避免使用它们。每天?分享?最新?软件?开发?,Devops,敏捷?,测试?以及?项目?管理?最新?,最热门?的?文章?,每天?花?...

MySQL 从零开始:05 数据类型(mysql数据类型有哪些,并举例)

前面的讲解中已经接触到了表的创建,表的创建是对字段的声明,比如:上述语句声明了字段的名称、类型、所占空间、默认值和是否可以为空等信息。其中的int、varchar、char和decimal都...

JSON对象花样进阶(json格式对象)

一、引言在现代Web开发中,JSON(JavaScriptObjectNotation)已经成为数据交换的标准格式。无论是从前端向后端发送数据,还是从后端接收数据,JSON都是不可或缺的一部分。...

深入理解 JSON 和 Form-data(json和formdata提交区别)

在讨论现代网络开发与API设计的语境下,理解客户端和服务器间如何有效且可靠地交换数据变得尤为关键。这里,特别值得关注的是两种主流数据格式:...

JSON 语法(json 语法 priority)

JSON语法是JavaScript语法的子集。JSON语法规则JSON语法是JavaScript对象表示法语法的子集。数据在名称/值对中数据由逗号分隔花括号保存对象方括号保存数组JS...

JSON语法详解(json的语法规则)

JSON语法规则JSON语法是JavaScript对象表示法语法的子集。数据在名称/值对中数据由逗号分隔大括号保存对象中括号保存数组注意:json的key是字符串,且必须是双引号,不能是单引号...

MySQL JSON数据类型操作(mysql的json)

概述mysql自5.7.8版本开始,就支持了json结构的数据存储和查询,这表明了mysql也在不断的学习和增加nosql数据库的有点。但mysql毕竟是关系型数据库,在处理json这种非结构化的数据...

JSON的数据模式(json数据格式示例)

像XML模式一样,JSON数据格式也有Schema,这是一个基于JSON格式的规范。JSON模式也以JSON格式编写。它用于验证JSON数据。JSON模式示例以下代码显示了基本的JSON模式。{"...

前端学习——JSON格式详解(后端json格式)

JSON(JavaScriptObjectNotation)是一种轻量级的数据交换格式。易于人阅读和编写。同时也易于机器解析和生成。它基于JavaScriptProgrammingLa...

什么是 JSON:详解 JSON 及其优势(什么叫json)

现在程序员还有谁不知道JSON吗?无论对于前端还是后端,JSON都是一种常见的数据格式。那么JSON到底是什么呢?JSON的定义...

PostgreSQL JSON 类型:处理结构化数据

PostgreSQL提供JSON类型,以存储结构化数据。JSON是一种开放的数据格式,可用于存储各种类型的值。什么是JSON类型?JSON类型表示JSON(JavaScriptO...

JavaScript:JSON、三种包装类(javascript 包)

JOSN:我们希望可以将一个对象在不同的语言中进行传递,以达到通信的目的,最佳方式就是将一个对象转换为字符串的形式JSON(JavaScriptObjectNotation)-JS的对象表示法...

Python数据分析 只要1分钟 教你玩转JSON 全程干货

Json简介:Json,全名JavaScriptObjectNotation,JSON(JavaScriptObjectNotation(记号、标记))是一种轻量级的数据交换格式。它基于J...

比较一下JSON与XML两种数据格式?(json和xml哪个好)

JSON(JavaScriptObjectNotation)和XML(eXtensibleMarkupLanguage)是在日常开发中比较常用的两种数据格式,它们主要的作用就是用来进行数据的传...

取消回复欢迎 发表评论:

请填写验证码