原文链接:https://resources.infosecinstitute.com/intro-to-fuzzing/
  Vulnserver是由Stephen Bradshaw编写的用于包含安全漏洞的TCP服务器应用程序,将用做本文的fuzz目标。SPIKE将作为fuzzer,后续还将 还将讨论SPIKE脚本和一种自动化SPIKE fuzz的简单方法。

要求和系统设置

  为了跟随本文中的fuzz练习,需要准备两个联网系统 - 一个带有漏洞的的应用程序Vulnserver的Windows系统(Windows XP,Vista或Windows 7),它将充当我们的fuzz目标,以及一个Linux系统用来操作SPIKE。 我建议使用BackTrack 4 Final或更高版本的Linux系统版本,因为我们执行fuzz工作所需的所有软件都已包含在内。 这些所需系统中的任何一个(或两个)都可以作为虚拟机运行。

  在接下来的练习中的许多步骤中,涉及到的命令和脚本将通过其IP地址来访问我的目标系统。 我的模糊测试目标系统IP地址192.168.56.101。 如果您的系统正在使用其他地址,请确保在后面的任何命令中输入相应的IP地址来代替192.168.56.101。

  在您实际编译SPIKE之前,我建议先对源码进行一些小改动。 编辑SPIKE / SPIKE / src中的文件spike.c,找到紧跟printf(“tired to send a closed socket!\ n”)行的两个return 0;。 用”exit(1);”替换这两个”return 0;”。 这种更改实质上会导致SPIKE在尝试将数据发送到已关闭的套接字时以非零返回值退出,这个用来配合再稍后本文中提到的从wrapper脚本运行SPIKE。 完成此更改后,您可以正常地从src目录运行”./configure;make”命令编译SPIKE。

  在模糊测试目标系统上,需要管理权限运行Ollydbg调试器。在Vista或Windows 7上,确保右键单击并选择以管理员身份运行,以确保以适当的权限运行调试器。

  运行Vulnserver应用程序本身并不是特别困难。 只需将发行版中的两个二进制文件 - vulnserver.exe和essfunc.dll放入同一目录,然后使用OllyDbg中的“打开”命令或双击可执行文件启动程序。 默认情况下,程序占用TCP端口9999,但如果您希望它侦听另一个端口,只需将端口号作为命令行参数提供给vulnserver.exe。

fuzz是什么?

  fuzz测试是将故意格式错误的数据发送到程序以便在应用程序中生成故障或错误的过程。软件开发人员用这个测试自己的代码漏洞,与二进制和源代码分析一起,模糊测试是发现可利用软件漏洞的主要方式之一。有许多流行的和免费的软件fuzzer可用,本文介绍的是SPIKE。

SPIKE Fuzzer

  从技术上讲,SPIKE实际上是一个模糊器创建工具包,它提供了API,允许用户使用C语言基于网络的协议来创建自己的fuzzer。 SPIKE定义了一些它可供C编码器使用的原语,它允许它构造称为“SPIKES”的模糊消息,这些消息可以发送到网络服务,以期产生错误。

  正如我所提到的,SPIKE是一个基于C的fuzzer创建工具包,但用户不必知道如何编写C程序来使用SPIKE。有一些命令行工具可以作为包含SPIKE原语的简单文本文件的解释器。SPIKE还包括一个简单的脚本编写功能,并且在SPIKE发行版中,有一些命令行工具可以作为包含SPIKE原语的解释器。

  众所周知,SPIKE记录很糟糕,但是通过查看发行版中提供的一些SPIKE脚本,并扫描SPIKE C代码的元素,我们可以开始构建一些可以使用的原语列表。 为了方便查阅,本文档稍后将列出其中一些原语。

  SPIKE上还有很多论文和演示文稿,其中最有趣的是SPIKE作者“Daily”Dave Aitel的旧版演示文稿(http://www.immunitysec.com/downloads/usingspike3.ppt)。虽然演示文稿缺乏详细的例子,但它确实概述了很多东西。这使得SPIKE成为一个非常棒的fuzzer。 让我们查看一下这些功能,看看这些对我们有用的地方。

SPIKE有哪些有用的功能?

  • SPIKE有大量用于模糊测试的内置字符串,可以非常有效地在程序中产生各种错误,并且SPIKE可以确定最“适合”发送给应用程序的值。 这意味着用户不必自己提供这些值,并且可以从程序作者所选择的良好的fuzz字符串的丰富经验中受益。
  • SPIKE具有“块”的概念,可用于计算由SPIKE代码生成的SPIKE中指定部分的大小。 然后可以以各种不同的格式将这些大小值插入到SPIKES中。 当fuzz协议需要为消息中的特定字段指定准确的大小时,可以节省用户进行这些计算的工作量。
  • SPIKE可以支持网络协议中常用的许多不同数据类型,并且可以以各种不同的格式接受它们,以便从许多不同的程序中轻松剪切和粘贴。

SPIKE Scripting

  如前所述,SPIKE还包括一个基本脚本功能,允许您使用SPIKE原语来模糊应用程序,而无需在C中编写自己的SPIKE fuzzer代码。SPIKE发行版提供了各种不同的解释器,允许您指定这些SPIKE原语的某些相关子集,以便针对各种类型的网络应用程序使用。为了简化本文其余部分的内容,我将引用可在SPIKE脚本中用作“命令”的SPIKE原语的子集。

  对于基于TCP的服务器应用程序,我们可以通过将SPIKE命令写入.spk脚本文件并使用TCP SPIKE脚本解释器generic_send_tcp运行它们来利用此脚本功能,该脚本将在特定IP地址和 TCP端口发送指定的SPIKE。还有一个generic_send_udp,它会做类似的事情,但是在这个解释器中,SPIKES将通过UDP发送。

  我们将用generic_send_tcp解释器fuzz我们的应用程序。该解释器可以在BackTrack上的/ pentest / fuzzers / spike /中找到(如果你自己下载并编译了SPIKE,则可以在src目录中找到),如下不带任何命令行参数的情况下运行它:

root@bt4r1vm:/pentest/fuzzers/spike/# ./generic_send_tcp
argc=1
Usage: ./generic_send_tcp host port spike_script SKIPVAR SKIPSTR
./generic_send_tcp 192.168.1.100 701 something.spk 0 0

  前三个必需的命令行选项是自解释(self explanatory)的,参数一和二定义了要连接的主机和TCP端口以进行fuzz,第三个参数定义了SPIKE脚本文件的名称。参数4和5可能需要更多解释。 这些参数SKIPVAR和SKIPSTR基本上允许您跳转到SPIKE脚本定义的fuzz会话的中间。

  在SPIKE脚本中,您可以指定“s_string_variables”,这些命令用于将实际模糊字符串插入到您发送的每个SPIKE中。 如果在脚本中使用多个“s_string_variables”,则可以通过为SKIPVAR设置适当的值来跳过使用“s_string_variables”的早期实例。 例如,如果在SPIKE脚本中包含三个“s_string_variables”,并且想要忽略前两个变量并且仅模糊第三个变量,则应将SKIPVAR设置为2(变量的编号从0开始向上计数,因此 第三个变量用数字2表示。

  每个“s_string_variables”还有一个由SPIKE构建的不同fuzz字符串值的数组,它将在SPIKE模糊测试会话中迭代。 如果要跳过这些字符串中的前10个字符串,并在字符串11处开始模糊测试,则可以将SKIPSTR设置为10(从0开始计数)。

  当您使用generic_send_tcp时,它会向命令行输出有关当前正在测试的变量和字符串的信息,因此,如果SPIKE会话中断并且需要稍后继续它,则可以使用这两个命令行参数来执行此操作。

  要从头开始模糊测试会话,只需设置这些参数为“0 0”,从头开始使用脚本文件“test.spk”在端口9999上针对主机192.168.1.101启动模糊测试会话,可以使用如下命令 (假设generic_send_tcp在/ pentest / fuzzers / spike /中):

root@bt4r1vm:~# /pentest/fuzzers/spike/generic_send_tcp 192.168.56.101 9999 test.spk 0

SPIKE Scripting Commands

  要编写SPIKE脚本,首先需要知道一些可用的命令是什么以及它们的作用。

  你可以通过SPIKE根目录搜索,可以通过查阅一些示例.spk文件以及SPIKE头文件spike.h来查看我们可以在脚本文件中当作命令的可用原语。spike.h文件将列出可用的原语(命令),.spk文件将提供如何使用这些命令的示例。

  请记住,SPIKE脚本功能仅支持spike.h中的原语子集 - 脚本“解释器”程序执行创建“SPIKE”并进行网络连接的工作,因此,您只能在脚本中使用定义SPIKE本身内容的命令。

  为了省去搜索这些文件的麻烦,我将在下面列出一些常用的用来编写脚本SPIKE原语。用C语言编写的spike.h文件中使用C数据类型列出了C语法中的每个SPIKE命令,但为了那些不熟悉C语法的朋友,我将使用“示例”格式(example format)指定命令。 编写脚本时可以更轻松地使用这些原语。在C中使用“//”表示法来指定基于行的注释(编译器忽略该点之后的所有内容),因此我使用下面的语法为每个命令提供了额外的详细解释。您可以在创建SPIKE脚本时保留这些注释,或添加自己的注释,SPIKE解释器会自动忽略这些。

  我将下面的命令分解为许多与字符串,二进制数据,块和其他有用功能相关的高级类别。

1.Strings
  字符串命令提供了一种将ASCII字符数据添加到SPIKES中的方法。 字符串命令中还包括s_string_variable命令,它是SPIKE中最重要的命令之一,因为它实际上允许您向SPIKE添加fuzz字符串。

  • s_string(“string”); // simply prints the string “string” as part of your “SPIKE”
  • s_string_repeat(“string”,200); // repeats the string “string” 200 times
  • s_string_variable(“string”); // inserts a fuzzed string into your “SPIKE”. The string “string” will be used for the first iteration of this variable, as well as for any SPIKES where other s_string_variables are being iterated

2.Binary Data
  二进制命令提供了一种向SPIKES添加二进制数据的方法。 它们支持多种指定二进制数据的方法。

  • s_binary(“\x41”); // inserts binary representation of hex 0x41 = ASCII “A”
  • s_binary_repeat(“\x41”, 200); //inserts binary representation of 0x41 200 times
      对于SPIKE中的二进制命令,还可以使用各种其他方法来指定相同的数据。 要输出如上所示的相同十六进制字符,我们可以使用“41”也可以使用“0x41”,当然混合和匹配这些值也行,(例如“410×41 \ x42”输出ASCII“AAB”)。 任何添加的空格都会被忽略。 所有这些结合在一起,可以从代表十六进制格式数据的各种不同应用程序中轻松剪切和粘贴,例如数据包捕获工具,调试器等。

3.Defining Blocks
  块定义命令允许您在SPIKE脚本中指定命名块的起点和终点。 这允许您使用块大小命令在SPIKES中定义这些数据部分的大小。

  • s_block_start(“block1”); // defines the start of block “block1”
  • s_block_end(“block1”); // defines the end of block “block1”

4.Block Sizes
  Block Sizes命令允许您在脚本生成的SPIKES内的命名块内插入使用各种不同尺寸格式的数据。

  • s_blocksize_string(“block1”, 2); // adds a string 2 characters long to the SPIKE that represents the size of block “block1”
  • s_binary_block_size_byte(“block1”); //adds a 1 byte value to the SPIKE that represents the size of block “block1

  这些只是Block Sizes如何添加到SPIKE的众多方法中的两个示例。 还有其他方法,可以允许您以各种格式表示Block Sizes,还有一些方法甚至允许您在Block Sizes之前添加预设值。

  要查看其他一些选项,只需在SPIKE src目录中的spike.h文件中grep命令搜索“block_size”或“blocksize”

5.Other Useful Commands
  其他有用的命令是那些不适合归类到上述类别的命令。

  • s_read_packet(); // Reads and prints to screen data received from the server
  • s_readline(); // Reads a single line of input from the server
      用户还可以在SPIKE脚本中使用通用C语言函数,提供其他便捷功能。 一个特别有用的功能是printf(),它可以用来向终端输出数据,这可以为我们的脚本提供更多的控制台输出信息。

SPIKE脚本的例子

  以下是一个示例SPIKE脚本,可用于通过对testserver.example.com的POST请求对php脚本testme.php中的inputvar变量进行fuzz处理。

s_string("POST /testme.php HTTP/1.1\r\n");
s_string("Host: testserver.example.com\r\n");
s_string("Content-Length: ");
s_blocksize_string("block1", 5);
s_string("\r\nConnection: close\r\n\r\n");
s_block_start("block1");
s_string("inputvar=");
s_string_variable("inputval");
s_block_end("block1");

  此脚本实际上指定了如下所示的消息,其中[fuzz_string]表示将SPIKE fuzz字符串插入到消息中的位置,[size_of_data]表示POST请求的数据部分的大小,其中包含固定字符串 “inputvar =”和fuzz字符串的变量数据。 随着fuzz字符串的更改,此大小字段将自动更新。

POST /testme.php HTTP/1.1
Host: testserver.example.com
Content-Length: [size_of_data]
Connection: close
inputvar=[fuzz_string]

了解fuzz协议

  成功的fuzz测试通常需要将错误的格式或意外的数据输入应用程序的特定区域。 这是因为程序通常需要对用户提供的数据执行某种处理,以便触发可利用的崩溃。 这要求我们将fuzz值输入网络协议的特定区域,并且在该区域中应用程序找到“正确”的输入。

  换句话说,要将“错误”数据放入“正确”的位置,我们需要了解我们用于与目标应用程序通信的网络协议的结构,以便将我们的模糊测试数据导入适用的应用领域。

  可以通过多种方式获得对网络协议的理解 - 通过查看RFC文档,使用客户端应用程序生成流量,使用Wireshark或tcpdump等工具捕获结果,对于非常简单的协议,可以直接与应用程序交互,看看它是如何工作的。

  在此次例子中Vulnserver便是直接交互。由于我们是fuzz的Vulnserver,假设我们发送给程序的数据导致了程序的异常,我们希望看到它发生了什么,可以在调试器中运行该程序这样我们可以及时查看到崩溃现场。 在Windows系统上启动OllyDbg,载入vulnserver.exe,然后按F9键,点击OllyDbg工具栏上的Play按钮或从OllyDbg Debug菜单中选择Run以允许程序 在调试器中运行。 此时,程序正常运行,但是如果我们在Vulnserver进程中触发崩溃,调试器将获得控制权并获取当前寄存器和堆栈的状态以便我们分析。

  现在,再Linux fuzz测试系统使用netcat连接到运行的Vulnserver。 以(-vv)运行netcat以获取有关连接的一些其他信息,禁用DNS解析(-n)。

root@bt4r1vm:~# nc -nvv 192.168.56.101 9999
(UNKNOWN) [192.168.56.101] 9999 (?) open
Welcome to Vulnerable Server! Enter HELP for help.

  我们在连接到Vulnserver后,我们可以输入HELP来获取一些帮助信息。 让我们尝试一下,看看会发生什么:

root@bt4r1vm:~# nc -nvv 192.168.56.101 9999
(UNKNOWN) [192.168.56.101] 9999 (?) open
Welcome to Vulnerable Server! Enter HELP for help.
HELP
Valid Commands:
HELP
STATS [stat_value]
RTIME [rtime_value]
LTIME [ltime_value]
SRUN [srun_value]
TRUN [trun_value]
GMON [gmon_value]
GDOG [gdog_value]
KSTET [kstet_value]
GTER [gter_value]
HTER [hter_value]
LTER [lter_value]
KSTAN [lstan_value]
EXIT

  好的,HELP为我们提供了一些被程序接受的有效命令列表。 让我们尝试输入其中一些命令,以及其他一些随机字符串,看看会发生什么。

STATS
UNKNOWN COMMAND

  似乎不支持运行不带参数的STATS命令。 如果我们再次尝试它会怎么样,但这次在命令之后加入一些通用文本。

STATS test
STATS VALUE NORMAL

  好的,这似乎有效。 如果我们改变STATS命令的情况呢?

stats test
UNKNOWN COMMAND

  命令看起来区分大小写。 现在让我们尝试一些未列为支持命令的其他指令。

BLAH
UNKNOWN COMMAND

  似乎任何不正确/不支持的命令都会生成UNKNOWN COMMAND响应。 现在让我们尝试另一个支持的命令,带上随机参数。

TRUN hhh
TRUN COMPLETE

  这也给出了不同的回应。 现在让我们看看HELP是否可以获得更多信息。

HELP STATS
Command specific help has not been implemented

  不,没有什么帮助。

  正如你所看到的那样,我试图通过这种方式询问程序接受了哪些类型的消息来完成工作,以为构建我的fuzz测试请求做准备。根据我们到目前为止看到的情况,似乎该程序支持许多“命令”,并且这些命令中的有些命令需要输入参数来进行操作。

  考虑到这一点,我们可以将fuzz数据插入应用程序的支持命令里:

  • 代替支持的命令
  • 作为某些带参数的命令的参数(STATS,RTIME,LTIME等)。
  • 作为某些不带参数的命令的参数,这些命令不表示它们支持参数(HELP,也可能是EXIT)。

  现在我们已经了解了如何与Vulnserver应用程序进行通信,以及在Vulnserver“协议”中我们可以插入我们的fuzz数据的位置。

  这是一个非常简单的协议分析示例,但我相信它可以很好地演示一般的预fuzz化过程 - 我们确定程序如何从用户接收输入数据,并使用该方法将fuzz数据插入到应用程序中。

使用SPIKE fuzz Vulnserver

  所以现在让我们使用从分析Vulnserver“协议”中获得的信息来实际使用SPIKE fuzzer。

  在上一节中,我们发现,发送模糊字符串代替支持的命令可能很有用,并且作为支持命令的参数可能很有用,不支持参数命令的参数。 让我们先从最简单的情况开始 - 发送一个fuzz字符串代替支持的命令。

  实现此目的的SPIKE脚本如下所示:

s_readline(); //print received line from server
s_string_variable("COMMAND"); //send fuzzed string

  在这里,我们正在等待从连接时看到的服务器收到初始“欢迎”消息,然后我们将模糊字符串发送到应用程序。将此内容保存为Linux fuzzing系统上的磁盘“vscommand.spk”。

  在我们实际使用SPIKE启动此脚本之前,让我们在Linux系统上使用Wireshark启动数据包捕获,以便我们可以看到SPIKE实际发送的内容。 我的Vulnserver的IP地址为192.168.56.101默认端口为9999,因此我将设置捕获过滤器以忽略所有其他流量。 过滤器如下所示 - 如果您的目标系统位于不同的IP地址上,或者Vulnserver正在侦听其他端口,请相应地调整过滤器。

host 192.168.56.101 and tcp port 9999

  启动Wireshark捕获,然后使用如下所示的命令启动SPIKE模糊器。 此命令假定您正在从BackTrack执行模糊测试,BackTrack默认将SPIKE存储在/ pentest / fuzzers / spike中,并且已将vscommand.spk文件保存到当前工作目录中。 如果您已经下载了SPIKE,那么generic_send_tcp将位于SPIKE归档内的SPIKE / SPIKE / src目录中(有关安装SPIKE的一些详细信息,请参阅“要求和系统设置”部分)。

root@bt4r1vm:~/vulnserver# /pentest/fuzzers/spike/generic_send_tcp 192.168.56.101 9999 vscommand.spk 0 0
Total Number of Strings is 681
Fuzzing
Fuzzing Variable 0:0
line read=Welcome to Vulnerable Server! Enter HELP for help.
Fuzzing Variable 0:1
Variablesize= 5004
Fuzzing Variable 0:2
line read=Welcome to Vulnerable Server! Enter HELP for help.
Variablesize= 5005
[...SNIP...]
Fuzzing Variable 0:2041
line read=Welcome to Vulnerable Server! Enter HELP for help.
Fuzzing Variable 0:2042
line read=Welcome to Vulnerable Server! Enter HELP for help.
Fuzzing Variable 0:2043
line read=Welcome to Vulnerable Server! Enter HELP for help.
Done.

  如果你让它继续运行,这个SPIKE脚本应该在几分钟后完成,如果你检查在其调试器中运行的VulnServer,你会发现它似乎运行正常 - 没有崩溃。 所以看来只是发送fuzz数据代替支持的命令不会导致VulnServer崩溃(或者至少发送SPIKE生成的坏字符串代替命令不会导致Vulnserver崩溃。)没关系,我们在程序中还有很多其他区域可以插入fuzz数据,但在我们继续之前,让我们看一下我们在Wireshark中捕获的数据,这样我们就可以看到SPIKE正在做什么。

  向上滚动到Wireshark窗口的最顶部,选择第一个数据包,然后右键单击并从菜单中选择Follow TCP Stream,我们应该能够看到发送的第一个SPIKE的内容。

  第一个SPIKE如下所示。 您可以看到这里发生了什么,SPIKE只是从服务器收到欢迎消息,然后将字符串COMMAND发送到应用程序。 这个字符串并非巧合,它正是我们在SPIKE脚本文件的s_string_variable命令中指定的字符串。 到目前为止,SPIKE还没有做任何特别有趣的事情,但也许当我们看到其他一些请求时....

  在我们继续之前要注意的另一件事是我们看到服务器对我们提供的输入的响应 - 即字符串“UNKNOWN COMMAND” - 被发送回客户端。 这表明在从SPIKE接收到该字符串后,Vulnserver应用程序仍然能够发回响应 - 这意味着它在此阶段没有崩溃。 后来当我们到达实际导致崩溃的程度时,这种类型的信息可以用来识别哪个模糊字符串造成崩溃。

  让我们看看下一个请求。 点击Follow TCP Stream窗口底部的Filter Out This Stream按钮,然后右键单击新过滤的Wireshark视图中的第一个数据包,再次选择Follow TCP Stream。 这次我们看到更有趣的东西。 不是我们在上一个请求中看到的文本“COMMAND”,而是SPIKE发送了一个非常长的大写字母“A”字符串,前面是一些其他随机字符。

  如果您感到好奇,可以继续查看SPIKE发送的其他一些fuzz 数据。 这里需要知道的是,每次在SPIKE脚本中使用s_string_variable命令时,SPIKE将首先在命令后发送括号中指定的文本,然后它将遍历其预定义的fuzz字符串列表,将列表项一个一个发送到应用程序,直到它完成。如果脚本中有多个变量,它将依次遍历每个变量的所有可能值。

  但是,你无法使用一个SPIKE脚本创建多个单独的SPIKES。如果您有多种不同的消息类型,其中包含您想要fuzz的固定数据,例如表示各种支持的命令(如STATS,RTIME,LTIME等)的字符串,则需要使用使用构造指定此数据的单个SPIKE脚本 例如s_string命令。

  这演示了运行单个SPIKE脚本的基本过程,但对于Vulnserver,程序中还有许多其他区域需要fuzz,并且当运行每个所需的SPIKE脚本以查看程序崩溃时,单独执行和查看这些脚本并不方便。

点击收藏 | 0 关注 | 1
  • 动动手指,沙发就是你的了!
登录 后跟帖