-Sulley is a fuzzer development and fuzz testing framework consisting of multiple extensible components. Sulley (IMHO) exceeds the capabilities of most previously published fuzzing technologies, commercial and public domain. The goal of the framework is to simplify not only data representation but to simplify data transmission and target monitoring as well. Sulley is affectionately named after the creature from Monsters Inc., because, well, he is fuzzy.
-
-Modern day fuzzers are, for the most part, solely focus on data generation. Sulley not only has impressive data generation but has taken this a step further and includes many other important aspects a modern fuzzer should provide. Sulley watches the network and methodically maintains records. Sulley instruments and monitors the health of the target, capable of reverting to a good state using multiple methods. Sulley detects, tracks and categorizes detected faults. Sulley can fuzz in parallel, significantly increasing test speed. Sulley can automatically determine what unique sequence of test cases trigger faults. Sulley does all this, and more, automatically and without attendance. Overall usage of Sulley breaks down to the following:
-
-
Data Representation: First step in using any fuzzer. Run your target and tickle some interfaces while snagging the packets. Break down the protocol into indvidual requests and represent that as blocks in Sulley.
-
Session: Link your developed requests together to form a session, attach the various available Sulley monitoring agents (socket, debugger, etc...) and commence fuzzing.
-
Post Mortem: Review the generated data and monitored results. Replay individual test cases.
-
-
-
Sulley Directory Structure
-There is some rhyme and reason to the Sulley directory structure. Maintaining the directory structure will ensure that everything remains organized while you expand the fuzzer with Legos, requests and utilities. The following hierarchy outlines what you will need to know about the directory structure:
-
-
archived_fuzzies: This is a free form directory, organized by fuzz target name, to store archived fuzzers and data generated from fuzz sessions.
-
-
trend_server_protect_5168: This retired fuzz is referenced during the step by step walk through later in this document.
-
trillian_jabber: Another retired fuzz referenced from the documentation.
-
-
audits: Recorded PCAPs, crash bins, code coverage and analysis graphs for active fuzz sessions should be saved to this directory. Once retired, recorded data should be moved to 'archived_fuzzies'.
-
docs: This documentation and generated Epydoc API references.
-
requests: Library of Sulley requests. Each target should get its own file which can be used to store multiple requests.
-
-
__REQUESTS__.html: This file contains the descriptions for stored request categories and lists individual types. Maintain alphabetical order.
-
http.py: Various web server fuzzing requests.
-
trend.py: Contains the requests associated with the complete fuzz walkthrough discussed later in this document.
-
-
sulley: The fuzzer framework. Unless you want to extend the framework, you shouldn't need to touch these files.
-
-
legos: User-defined complex primitives.
-
-
ber.py: ASN.1 / BER primitives.
-
dcerpc.py: Microsoft RPC NDR primitives.
-
misc.py: Various uncategorized complex primitives such as e-mail addresses and hostnames.
-
xdr.py: XDR types
-
-
pgraph: Python graph abstraction library. Utilized in building sessions.
-
utils: Various helper routines.
-
-
dcerpc.py: Microsoft RPC helper routines such as for binding to an interface and generating a request.
-
misc.py: Various uncategorized routines such as CRC-16 and UUID manipulation routines.
-
scada.py: SCADA specific helper routines including a DNP3 block encoder.
-
-
__init__.py: The various 's_' aliases that are used in creating requests are defined here.
-
blocks.py: Blocks and block helpers are defined here.
-
instrumentation.py: External instrumentation feature to monitor targets not supporting debugger.
-
pedrpc.py: This file defines client and server classes which are used by Sulley for communications between the various agents and the main fuzzer.
-
primitives.py: The various fuzzer primitives including static, random, strings and integers are defined here.
-
sessions.py: Functionality for building and executing a session.
-
-Aitel had it right with SPIKE, we've taken a good look at every fuzzer I can get my hands on and the block based approach to protocol representation stands above the others combining both simplicity and the flexibility to represent most protocols. Sulley utilizes a block based approach to generate individual "requests". These requests are then later tied together to form a "session". To begin, initialize with a new name for your request:
-
-s_initialize("new request")
-
-Now you start adding primitives, blocks and nested blocks to the request. Each primitive can be individually rendered and mutated. Rendering a primitive returns its contents in raw data format. Mutating a primitive transforms its internal contents. The concepts of rendering and mutating are abstracted from fuzzer developers for the most part, so don't worry about it. Know however that each mutatable primitive accepts a default value which is restored when the fuzzable values are exhausted.
-
-
Static and Random Primitives
-Let's begin with the simplest primitive, s_static(), which adds a static unmutating value of arbitrary length to the request. There are various aliases sprinkled throughout Sulley for your convenience, s_dunno(), s_raw() and s_unknown() are aliases of s_static():
-
-# these are all equivalent:
-s_static("pedram\x00was\x01here\x02")
-s_raw("pedram\x00was\x01here\x02")
-s_dunno("pedram\x00was\x01here\x02")
-s_unknown("pedram\x00was\x01here\x02")
-
-Primitives, blocks etc. all take an optional name keyword argument. Specifying a name allows you to access the named item directly from the request via request.names["name"] instead of having to walk the block structure to reach the desired element.
-
-Related to the above, but not equivalent, is the s_binary() primitive which accepts binary data represented in multiple formats. SPIKE users will recognize this API, its functionality is (or rather should be) equivalent to what you are already familiar with:
-
- # yeah, it can handle all these formats.
- s_binary("0xde 0xad be ef \xca fe 00 01 02 0xba0xdd f0 0d", name="complex")
-
-Most of Sulley's primitives are driven by "fuzz heuristics" and therefore have a limited number of mutations. An exception to this is the s_random() primitive which can be utilized to generate random data of varying lengths. This primitive takes two mandatory arguments, 'min_length' and 'max_length', specifying the minimum and maximum length of random data to generate on each iteration respectively. This primitive also accepts the following optional keyword arguments:
-
-
num_mutations: (integer, default=25) Number of mutations to make before reverting to default.
-
fuzzable: (boolean, default=True) Enable or disable fuzzing of this primitive.
-
name: (string, default=None) As with all Sulley objects specifying a name gives you direct access to this primitive throughout the request.
-
-The 'num_mutations' keyword argument specifies how many times this primitive should be re-rendered before it is considered exhausted. To fill a static sized field with random data, set the values for 'min_length' and 'max_length' to be the same.
-
-
Integers
-Binary and ASCII protocols alike have various sized integers sprinkled all throughout them, for instance the Content-Length field in HTTP. Like most fuzzing frameworks, a portion of Sulley is dedicated to representing these types:
-
-
1 byte: s_byte(), s_char()
-
2 bytes: s_word(), s_short()
-
4 bytes: s_dword(), s_long(), s_int()
-
8 bytes: s_qword(), s_double()
-
-The integer types each accept at least a single parameter, the default integer value. Additionally the following optional keyword arguments may be specified:
-
-
endian: (character, default='<') Endianess of the bit field. Specify '<' for little endian and '>' for big endian.
-
format: (string, default="binary") Output format, "binary" or "ascii", controls the format which the integer primitives renders in. For example the value 100 is rendered as "100" in ASCII and "\x64" in binary.
-
signed: (boolean, default=False) Make size signed vs. unsigned, applicable only when format="ascii".
-
full_range: (boolean, default=False) If enabled then this primitive mutates through all possible values. More on this later.
-
fuzzable: (boolean, default=True) Enable or disable fuzzing of this primitive.
-
name: (string, default=None) As with all Sulley objects specifying a name gives you direct access to this primitive throughout the request.
-
-The full_range modifier is of particular interest from above. Consider you want to fuzz a DWORD value, that's 4,294,967,295 total possible values. At a rate of 10 test cases per second, it would take 13 years to finish fuzzing this single primitive! To reduce this vast input space Sulley defaults to trying only "smart" values. This includes the plus and minus 10 border cases around 0, the maximum integer value (MAX_VAL), MAX_VAL divided by 2, MAX_VAL divided by 3, MAX_VAL divided by 4, MAX_VAL divided by 8, MAX_VAL divided by 16 and MAX_VAL divided by 32. Exhausting this reduced input space of 141 test cases requires only seconds.
-
-
Strings and Delimiters
-Strings can be found everywhere. E-mail addresses, hostnames, usernames, passwords and more all examples of string components you will no doubt come across when fuzzing. Sulley provides the s_string() primitive for representing these fields. The primitive takes a single mandatory argument specifying the default, valid, value for the primitive. The following additional keyword arguments may be specified:
-
-
size: (integer, default=-1) Static size for this string. For dynamic sizing, leave this as -1.
-
padding: (character, default='\x00') If an explicit size is specified and the generated string is smaller than size, use this value to padd the field up to size.
-
encoding: (string, default="ascii") Encoding to use for string. Valid options include whatever the Python str.encode() routine can accept. For Microsoft Unicode strings, specify "utf_16_le".
-
fuzzable: (boolean, default=True) Enable or disable fuzzing of this primitive.
-
name: (string, default=None) As with all Sulley objects specifying a name gives you direct access to this primitive throughout the request.
-
-Strings are frequently parsed into sub-fields through the usage of delimiters. The space character for example is used as a delimiter in the HTTP request "GET /index.html HTTP/1.0". The front slash (/) and dot (.) characters in that same request are also delimiters. When defining a protocol in Sulley, be sure to represent delimiters using the s_delim() primitive. As with other primitives, the first argument is mandatory and used to specify the default value. Also as with other primitives, s_delim() accepts the optionals 'fuzzable' and 'name' keyword arguments. Delimiter mutations include repetition, substitution and exclusion. As a complete example, consider the following sequence of primitives for fuzzing the HTML body tag.
-
-Having mastered primitives, let's next take a look at how they may be organized and nested within blocks. New blocks are defined and opened with s_block_start() and closed with s_block_end(). Each block must be given a name, specified as the first argument to s_block_start(). This routine also accepts the following optional keyword arguments:
-
-
group: (string, default=None) Name of group to associate this block with, more on this later.
-
encoder: (function pointer, default=None) Pointer to a function to pass rendered data to prior to returning it.
-
dep: (string, default=None) Optional primitive whose specific value this block is dependant on.
-
dep_value: (mixed, default=None) Value that field "dep" must contain for block to be rendered.
-
dep_values: (list of mixed types, default=[]) Values that field "dep" may contain for block to be rendered.
-
dep_compare(string, default="==") Comparison method to apply to dependency. Valid options include: "==", "!=", ">", ">=", "<" and "<=".
-
-Grouping, encoding and dependencies are powerful features not seen in most other frameworks and deserve further dissection.
-
-
Groups
-Grouping allows you to tie a block to a group primitive to specify that the block should cycle through all possible mutations for each value within the group. The group primitive is useful for example for representing a list of valid opcodes or verbs with similar argument structures. The primitive s_group() defines a group and accepts two mandatory arguments. The first specifies the name of the group, the second specifies the list of possible raw values to iterate through. As a simple example, consider the following complete Sulley request designed to fuzz a web server:
-
-# import all of Sulley's functionality.
-from sulley import *
-
-# this request is for fuzzing: {GET,HEAD,POST,TRACE} /index.html HTTP/1.1
-
-# define a new block named "HTTP BASIC".
-s_initialize("HTTP BASIC")
-
-# define a group primitive listing the various HTTP verbs we wish to fuzz.
-s_group("verbs", values=["GET", "HEAD", "POST", "TRACE"])
-
-# define a new block named "body" and associate with the above group.
-if s_block_start("body", group="verbs"):
- # break the remainder of the HTTP request into individual primitives.
- s_delim(" ")
- s_delim("/")
- s_string("index.html")
- s_delim(" ")
- s_string("HTTP")
- s_delim("/")
- s_string("1")
- s_delim(".")
- s_string("1")
- # end the request with the mandatory static sequence.
- s_static("\r\n\r\n")
-# close the open block, the name argument is optional here.
-s_block_end("body")
-
-The script begins by importing all of Sulley's components. Next a new request is initialized and given the name "HTTP BASIC". This name can later be referenced for accessing this request directly. Next, a group is defined with the name "verbs" and the possible string values "GET", "HEAD", "POST" and "TRACE". A new block is started with the name "body" and tied to the previously defined group primitive through the optional 'group' keyword argument. Note that s_block_start() always returns True which allows you to optionally "tab out" its contained primitives using a simple if-clause. Also note that the name argument to s_block_end() is optional. These framework design decisions were made purely for aesthetic purposes. A series of basic delimiter and string primitives are then defined within the confinements of the "body" block and the block is closed. When this defined request is loaded into a Sulley session, the fuzzer will generate and transmit all possible values for the block "body", once for each verb defined in the group.
-
-
Encoders
-Encoders are a simple, yet powerful block modifier. A function can be specified and attached to a block in order to modify the rendered contents of that block prior to return and transmission over the wire. This is best explained with a real world example. The DcsProcessor.exe daemon from Trend Micro Control Manager listens on TCP port 20901 and expects to receive data formatted with a proprietary XOR encoding routine. Through reverse engineering of the decoder, the following XOR encoding routine was developed:
-
-def trend_xor_encode (str):
- key = 0xA8534344
- ret = ""
-
- # pad to 4 byte boundary.
- pad = 4 - (len(str) % 4)
-
- if pad == 4:
- pad = 0
-
- str += "\x00" * pad
-
- while str:
- dword = struct.unpack("<L", str[:4])[0]
- str = str[4:]
- dword ^= key
- ret += struct.pack("<L", dword)
- key = dword
-
- return ret
-
-Sulley encoders take a single parameter, the data to encode and return the encoded data. This defined encoder can now be attached to a block containing fuzzable primitives allowing the fuzzer developer to continue as if this little hurdle never existed.
-
-
Dependencies
-Dependencies allow you to apply a conditional to the rendering of an entire block. This is accomplished by first linking a block to a primitive it will be dependant on using the optional 'dep' keyword parameter. When the time comes for Sulley to render the dependant block, it will check the value of the linked primitive and behave accordingly. A dependant value can be specified with the 'dep_value' keyword parameter. Alternatively a list of dependant values can be specified with the 'dep_values' keyword parameter. Finally, the actual conditional comparison can be modified through the 'dep_compare' keyword parameter. For example, consider a situation where depending on the value of an integer, different data is expected:
-
-s_short("opcode", full_range=True)
-
-# opcode 10 expects an authentication sequence.
-if s_block_start("auth", dep="opcode", dep_value=10):
- s_string("USER")
- s_delim(" ")
- s_string("pedram")
- s_static("\r\n")
- s_string("PASS")
- s_delim(" ")
- s_delim("fuzzywuzzy")
-s_block_end()
-
-# opcodes 15 and 16 expect a single string hostname.
-if s_block_start("hostname", dep="opcode", dep_values=[15, 16]):
- s_string("pedram.openrce.org")
-s_block_end()
-
-# the rest of the opcodes take a string prefixed with two underscores.
-if s_block_start("something", dep="opcode", dep_values=[10, 15, 16], dep_compare="!="):
- s_static("__")
- s_string("some string")
-s_block_end()
-
-Block dependencies can be chained together in any number of ways allowing for powerful (and unfortunately complex) combinations.
-
-
Block Helpers
-An important aspect of data generation that you must become familiar with to effectively utilize Sulley are block helpers. This includes sizers, checksums and repeaters.
-
-
Sizers
-SPIKE users will be familiar with the s_sizer() (or s_size()) block helper. This helper takes the block name to measure the size of as the first parameter and accepts the following additional keyword arguments:
-
-
length: (integer, default=4) Length of size field.
-
endian: (character, default='<') Endianess of the bit field. Specify '<' for little endian and '>' for big endian.
-
format: (string, default="binary") Output format, "binary" or "ascii", controls the format which the integer primitives renders in.
-
inclusive: (boolean, default=False) Should the sizer count its own length?
-
signed: (boolean, default=False) Make size signed vs. unsigned, applicable only when format="ascii".
-
fuzzable: (boolean, default=False) Enable or disable fuzzing of this primitive.
-
name: (string, default=None) As with all Sulley objects specifying a name gives you direct access to this primitive throughout the request.
-
-Sizers are a crucial component in data generation that allow for the representation of complex protocols such as XDR notation, ASN.1 etc. Sulley will dynamically calculate the length of the associated block when rendering the sizer. By default Sulley will not fuzz size fields. In many cases this is the desired behaviour, in the event it isn't however, enable the 'fuzzable' flag.
-
-
Checksums
-Similar to sizers, the s_checksum() helper takes the block name to calculate the checksum of as the first parameter. The following optional keyword arguments may also be specified:
-
-
algorithm: (string or function pointer, default="crc32"). Checksum algorithm to apply to target block. (crc32, adler32, md5, sha1)
-
endian: (character, default='<') Endianess of the bit field. Specify '<' for little endian and '>' for big endian.
-
length: (integer, default=0) Length of checksum, leave as 0 to auto-calculate.
-
name: (string, default=None) As with all Sulley objects specifying a name gives you direct access to this primitive throughout the request.
-
-The 'algorithm' argument can be one of "crc32", "adler32", "md5" or "sha1". Alternatively, you can specify a function pointer for this parameter to apply a custom checksum algorithm.
-
-
Repeaters
-The s_repeat() (or s_repeater()) helper is used for replicating a block a variable number of times. This is useful for example when testing for overflows during the parsing of tables with multiple elements. This helper takes three mandatory arguments, the name of the block to be repeated, the minimum number of repetitions and the maximum number of repetitions. Additionally, the following optional keyword arguments are available:
-
-
step: (integer, default=1) Step count between min and max reps.
-
fuzzable: (boolean, default=False) Enable or disable fuzzing of this primitive.
-
name: (string, default=None) As with all Sulley objects specifying a name gives you direct access to this primitive throughout the request.
-
-Consider the following example that ties all three of the introduced helpers together. We are fuzzing a portion of a protocol which contains a table of strings. Each entry in the table consists of a 2-byte string type field, a 2-byte length field, a string field and finally a CRC-32 checksum field which is calculated over the string field. We don't know what the valid values for the type field are, so we'll fuzz that with random data. Here is what this portion of the protocol may look like in Sulley:
-
-# table entry: [type][len][string][checksum]
-if s_block_start("table entry"):
- # we don't know what the valid types are, so we'll fill this in with random data.
- s_random("\x00\x00", 2, 2)
-
- # next, we insert a sizer of length 2 for the string field to follow.
- s_size("string field", length=2)
-
- # block helpers only apply to blocks, so encapsulate the string primitive in one.
- if s_block_start("string field"):
- # the default string will simply be a short sequence of C's.
- s_string("C" * 10)
- s_block_end()
-
- # append the CRC-32 checksum of the string to the table entry.
- s_checksum("string field")
-s_block_end()
-
-# repeat the table entry from 100 to 1,000 reps stepping 50 elements on each iteration.
-s_repeat("table entry", min_reps=100, max_reps=1000, step=50)
-
-This Sulley script will fuzz not only table entry parsing but may potentially discover a fault in the processing of overly long tables.
-
-
Legos
-Sulley utilizes "Legos" for representing user-defined components such as e-mail addresses, hostnames and protocol primitives used in Microsoft RPC, XDR, ASN.1 and others. In ASN.1 / BER strings are represented as the sequence [0x04][0x84][dword length][string]. When fuzzing an ASN.1 based protocol, including the length and type prefixes in front of every string can become cumbersome. Instead we can define a Lego and reference it:
-
-s_lego("ber_string", "anonymous")
-
-Every Lego follows a similar format with the exception of the optional 'options' keyword argument, which is specific to individual legos. As a simple example, consider the definition of the 'tag' lego, helpful when fuzzing XML-ish protocols:
-
-This example Lego simply accepts the desired tag as a string and encapsulates it within the appropriate delimiters. It does so by extending the block class and manually adding the tag delimiters and user-supplied string to the block stack via self.push().
-
-Here is another example which produces a simple lego for representing ASN.1 / BER integers in Sulley. The "lowest common denominator" was chosen to represent all integers as 4-byte integers which following the form: [0x02][0x04][dword], where 0x02 specifies integer type, 0x04 specifies the integer is 4 bytes long and the 'dword' represents the actual integer we are passing. Here is what the definition looks like from sulley\legos\ber.py:
-
-Similar to the previous example, the supplied integer is added to the block stack with self.push(). Unlike the previous example, the render() routine is overloaded to prefix the rendered contents with the static sequence "\x02\x04" to satisfy the integer representation requirements previously described.
-
-
Final Notes
-Sulley grows with the creation of every new fuzzer. Developed blocks/requests expand the request library and can be easily referenced and used in the construction of future fuzzers. For a more detailed API reference, see the Epydoc generated Sulley API Docs.
-
-
-
Session
-Once you have defined a number of requests it's time to tie them together in a session. One of the major benefits of Sulley over other fuzzing frameworks is its capability of fuzzing "deep" within a protocol. This is accomplished by linking requests together in a graph. In the following example a sequence of requests are tied together and the pgraph library, which the session and request classes extend from, is leveraged to render the graph in uDraw format:
-
-When it comes time to fuzz, Sulley walks the graph structure starting with the root node and fuzzing each component along the way. In this example it will begin with the 'helo' request. Once complete, Sulley will begin fuzzing the 'mail from' request. It does so by prefixing each test case with a valid 'helo' request. Next, Sulley moves on to fuzzing the 'rcpt to' request. Again, this is accomplished by prefixing each test case with a valid 'helo' and 'mail from' request. The process continues through 'data' and then restarts down the 'ehlo' path. The ability to break a protocol into individual requests and fuzz all possible paths through the constructed protocol graph is powerful. Consider for example an issue disclosed against Ipswitch Collaboration Suite in September of 2006. The software fault in this case was a stack overflow during the parsing of long strings contained within the characters '@' and ':'. What makes this case interesting is that this vulnerability is only exposed over the 'ehlo' route and not the 'helo' route. If our fuzzer is unable to walk all possible protocol paths, then issues such as this may be missed.
-
-When instantiating a session, the following optional keywords arguments may be specified:
-
-
session_filename: (string, default=None) Filename to serialize persistent data to. Specifying a filename allows you to stop and resume the fuzzer.
-
skip: (integer, default=0) Number of test cases to skip.
-
sleep_time: (float, default=1.0) Time to sleep in between transmission of test cases.
-
log_level: (integer, default=2) Set the log level, higher number == more log messages.
-
proto: (string, default="tcp") Communication protocol.
-
timeout: (float, default=5.0) Seconds to wait for a send() / recv() to return prior to timing out.
-
-Another advanced feature that Sulley introduces is the ability to register callbacks on every edge defined within the protocol graph structure. This allows us to register a function to call between node transmissions to implement functionality such as challenge response systems. The callback method must follow this prototype:
-
- def callback(node, edge, last_recv, sock)
-
-Where 'node' is the node about to be sent, 'edge' is the last edge along the current fuzz path to 'node', 'last_recv' contains the data returned from the last socket transmission and 'sock' is the live socket. A callback is also useful in situations where, for example, the size of the next pack is specified in the first packet. As another example, if you need to fill in the dynamic IP address of the target register a callback that snags the IP from sock.getpeername()[0]. Edge callbacks can also be registered through the optional keyword argument 'callback' to the session.connect() method.
-
-
Targets and Agents
-The next step is to define targets, link them with agents and add the targets to the session. In the following example we instantiate a new target which is running inside a VMWare virtual machine and link it to three agents:
-
-The instantiated target is bound on TCP port 5168 on the host 10.0.0.1. A network monitor agent is running on the target system, listening by default on port 26001. The network monitor will record all socket communications to individual PCAP files labeled by test case number. The process monitor agent is also running on the target system, listening by default on port 26002. This agent accepts additional arguments specifying the process name to attach to, the command to stop the target process and the command to start the target process. Finally the VMWare control agent is running on the local system, listening by default on port 26003. The target is added to the session and fuzzing begins. Sulley is capable of fuzzing multiple targets, each with a unique set of linked agents. This allows you to save time by splitting the total test space across the various targets.
-
-Let's take a closer look at each individual agents functionality.
-
-
Agent: Network Monitor (network_monitor.py)
-The network monitor agent is responsible for monitoring network communications and logging them to PCAP files on disk. The agent is hard coded to bind to TCP port 26001 and accepts connections from the Sulley session over the PedRPC custom binary protocol. Prior to transmitting a test case to the target, Sulley contacts this agent and requests that it begins recording network traffic. Once the test case has been successfully transmitted, Sulley again contacts this agent requesting it to flush recorded traffic to a PCAP file on disk. The PCAP files are named by test case number for easy retrieval. This agent does not have to be launched on the same system as the target software. It must however have visibility into sent and received network traffic. This agent accepts the following command line arguments:
-
-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
-
-Network Device List:
- [0] \Device\NPF_GenericDialupAdapter
- [1] {2D938150-427D-445F-93D6-A913B4EA20C0} 192.168.181.1
- [2] {9AF9AAEC-C362-4642-9A3F-0768CDA60942} 0.0.0.0
- [3] {9ADCDA98-A452-4956-9408-0968ACC1F482} 192.168.81.193
- ...
-
-
-
Agent: Process Monitor (process_monitor.py)
-The process monitor agent is responsible for detecting faults which may occur in the target process during fuzz testing. The agent is hard coded to bind to TCP port 26002 and accepts connections from the Sulley session over the PedRPC custom binary protocol. After have successfully transmitted each individual test case to the target, Sulley contacts this agent to determine if a fault was triggered. If so, high level information regarding the nature of the fault is transmitted back to the Sulley session for display through the internal web server (more on this later). Triggered faults are also logged in a serialized "crash bin" for post mortem analysis. This functionality is explored in further detailed later. This agent accepts the following command line arguments:
-
-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
-
-
-
Agent: VMWare Control (vmcontrol.py)
-The VMWare control agent is hard coded to bind to TCP port 26003 and accepts connections from the Sulley session over the PedRPC custom binary protocol. This agent exposes an API for interacting with a virtual machine image including the ability to start, stop, suspend or reset the image as well as take, delete and restore snapshots. In the event that a fault has been detected or the target can not be reached, Sulley can contact this agent and revert the virtual machine to a known good state. The test sequence honing tool will heavily rely on this agent to accomplish its task of identifying the exact sequence of test cases that trigger any given complex fault. This agent accepts the following command line arguments:
-
-ERR> USAGE: vmcontrol.py
- <-x|--vmx FILENAME> path to VMX to control
- <-r|--vmrun FILENAME> path to vmrun.exe
- [-s|--snapshot NAME> set the snapshot name
- [-l|--log_level LEVEL] log level (default 1), increase for more verbosity
-
-
-
External instrumentation
-Some kind of targets (embedded devices for example) don't support debugger, and the process monitor agent can't be used in these cases. The external instrumentation class allows external commands to be called to detect fault and restart the target . SSH is used in the following example, but any python function or external script can be used:
-
-import os
-
-def ssh_is_alive():
- '''Check that the target is alive. Called after each test case. Return True if alive, False otherwise'''
-
- _, stdout = os.popen2('ssh %s pidof target' % IP_DST)
- pid = stdout.read()
- return pid != ''
-
-def ssh_restart():
- '''Restart the target. Called when instrumentation (post) fail.'''
-
- os.popen2('ssh %s /etc/init.d/target restart' % IP_DST)
-
-sess = sessions.session()
-target = sessions.target(IP_DST, PORT_DST)
-target.procmon = instrumentation.external(post=ssh_is_alive, start=ssh_restart)
-sess.add_target(target)
-sess.connect(s_get('node'))
-sess.fuzz()
-
-
-
Web Monitoring Interface
-The Sulley session class has a built in minimal web server which is hard coded to bind to port 26000. Once the fuzz() method of the session class is called the web server thread spins off and the progress of the fuzzer including intermediary results can be seen. Here is an example screenshot:
-
-The fuzzer can be paused and resumed by hitting the appropriate buttons. A synopsis of each detected fault is displayed as a list with the offending test case number listed in the first column. Clicking on the test case number loads a detailed crash dump at the time of the fault. This information is of course also available in the "crash bin" file and accessible programmatically.
-
-
-
Post Mortem
-Once a Sulley fuzz session is complete, it is time to review the results and enter the post mortem phase. The sessions built in web server will provide you with early indications on potentially uncovered issues, but this is the time you will actually separate out the results. A couple of utilities exist to help you along in this process. The first is the 'crashbin_explorer.py' utility, which accepts the following command line arguments:
-
-$ ./utils/crashbin_explorer.py
- USAGE: crashbin_explorer.py <xxx.crashbin>
- [-t|--test #] dump the crash synopsis for a specific test case number
- [-g|--graph name] generate a graph of all crash paths, save to 'name'.udg
-
-We can use this utility for example to view every location where a fault was detected at and furthermore list the individual test case numbers which triggered a fault at that address. The following results are from a real world audit against Trillians Jabber protocol parser:
-
-None of these listed fault points may stand out as an obviously exploitable issue. We can further drill down into the specifics of an individual fault by specifying a test case number with the '-t' command line switch. Let's take a look at test case number 1416:
-
-Again nothing too obvious may stand out but we know that we are influencing this specific access violation as the register being invalidly dereferenced, ECX, contains the ASCII string: "&;tg". String expansion issue perhaps? We can view the crash locations graphically which adds an extra dimension displaying the known execution paths using the '-g' command line switch. The following generated graph is again from a real world audit against the Trillian Jabber parser:
-
-We can see that although we've uncovered 4 different crash locations, the source of the issue appears to be the same. Further research reveals that this is indeed correct. The specific flaw exists in the Rendezvous / XMPP (Extensible Messaging and Presence Protocol) messaging subsystem. Trillian locates nearby users through the '_presence' mDNS (multicast DNS) service on UDP port 5353. Once a user is registered through mDNS, messaging is accomplished via XMPP over TCP port 5298. Within plugins\rendezvous.dll the follow logic is applied to received messages:
-
-The string length of the the supplied message is calculated and a heap buffer in the amount of length + 128 is allocated to store a copy of the message which is then passed through expatxml.xmlComposeString(), a function called with the following prototype:
-
-The xmlComposeString() routine calls through to expatxml.19002420() which, among other things, HTML encodes the characters &, > and < as &, > and < respectively. This behavior can be seen in the following disassembly snippet:
-
-As the originally calculated string length does not account for this string expansion, the following subsequent in-line memory copy operation within rendezvous.dll can trigger an exploitable memory corruption:
-
-Each of the faults detected by Sulley were in response to this logic error. Tracking fault locations and paths allowed us to quickly postulate that a single source was responsible. A final step we may wish to take is to remove all PCAP files which do not contain information regarding a fault. The 'pcap_cleaner.py' utility was written for exactly this task:
-
-$ ./utils/pcap_cleaner.py
- USAGE: pcap_cleaner.py <xxx.crashbin> <path to pcaps>
-
-This utility will open the specified crashbin file, read in the list of test cases numbers that triggered a fault and erase all other PCAP files from the specified directory.
-
-
-
A Complete Walkthrough
-To better understand how everything ties together, from start to finish, we will walk through a complete real world example audit. This example will touch on many intermediate to advanced Sulley concepts and should hopefully solidify your understanding of the framework. Many details regarding the specifics of the target are skipped in this walkthrough, the main purpose of this section is to demonstrate the usage of a number of advanced Sulley features. The chosen target is Trend Micro Server Protect, specifically a Microsoft DCE/RPC endpoint on TCP port 5168 bound to by the service SpntSvc.exe. The RPC endpoint is exposed from TmRpcSrv.dll with the following Interface Definition Language (IDL) stub information:
-
- // opcode: 0x00, address: 0x65741030
- // uuid: 25288888-bd5b-11d1-9d53-0080c83a5c2c
- // version: 1.0
-
- error_status_t rpc_opnum_0 (
- [in] handle_t arg_1, // not sent on wire
- [in] long trend_req_num,
- [in][size_is(arg_4)] byte some_string[],
- [in] long arg_4,
- [out][size_is(arg_6)] byte arg_5[], // not sent on wire
- [in] long arg_6
- );
-
-Neither the parameters 'arg_1' nor 'arg_6' are actually transmitted across the wire. This is an important fact to consider later when we write the actual fuzz requests. Further examination reveals that the parameter 'trend_req_num' has special meaning. The upper and lower halves of this parameter control a pair of jump tables which expose a plethora of reachable sub-routines through this single RPC function. Reverse engineering the jump tables reveals the following combinations:
-
-
When the value for the upper half is 0x0001, 1 through 21 are valid lower half values.
-
When the value for the upper half is 0x0002, 1 through 18 are valid lower half values.
-
When the value for the upper half is 0x0003, 1 through 84 are valid lower half values.
-
When the value for the upper half is 0x0005, 1 through 24 are valid lower half values.
-
When the value for the upper half is 0x000A, 1 through 48 are valid lower half values.
-
When the value for the upper half is 0x001F, 1 through 24 are valid lower half values.
-
-We must next create a custom encoder routine which will be responsible for encapsulating defined blocks as a valid DCE/RPC request. There is only a single function number, so this is simple. We define a basic wrapper around utisl.dcerpc.request() which hard codes the opcode parameter to zero:
-
-# dce rpc request encoder used for trend server protect 5168 RPC service.
-# opnum is always zero.
-def rpc_request_encoder (data):
- return utils.dcerpc.request(0, data)
-
-
Building the Requests
-Armed with this information and our encoder we can begin to define our Sulley requests. We create a file 'requests\trend.py' to contain all our Trend related request and helper definitions and begin coding. This is an excellent example of how building a fuzzer request within a language (as opposed to a custom language) is beneficial as we take advantage of some Python looping to automatically generate a separate request for each valid upper value from 'trend_req_num':
-
-for op, submax in [(0x1, 22), (0x2, 19), (0x3, 85), (0x5, 25), (0xa, 49), (0x1f, 25)]:
- s_initialize("5168: op-%x" % op)
- if s_block_start("everything", encoder=rpc_request_encoder):
- # [in] long trend_req_num,
- s_group("subs", values=map(chr, range(1, submax)))
- s_static("\x00") # subs is actually a little endian word
- s_static(struct.pack("<H", op)) # opcode
-
- # [in][size_is(arg_4)] byte some_string[],
- s_size("some_string")
- if s_block_start("some_string", group="subs"):
- s_static("A" * 0x5000, name="arg3")
- s_block_end()
-
- # [in] long arg_4,
- s_size("some_string")
-
- # [in] long arg_6
- s_static(struct.pack("<L", 0x5000)) # output buffer size
- s_block_end()
-
-Within each generated request a new block is initialized and passed our previously defined custom encoder. Next, the s_group() primitive is used to define a sequence named 'subs' which represents the lower half value of 'trend_req_num' we saw earlier. The upper half word value is next added to the request stream as a static value. We will not be fuzzing the 'trend_req_num' as we have reverse engineered its valid values, had we not, we could enable fuzzing for these fields as well. Next, the NDR size prefix for 'some_string' is added to the request. We could optionally use the Sulley DCE/RPC NDR Lego primitives here, but since the RPC request is so simple we decide to represent the NDR format manually. Next, the 'some_string' value is added to the request. The string value is encapsulated in a block so that it's length can be measured. In this case we use a static sized string of the character 'A' (roughly 20k worth). Normally we would insert an s_string() primitive here, but since we know Trend will crash with any long string we reduce the test set by utilizing a static value. The length of the string is appended to the request again to fullfill the size_is requirement for 'arg_4'. Finally, we specify an arbitrary static size for the output buffer size and close the block. Our requests are now ready and we can move on to creating a session.
-
Creating the Session
-We create a new file in the top level Sulley folder named 'fuzz_trend_server_protect_5168.py' for our session. This file has since been moved to the 'archived_fuzzies' folder since it has completed its life. First thing's first, we import Sulley and the created Trend requests from the request library:
-
-Next, we are going to define a pre-send function which is responsible for establishing the DCE/RPC connection prior to the transmission of any individual test case. The pre-send routine accepts a single parameter, the socket to transmit data on. This is a simple routine to write thanks to the availability of utils.dcerpc.bind(), a Sulley utility routine:
-
-Now it's time to initiate the session and define a target. We'll fuzz a single target, an installation of Trend Server Protect housed inside a VMWare virtual machine with the address 10.0.0.1. We'll follow the framework guidelines by saving the serialized session information to the 'audits' directory. Finally, we register a network monitor, process monitor and virtual machine control agent with the defined target:
-
-Since a VMWare control agent is present, Sulley will default to reverting to a known good snapshot whenever a fault is detected or the target is unable to be reached. If a VMWare control agent is not available but a process monitor agent is, then Sulley attempts to restart the target process to resume fuzzing. This is accomplished by specifying the 'stop_commands' and 'start_commands' options to the process monitor agent:
-
-The 'proc_name' parameter is mandatory whenever you use the process monitor agent, it specifies what process name the debugger should attach to and look for faults in. If neither a VMWare control agent nor a process monitor agent are available, then Sulley has no choice but to simply provide the target time to recover in the event a data transmission is unsuccessful.
-
-Next, we instruct the target to start by calling the VMWare control agents restart_target() routine. Once running, the target is added to the session, the pre-send routine is defined and each of the defined requests is connected to the root fuzzing node. Finally, fuzzing commences with a call to the session classes fuzz() routine.
-
-# start up the target.
-target.vmcontrol.restart_target()
-
-print "virtual machine up and running"
-
-sess.add_target(target)
-sess.pre_send = rpc_bind
-sess.connect(s_get("5168: op-1"))
-sess.connect(s_get("5168: op-2"))
-sess.connect(s_get("5168: op-3"))
-sess.connect(s_get("5168: op-5"))
-sess.connect(s_get("5168: op-a"))
-sess.connect(s_get("5168: op-1f"))
-sess.fuzz()
-
-
Setting up the Environment
-The final step before launching the fuzz session is to setup the environment. We do so by bringing up the target virtual machine image and launching the network and process monitor agents directly within the test image with the following command line parameters:
-
-Both agents are executed from a mapped share which corresponds with the Sulley top-level directory where the session script is running from. A BPF filter string is passed to the network monitor to ensure that only the packets we are interested in are recorded. A directory within the audits folder is also chosen where the network monitor will create PCAPs for every test case. With both agents and the target process running a live snapshot is made as named "sulley ready and waiting".
-
-Next, we shut down VMWare and launch the VMWare control agent on the host system (the fuzzing system). This agent requires the path to the vmrun.exe executable, the path to the actual image to control and finally the name of the snapshot to revert to in the event of a fault discovery of data transmission failure:
-
-Finally, we are ready. Simply launch 'fuzz_trend_server_protect_5168.py', connect a web browser to http://127.0.0.1:26000 to monitor the fuzzer progress, sit back, watch and enjoy.
-
-When the fuzzer completes running through its list of 221 test cases we discover that 19 of them triggered faults. Using the 'crashbin_explorer.py' utility we can explore the faults categorized by exception address:
-
-$ ./utils/crashbin_explorer.py audits/trend_server_protect_5168.crashbin
- [6] [INVALID]:41414141 Unable to disassemble at 41414141 from thread 568 caused access violation
- 42, 109, 156, 164, 170, 198,
- [3] LogMaster.dll:63272106 push ebx from thread 568 caused access violation
- 53, 56, 151,
- [1] ntdll.dll:77fbb267 push dword [ebp+0xc] from thread 568 caused access violation
- 195,
- [1] Eng50.dll:6118954e rep movsd from thread 568 caused access violation
- 181,
- [1] ntdll.dll:77facbbd push edi from thread 568 caused access violation
- 118,
- [1] Eng50.dll:61187671 cmp word [eax],0x3b from thread 568 caused access violation
- 116,
- [1] [INVALID]:0058002e Unable to disassemble at 0058002e from thread 568 caused access violation
- 70,
- [2] Eng50.dll:611896d1 rep movsd from thread 568 caused access violation
- 152, 182,
- [1] StRpcSrv.dll:6567603c push esi from thread 568 caused access violation
- 106,
- [1] KERNEL32.dll:7c57993a cmp ax,[edi] from thread 568 caused access violation
- 165,
- [1] Eng50.dll:61182415 mov edx,[edi+0x20c] from thread 568 caused access violation
- 50,
-
-Some of these are clearly exploitable issues. The test cases that resulted with an EIP of 0x41414141 for example. Test case 70 seems to have stumbled upon a possible code execution issue as well, a UNICODE overflow (actually this can be a straight overflow with a bit more research). The crash bin explorer utility can generate a graph view of the detected faults as well, drawing paths based on observed stack backtraces. This can help pin point the root cause of certain issues. The utility accepts the following command line arguments:
-
-$ ./utils/crashbin_explorer.py
- USAGE: crashbin_explorer.py <xxx.crashbin>
- [-t|--test #] dump the crash synopsis for a specific test case number
- [-g|--graph name] generate a graph of all crash paths, save to 'name'.udg
-
-We can for example further examine the CPU state at the time of the fault detected in response to test case #70:
-
-You can see here that the stack has been blown away by what appears to be a UNICODE string of file extensions. You can pull up the archived PCAP file for the given test case as well. Here is an excerpt of a screenshot from Wireshark examining the contents of one of the captured PCAP files:
-
-A final step we may wish to take is to remove all PCAP files which do not contain information regarding a fault. The 'pcap_cleaner.py' utility was written for exactly this task:
-
-$ ./utils/pcap_cleaner.py
- USAGE: pcap_cleaner.py <xxx.crashbin> <path to pcaps>
-
-This utility will open the specified crashbin file, read in the list of test cases numbers that triggered a fault and erase all other PCAP files from the specified directory. The discovered code execution vulnerabilities in this fuzz were all reported to Trend and have resulted in the following advisories:
-
-This is not to say that all possible vulnerabilities have been exhausted in this interface. In fact, this was the most rudimentary fuzzing possible of this interface. A secondary fuzz which actually uses the s_string() primitive as opposed to simply a long string can now be beneficial.
-
-
-
Upcoming Features
-
Feedback Loop
-A new monitoring agent is in works that will monitor individual instruction execution in search of comparisons to constant values. These "hints" will get passed back to the fuzz session for inclusion in data generation and hopes of improved code coverage.
-
-
Parallel Fuzzing
-The current "architecture" is designed to allow for the addition of multiple targets each with its own configuration of monitoring agents. The completion of parallel fuzzing will disperse the total fuzz set workload across all available target to drastically improve performance.
-
-
Sequence Honing
-A common problem that arrises with fuzzing is that replaying a test case previously deemed to generate a fault does not reproduce the issue. The likely reasoning behind this is that some sequence of previously transmitted test cases put the target in a vulnerable state. The honing tool will apply a sliding / splitting window algorithm to determine the exact sequence of events necessary to reproduce a given issue.
-
-
Code Coverage Monitor
-A new monitoring agent is in the works that will record code coverage in reaction to each individually transmitted test case. This will assist in determining the exact source of detected faults as well as provide an indicator as to the effectiveness of the fuzzing session.
-
-
PCAP Binning
-Similar to the crash binning functionality which handles automatic categorization of fault addresses and paths, the PCAP binning tool will automatically categorize observed responses to fuzz data.
-
-
-
-
-
\ No newline at end of file
diff --git a/docs/introducing_sulley.pdf b/docs/introducing_sulley.pdf
deleted file mode 100755
index c8b1ff0..0000000
Binary files a/docs/introducing_sulley.pdf and /dev/null differ
diff --git a/docs/stylesheet.css b/docs/stylesheet.css
deleted file mode 100644
index 9114c77..0000000
--- a/docs/stylesheet.css
+++ /dev/null
@@ -1,59 +0,0 @@
-body
-{
- font-family: arial, helvetica, sans-serif;
- font-size: 12px;
- color: #000000;
-}
-
-a:link {color: #990000; text-decoration: underline;}
-a:visited {color: #990000; text-decoration: underline;}
-a:hover {color: #FFFFFF; text-decoration: none; background-color: #990000}
-a:active {color: #990000; text-decoration: underline;}
-
-td
-{
- font-family: arial, helvetica, sans-serif;
- font-size: 12px;
- color: #000000;
-}
-
-h1
-{
- font-family: arial, helvetica, sans-serif;
- font-size: 35px;
- color: #FFFFFF;
- background-color: #004080;
- margin-top: 10px;
-}
-
-h2
-{
- font-family: arial, helvetica, sans-serif;
- font-size: 20px;
- color: #004080;
- border-bottom: 1px solid #B0C8E3;
- margin-top: 30px;
- margin-bottom: 10px;
-}
-
-h3
-{
- font-family: arial, helvetica, sans-serif;
- font-size: 15px;
- color: #005BB7;
- margin-bottom: 5px;
-}
-
-.code
-{
- border: 2px dashed #cccccc;
- padding: 10px;
- margin: 10px;
- margin-left: 5%;
- margin-right: 5%;
- background-color: #D2E9FF;
- color: #000000;
- font-size: 12px;
- font-family: courier new;
- word-wrap: break-word;
-}
\ No newline at end of file
diff --git a/examples/fuzz_trend_control_manager_20901.py b/examples/fuzz_trend_control_manager_20901.py
index 2f41970..879c952 100644
--- a/examples/fuzz_trend_control_manager_20901.py
+++ b/examples/fuzz_trend_control_manager_20901.py
@@ -6,12 +6,13 @@
# this was a really half assed fuzz. someone should take it further, see my notes in the requests file for more info.
#
-from sulley import *
+from sulley import *
+# noinspection PyUnresolvedReferences
from requests import trend
-########################################################################################################################
-sess = sessions.session(session_filename="audits/trend_server_protect_20901.session", sleep_time=.25, log_level=10)
-sess.add_target(sessions.target("192.168.181.2", 20901))
+
+sess = sessions.Session(session_filename="audits/trend_server_protect_20901.session", sleep_time=.25, log_level=10)
+sess.add_target(sessions.Target("192.168.181.2", 20901))
sess.connect(s_get("20901"))
sess.fuzz()
diff --git a/examples/fuzz_trend_server_protect_5168.py b/examples/fuzz_trend_server_protect_5168.py
index 330221f..7ce6fde 100644
--- a/examples/fuzz_trend_server_protect_5168.py
+++ b/examples/fuzz_trend_server_protect_5168.py
@@ -9,7 +9,9 @@
# network_monitor.py -d 1 -f "src or dst port 5168" -p audits\trend_server_protect_5168
#
# on localhost:
-# vmcontrol.py -r "c:\Progra~1\VMware\VMware~1\vmrun.exe" -x "v:\vmfarm\images\windows\2000\win_2000_pro-clones\TrendM~1\win_2000_pro.vmx" --snapshot "sulley ready and waiting"
+# vmcontrol.py -r "c:\Progra~1\VMware\VMware~1\vmrun.exe" \
+# -x "v:\vmfarm\images\windows\2000\win_2000_pro-clones\TrendM~1\win_2000_pro.vmx" \
+# --snapshot "sulley ready and waiting"
#
# this key gets written which fucks trend service even on reboot.
# HKEY_LOCAL_MACHINE\SOFTWARE\TrendMicro\ServerProtect\CurrentVersion\Engine
@@ -17,23 +19,23 @@
# uncomment the req/num to do a single test case.
#
-import time
-
-from sulley import *
+from sulley import utils, s_get, s_mutate, s_render, sessions, pedrpc
+# noinspection PyUnresolvedReferences
from requests import trend
req = num = None
#req = "5168: op-3"
#num = "\x04"
-def rpc_bind (sock):
+
+def rpc_bind(sock):
bind = utils.dcerpc.bind("25288888-bd5b-11d1-9d53-0080c83a5c2c", "1.0")
sock.send(bind)
utils.dcerpc.bind_ack(sock.recv(1000))
-def do_single (req, num):
+def do_single(req, num):
import socket
# connect to the server.
@@ -56,20 +58,20 @@ def do_single (req, num):
print "done."
-def do_fuzz ():
- sess = sessions.session(session_filename="audits/trend_server_protect_5168.session")
- target = sessions.target("192.168.181.133", 5168)
+def do_fuzz():
+ sess = sessions.Session(session_filename="audits/trend_server_protect_5168.session")
+ target = sessions.Target("192.168.181.133", 5168)
- target.netmon = pedrpc.client("192.168.181.133", 26001)
- target.procmon = pedrpc.client("192.168.181.133", 26002)
- target.vmcontrol = pedrpc.client("127.0.0.1", 26003)
+ target.netmon = pedrpc.Client("192.168.181.133", 26001)
+ target.procmon = pedrpc.Client("192.168.181.133", 26002)
+ target.vmcontrol = pedrpc.Client("127.0.0.1", 26003)
target.procmon_options = \
- {
- "proc_name" : "SpntSvc.exe",
- "stop_commands" : ['net stop "trend serverprotect"'],
- "start_commands" : ['net start "trend serverprotect"'],
- }
+ {
+ "proc_name": "SpntSvc.exe",
+ "stop_commands": ['net stop "trend serverprotect"'],
+ "start_commands": ['net start "trend serverprotect"'],
+ }
# start up the target.
target.vmcontrol.restart_target()
@@ -88,7 +90,6 @@ def do_fuzz ():
print "done fuzzing. web interface still running."
-
if not req or not num:
do_fuzz()
else:
diff --git a/examples/fuzz_trillian_jabber.py b/examples/fuzz_trillian_jabber.py
index 8bf094b..3f1396c 100644
--- a/examples/fuzz_trillian_jabber.py
+++ b/examples/fuzz_trillian_jabber.py
@@ -9,29 +9,36 @@
# process_monitor.py -c audits\trillian_jabber.crashbin -p trillian.exe
#
# on localhost:
-# vmcontrol.py -r "c:\Progra~1\VMware\VMware~1\vmrun.exe" -x "v:\vmfarm\images\windows\xp\win_xp_pro-clones\allsor~1\win_xp_pro.vmx" --snapshot "sulley ready and waiting"
+# vmcontrol.py -r "c:\Progra~1\VMware\VMware~1\vmrun.exe" \
+# -x "v:\vmfarm\images\windows\xp\win_xp_pro-clones\allsor~1\win_xp_pro.vmx" \
+# --snapshot "sulley ready and waiting"
#
# note:
# you MUST register the IP address of the fuzzer as a valid MDNS "presence" host. to do so, simply install and
# launch trillian on the fuzz box with rendezvous enabled. otherwise the target will drop the connection.
#
-from sulley import *
+from sulley import sessions, \
+ pedrpc, \
+ s_get
+
+# noinspection PyUnresolvedReferences
from requests import jabber
-def init_message (sock):
+
+def init_message(sock):
init = '\n'
init += ''
sock.send(init)
sock.recv(1024)
-sess = sessions.session(session_filename="audits/trillian.session")
-target = sessions.target("152.67.137.126", 5298)
-target.netmon = pedrpc.client("152.67.137.126", 26001)
-target.procmon = pedrpc.client("152.67.137.126", 26002)
-target.vmcontrol = pedrpc.client("127.0.0.1", 26003)
-target.procmon_options = { "proc_name" : "trillian.exe" }
+sess = sessions.Session(session_filename="audits/trillian.session")
+target = sessions.Target("152.67.137.126", 5298)
+target.netmon = pedrpc.Client("152.67.137.126", 26001)
+target.procmon = pedrpc.Client("152.67.137.126", 26002)
+target.vmcontrol = pedrpc.Client("127.0.0.1", 26003)
+target.procmon_options = {"proc_name": "trillian.exe"}
# start up the target.
target.vmcontrol.restart_target()
diff --git a/examples/mdns.py b/examples/mdns.py
index 77da174..7423012 100755
--- a/examples/mdns.py
+++ b/examples/mdns.py
@@ -3,13 +3,23 @@
# A partial MDNS fuzzer. Could be made to be a DNS fuzzer trivially
# Charlie Miller
-from sulley import *
-from binascii import *
-from struct import *
+from sulley import s_word, \
+ s_initialize, \
+ sessions, \
+ s_block_start, \
+ s_size, \
+ s_block_end, \
+ s_string, \
+ s_repeat, \
+ s_group, \
+ s_dword, \
+ s_binary, \
+ s_get
-def insert_questions (sess, node, edge, sock):
- node.names['Questions'].value = 1+node.names['queries'].current_reps
- node.names['Authority'].value = 1+node.names['auth_nameservers'].current_reps
+
+def insert_questions(sess, node, edge, sock):
+ node.names['Questions'].value = 1 + node.names['queries'].current_reps
+ node.names['Authority'].value = 1 + node.names['auth_nameservers'].current_reps
s_initialize("query")
s_word(0, name="TransactionID")
@@ -19,50 +29,50 @@ def insert_questions (sess, node, edge, sock):
s_word(1, name="Authority", endian='>')
s_word(0, name="Additional", endian='>')
-######### Queries ################
+# ######## Queries ################
if s_block_start("query"):
- if s_block_start("name_chunk"):
- s_size("string", length=1)
- if s_block_start("string"):
- s_string("A"*10)
- s_block_end()
- s_block_end()
- s_repeat("name_chunk", min_reps=2, max_reps=4, step=1, fuzzable=True, name="aName")
+ if s_block_start("name_chunk"):
+ s_size("string", length=1)
+ if s_block_start("string"):
+ s_string("A" * 10)
+ s_block_end()
+ s_block_end()
+ s_repeat("name_chunk", min_reps=2, max_reps=4, step=1, fuzzable=True, name="aName")
- s_group("end", values=["\x00", "\xc0\xb0"]) # very limited pointer fuzzing
- s_word(0xc, name="Type", endian='>')
- s_word(0x8001, name="Class", endian='>')
+ s_group("end", values=["\x00", "\xc0\xb0"]) # very limited pointer fuzzing
+ s_word(0xc, name="Type", endian='>')
+ s_word(0x8001, name="Class", endian='>')
s_block_end()
s_repeat("query", 0, 1000, 40, name="queries")
######## Authorities ############
if s_block_start("auth_nameserver"):
- if s_block_start("name_chunk_auth"):
- s_size("string_auth", length=1)
- if s_block_start("string_auth"):
- s_string("A"*10)
- s_block_end()
- s_block_end()
- s_repeat("name_chunk_auth", min_reps=2, max_reps=4, step=1, fuzzable=True, name="aName_auth")
- s_group("end_auth", values=["\x00", "\xc0\xb0"]) # very limited pointer fuzzing
+ if s_block_start("name_chunk_auth"):
+ s_size("string_auth", length=1)
+ if s_block_start("string_auth"):
+ s_string("A" * 10)
+ s_block_end()
+ s_block_end()
+ s_repeat("name_chunk_auth", min_reps=2, max_reps=4, step=1, fuzzable=True, name="aName_auth")
+ s_group("end_auth", values=["\x00", "\xc0\xb0"]) # very limited pointer fuzzing
- s_word(0xc, name="Type_auth", endian='>')
- s_word(0x8001, name="Class_auth", endian='>')
- s_dword(0x78, name="TTL_auth", endian='>')
- s_size("data_length", length=2, endian='>')
- if s_block_start("data_length"):
- s_binary("00 00 00 00 00 16 c0 b0") # This should be fuzzed according to the type, but I'm too lazy atm
- s_block_end()
+ s_word(0xc, name="Type_auth", endian='>')
+ s_word(0x8001, name="Class_auth", endian='>')
+ s_dword(0x78, name="TTL_auth", endian='>')
+ s_size("data_length", length=2, endian='>')
+ if s_block_start("data_length"):
+ s_binary("00 00 00 00 00 16 c0 b0") # This should be fuzzed according to the type, but I'm too lazy atm
+ s_block_end()
s_block_end()
s_repeat("auth_nameserver", 0, 1000, 40, name="auth_nameservers")
s_word(0)
-sess = sessions.session(proto="udp")
-target = sessions.target("224.0.0.251", 5353)
+sess = sessions.Session(proto="udp")
+target = sessions.Target("224.0.0.251", 5353)
sess.add_target(target)
-sess.connect(s_get("query"), callback=insert_questions )
+sess.connect(s_get("query"), callback=insert_questions)
sess.fuzz()
diff --git a/installer/Nullsoft Installer Script.nsi b/installer/Nullsoft Installer Script.nsi
deleted file mode 100644
index f8c7cec..0000000
--- a/installer/Nullsoft Installer Script.nsi
+++ /dev/null
@@ -1,128 +0,0 @@
-; Sulley Fuzzing Framework Installer
-; Aaron Portnoy
-; TippingPoint Security Research Team
-; (c) 2007
-
-; HM NIS Edit Wizard helper defines
-!define PRODUCT_NAME "Sulley Fuzzing Framework"
-!define PRODUCT_VERSION "1.0"
-!define PRODUCT_PUBLISHER "Pedram Amini and Aaron Portnoy"
-!define PRODUCT_WEB_SITE "http://www.fuzzing.org"
-!define PRODUCT_DIR_REGKEY "Software\Microsoft\Windows\CurrentVersion\App Paths\Sulley.exe"
-!define PRODUCT_UNINST_KEY "Software\Microsoft\Windows\CurrentVersion\Uninstall\${PRODUCT_NAME}"
-!define PRODUCT_UNINST_ROOT_KEY "HKLM"
-
-; MUI 1.67 compatible ------
-!include "MUI.nsh"
-
-; ZIP support
-!include "ZipDLL.nsh"
-
-!define MUI_ABORTWARNING
-
-; icons
-!define MUI_ICON "..\..\sulley_icon.ico"
-!define MUI_UNICON "..\..\sulley_icon.ico"
-
-; Welcome page
-!insertmacro MUI_PAGE_WELCOME
-; License page
-!insertmacro MUI_PAGE_LICENSE "..\LICENSE.txt"
-; Directory page
-!insertmacro MUI_PAGE_DIRECTORY
-; Instfiles page
-!insertmacro MUI_PAGE_INSTFILES
-; Finish page
-!define MUI_FINISHPAGE_RUN
-; Run on completion
-!define MUI_FINISHPAGE_RUN_FUNCTION "LaunchDocsAndShell"
-!insertmacro MUI_PAGE_FINISH
-; Uninstaller pages
-!insertmacro MUI_UNPAGE_INSTFILES
-; Language files
-!insertmacro MUI_LANGUAGE "English"
-
-
-
-Function LaunchDocsAndShell
- ExecShell "" "$INSTDIR\docs\index.html"
- Exec 'cmd.exe /c cd "$INSTDIR"'
-FunctionEnd
-
-Name "${PRODUCT_NAME} ${PRODUCT_VERSION}"
-OutFile "Sulley Fuzzing Framework.exe"
-InstallDir "$PROGRAMFILES\Sulley Fuzzing Framework"
-InstallDirRegKey HKLM "${PRODUCT_DIR_REGKEY}" ""
-ShowInstDetails show
-ShowUnInstDetails show
-
-
-Section "Sulley" SEC01
- SetOutPath "$INSTDIR\install_files"
- File "install_files\python.msi"
- File "install_files\winpcap.exe"
- File "install_files\pcapy.exe"
- File "install_files\ctypes.exe"
- SetOutPath "$INSTDIR"
- File "sulley.zip"
- ZipDLL::extractall "$INSTDIR\sulley.zip" "$INSTDIR"
-SectionEnd
-
-
-Section "Python" SEC02
- SetOverwrite ifnewer
- ExecWait 'msiexec /i "$INSTDIR\install_files\python.msi"'
-SectionEnd
-
-Section "Pcapy" SEC03
- SetOverwrite ifnewer
- ExecWait "$INSTDIR\install_files\pcapy.exe"
-SectionEnd
-
-Section "WinPCAP" SEC04
- SetOverwrite ifnewer
- ExecWait "$INSTDIR\install_files\winpcap.exe"
-SectionEnd
-
-Section "ctypes" SEC05
- SetOverwrite ifnewer
- ExecWait "$INSTDIR\install_files\ctypes.exe"
-SectionEnd
-
-
-Section -AdditionalIcons
- SetOutPath $INSTDIR
- WriteIniStr "$INSTDIR\${PRODUCT_NAME}.url" "InternetShortcut" "URL" "${PRODUCT_WEB_SITE}"
- CreateShortCut "$SMPROGRAMS\Sulley Fuzzing Framework\Website.lnk" "$INSTDIR\${PRODUCT_NAME}.url"
- CreateShortCut "$SMPROGRAMS\Sulley Fuzzing Framework\Uninstall.lnk" "$INSTDIR\uninst.exe"
-SectionEnd
-
-Section -Post
- WriteUninstaller "$INSTDIR\uninst.exe"
- WriteRegStr HKLM "${PRODUCT_DIR_REGKEY}" "" "$PROGRAMFILES\pcapy-0.10.5.win32-py2.5.exe"
- WriteRegStr ${PRODUCT_UNINST_ROOT_KEY} "${PRODUCT_UNINST_KEY}" "DisplayName" "$(^Name)"
- WriteRegStr ${PRODUCT_UNINST_ROOT_KEY} "${PRODUCT_UNINST_KEY}" "UninstallString" "$INSTDIR\uninst.exe"
- WriteRegStr ${PRODUCT_UNINST_ROOT_KEY} "${PRODUCT_UNINST_KEY}" "DisplayIcon" "$PROGRAMFILES\pcapy-0.10.5.win32-py2.5.exe"
- WriteRegStr ${PRODUCT_UNINST_ROOT_KEY} "${PRODUCT_UNINST_KEY}" "DisplayVersion" "${PRODUCT_VERSION}"
- WriteRegStr ${PRODUCT_UNINST_ROOT_KEY} "${PRODUCT_UNINST_KEY}" "URLInfoAbout" "${PRODUCT_WEB_SITE}"
- WriteRegStr ${PRODUCT_UNINST_ROOT_KEY} "${PRODUCT_UNINST_KEY}" "Publisher" "${PRODUCT_PUBLISHER}"
-SectionEnd
-
-
-Function un.onUninstSuccess
- HideWindow
- MessageBox MB_ICONINFORMATION|MB_OK "Sulley was successfully removed from your computer."
-FunctionEnd
-
-Function un.onInit
- MessageBox MB_ICONQUESTION|MB_YESNO|MB_DEFBUTTON2 "Are you sure you want to completely remove Sulley and all of its components?" IDYES +2
- Abort
-FunctionEnd
-
-Section Uninstall
- RMDir /r "$INSTDIR"
-
- DeleteRegKey ${PRODUCT_UNINST_ROOT_KEY} "${PRODUCT_UNINST_KEY}"
- DeleteRegKey HKLM "${PRODUCT_DIR_REGKEY}"
- SetAutoClose true
-SectionEnd
\ No newline at end of file
diff --git a/network_monitor.py b/network_monitor.py
index 9512fd8..e803f87 100644
--- a/network_monitor.py
+++ b/network_monitor.py
@@ -4,6 +4,7 @@
import time
import sys
import os
+# noinspection PyUnresolvedReferences
import pcapy
import impacket
import impacket.ImpactDecoder
@@ -35,9 +36,9 @@ def create_usage():
try:
# extract the device UUID and open the TCP/IP parameters key for it.
- pcapy_device = pcapy_device[pcapy_device.index("{"):pcapy_device.index("}") + 1]
+ pcapy_device = pcapy_device[pcapy_device.index("{"):pcapy_device.index("}") + 1]
subkey = r"SYSTEM\CurrentControlSet\Services\Tcpip\Parameters\Interfaces\%s" % pcapy_device
- key = _winreg.OpenKey(_winreg.HKEY_LOCAL_MACHINE, subkey)
+ key = _winreg.OpenKey(_winreg.HKEY_LOCAL_MACHINE, subkey)
# if there is a DHCP address snag that, otherwise fall back to the IP address.
try:
@@ -53,15 +54,14 @@ def create_usage():
return message
-########################################################################################################################
-class PcapThread (threading.Thread):
- def __init__ (self, network_monitor, pcap, pcap_save_path):
+class PcapThread(threading.Thread):
+ def __init__(self, network_monitor, pcap, pcap_save_path):
self.network_monitor = network_monitor
- self.pcap = pcap
- self.decoder = None
- self.dumper = self.pcap.dump_open(pcap_save_path)
- self.active = True
- self.data_bytes = 0
+ self.pcap = pcap
+ self.decoder = None
+ self.dumper = self.pcap.dump_open(pcap_save_path)
+ self.active = True
+ self.data_bytes = 0
# register the appropriate decoder.
if pcap.datalink() == pcapy.DLT_EN10MB:
@@ -73,7 +73,7 @@ def __init__ (self, network_monitor, pcap, pcap_save_path):
threading.Thread.__init__(self)
- def packet_handler (self, header, data):
+ def packet_handler(self, header, data):
# add the captured data to the PCAP.
self.dumper.dump(header, data)
@@ -83,15 +83,14 @@ def packet_handler (self, header, data):
# log the decoded data at the appropriate log level.
self.network_monitor.log(self.decoder.decode(data), 15)
- def run (self):
+ def run(self):
# process packets while the active flag is raised.
while self.active:
self.pcap.dispatch(0, self.packet_handler)
-########################################################################################################################
-class NetworkMonitorPedrpcServer (pedrpc.server):
- def __init__ (self, host, port, monitor_device, bpf_filter="", path="./", level=1):
+class NetworkMonitorPedrpcServer(pedrpc.Server):
+ def __init__(self, host, port, monitor_device, bpf_filter="", path="./", level=1):
"""
@type host: str
@param host: Hostname or IP address to bind server to
@@ -108,12 +107,12 @@ def __init__ (self, host, port, monitor_device, bpf_filter="", path="./", level=
"""
# initialize the PED-RPC server.
- pedrpc.server.__init__(self, host, port)
- self.device = monitor_device
- self.filter = bpf_filter
- self.log_path = path
- self.log_level = level
- self.pcap = None
+ pedrpc.Server.__init__(self, host, port)
+ self.device = monitor_device
+ self.filter = bpf_filter
+ self.log_path = path
+ self.log_level = level
+ self.pcap = None
self.pcap_thread = None
# ensure the log path is valid.
@@ -128,7 +127,7 @@ def __init__ (self, host, port, monitor_device, bpf_filter="", path="./", level=
self.log("\t log_level: %d" % self.log_level)
self.log("Awaiting requests...")
- def __stop (self):
+ def __stop(self):
"""
Kill the PCAP thread.
"""
@@ -137,16 +136,17 @@ def __stop (self):
self.log("stopping active packet capture thread.", 10)
self.pcap_thread.active = False
- self.pcap_thread = None
+ self.pcap_thread = None
- def alive (self):
+ # noinspection PyMethodMayBeStatic
+ def alive(self):
"""
Returns True. Useful for PED-RPC clients who want to see if the PED-RPC connection is still alive.
"""
return True
- def post_send (self):
+ def post_send(self):
"""
This routine is called after the fuzzer transmits a test case and returns the number of bytes captured by the
PCAP thread.
@@ -164,7 +164,7 @@ def post_send (self):
self.log("stopped PCAP thread, snagged %d bytes of data" % data_bytes)
return data_bytes
- def pre_send (self, test_number):
+ def pre_send(self, test_number):
"""
This routine is called before the fuzzer transmits a test case and spin off a packet capture thread.
"""
@@ -180,7 +180,7 @@ def pre_send (self, test_number):
self.pcap_thread = PcapThread(self, self.pcap, pcap_log_path)
self.pcap_thread.start()
- def log (self, msg="", level=1):
+ def log(self, msg="", level=1):
"""
If the supplied message falls under the current log level, print the specified message to screen.
@@ -191,7 +191,7 @@ def log (self, msg="", level=1):
if self.log_level >= level:
print "[%s] %s" % (time.strftime("%I:%M.%S"), msg)
- def retrieve (self, test_number):
+ def retrieve(self, test_number):
"""
Return the raw binary contents of the PCAP saved for the specified test case number.
@@ -202,23 +202,21 @@ def retrieve (self, test_number):
self.log("retrieving PCAP for test case #%d" % test_number)
pcap_log_path = "%s/%d.pcap" % (self.log_path, test_number)
- fh = open(pcap_log_path, "rb")
- data = fh.read()
+ fh = open(pcap_log_path, "rb")
+ data = fh.read()
fh.close()
return data
- def set_filter (self, new_filter):
+ def set_filter(self, new_filter):
self.log("updating PCAP filter to '%s'" % new_filter)
self.filter = new_filter
- def set_log_path (self, new_log_path):
+ def set_log_path(self, new_log_path):
self.log("updating log path to '%s'" % new_log_path)
self.log_path = new_log_path
-########################################################################################################################
-
if __name__ == "__main__":
usage_message = create_usage()
rpc_port = 26001
@@ -231,10 +229,10 @@ def set_log_path (self, new_log_path):
except getopt.GetoptError:
log_error(usage_message)
- device = None
+ device = None
pcap_filter = ""
- log_path = "./"
- log_level = 1
+ log_path = "./"
+ log_level = 1
for opt, arg in opts:
if opt in ("-d", "--device"):
diff --git a/new_examples/fuzz_http_server.py b/new_examples/fuzz_http_server.py
new file mode 100644
index 0000000..5fb627a
--- /dev/null
+++ b/new_examples/fuzz_http_server.py
@@ -0,0 +1,77 @@
+import sys
+sys.path.insert(0, '../')
+
+from sulley.primitives import String, Static, Delim
+
+
+class Group(object):
+ blocks = []
+
+ def __init__(self, name, definition=None):
+ self.name = name
+ if definition:
+ self.definition = definition
+
+ def add_definition(self, definition):
+ assert isinstance(definition, (list, tuple)), "Definition must be a list or a tuple!"
+ self.definition = definition
+
+ def render(self):
+ return "".join([x.value for x in self.definition])
+
+ def exhaust(self):
+ for item in self.definition:
+ while item.mutate():
+ current_value = item.value
+ self.log_send(current_value)
+ recv_data = self.send_buffer(current_value)
+ self.log_recv(recv_data)
+
+ def __repr__(self):
+ return '<%s [%s items]>' % (self.__class__.__name__, len(self.definition))
+
+ # noinspection PyMethodMayBeStatic
+ def send_buffer(self, current_value):
+ return "Sent %s!" % current_value
+
+ def log_send(self, current_value):
+ pass
+
+ def log_recv(self, recv_data):
+ pass
+
+
+s_static = Static
+s_delim = Delim
+s_string = String
+
+CloseHeader = Group(
+ "HTTP Close Header",
+ definition=[
+ # GET / HTTP/1.1\r\n
+ s_static("GET / HTTP/1.1\r\n"),
+ # Connection: close
+ s_static("Connection"), s_delim(":"), s_delim(" "), s_string("close"),
+ s_static("\r\n\r\n")
+ ]
+)
+
+OpenHeader = Group(
+ "HTTP Open Header",
+ definition=[
+ # GET / HTTP/1.1\r\n
+ Static("GET / HTTP/1.1\r\n"),
+ # Connection: close
+ Static("Connection"), Delim(":"), Delim(" "), String("open"),
+ Static("\r\n\r\n")
+ ]
+)
+
+# CloseHeader = Group("HTTP Close Header")
+# CloseHeader.add_definition([
+# # GET / HTTP/1.1\r\n
+# s_static("GET / HTTP/1.1\r\n"),
+# # Connection: close
+# s_static("Connection"), s_delim(":"), s_delim(" "), s_string("close"),
+# s_static("\r\n\r\n")
+# ])
\ No newline at end of file
diff --git a/process_monitor.py b/process_monitor.py
index be13aac..af3df79 100644
--- a/process_monitor.py
+++ b/process_monitor.py
@@ -12,8 +12,8 @@
from sulley import pedrpc
-PORT = 26002
-ERR = lambda msg: sys.stderr.write("ERR> " + msg + "\n") or sys.exit(1)
+PORT = 26002
+ERR = lambda msg: sys.stderr.write("ERR> " + msg + "\n") or sys.exit(1)
USAGE = """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
@@ -23,23 +23,22 @@
"""
-########################################################################################################################
-class DebuggerThread (threading.Thread):
- def __init__ (self, process_monitor, process, pid_to_ignore=None):
+class DebuggerThread(threading.Thread):
+ def __init__(self, process_monitor, process, pid_to_ignore=None):
"""
Instantiate a new PyDbg instance and register user and access violation callbacks.
"""
threading.Thread.__init__(self)
- self.process_monitor = process_monitor
- self.proc_name = process
- self.ignore_pid = pid_to_ignore
+ self.process_monitor = process_monitor
+ self.proc_name = process
+ self.ignore_pid = pid_to_ignore
self.access_violation = False
- self.active = True
- self.dbg = pydbg.pydbg()
- self.pid = None
+ self.active = True
+ self.dbg = pydbg.pydbg()
+ self.pid = None
# give this thread a unique name.
self.setName("%d" % time.time())
@@ -50,7 +49,7 @@ def __init__ (self, process_monitor, process, pid_to_ignore=None):
self.dbg.set_callback(pydbg.defines.USER_CALLBACK_DEBUG_EVENT, self.dbg_callback_user)
self.dbg.set_callback(pydbg.defines.EXCEPTION_ACCESS_VIOLATION, self.dbg_callback_access_violation)
- def dbg_callback_access_violation (self, dbg):
+ def dbg_callback_access_violation(self, dbg):
"""
Ignore first chance exceptions. Record all unhandled exceptions to the process monitor crash bin and kill
the target process.
@@ -69,7 +68,7 @@ def dbg_callback_access_violation (self, dbg):
# save the the crash synopsis.
self.process_monitor.last_synopsis = self.process_monitor.crash_bin.crash_synopsis()
- first_line = self.process_monitor.last_synopsis.split("\n")[0]
+ first_line = self.process_monitor.last_synopsis.split("\n")[0]
self.process_monitor.log("debugger thread-%s caught access violation: '%s'" % (self.getName(), first_line))
@@ -80,7 +79,7 @@ def dbg_callback_access_violation (self, dbg):
dbg.terminate_process()
return pydbg.defines.DBG_CONTINUE
- def dbg_callback_user (self, dbg):
+ def dbg_callback_user(self, dbg):
"""
The user callback is run roughly every 100 milliseconds (WaitForDebugEvent() timeout from pydbg_core.py). Simply
check if the active flag was lowered and if so detach from the target process. The thread should then exit.
@@ -92,7 +91,7 @@ def dbg_callback_user (self, dbg):
return pydbg.defines.DBG_CONTINUE
- def run (self):
+ def run(self):
"""
Main thread routine, called on thread.start(). Thread exits when this routine returns.
"""
@@ -111,7 +110,7 @@ def run (self):
# TODO: removing the following line appears to cause some concurrency issues.
del self.dbg
- def watch (self):
+ def watch(self):
"""
Continuously loop, watching for the target process. This routine "blocks" until the target process is found.
Update self.pid when found and return.
@@ -130,9 +129,8 @@ def watch (self):
self.process_monitor.log("debugger thread-%s found match on pid %d" % (self.getName(), self.pid))
-########################################################################################################################
-class ProcessMonitorPedrpcServer (pedrpc.server):
- def __init__ (self, host, port, crash_filename, proc=None, pid_to_ignore=None, level=1):
+class ProcessMonitorPedrpcServer(pedrpc.Server):
+ def __init__(self, host, port, crash_filename, proc=None, pid_to_ignore=None, level=1):
"""
@type host: str
@param host: Hostname or IP address
@@ -149,20 +147,20 @@ def __init__ (self, host, port, crash_filename, proc=None, pid_to_ignore=None, l
"""
# initialize the PED-RPC server.
- pedrpc.server.__init__(self, host, port)
+ pedrpc.Server.__init__(self, host, port)
- self.crash_filename = crash_filename
- self.proc_name = proc
- self.ignore_pid = pid_to_ignore
- self.log_level = level
+ self.crash_filename = crash_filename
+ self.proc_name = proc
+ self.ignore_pid = pid_to_ignore
+ self.log_level = level
- self.stop_commands = []
- self.start_commands = []
- self.test_number = None
- self.debugger_thread = None
- self.crash_bin = utils.crash_binning.crash_binning()
+ self.stop_commands = []
+ self.start_commands = []
+ self.test_number = None
+ self.debugger_thread = None
+ self.crash_bin = utils.crash_binning.CrashBinning()
- self.last_synopsis = ""
+ self.last_synopsis = ""
if not os.access(os.path.dirname(self.crash_filename), os.X_OK):
self.log("invalid path specified for crash bin: %s" % self.crash_filename)
@@ -181,14 +179,15 @@ def __init__ (self, host, port, crash_filename, proc=None, pid_to_ignore=None, l
self.log("\t log level: %d" % self.log_level)
self.log("awaiting requests...")
- def alive (self):
+ # noinspection PyMethodMayBeStatic
+ def alive(self):
"""
Returns True. Useful for PED-RPC clients who want to see if the PED-RPC connection is still alive.
"""
return True
- def get_crash_synopsis (self):
+ def get_crash_synopsis(self):
"""
Return the last recorded crash synopsis.
@@ -198,7 +197,7 @@ def get_crash_synopsis (self):
return self.last_synopsis
- def get_bin_keys (self):
+ def get_bin_keys(self):
"""
Return the crash bin keys, ie: the unique list of exception addresses.
@@ -208,7 +207,7 @@ def get_bin_keys (self):
return self.crash_bin.bins.keys()
- def get_bin (self, binary):
+ def get_bin(self, binary):
"""
Return the crash entries from the specified bin or False if the bin key is invalid.
@@ -224,7 +223,7 @@ def get_bin (self, binary):
return self.crash_bin.bins[binary]
- def log (self, msg="", level=1):
+ def log(self, msg="", level=1):
"""
If the supplied message falls under the current log level, print the specified message to screen.
@@ -235,7 +234,7 @@ def log (self, msg="", level=1):
if self.log_level >= level:
print "[%s] %s" % (time.strftime("%I:%M.%S"), msg)
- def post_send (self):
+ def post_send(self):
"""
This routine is called after the fuzzer transmits a test case and returns the status of the target.
@@ -258,12 +257,12 @@ def post_send (self):
# serialize the crash bin to disk.
self.crash_bin.export_file(self.crash_filename)
# for binary in self.crash_bin.bins.keys():
- # crashes += len(self.crash_bin.bins[binary])
+ # crashes += len(self.crash_bin.bins[binary])
for binary, crash_list in self.crash_bin.bins.iteritems():
crashes += len(crash_list)
return not av
- def pre_send (self, test_number):
+ def pre_send(self, test_number):
"""
This routine is called before the fuzzer transmits a test case and ensure the debugger thread is operational.
@@ -288,7 +287,7 @@ def pre_send (self, test_number):
self.log("giving debugger thread 2 seconds to settle in", 5)
time.sleep(2)
- def start_target (self):
+ def start_target(self):
"""
Start up the target process by issuing the commands in self.start_commands.
"""
@@ -301,7 +300,7 @@ def start_target (self):
self.log("done. target up and running, giving it 5 seconds to settle in.")
time.sleep(5)
- def stop_target (self):
+ def stop_target(self):
"""
Kill the current debugger thread and stop the target process by issuing the commands in self.stop_commands.
"""
@@ -321,21 +320,19 @@ def stop_target (self):
else:
os.system(command)
- def set_proc_name (self, new_proc_name):
+ def set_proc_name(self, new_proc_name):
self.log("updating target process name to '%s'" % new_proc_name)
self.proc_name = new_proc_name
- def set_start_commands (self, new_start_commands):
+ def set_start_commands(self, new_start_commands):
self.log("updating start commands to: %s" % new_start_commands)
self.start_commands = new_start_commands
- def set_stop_commands (self, new_stop_commands):
+ def set_stop_commands(self, new_stop_commands):
self.log("updating stop commands to: %s" % new_stop_commands)
self.stop_commands = new_stop_commands
-########################################################################################################################
-
if __name__ == "__main__":
opts = None
# parse command line options.
diff --git a/process_monitor_unix.py b/process_monitor_unix.py
index 7f33d3b..c23ace4 100644
--- a/process_monitor_unix.py
+++ b/process_monitor_unix.py
@@ -46,14 +46,14 @@
ERR = lambda msg: sys.stderr.write("ERR> " + msg + "\n") or sys.exit(1)
-class debugger_thread:
+class DebuggerThread:
def __init__(self, start_command):
- '''
+ """
This class isn't actually ran as a thread, only the start_monitoring
method is. It can spawn/stop a process, wait for it to exit and report on
the exit status/code.
- '''
-
+ """
+
self.start_command = start_command
self.tokens = start_command.split(' ')
self.cmd_args = []
@@ -65,18 +65,18 @@ def spawn_target(self):
print self.tokens
self.pid = os.spawnv(os.P_NOWAIT, self.tokens[0], self.tokens)
self.alive = True
-
+
def start_monitoring(self):
- '''
+ """
self.exit_status = os.waitpid(self.pid, os.WNOHANG | os.WUNTRACED)
- while self.exit_status == (0, 0):
+ while self.exit_status == (0, 0):
self.exit_status = os.waitpid(self.pid, os.WNOHANG | os.WUNTRACED)
- '''
-
+ """
+
self.exit_status = os.waitpid(self.pid, 0)
# [0] is the pid
self.exit_status = self.exit_status[1]
-
+
self.alive = False
def get_exit_status(self):
@@ -86,58 +86,62 @@ def stop_target(self):
os.kill(self.pid, signal.SIGKILL)
self.alive = False
- def isAlive(self):
+ def is_alive(self):
return self.alive
-########################################################################################################################
-class nix_process_monitor_pedrpc_server(pedrpc.server):
- def __init__(self, host, port, crash_bin, log_level=1):
- '''
- @type host: String
+class NIXProcessMonitorPedrpcServer(pedrpc.Server):
+ def __init__(self, host, port, cbin, level=1):
+ """
+ @type host: str
@param host: Hostname or IP address
- @type port: Integer
+ @type port: int
@param port: Port to bind server to
- @type crash_bin: String
- @param crash_bin: Where to save monitored process crashes for analysis
-
- '''
-
- pedrpc.server.__init__(self, host, port)
- self.crash_bin = crash_bin
- self.log_level = log_level
- self.dbg = None
+ @type cbin: str
+ @param cbin: Where to save monitored process crashes for analysis
+ """
+
+ pedrpc.Server.__init__(self, host, port)
+ self.crash_bin = cbin
+ self.log_level = level
+ self.dbg = None
+ self.last_synopsis = None
+ self.test_number = 0
+ self.start_commands = None
+ self.stop_commands = None
+ self.proc_name = None
self.log("Process Monitor PED-RPC server initialized:")
self.log("Listening on %s:%s" % (host, port))
self.log("awaiting requests...")
- def alive (self):
- '''
+ # noinspection PyMethodMayBeStatic
+ def alive(self):
+ """
Returns True. Useful for PED-RPC clients who want to see if the PED-RPC connection is still alive.
- '''
+ """
return True
- def log (self, msg="", level=1):
- '''
+ def log(self, msg="", level=1):
+ """
If the supplied message falls under the current log level, print the specified message to screen.
- @type msg: String
+ @type msg: str
@param msg: Message to log
- '''
+ """
if self.log_level >= level:
print "[%s] %s" % (time.strftime("%I:%M.%S"), msg)
- def post_send (self):
- '''
+ def post_send(self):
+ """
This routine is called after the fuzzer transmits a test case and returns the status of the target.
- @rtype: Boolean
+ @rtype: bool
@return: Return True if the target is still active, False otherwise.
- '''
+ """
- if not self.dbg.isAlive():
+ if not self.dbg.is_alive():
exit_status = self.dbg.get_exit_status()
rec_file = open(self.crash_bin, 'a')
if os.WCOREDUMP(exit_status):
@@ -150,45 +154,49 @@ def post_send (self):
reason = 'Exit with code - ' + str(os.WEXITSTATUS(exit_status))
else:
reason = 'Process died for unknown reason'
-
- self.last_synopsis = '[%s] Crash : Test - %d Reason - %s\n' % (time.strftime("%I:%M.%S"), self.test_number, reason)
+
+ self.last_synopsis = '[%s] Crash : Test - %d Reason - %s\n' % (
+ time.strftime("%I:%M.%S"),
+ self.test_number,
+ reason
+ )
rec_file.write(self.last_synopsis)
rec_file.close()
- return self.dbg.isAlive()
+ return self.dbg.is_alive()
- def pre_send (self, test_number):
- '''
+ def pre_send(self, test_number):
+ """
This routine is called before the fuzzer transmits a test case and ensure the debugger thread is operational.
(In this implementation do nothing for now)
@type test_number: Integer
@param test_number: Test number to retrieve PCAP for.
- '''
- if self.dbg == None:
+ """
+ if not self.dbg:
self.start_target()
-
+
self.log("pre_send(%d)" % test_number, 10)
self.test_number = test_number
- def start_target (self):
- '''
+ def start_target(self):
+ """
Start up the target process by issuing the commands in self.start_commands.
- '''
+ """
self.log("starting target process")
-
- self.dbg = debugger_thread(self.start_commands[0])
+
+ self.dbg = DebuggerThread(self.start_commands[0])
self.dbg.spawn_target()
# prevent blocking by spawning off another thread to waitpid
threading.Thread(target=self.dbg.start_monitoring).start()
self.log("done. target up and running, giving it 5 seconds to settle in.")
time.sleep(5)
- def stop_target (self):
- '''
+ def stop_target(self):
+ """
Kill the current debugger thread and stop the target process by issuing the commands in self.stop_commands.
- '''
+ """
# give the debugger thread a chance to exit.
time.sleep(1)
@@ -201,11 +209,11 @@ def stop_target (self):
else:
os.system(command)
- def set_start_commands (self, start_commands):
- '''
+ def set_start_commands(self, start_commands):
+ """
We expect start_commands to be a list with one element for example
['/usr/bin/program arg1 arg2 arg3']
- '''
+ """
if len(start_commands) > 1:
self.log("This process monitor does not accept > 1 start command")
@@ -214,33 +222,32 @@ def set_start_commands (self, start_commands):
self.log("updating start commands to: %s" % start_commands)
self.start_commands = start_commands
-
- def set_stop_commands (self, stop_commands):
+ def set_stop_commands(self, stop_commands):
self.log("updating stop commands to: %s" % stop_commands)
self.stop_commands = stop_commands
- def set_proc_name (self, proc_name):
+ def set_proc_name(self, proc_name):
self.log("updating target process name to '%s'" % proc_name)
self.proc_name = proc_name
- def get_crash_synopsis (self):
- '''
+ def get_crash_synopsis(self):
+ """
Return the last recorded crash synopsis.
@rtype: String
@return: Synopsis of last recorded crash.
- '''
+ """
return self.last_synopsis
-
-########################################################################################################################
+
if __name__ == "__main__":
# parse command line options.
+ opts = None
try:
- opts, args = getopt.getopt(sys.argv[1:], "c:P:l:", ["crash_bin=","port=","log_level=",])
+ opts, args = getopt.getopt(sys.argv[1:], "c:P:l:", ["crash_bin=", "port=", "log_level="])
except getopt.GetoptError:
ERR(USAGE)
@@ -248,17 +255,21 @@ def get_crash_synopsis (self):
PORT = None
crash_bin = None
for opt, arg in opts:
- if opt in ("-c", "--crash_bin"): crash_bin = arg
- if opt in ("-P", "--port"): PORT = int(arg)
- if opt in ("-l", "--log_level"): log_level = int(arg)
+ if opt in ("-c", "--crash_bin"):
+ crash_bin = arg
+ if opt in ("-P", "--port"):
+ PORT = int(arg)
+ if opt in ("-l", "--log_level"):
+ log_level = int(arg)
+
+ if not crash_bin:
+ ERR(USAGE)
- if not crash_bin: ERR(USAGE)
-
- if PORT == None:
+ if not PORT:
PORT = 26002
-
+
# spawn the PED-RPC servlet.
- servlet = nix_process_monitor_pedrpc_server("0.0.0.0", PORT, crash_bin, log_level)
+ servlet = NIXProcessMonitorPedrpcServer("0.0.0.0", PORT, crash_bin, log_level)
servlet.serve_forever()
diff --git a/requests/___REQUESTS___.html b/requests/___REQUESTS___.html
index cbe8f2c..fab80d6 100644
--- a/requests/___REQUESTS___.html
+++ b/requests/___REQUESTS___.html
@@ -53,7 +53,7 @@
Trend Micro
20901
This fuzz request targets Trend Micro Control Manager (DcsProcessor.exe) TCP port 20901. For more information about the audit target see:
-