在渗透测试中使用fuzz技术(附windows安装指南)

  • A+
所属分类:网络安全

前言:本文翻译自sans.org,有删节。在学习sulley的过程中发现中文参考资料很少,所以抛砖引玉翻译一下,希望对学习协议fuzz测试的同学有帮助。本文适合作为学习sulley协议测试过程中参考资料,如果需要学习sulley的使用可以参考其文档。

背景

不久前,我们接到了一个渗透测试的项目。分配任务的时候,团队里有的同学负责无线渗透,有的负责web应用渗透,我分到的任务是对外网开放的网络端口进行测试。我的主要测试范围包括:从外部可以访问的系统接口,不包括客户测的漏洞利用,不包括web应用,不包括外部的资源管理类的攻击(比如域名管理类的攻击),不包括账号密码猜解。

前期的扫描中并没有发现很多有意思的漏洞,这也在意料之中。不过Nmap的扫描也发现了一些有意义的目标。如下图所示。目标部署了一个CheckPoint服务器作为远程VPN接入。一共开放了两个端口。

在渗透测试中使用fuzz技术(附windows安装指南)

264端口引起了我的兴趣,经过一番查证得知这是CheckPoint VPN 客户端用来通信的一个通道,使用的是一种私有协议,叫做SecuRemote。这个协议是没有加密的。网上能找到一些这个协议存在信息泄露的漏洞,不过我们客户这边都是新版本,不存在那些已知的问题。现在我有的资源就是一些协议资料和大把剩余的任务时间,我决定做一下协议fuzzing。

协议分析

做协议fuzz的首要条件就是要熟悉自己测试的协议。SecuRemote作为私有协议,没有详细的文档,而且我也没发现有人作为逆向分析。所以我得通过抓包自己分析协议。在Google上搜索了一下,找到几个别人抓的网络包文件。都是他们抓包让别人帮分析问题的。通过分析这些网络包,我分析出了SecuRemote协议的一些细节。

在渗透测试中使用fuzz技术(附windows安装指南)

3次握手之后,CheckPoint客户端会发送两个4字节的小包。对于特定的CheckPoint服务器和客户端来说这两个字节看起来是固定的。在客户端发送第二个包之后,服务端返回了一个4字节的包。跟客户端发的第一个包很像。

第四个包发送字符串"securemote"到服务器。最后是一个NULL的结束字节。开始的4个字节看起来是大端存储的后面字符串的长度。因为0x0b刚好是"securemote0"的长度。第四个包发完之后,服务器返回了一个同样的带字符串长度的包,包含了一些服务器的相关信息。根据之前版本的CheckPoint exp,可以获取更多的协议数据。虽然没法简单的识别出每一个比特位代表的意义。但是根据上面的分析,也可以找到一些有意思的模糊测试目标。

四字节的长度标志。任何时候,在协议中发现了标志长度的字节,一定要对它进行模糊测试。几个固定的\x00的字节。减少了我们进行枚举组合的次数。在字符串结尾使用NULL结束符。因为协议提供了长度标志,所以结束符不是必须的。所以测试一下结束符不存在的情况也是不错的选择。

下一步就是准备测试样例,进行模糊测试了。

工具选择

我比较喜欢的智能模糊器是Sulley,一个用python完成的模糊测试框架。不过开发者Pedram已经不再维护Sulley。使用Peach也是一个不错的选择,peach使用XML进行协议描述,相对来说我更喜欢python的语法。在sulley中,我们使用基本的数据类型:字符串区,数字区,分隔符和静态值来描述协议。每一种类型的数据都可以自动突变来进行模糊测试。Sulley也提供了一些高级的功能来描述复杂协议,比如计算数据段长度和进行摘要校检。更多详细的sulley使用方法可以参考Sulley的项目文档。下面的脚本是对客户端发送的4个字节进行测试。其中使用了s_byte()这个定义一个字节的函数。将下面的脚本保存到sulley的目录就可以开始测试了。

#!/usr/bin/env python
# Fuzzing the initial 4-byte packet from client to CheckPoint VPN server.
import time
import sys
from sulley import *
# Time to wait between mutations
SLEEP_TIME=0.5
# Time to wait before claiming a host is unresponsive
TIMEOUT=3
# number of crashes to observe before skipping the remainder of a group
CRASH_THRESHOLD=3
# Initialize the Sulley mutation descriptor
s_initialize("SecuRemote-Initial-Packet")
s_byte("\x51",full_range=True)
s_static("\x00\x00\x00")
print "Total mutations: " + str(s_num_mutations()) + "\n"
print "Minimum time for execution: " + str(round(((s_num_mutations() * (SLEEP_TIME))/3600),2)) + " hours."
print "Press CTRL/C to cancel in ",
for i in range(5):
print str(5 - i) + " ",
sys.stdout.flush()
time.sleep(1)
# For debugging purposes, uncomment these lines to see Sulley's mutations
# in hex dump format
#print "Hex dump mutation output:"
#while s_mutate():
# print s_hex_dump(s_render())
sess = sessions.session(session_filename="SecuRemote-Initial-Packet.sess", sleep_time=SLEEP_TIME, timeout=TIMEOUT, crash_threshold=CRASH_THRESHOLD)
# Tie this session to the SecuRemote-Simple-String fuzzing cases
sess.connect(s_get("SecuRemote-Initial-Packet"))
# Change this IP address to the target system
target = sessions.target("127.0.0.1", 264)
# Add the target to the session (can be repeated for multiple targets)
sess.add_target(target)
# Kick off the fuzzer, monitoring with WebUI on localhost:26000
sess.fuzz()

很不幸的是经过上面的测试,并没有发现有意义的结果。为了更深入的对目标系统进行评估,需要我们的Sulley实现一些协议状态,对协议进行更深入的测试。

对有状态的协议进行测试

在SecuRemote的案例中,从客户端发给VPN服务器的头两个包对我们来说并不够吸引人。客户端交换的第四个包才更有趣,包含了一个字符串和长度值,以及NULL结束符。为了测试VPN服务器对这个位置的畸形包的返回,我们需要在sulley中先模拟前面的两步。然后再发送这一步的畸形包。Sulley中已经提供了相关的接口。在发送每一个畸形包之前,Sulley会检查一个叫做pre_send的回调函数。通过定义我们自己的回调函数,读写socket,完成初始化的头两个4字节包的发送,然后读取服务器的返回。之后再发送我们的测试畸形包。进行测试的脚本如下:

#!/usr/bin/env python
from sulley import *
SLEEP_TIME=0.5TIMEOUT=3CRASH_THRESHOLD=3# The function Sulley will run prior to sending each mutation.  We leverage
# it to setup the target system with the initial packets and response in the
# protocol exchange prior to our target packet.
def preconn(sock):
    sock.send("\x51\x00\x00\x00")    time.sleep(0.5)
    sock.send("\x00\x00\x00\x21")


    # Set a socket timeout on the recv so we aren't waiting indefinitely if
    # the server crashed from a previous test case.
    sock.settimeout(5)    response = sock.recv(4)
    print "Setup response: ",    for i in response:
        print "%02x" % ord(i),
    print


s_initialize("SecuRemote-Simple-String")


# Create a size field, which is based on the content of the named block
# Sulley uses ">" to indicate big-endian values, "<" is little-endian
s_size("client-name-string", length=4, endian=">")


# This is the block of data used for filling in the s_sizeif s_block_start("client-name-string"):
    # "securemote" is the default string
    s_string("securemote")
    # constant null terminator
    s_byte("\x00")
s_block_end()


sess = sessions.session(session_filename="Securemote-Simple-string.sess", sleep_time=SLEEP_TIME, timeout=TIMEOUT, crash_threshold=CRASH_THRESHOLD)


# Call preconn() before each mutation is sent to setup the target
sess.pre_send = preconn


sess.connect(s_get("SecuRemote-Simple-String"))
target = sessions.target("127.0.0.1", 264)
sess.add_target(target)
sess.fuzz()

通过这种方式,可以继续对其他的请求包进行测试。

成果

我们一共进行了好几个晚上的fuzz测试,通过记录的数据发现,确实发现了一些小问题,一些畸形的数据包会导致VPN服务重启,达到Dos的效果。客户对我们的这个报告也很认可。

附录:sulley的windows安装指南

测试环境win7+python2.7

安装MinGW Complilier

在安装引导界面选择C++ Compiler和ObjC Compiler。这里有个问题就是新版的MinGW已经不支持-mno-cygwin选项了。可以选择老版本的MinGW或者安装最新版,然后patch python代码。这里选择后者。

安装python2.7(x64)

安装Git 工具

更新$PATH环境变量,添加C:\Python27 和 C:\MinGW\bin(根据自己的安装路径填写)

安装pydbg

git clone https://[email protected]/Fitblip/pydbg.git

C:\sulley_build>git clone https://[email protected]/Fitblip/pydbg.git
Cloning into 'pydbg'...
remote: Counting objects: 17, done.
remote: Compressing objects: 100% (12/12), done.
remote: Total 17 (delta 4), reused 17 (delta 4)
Unpacking objects: 100% (17/17), done.


C:\sulley_build\pydbg>python setup.py install
running install
running build
running build_py
creating build
creating build\lib
creating build\lib\pydbg
...snip...
running install_egg_info
Removing C:\python27\Lib\site-packages\pydbg-0.0.0-py2.7.egg-info
Writing C:\python27\Lib\site-packages\pydbg-0.0.0-py2.7.egg-info

安装libdasm

下载libdasm

编译扩展并安装。这里需要用到gcc,而gcc4.7.x已经去掉了对-mn0-cygwin的支持。需要先把distutils\cygwinccompiler.py文件中的”-mno-cygwin”删掉。总过4,5个地方的样子。参考http://stackoverflow.com/q/6034390/333353

C:\sulley_build\libdisasm\pydasm>python setup.py build_ext -c mingw32
running build_ext
building 'pydasm' extension
...snip...
C:\sulley_build\libdisasm\pydasm>python setup.py install
running install
running build
running build_ext
running install_lib
copying build\lib.win32-2.7\pydasm.pyd -> C:\python27\Lib\site-packages
running install_egg_info
Writing C:\python27\Lib\site-packages\pydasm-1.5-py2.7.egg-info

下载sulley

C:\sulley_build>git clone https://github.com/OpenRCE/sulley.git
Cloning into 'sulley'...
remote: Counting objects: 148, done.
remote: Compressing objects: 100% (91/91), done.
remote: Total 148 (delta 53), reused 146 (delta 51)
Receiving objects: 100% (148/148), 267.03 KiB, done.
Resolving deltas: 100% (53/53), done.

确认process_monitor.py正常工作。(没有提示缺少模块)

 C:\sulley_build\sulley>python process_monitor.py
 ERR> USAGE: process_monitor.py
     <-c|--crash_bin FILENAME> filename to serialize crash bin class to
     [-p|--proc_name NAME]     process name to search for and attach to
     [-i|--ignore_pid PID]     ignore this PID when searching for the target process
     [-l|--log_level LEVEL]    log level (default 1), increase for more verbosity
     [--port PORT]             TCP port to bind this agent to

下载解压PCapy

下载解压WinPcap Dev Kit,记下文件路径。这里用C:\sulley_build\WpdPack

编译PCap并安装

C:\sulley_build\pcapy-0.10.5>python setup.py build_ext -c mingw32 -I "C:\sulley_build\WpdPack\Include" -L "C:\sulley_build\WpdPack\Lib"
running build_ext
building 'pcapy' extension
creating build
creating build\temp.win32-2.7
creating build\temp.win32-2.7\Release
creating build\temp.win32-2.7\Release\win32
...snip...
C:\sulley_build\pcapy-0.10.5>python setup.py install
running install
running build
running build_ext
running install_lib
copying build\lib.win32-2.7\pcapy.pyd -> C:\python27\Lib\site-packages
running install_data
creating C:\python27\share
creating C:\python27\share\doc
creating C:\python27\share\doc\pcapy
copying README -> C:\python27\share\doc\pcapy
copying LICENSE -> C:\python27\share\doc\pcapy
copying pcapy.html -> C:\python27\share\doc\pcapy
running install_egg_info
Writing C:\python27\Lib\site-packages\pcapy-0.10.5-py2.7.egg-info

安装WinPcap这个相信大家机器上都有。

安装Impacket

安装的时候注意要进入到setup.py的目录敲命令

C:\sulley_build\Impacket-0.9.6.0>python setup.py install
running install
running build
running build_py
creating build
creating build\lib
creating build\lib\impacket
copying impacket\ImpactDecoder.py -> build\lib\impacket
copying impacket\ImpactPacket.py -> build\lib\impacket
copying impacket\nmb.py -> build\lib\impacket
copying impacket\ntlm.py -> build\lib\impacket
copying impacket\smb.py -> build\lib\impacket
copying impacket\structure.py -> build\lib\impacket
copying impacket\uuid.py -> build\lib\impacket
copying impacket\__init__.py -> build\lib\impacket
creating build\lib\impacket\dcerpc
...snip...

确认network_monitor.py正常工作

C:\sulley_build\sulley>python network_monitor.py
ERR> USAGE: network_monitor.py
    <-d|--device DEVICE #>    device to sniff on (see list below)
    [-f|--filter PCAP FILTER] BPF filter string
    [-P|--log_path PATH]      log directory to store pcaps to
    [-l|--log_level LEVEL]    log level (default 1), increase for more verbosity

    [--port PORT]             TCP port to bind this agent to

Network Device List:
    [0] \Device\NPF_GenericDialupAdapter
    [1] {CF0B388B-8DF5-4BC4-8ECF-404F2A1B489C}  10.0.2.64

安装到这里基本就完成了。对于windows平台,还存在几个已知的bug。比如连接需要使用ssl的时候,需要修改sulley/sessions.py的486行。把

if self.ssl:

try:
ssl = socket.ssl(sock)
sock = httplib.FakeSocket(sock, ssl)
except Exception, e:
...

替换成

if self.ssl:
try:
import ssl
sock = ssl.wrap_socket(sock)

except Exception, e:

  • 我的微信
  • 这是我的微信扫一扫
  • weinxin
  • 我的微信公众号
  • 我的微信公众号扫一扫
  • weinxin

发表评论

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen: