- A+
前言:本文翻译自sans.org,有删节。在学习sulley的过程中发现中文参考资料很少,所以抛砖引玉翻译一下,希望对学习协议fuzz测试的同学有帮助。本文适合作为学习sulley协议测试过程中参考资料,如果需要学习sulley的使用可以参考其文档。
背景
不久前,我们接到了一个渗透测试的项目。分配任务的时候,团队里有的同学负责无线渗透,有的负责web应用渗透,我分到的任务是对外网开放的网络端口进行测试。我的主要测试范围包括:从外部可以访问的系统接口,不包括客户测的漏洞利用,不包括web应用,不包括外部的资源管理类的攻击(比如域名管理类的攻击),不包括账号密码猜解。
前期的扫描中并没有发现很多有意思的漏洞,这也在意料之中。不过Nmap的扫描也发现了一些有意义的目标。如下图所示。目标部署了一个CheckPoint服务器作为远程VPN接入。一共开放了两个端口。
264端口引起了我的兴趣,经过一番查证得知这是CheckPoint VPN 客户端用来通信的一个通道,使用的是一种私有协议,叫做SecuRemote。这个协议是没有加密的。网上能找到一些这个协议存在信息泄露的漏洞,不过我们客户这边都是新版本,不存在那些已知的问题。现在我有的资源就是一些协议资料和大把剩余的任务时间,我决定做一下协议fuzzing。
协议分析
做协议fuzz的首要条件就是要熟悉自己测试的协议。SecuRemote作为私有协议,没有详细的文档,而且我也没发现有人作为逆向分析。所以我得通过抓包自己分析协议。在Google上搜索了一下,找到几个别人抓的网络包文件。都是他们抓包让别人帮分析问题的。通过分析这些网络包,我分析出了SecuRemote协议的一些细节。
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
在安装引导界面选择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:
- 我的微信
- 这是我的微信扫一扫
- 我的微信公众号
- 我的微信公众号扫一扫