From afcd647aa9cbe02835b045b623f63d5f7bdf5122 Mon Sep 17 00:00:00 2001 From: Fitblip Date: Wed, 18 Jun 2014 21:25:24 -0700 Subject: [PATCH 01/14] Implement a quick fix to network_monitor.py Thanks to Julio for letting me know about this! --- network_monitor.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/network_monitor.py b/network_monitor.py index 9512fd8..7f97449 100644 --- a/network_monitor.py +++ b/network_monitor.py @@ -26,9 +26,10 @@ def create_usage(): [-l|--log_level LEVEL] log level (default 1), increase for more verbosity [--port PORT] TCP port to bind this agent to -Network Device List:""" +Network Device List: +""" for index, pcapy_device in enumerate(pcapy.findalldevs()): - IFS.append(device) + IFS.append(pcapy_device) # if we are on windows, try and resolve the device UUID into an IP address. if sys.platform.startswith("win"): import _winreg @@ -220,9 +221,9 @@ def set_log_path (self, new_log_path): ######################################################################################################################## if __name__ == "__main__": + IFS = [] usage_message = create_usage() rpc_port = 26001 - IFS = [] opts = None # parse command line options. From fead662f250592a26cce224f1b307c6a3a758a0e Mon Sep 17 00:00:00 2001 From: = <=> Date: Mon, 27 Oct 2014 12:44:41 -0400 Subject: [PATCH 02/14] Added return value on function start_target so sulley doesn't close on "Restarting the target failed, exiting.". --- process_monitor.py | 1 + 1 file changed, 1 insertion(+) diff --git a/process_monitor.py b/process_monitor.py index be13aac..11bb847 100644 --- a/process_monitor.py +++ b/process_monitor.py @@ -300,6 +300,7 @@ def start_target (self): self.log("done. target up and running, giving it 5 seconds to settle in.") time.sleep(5) + return True def stop_target (self): """ From 8952ad00cd485859693139edf6b21c6c50cb8566 Mon Sep 17 00:00:00 2001 From: Andre Cruz Date: Mon, 10 Nov 2014 18:14:44 +0000 Subject: [PATCH 03/14] Allow the loopback interface to be used. --- network_monitor.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/network_monitor.py b/network_monitor.py index 7f97449..1468fed 100644 --- a/network_monitor.py +++ b/network_monitor.py @@ -65,7 +65,7 @@ def __init__ (self, network_monitor, pcap, pcap_save_path): self.data_bytes = 0 # register the appropriate decoder. - if pcap.datalink() == pcapy.DLT_EN10MB: + if pcap.datalink() == pcapy.DLT_EN10MB or pcap.datalink() == pcapy.DLT_NULL: self.decoder = impacket.ImpactDecoder.EthDecoder() elif pcap.datalink() == pcapy.DLT_LINUX_SLL: self.decoder = impacket.ImpactDecoder.LinuxSLLDecoder() From 2edc1ca879592ab3eec4901f3b58665490c4acf8 Mon Sep 17 00:00:00 2001 From: Fitblip Date: Mon, 10 Nov 2014 16:03:45 -0800 Subject: [PATCH 04/14] Fix issue #66 Good catch @tutengfei! --- sulley/pgraph/graph.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sulley/pgraph/graph.py b/sulley/pgraph/graph.py index f016cf0..1753e9f 100644 --- a/sulley/pgraph/graph.py +++ b/sulley/pgraph/graph.py @@ -287,7 +287,7 @@ def find_edge (self, attribute, value): # step through all the edges looking for the given attribute/value pair. else: - for edges in self.edges.values(): + for edge in self.edges.values(): if hasattr(edge, attribute): if getattr(edge, attribute) == value: return edge @@ -674,4 +674,4 @@ def sorted_nodes (self): node_keys = self.nodes.keys() node_keys.sort() - return [self.nodes[key] for key in node_keys] \ No newline at end of file + return [self.nodes[key] for key in node_keys] From 829758b9ec3aa43ab909d9c50173b10dce83c159 Mon Sep 17 00:00:00 2001 From: Steve Martin Date: Wed, 10 Jun 2015 21:29:25 +0100 Subject: [PATCH 05/14] Add "offset" parameter to the s_size funtion. TL;DR I had a block whereby the whole block contents had to be considered for checksum calculation, but one field (fixed length) not included in the size field for the block. Detail: I was creating a fuzzer for PNG fields whose "Chunks" required the above consideration, see: http://www.w3.org/TR/PNG/#5Chunk-layout --- docs/generate_epydocs.bat | 2 +- sulley/__init__.py | 6 ++++-- sulley/blocks.py | 11 +++++++---- 3 files changed, 12 insertions(+), 7 deletions(-) diff --git a/docs/generate_epydocs.bat b/docs/generate_epydocs.bat index fae8a18..636808e 100644 --- a/docs/generate_epydocs.bat +++ b/docs/generate_epydocs.bat @@ -1 +1 @@ -c:\Python26\python.exe c:\Python26\Scripts\epydoc -o Sulley --css blue --name "Sulley: Fuzzing Framework" --url "http://pedram.openrce.org" ..\sulley \ No newline at end of file +c:\Python27\python.exe c:\Python27\Scripts\epydoc.py -o Sulley --css blue --name "Sulley: Fuzzing Framework" --url "http://pedram.openrce.org" ..\sulley \ No newline at end of file diff --git a/sulley/__init__.py b/sulley/__init__.py index 87f96b9..dc0918a 100644 --- a/sulley/__init__.py +++ b/sulley/__init__.py @@ -208,7 +208,7 @@ def s_repeat (block_name, min_reps=0, max_reps=None, step=1, variable=None, fuzz blocks.CURRENT.push(repeat) -def s_size (block_name, length=4, endian="<", format="binary", inclusive=False, signed=False, math=None, fuzzable=False, name=None): +def s_size (block_name, offset=0, length=4, endian="<", format="binary", inclusive=False, signed=False, math=None, fuzzable=False, name=None): ''' Create a sizer block bound to the block with the specified name. You *can not* create a sizer for any currently open blocks. @@ -217,6 +217,8 @@ def s_size (block_name, length=4, endian="<", format="binary", inclusive=False, @type block_name: String @param block_name: Name of block to apply sizer to + @type offset: Integer + @param offset: (Optional, def=0) Offset to calculated size of block @type length: Integer @param length: (Optional, def=4) Length of sizer @type endian: Character @@ -239,7 +241,7 @@ def s_size (block_name, length=4, endian="<", format="binary", inclusive=False, if block_name in blocks.CURRENT.block_stack: raise sex.SullyRuntimeError("CAN NOT ADD A SIZE FOR A BLOCK CURRENTLY IN THE STACK") - size = blocks.size(block_name, blocks.CURRENT, length, endian, format, inclusive, signed, math, fuzzable, name) + size = blocks.size(block_name, blocks.CURRENT, offset, length, endian, format, inclusive, signed, math, fuzzable, name) blocks.CURRENT.push(size) diff --git a/sulley/blocks.py b/sulley/blocks.py index 7ad88cb..6752004 100644 --- a/sulley/blocks.py +++ b/sulley/blocks.py @@ -485,10 +485,10 @@ def checksum (self, data): if type(self.algorithm) is str: if self.algorithm == "crc32": - return struct.pack(self.endian+"L", zlib.crc32(data)) + return struct.pack(self.endian+"L", (zlib.crc32(data) & 0xFFFFFFFFL)) elif self.algorithm == "adler32": - return struct.pack(self.endian+"L", zlib.adler32(data)) + return struct.pack(self.endian+"L", (zlib.adler32(data) & 0xFFFFFFFFL)) elif self.algorithm == "md5": digest = hashlib.md5(data).digest() @@ -692,7 +692,7 @@ class size: user does not need to be wary of this fact. ''' - def __init__ (self, block_name, request, length=4, endian="<", format="binary", inclusive=False, signed=False, math=None, fuzzable=False, name=None): + def __init__ (self, block_name, request, offset=0, length=4, endian="<", format="binary", inclusive=False, signed=False, math=None, fuzzable=False, name=None): ''' Create a sizer block bound to the block with the specified name. You *can not* create a sizer for any currently open blocks. @@ -703,6 +703,8 @@ def __init__ (self, block_name, request, length=4, endian="<", format="binary", @param request: Request this block belongs to @type length: Integer @param length: (Optional, def=4) Length of sizer + @type offset: Integer + @param offset: (Optional, def=0) Offset for calculated size value @type endian: Character @param endian: (Optional, def=LITTLE_ENDIAN) Endianess of the bit field (LITTLE_ENDIAN: <, BIG_ENDIAN: >) @type format: String @@ -721,6 +723,7 @@ def __init__ (self, block_name, request, length=4, endian="<", format="binary", self.block_name = block_name self.request = request + self.offset = offset self.length = length self.endian = endian self.format = format @@ -805,7 +808,7 @@ def render (self): else: self_size = 0 block = self.request.closed_blocks[self.block_name] - self.bit_field.value = self.math(len(block.rendered) + self_size) + self.bit_field.value = self.math(len(block.rendered) + self_size + self.offset) self.rendered = self.bit_field.render() # otherwise, add this sizer block to the requests callback list. From 68dc3f579afc64c3b5a72ed490e8946082ad1cbd Mon Sep 17 00:00:00 2001 From: Steve Martin Date: Sun, 14 Jun 2015 18:58:21 +0100 Subject: [PATCH 06/14] Add support for binary prinitives to be defined with a list / tuple of values. --- sulley/primitives.py | 53 +++++++++++++++++++++++++++++--------------- 1 file changed, 35 insertions(+), 18 deletions(-) diff --git a/sulley/primitives.py b/sulley/primitives.py index 38a5e64..1c97069 100644 --- a/sulley/primitives.py +++ b/sulley/primitives.py @@ -657,11 +657,16 @@ def __init__ (self, value, width, max_num=None, endian="<", format="binary", sig @param name: (Optional, def=None) Specifying a name gives you direct access to a primitive ''' - assert(type(value) is int or type(value) is long) assert(type(width) is int or type(value) is long) + if type(value) in [int, long]: + self.value = self.original_value = value + elif type(value) in [list, tuple]: + self.original_value = value + self.value = value[0] + else: + raise AssertionError() - self.value = self.original_value = value self.width = width self.max_num = max_num self.endian = endian @@ -687,15 +692,20 @@ def __init__ (self, value, width, max_num=None, endian="<", format="binary", sig for i in xrange(0, self.max_num): self.fuzz_library.append(i) else: - # try only "smart" values. - self.add_integer_boundaries(0) - self.add_integer_boundaries(self.max_num / 2) - self.add_integer_boundaries(self.max_num / 3) - self.add_integer_boundaries(self.max_num / 4) - self.add_integer_boundaries(self.max_num / 8) - self.add_integer_boundaries(self.max_num / 16) - self.add_integer_boundaries(self.max_num / 32) - self.add_integer_boundaries(self.max_num) + if type(value) in [list, tuple]: + # Use the supplied values as the fuzz library. + for val in value: + self.fuzz_library.append(val) + else: + # try only "smart" values. + self.add_integer_boundaries(0) + self.add_integer_boundaries(self.max_num / 2) + self.add_integer_boundaries(self.max_num / 3) + self.add_integer_boundaries(self.max_num / 4) + self.add_integer_boundaries(self.max_num / 8) + self.add_integer_boundaries(self.max_num / 16) + self.add_integer_boundaries(self.max_num / 32) + self.add_integer_boundaries(self.max_num) # if the optional file '.fuzz_ints' is found, parse each line as a new entry for the fuzz library. try: @@ -775,7 +785,7 @@ def render (self): if self.signed and self.to_binary()[0] == "1": max_num = self.to_decimal("0" + "1" * (self.width - 1)) # chop off the sign bit. - val = self.value & max_num + val = self.to_binary() & max_num # account for the fact that the negative scale works backwards. val = max_num - val @@ -802,9 +812,16 @@ def to_binary (self, number=None, bit_count=None): @rtype: String @return: Bit string ''' - if number == None: - number = self.value + if type(self.original_value) in [list, tuple]: + # We have been given a list to cycle through... + if self.mutant_index == self.num_mutations(): + # Reset the index. + self.mutant_index = 0 + number = self.original_value[self.mutant_index] + self.mutant_index += 1 + else: + number = self.value if bit_count == None: bit_count = self.width @@ -830,7 +847,7 @@ def to_decimal (self, binary): class byte (bit_field): def __init__ (self, value, endian="<", format="binary", signed=False, full_range=False, fuzzable=True, name=None): self.s_type = "byte" - if type(value) not in [int, long]: + if type(value) not in [int, long, list, tuple]: value = struct.unpack(endian + "B", value)[0] bit_field.__init__(self, value, 8, None, endian, format, signed, full_range, fuzzable, name) @@ -840,7 +857,7 @@ def __init__ (self, value, endian="<", format="binary", signed=False, full_range class word (bit_field): def __init__ (self, value, endian="<", format="binary", signed=False, full_range=False, fuzzable=True, name=None): self.s_type = "word" - if type(value) not in [int, long]: + if type(value) not in [int, long, list, tuple]: value = struct.unpack(endian + "H", value)[0] bit_field.__init__(self, value, 16, None, endian, format, signed, full_range, fuzzable, name) @@ -850,7 +867,7 @@ def __init__ (self, value, endian="<", format="binary", signed=False, full_range class dword (bit_field): def __init__ (self, value, endian="<", format="binary", signed=False, full_range=False, fuzzable=True, name=None): self.s_type = "dword" - if type(value) not in [int, long]: + if type(value) not in [int, long, list, tuple]: value = struct.unpack(endian + "L", value)[0] bit_field.__init__(self, value, 32, None, endian, format, signed, full_range, fuzzable, name) @@ -860,7 +877,7 @@ def __init__ (self, value, endian="<", format="binary", signed=False, full_range class qword (bit_field): def __init__ (self, value, endian="<", format="binary", signed=False, full_range=False, fuzzable=True, name=None): self.s_type = "qword" - if type(value) not in [int, long]: + if type(value) not in [int, long, list, tuple]: value = struct.unpack(endian + "Q", value)[0] bit_field.__init__(self, value, 64, None, endian, format, signed, full_range, fuzzable, name) From 7bc54f28d045de78359a56bd86cf2a472b92a126 Mon Sep 17 00:00:00 2001 From: Steve Martin Date: Tue, 16 Jun 2015 20:01:20 +0100 Subject: [PATCH 07/14] Fixed primitives "off-by-one" error. This was preventing "full_range" from emitting the maximum possible value of a primitive. Fixed a failing unit test. --- sulley/primitives.py | 15 ++++++++------- unit_tests/blocks.py | 5 ++++- 2 files changed, 12 insertions(+), 8 deletions(-) diff --git a/sulley/primitives.py b/sulley/primitives.py index 38a5e64..5bd7422 100644 --- a/sulley/primitives.py +++ b/sulley/primitives.py @@ -677,7 +677,7 @@ def __init__ (self, value, width, max_num=None, endian="<", format="binary", sig self.mutant_index = 0 # current mutation number if self.max_num == None: - self.max_num = self.to_decimal("1" * width) + self.max_num = self.to_decimal("1" + "0" * width) assert(type(self.max_num) is int or type(self.max_num) is long) @@ -708,7 +708,7 @@ def __init__ (self, value, width, max_num=None, endian="<", format="binary", sig except: continue - if fuzz_int <= self.max_num: + if fuzz_int < self.max_num: self.fuzz_library.append(fuzz_int) fh.close() @@ -728,7 +728,7 @@ def add_integer_boundaries (self, integer): case = integer + i # ensure the border case falls within the valid range for this field. - if 0 <= case <= self.max_num: + if 0 <= case < self.max_num: if case not in self.fuzz_library: self.fuzz_library.append(case) @@ -773,12 +773,13 @@ def render (self): else: # if the sign flag is raised and we are dealing with a signed integer (first bit is 1). if self.signed and self.to_binary()[0] == "1": - max_num = self.to_decimal("0" + "1" * (self.width - 1)) - # chop off the sign bit. - val = self.value & max_num + max_num = self.to_decimal("1" + "0" * (self.width - 1)) + + # mask off the sign bit. + val = self.value & self.to_decimal("1" * (self.width - 1)) # account for the fact that the negative scale works backwards. - val = max_num - val + val = max_num - val - 1 # toss in the negative sign. self.rendered = "%d" % ~val diff --git a/unit_tests/blocks.py b/unit_tests/blocks.py index 0fcd1da..b9cc1b2 100644 --- a/unit_tests/blocks.py +++ b/unit_tests/blocks.py @@ -84,9 +84,12 @@ def dependencies (): s_static("TWO" * 100) s_block_end() + assert(s_num_mutations() == 2) + assert(s_mutate() == True) assert(s_render().find("TWO") == -1) - s_mutate() + assert(s_mutate() == True) assert(s_render().find("ONE") == -1) + assert(s_mutate() == False) ######################################################################################################################## From b742a0dc30a4b3abd7428691f29acaf38746c890 Mon Sep 17 00:00:00 2001 From: Steve Martin Date: Wed, 17 Jun 2015 18:32:04 +0100 Subject: [PATCH 08/14] Mutate primitives defined with lists / tuples through the values. When these primitives are not mutating, cycle them through the values. If they are "Fuzzable" they will generate a mutation with each possible value. If they are not fuzzable they will cycle through values according to the demands of other mutating item, with no guarantee that all specified values will be used. --- sulley/primitives.py | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/sulley/primitives.py b/sulley/primitives.py index e602413..35c9d5b 100644 --- a/sulley/primitives.py +++ b/sulley/primitives.py @@ -659,11 +659,8 @@ def __init__ (self, value, width, max_num=None, endian="<", format="binary", sig assert(type(width) is int or type(value) is long) - if type(value) in [int, long]: + if type(value) in [int, long, list, tuple]: self.value = self.original_value = value - elif type(value) in [list, tuple]: - self.original_value = value - self.value = value[0] else: raise AssertionError() @@ -680,6 +677,7 @@ def __init__ (self, value, width, max_num=None, endian="<", format="binary", sig self.fuzz_complete = False # flag if this primitive has been completely fuzzed self.fuzz_library = [] # library of fuzz heuristics self.mutant_index = 0 # current mutation number + self.cyclic_index = 0 # when cycling through non-mutating values if self.max_num == None: self.max_num = self.to_decimal("1" + "0" * width) @@ -814,13 +812,13 @@ def to_binary (self, number=None, bit_count=None): @return: Bit string ''' if number == None: - if type(self.original_value) in [list, tuple]: - # We have been given a list to cycle through... - if self.mutant_index == self.num_mutations(): + if type(self.value) in [list, tuple]: + # We have been given a list to cycle through that is not being mutated... + if self.cyclic_index == len(self.value): # Reset the index. - self.mutant_index = 0 - number = self.original_value[self.mutant_index] - self.mutant_index += 1 + self.cyclic_index = 0 + number = self.value[self.cyclic_index] + self.cyclic_index += 1 else: number = self.value From 007edd3ab83cc660659d6db8bcb7368778ba4549 Mon Sep 17 00:00:00 2001 From: jtpereyda Date: Thu, 18 Jun 2015 17:33:09 -0700 Subject: [PATCH 09/14] Adding workaround since singal.pause() is missing in Windows. OpenRCE/sulley issue #49 --- sulley/sessions.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/sulley/sessions.py b/sulley/sessions.py index 64d8c4c..5f0921c 100644 --- a/sulley/sessions.py +++ b/sulley/sessions.py @@ -551,8 +551,13 @@ def error_handler (e, msg, target, sock=None): # if fuzzing is not finished, web interface thread will catch it if self.total_mutant_index == self.total_num_mutations: import signal - while True: - signal.pause() + try: + while True: + signal.pause() + except AttributeError: + # signal.pause() is missing for Windows; wait 1ms and loop instead + while True: + time.sleep(1) #################################################################################################################### From ac0c625bd651b7b4bb4f8bc226692b1bdda8a2a3 Mon Sep 17 00:00:00 2001 From: Steve Martin Date: Fri, 19 Jun 2015 18:40:44 +0100 Subject: [PATCH 10/14] Corrected exception thrown when the wrong value is used. Updated .gitignote to ignored generated pydocs. Updated user guide to include details on defining integer primitives with lists of values. Also further minor improvements to assist with readabiity that I noted from my initial read through of the guide when familiarising myself with the product. --- .gitignore | 1 + docs/index.html | 138 +++++++++++++++++++++++++------------------ sulley/primitives.py | 2 +- 3 files changed, 83 insertions(+), 58 deletions(-) diff --git a/.gitignore b/.gitignore index 96aa4d8..30dd400 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ *.pyc archived_fuzzies/*.session +docs/ .idea/ \ No newline at end of file diff --git a/docs/index.html b/docs/index.html index a8110c0..ebd2175 100644 --- a/docs/index.html +++ b/docs/index.html @@ -10,13 +10,21 @@

Sulley - Fuzzing Framework

Table of Contents

    -
  1. Overview
  2. +
  3. Introduction
  4. Installation and Requirements
  5. Data Representation
  6. +
  7. Session
  8. Post Mortem
  9. A Complete Walkthrough
  10. @@ -28,14 +36,14 @@

    Table of Contents

    Introduction

    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: +Modern day fuzzers, 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, and has a number of methods for reverting the target to a healthy state. Sulley detects, tracks and categorizes detected faults. Sulley can fuzz in parallel, significantly increasing test speed. Sulley can automatically determine the unique sequence of test cases that triggered a fault. 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. +
    • Data Representation: First step in using any fuzzer. Define your data as Requests built from a hierachy of Blocks, Legos and Primitives that will be sent to your target to tickle its interfaces. +
    • Session: Link your developed requests together to form a session, attach the various available Sulley monitoring agents (socket, debugger, etc...) to manage the fuzzing and monitor your target.
    • Post Mortem: Review the generated data and monitored results. Replay individual test cases.
    -

    Sulley Directory Structure

    +

    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. @@ -89,7 +97,7 @@

      Sulley Directory Structure

      -

      Authors

      +

      Authors

      Pedram Amini
      http://pedram.openrce.org

      @@ -101,7 +109,7 @@

      Sulley Directory Structure

      Installation and Requirements

      -For simple data representation and transmission there are no requirements. However, the various monitoring components do have further needs: +For simple data representation and transmission there are no requirements. However, the various monitoring components do have further dependencies:
      • network_monitor.py: CORE Pcapy, CORE Impacket
      • process_monitor.py: PaiMei @@ -116,9 +124,12 @@

        Sulley Directory Structure

        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(): +

        +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. The name of a block is also required when using Block Helpers, as we will see later. +

        +Most of Sulley's primitives are driven by "fuzz heuristics" that will cause a series of value mutations. You will want to carefully consider which types of primitive you require for each element of your data, whether it is worth mutating its value, and the manner in which it mutates. +

        Static Binary and Random Data

        +We will begin with the simplest primitive; s_static(). This 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")
        @@ -126,50 +137,63 @@ 

        Static and Random Primitives

        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: +The next primitive is s_binary() which accepts binary data represented in multiple formats. SPIKE users should recognize this API:
             # 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: +Like the s_static() primitive, the s_binary() primitive will always render the same value. +

        +The next primitive is s_random(), this is used to generate random data of varying lengths. This primitive takes thres mandatory arguments; its default value and two keyword arguments 'min_length' and 'max_length'. These specify the minimum and maximum length of random data that will be generated for each mutation. To fill a fixed sized field with random data set both 'min_length' and 'max_length' to the same value. The s_random() 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. +
        • 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: +

        Integer Primitives

        +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 integers with the following sizes:
          -
        • 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() +
        • 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: +The integer types each take a mandatory parameter, the default value, see the note below on 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. +
        • 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 the three characters "1", "0", "0" 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, see below.
        • +
        • 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: +The Value may either be a single Python integer expressed in decimal (for example 1, 23, or 378), octal (for example 01, 027, or 0667), or hexadecimal (for example 0x1, 0x17, or 0x1FA8). Alternatively a list, or tuple of integer values may be specified. Lists are enclosed in square braces ('[' and ']') with each value separated by a comma. Tuples are enclosed in round braces ('(' and ')') with each value also separted by a comma. When a list of values is specified then each time the primitive is rendered the next value in the list/tuple is selected, once all values for the primitive have been used, the value will cycle back to the first value in the list/tuple. Note that the value used will cycle through the list/tuple regardless of whether the primitive is set to "fuzzable" or not. However, if the primitive is not fuzzable there is no guarantee that all of the specified values will be used, that will depend upon the number of supplied values and the number of possible mutations arrising from other fields in the data. If the primitive is set to fuzzable then there is a guarantee that all of the supplied values will be used, although this will inflate the total number of mutations for the fuzzing session. Consider the following scenarios: +
        +# Scenario One                                        # Scenario Two
        +s_byte([1, 2, 3, 4, 10, 128, -1], name="A")           s_byte([1, 2, 3, 4, 10, 128, -1, -1 -2], name="A", fuzzable=False)
        +s_byte([0, 1, 2], name="B")                           s_byte([0, 1, 2], name="B", fuzzable=False)
        +s_byte(0, name="C")                                   s_byte(0, name="C")
        +
        +In scenario One first primitive "A" will be fuzzed, during this time primitive "B" will cycle through its defined values, and primitive "C" will remain at its default value. Once all values for primitive "A" have been exhausted, primitive "B" will be fuzzed, at which point primitive "A" will commence cycling through its values. Finally, primitive "C" will be fuzzed (a byte will yield 112 mutations), while both primitives "A" and "B" will continue to cycle. This will result in a total of 121 mutations. Note that since the number of values for primitive "A" is not an exact multiple of the number of values for primitive "B" and the number of mutations triggered by primitive C is sufficiently large, all combinations of primitives "A" and "B" will occur in the fuzzed data. +

        +In scenario Two primitives "A" and "B" will not be fuzzed. Fuzzing will commence with primitive "C". However, primitives "A" and "B" will cycle through their respective values. This will result in a total of 112 mutations (those arrising from fuzzing the single byte primitive). Note that in this scenarion, as there is an exact multiple of values for primitive "A" as the number of values for primitive "B", not all combinations of values for primitives "A" and "B" will be generated; when primitive "A" is "1" primitive "B" will always be "0", when primitive "A" is "2" primitive "B" will always bb "1", and so on. You may want to consider the effect that this will have on the data generated by your fuzzer. Note that you are not restricted to using only valid values for primitives defined with a list of values, you may include any combination of valid and invalid values, and may even repeat values as required. +

        +The full_range modifier is of particular interest from above. It is only really appropriate to use this for 1, or maybe 2, byte values. Consider the effect of using it to fuzz a DWORD value, that's 4,294,967,295 total possible values. At a hyperthetical 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. +

        +If you specify full_range modifier for a primitive that has a list of values, then during the fuzzing phase of that primitive it will yield its full range of values. During the fuzzing phase of other primitives, the value will cycle only through the defined list of values.. +

        String and Delimiter Primitives

        +Strings can be found everywhere; E-mail addresses, hostnames, usernames, passwords, etc are all examples of string primitives that 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 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. +
        • 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. +Strings are frequently made up of a number of sub-fields these may be represented as separate delimiters. For example, the space character is used as a delimiter within an HTTP request "GET /index.html HTTP/1.0". The front slash (/) and dot (.) characters in that same request may also be considered as 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. +

        +As with other primitives; s_string() and s_delim() accept the optional 'fuzzable' and 'name' keyword arguments. String mutations include a large number of common base values, adaptations of the supplied values, and very long values in an attempt to cause buffer overflows. Delimiter mutations include repetition, substitution and exclusion. As an example, consider the following sequence of primitives for fuzzing the HTML body tag.
         # fuzzes the string: <BODY bgcolor="black">
         s_delim("<")
        @@ -183,15 +207,15 @@ 

        Strings and Delimiters

        s_delim(">")
        -

        Blocks

        -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: +

        Blocks

        +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(). The block definition 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 "<=". +
        • 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. @@ -283,7 +307,7 @@

        Dependencies

        Block dependencies can be chained together in any number of ways allowing for powerful (and unfortunately complex) combinations. -

        Block Helpers

        +

        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

        @@ -292,7 +316,7 @@

        Sizers

      • 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? +
      • 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. @@ -322,16 +346,16 @@

        Repeaters

        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() @@ -341,7 +365,7 @@

        Repeaters

        This Sulley script will fuzz not only table entry parsing but may potentially discover a fault in the processing of overly long tables. -

        Legos

        +

        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")
        @@ -589,7 +613,7 @@ 

        Web Monitoring Interface

        0x7c910f2b cmp ecx,[edx+0x4] 0x7c910f2e mov [ebp+0x14],edx 0x7c910f31 jnz 0x7c911f21 - + stack unwind: ntdll.dll:7c910d5c rendezvous.dll:49023967 diff --git a/sulley/primitives.py b/sulley/primitives.py index 35c9d5b..2ef5789 100644 --- a/sulley/primitives.py +++ b/sulley/primitives.py @@ -662,7 +662,7 @@ def __init__ (self, value, width, max_num=None, endian="<", format="binary", sig if type(value) in [int, long, list, tuple]: self.value = self.original_value = value else: - raise AssertionError() + raise ValueError("The supplied value must be either an Int, Long, List or Tuple.") self.width = width self.max_num = max_num From 8928ce859f741c248f68c7b744c560ec1154c3ed Mon Sep 17 00:00:00 2001 From: jtpereyda Date: Fri, 19 Jun 2015 16:40:26 -0700 Subject: [PATCH 11/14] process_monitor.py now accepts relative filenames in -c argument. --- process_monitor.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/process_monitor.py b/process_monitor.py index 11bb847..5ac7b15 100644 --- a/process_monitor.py +++ b/process_monitor.py @@ -151,7 +151,7 @@ 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) - self.crash_filename = crash_filename + self.crash_filename = os.path.abspath(crash_filename) self.proc_name = proc self.ignore_pid = pid_to_ignore self.log_level = level From b842560b2cb7b123cdb20d163c66d4d30022957b Mon Sep 17 00:00:00 2001 From: jtpereyda Date: Mon, 22 Jun 2015 13:51:45 -0700 Subject: [PATCH 12/14] network_monitor.py now runs its server in a thread, making Ctrl+C work in Windows. --- network_monitor.py | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/network_monitor.py b/network_monitor.py index 1468fed..e9fcfa5 100644 --- a/network_monitor.py +++ b/network_monitor.py @@ -7,6 +7,7 @@ import pcapy import impacket import impacket.ImpactDecoder +import signal from sulley import pedrpc @@ -254,6 +255,17 @@ def set_log_path (self, new_log_path): try: servlet = NetworkMonitorPedrpcServer("0.0.0.0", rpc_port, device, pcap_filter, log_path, log_level) - servlet.serve_forever() + t = threading.Thread(target=servlet.serve_forever) + t.daemon = True + t.start() + # Now wait in a way that will not block signals like SIGINT + try: + while True: + signal.pause() + except AttributeError: + # signal.pause() is missing for Windows; wait 1ms and loop instead + while True: + time.sleep(.001) + except: pass From cf74c683703fc0b02ff7406f00e85fa71c9e8022 Mon Sep 17 00:00:00 2001 From: jtpereyda Date: Wed, 24 Jun 2015 16:34:53 -0700 Subject: [PATCH 13/14] OpenRCE/sulley Issue #79 Changing code and usage string to indicate that -s is required. Also removed default vmrun value and fixed two typos. --- vmcontrol.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/vmcontrol.py b/vmcontrol.py index 98d4190..10da683 100755 --- a/vmcontrol.py +++ b/vmcontrol.py @@ -22,7 +22,7 @@ USAGE = "USAGE: vmcontrol.py" \ "\n <-x|--vmx FILENAME|NAME> path to VMX to control or name of VirtualBox image" \ "\n <-r|--vmrun FILENAME> path to vmrun.exe or VBoxManage" \ - "\n [-s|--snapshot NAME> set the snapshot name" \ + "\n <-s|--snapshot NAME> set the snapshot name" \ "\n [-l|--log_level LEVEL] log level (default 1), increase for more verbosity" \ "\n [-i|--interactive] Interactive mode, prompts for input values" \ "\n [--port PORT] TCP port to bind this agent to" \ @@ -73,7 +73,7 @@ def __init__ (self, host, port, vmrun, vmx, snap_name=None, log_level=1, interac print "[*] Using %s" % vmrun break except: - print "[!] Error while trying to find vmrun.exe. Try again without -I." + print "[!] Error while trying to find vmrun.exe. Try again without -i." sys.exit(1) # get vmx path @@ -98,7 +98,7 @@ def __init__ (self, host, port, vmrun, vmx, snap_name=None, log_level=1, interac print "[!] No .vmx file found in the selected folder, please try again" except: raise - print "[!] Error while trying to find the .vmx file. Try again without -I." + print "[!] Error while trying to find the .vmx file. Try again without -i." sys.exit(1) # Grab snapshot name and log level if we're in interactive mode @@ -564,7 +564,7 @@ def is_target_paused (self): except getopt.GetoptError: ERR(USAGE) - vmrun = r"C:\progra~1\vmware\vmware~1\vmrun.exe" + vmrun = None vmx = None snap_name = None log_level = 1 @@ -585,7 +585,7 @@ def is_target_paused (self): print "[!] Interactive mode currently only works on Windows operating systems." ERR(USAGE) - if not vmx and not interactive: + if (not vmx or not vmrun or not snap_name) and not interactive: ERR(USAGE) if not virtualbox: From 54bdbbebdc2348a0c845d8425027f5557d05d515 Mon Sep 17 00:00:00 2001 From: Joshua Pereyda Date: Wed, 24 Jun 2015 17:39:20 -0700 Subject: [PATCH 14/14] Update sessions.py Fixing 1ms error. --- sulley/sessions.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sulley/sessions.py b/sulley/sessions.py index 5f0921c..c657b6d 100644 --- a/sulley/sessions.py +++ b/sulley/sessions.py @@ -557,7 +557,7 @@ def error_handler (e, msg, target, sock=None): except AttributeError: # signal.pause() is missing for Windows; wait 1ms and loop instead while True: - time.sleep(1) + time.sleep(0.001) ####################################################################################################################