Files
2019-10-21 13:02:41 -07:00

876 lines
32 KiB
Python

import random
import struct
from sys import version
def isinteger(var):
if version[0] == '2':
return isinstance(var,(int,long))
else:
return isinstance(var,int)
########################################################################################################################
class base_primitive (object):
'''
The primitive base class implements common functionality shared across most primitives.
'''
def __init__ (self):
self.fuzz_complete = False # this flag is raised when the mutations are exhausted.
self.fuzz_library = [] # library of static fuzz heuristics to cycle through.
self.fuzzable = True # flag controlling whether or not the given primitive is to be fuzzed.
self.mutant_index = 0 # current mutation index into the fuzz library.
self.original_value = None # original value of primitive.
self.rendered = "" # rendered value of primitive.
self.value = None # current value of primitive.
def exhaust (self):
'''
Exhaust the possible mutations for this primitive.
@rtype: Integer
@return: The number of mutations to reach exhaustion
'''
num = self.num_mutations() - self.mutant_index
self.fuzz_complete = True
self.mutant_index = self.num_mutations()
self.value = self.original_value
return num
def mutate (self):
'''
Mutate the primitive by stepping through the fuzz library, return False on completion.
@rtype: Boolean
@return: True on success, False otherwise.
'''
# if we've ran out of mutations, raise the completion flag.
if self.mutant_index == self.num_mutations():
self.fuzz_complete = True
# if fuzzing was disabled or complete, and mutate() is called, ensure the original value is restored.
if not self.fuzzable or self.fuzz_complete:
self.value = self.original_value
return False
# update the current value from the fuzz library.
self.value = self.fuzz_library[self.mutant_index]
# increment the mutation count.
self.mutant_index += 1
return True
def num_mutations (self):
'''
Calculate and return the total number of mutations for this individual primitive.
@rtype: Integer
@return: Number of mutated forms this primitive can take
'''
return len(self.fuzz_library)
def render (self):
'''
Nothing fancy on render, simply return the value.
'''
self.rendered = self.value
return self.rendered
def reset (self):
'''
Reset this primitive to the starting mutation state.
'''
self.fuzz_complete = False
self.mutant_index = 0
self.value = self.original_value
########################################################################################################################
class delim (base_primitive):
def __init__ (self, value, fuzzable=True, name=None):
'''
Represent a delimiter such as :,\r,\n, ,=,>,< etc... Mutations include repetition, substitution and exclusion.
@type value: Character
@param value: Original value
@type fuzzable: Boolean
@param fuzzable: (Optional, def=True) Enable/disable fuzzing of this primitive
@type name: String
@param name: (Optional, def=None) Specifying a name gives you direct access to a primitive
'''
super(delim,self).__init__()
self.value = self.original_value = value
self.fuzzable = fuzzable
self.name = name
self.s_type = "delim" # for ease of object identification
#
# build the library of fuzz heuristics.
#
# if the default delim is not blank, repeat it a bunch of times.
if self.value:
self.fuzz_library.append(self.value * 2)
self.fuzz_library.append(self.value * 5)
self.fuzz_library.append(self.value * 10)
self.fuzz_library.append(self.value * 25)
self.fuzz_library.append(self.value * 100)
self.fuzz_library.append(self.value * 500)
self.fuzz_library.append(self.value * 1000)
# try ommitting the delimiter.
self.fuzz_library.append("")
# if the delimiter is a space, try throwing out some tabs.
if self.value == " ":
self.fuzz_library.append("\t")
self.fuzz_library.append("\t" * 2)
self.fuzz_library.append("\t" * 100)
# toss in some other common delimiters:
self.fuzz_library.append(" ")
self.fuzz_library.append("\t")
self.fuzz_library.append("\t " * 100)
self.fuzz_library.append("\t\r\n" * 100)
self.fuzz_library.append("!")
self.fuzz_library.append("@")
self.fuzz_library.append("#")
self.fuzz_library.append("$")
self.fuzz_library.append("%")
self.fuzz_library.append("^")
self.fuzz_library.append("&")
self.fuzz_library.append("*")
self.fuzz_library.append("(")
self.fuzz_library.append(")")
self.fuzz_library.append("-")
self.fuzz_library.append("_")
self.fuzz_library.append("+")
self.fuzz_library.append("=")
self.fuzz_library.append(":")
self.fuzz_library.append(": " * 100)
self.fuzz_library.append(":7" * 100)
self.fuzz_library.append(";")
self.fuzz_library.append("'")
self.fuzz_library.append("\"")
self.fuzz_library.append("/")
self.fuzz_library.append("\\")
self.fuzz_library.append("?")
self.fuzz_library.append("<")
self.fuzz_library.append(">")
self.fuzz_library.append(".")
self.fuzz_library.append(",")
self.fuzz_library.append("\r")
self.fuzz_library.append("\n")
self.fuzz_library.append("\r\n" * 64)
self.fuzz_library.append("\r\n" * 128)
self.fuzz_library.append("\r\n" * 512)
########################################################################################################################
class group (base_primitive):
def __init__ (self, name, values):
'''
This primitive represents a list of static values, stepping through each one on mutation. You can 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.
@type name: String
@param name: Name of group
@type values: List or raw data
@param values: List of possible raw values this group can take.
'''
super(group, self).__init__()
self.name = name
self.values = values
self.fuzzable = True
self.s_type = "group"
self.value = self.values[0]
self.original_value = self.values[0]
# sanity check that values list only contains strings (or raw data)
if self.values != []:
for val in self.values:
assert isinstance(val,str), "Value list may only contain strings or raw data"
def mutate (self):
'''
Move to the next item in the values list.
@rtype: False
@return: False
'''
if self.mutant_index == self.num_mutations():
self.fuzz_complete = True
# if fuzzing was disabled or complete, and mutate() is called, ensure the original value is restored.
if not self.fuzzable or self.fuzz_complete:
self.value = self.values[0]
return False
# step through the value list.
self.value = self.values[self.mutant_index]
# increment the mutation count.
self.mutant_index += 1
return True
def num_mutations (self):
'''
Number of values in this primitive.
@rtype: Integer
@return: Number of values in this primitive.
'''
return len(self.values)
########################################################################################################################
class random_data (base_primitive):
def __init__ (self, value, min_length, max_length, max_mutations=25, fuzzable=True, step=None, name=None):
'''
Generate a random chunk of data while maintaining a copy of the original. A random length range can be specified.
For a static length, set min/max length to be the same.
@type value: Raw
@param value: Original value
@type min_length: Integer
@param min_length: Minimum length of random block
@type max_length: Integer
@param max_length: Maximum length of random block
@type max_mutations: Integer
@param max_mutations: (Optional, def=25) Number of mutations to make before reverting to default
@type fuzzable: Boolean
@param fuzzable: (Optional, def=True) Enable/disable fuzzing of this primitive
@type step: Integer
@param step: (Optional, def=None) If not null, step count between min and max reps, otherwise random
@type name: String
@param name: (Optional, def=None) Specifying a name gives you direct access to a primitive
'''
super(random_data,self).__init__()
self.value = self.original_value = str(value)
self.min_length = min_length
self.max_length = max_length
self.max_mutations = max_mutations
self.fuzzable = fuzzable
self.step = step
self.name = name
self.s_type = "random_data" # for ease of object identification
if self.step:
self.max_mutations = (self.max_length - self.min_length) // self.step + 1
def mutate (self):
'''
Mutate the primitive value returning False on completion.
@rtype: Boolean
@return: True on success, False otherwise.
'''
# if we've ran out of mutations, raise the completion flag.
if self.mutant_index == self.num_mutations():
self.fuzz_complete = True
# if fuzzing was disabled or complete, and mutate() is called, ensure the original value is restored.
if not self.fuzzable or self.fuzz_complete:
self.value = self.original_value
return False
# select a random length for this string.
if not self.step:
length = random.randint(self.min_length, self.max_length)
# select a length function of the mutant index and the step.
else:
length = self.min_length + self.mutant_index * self.step
# reset the value and generate a random string of the determined length.
self.value = b""
for i in range(length):
self.value += struct.pack("B",random.randint(0, 255))
# increment the mutation count.
self.mutant_index += 1
return True
def num_mutations (self):
'''
Calculate and return the total number of mutations for this individual primitive.
@rtype: Integer
@return: Number of mutated forms this primitive can take
'''
return self.max_mutations
########################################################################################################################
class static (base_primitive):
def __init__ (self, value, name=None):
'''
Primitive that contains static content.
@type value: Raw
@param value: Raw static data
@type name: String
@param name: (Optional, def=None) Specifying a name gives you direct access to a primitive
'''
super(static,self).__init__()
self.value = self.original_value = value
self.name = name
self.fuzzable = False # every primitive needs this attribute.
self.s_type = "static" # for ease of object identification
self.fuzz_complete = True
def mutate (self):
'''
Do nothing.
@rtype: False
@return: False
'''
return False
def num_mutations (self):
'''
Return 0.
@rtype: 0
@return: 0
'''
return 0
########################################################################################################################
class string (base_primitive):
# store fuzz_library as a class variable to avoid copying the ~70MB structure across each instantiated primitive.
fuzz_library = []
def __init__ (self, value, size=-1, padding="\x00", encoding="ascii", fuzzable=True, max_len=0, name=None):
'''
Primitive that cycles through a library of "bad" strings. The class variable 'fuzz_library' contains a list of
smart fuzz values global across all instances. The 'this_library' variable contains fuzz values specific to
the instantiated primitive. This allows us to avoid copying the near ~70MB fuzz_library data structure across
each instantiated primitive.
@type value: String
@param value: Default string value
@type size: Integer
@param size: (Optional, def=-1) Static size of this field, leave -1 for dynamic.
@type padding: Character
@param padding: (Optional, def="\\x00") Value to use as padding to fill static field size.
@type encoding: String
@param encoding: (Optonal, def="ascii") String encoding, ex: utf_16_le for Microsoft Unicode.
@type fuzzable: Boolean
@param fuzzable: (Optional, def=True) Enable/disable fuzzing of this primitive
@type max_len: Integer
@param max_len: (Optional, def=0) Maximum string length
@type name: String
@param name: (Optional, def=None) Specifying a name gives you direct access to a primitive
'''
super(string,self).__init__()
self.value = self.original_value = value
self.size = size
self.padding = padding
self.encoding = encoding
self.fuzzable = fuzzable
self.name = name
self.s_type = "string" # for ease of object identification
# add this specific primitives repitition values to the unique fuzz library.
self.this_library = \
[
self.value * 2,
self.value * 10,
self.value * 100,
# UTF-8
self.value * 2 + "\xfe",
self.value * 10 + "\xfe",
self.value * 100 + "\xfe",
]
# if the fuzz library has not yet been initialized, do so with all the global values.
if not self.fuzz_library:
string.fuzz_library = \
[
# omission.
"",
# strings ripped from spike (and some others I added)
"/.:/" + "A"*5000 + "\x00\x00",
"/.../" + "A"*5000 + "\x00\x00",
"/.../.../.../.../.../.../.../.../.../.../",
"/../../../../../../../../../../../../etc/passwd",
"/../../../../../../../../../../../../boot.ini",
"..:..:..:..:..:..:..:..:..:..:..:..:..:",
"\\\\*",
"\\\\?\\",
"/\\" * 5000,
"/." * 5000,
"!@#$%%^#$%#$@#$%$$@#$%^^**(()",
"%01%02%03%04%0a%0d%0aADSF",
"%01%02%03@%04%0a%0d%0aADSF",
"/%00/",
"%00/",
"%00",
"%u0000",
"%\xfe\xf0%\x00\xff",
"%\xfe\xf0%\x01\xff" * 20,
# format strings.
"%n" * 100,
"%n" * 500,
"\"%n\"" * 500,
"%s" * 100,
"%s" * 500,
"\"%s\"" * 500,
# command injection.
"|touch /tmp/SULLEY",
";touch /tmp/SULLEY;",
"|notepad",
";notepad;",
"\nnotepad\n",
# SQL injection.
"1;SELECT%20*",
"'sqlattempt1",
"(sqlattempt2)",
"OR%201=1",
# some binary strings.
"\xde\xad\xbe\xef",
"\xde\xad\xbe\xef" * 10,
"\xde\xad\xbe\xef" * 100,
"\xde\xad\xbe\xef" * 1000,
"\xde\xad\xbe\xef" * 10000,
"\x00" * 1000,
# miscellaneous.
"\r\n" * 100,
"<>" * 500, # sendmail crackaddr (http://lsd-pl.net/other/sendmail.txt)
]
# add some long strings.
self.add_long_strings("A")
self.add_long_strings("B")
self.add_long_strings("1")
self.add_long_strings("2")
self.add_long_strings("3")
self.add_long_strings("<")
self.add_long_strings(">")
self.add_long_strings("'")
self.add_long_strings("\"")
self.add_long_strings("/")
self.add_long_strings("\\")
self.add_long_strings("?")
self.add_long_strings("=")
self.add_long_strings("a=")
self.add_long_strings("&")
self.add_long_strings(".")
self.add_long_strings(",")
self.add_long_strings("(")
self.add_long_strings(")")
self.add_long_strings("]")
self.add_long_strings("[")
self.add_long_strings("%")
self.add_long_strings("*")
self.add_long_strings("-")
self.add_long_strings("+")
self.add_long_strings("{")
self.add_long_strings("}")
self.add_long_strings("\x14")
self.add_long_strings("\xFE") # expands to 4 characters under utf16
self.add_long_strings("\xFF") # expands to 4 characters under utf16
# add some long strings with null bytes thrown in the middle of it.
for length in [128, 256, 1024, 2048, 4096, 32767, 0xFFFF]:
s = "B" * length
s = s[:len(s)//2] + "\x00" + s[len(s)//2:]
string.fuzz_library.append(s)
# if the optional file '.fuzz_strings' is found, parse each line as a new entry for the fuzz library.
try:
fh = open(".fuzz_strings", "r")
for fuzz_string in fh.readlines():
fuzz_string = fuzz_string.rstrip("\r\n")
if fuzz_string != "":
string.fuzz_library.append(fuzz_string)
fh.close()
except:
pass
# delete strings which length is greater than max_len.
if max_len > 0:
if any(len(s) > max_len for s in self.this_library):
self.this_library = list(set([s[:max_len] for s in self.this_library]))
if any(len(s) > max_len for s in self.fuzz_library):
self.fuzz_library = list(set([s[:max_len] for s in self.fuzz_library]))
def add_long_strings (self, sequence):
'''
Given a sequence, generate a number of selectively chosen strings lengths of the given sequence and add to the
string heuristic library.
@type sequence: String
@param sequence: Sequence to repeat for creation of fuzz strings.
'''
for length in [128, 255, 256, 257, 511, 512, 513, 1023, 1024, 2048, 2049, 4095, 4096, 4097, 5000, 10000, 20000,
32762, 32763, 32764, 32765, 32766, 32767, 32768, 32769, 0xFFFF-2, 0xFFFF-1, 0xFFFF, 0xFFFF+1,
0xFFFF+2, 99999, 100000, 500000, 1000000]:
long_string = sequence * length
string.fuzz_library.append(long_string)
def mutate (self):
'''
Mutate the primitive by stepping through the fuzz library extended with the "this" library, return False on
completion.
@rtype: Boolean
@return: True on success, False otherwise.
'''
# loop through the fuzz library until a suitable match is found.
while 1:
# if we've ran out of mutations, raise the completion flag.
if self.mutant_index == self.num_mutations():
self.fuzz_complete = True
# if fuzzing was disabled or complete, and mutate() is called, ensure the original value is restored.
if not self.fuzzable or self.fuzz_complete:
self.value = self.original_value
return False
# update the current value from the fuzz library.
self.value = (self.fuzz_library + self.this_library)[self.mutant_index]
# increment the mutation count.
self.mutant_index += 1
# if the size parameter is disabled, break out of the loop right now.
if self.size == -1:
break
# ignore library items greather then user-supplied length.
# TODO: might want to make this smarter.
if len(self.value) > self.size:
continue
# pad undersized library items.
if len(self.value) < self.size:
self.value = self.value + self.padding * (self.size - len(self.value))
break
return True
def num_mutations (self):
'''
Calculate and return the total number of mutations for this individual primitive.
@rtype: Integer
@return: Number of mutated forms this primitive can take
'''
return len(self.fuzz_library) + len(self.this_library)
def render (self):
'''
Render the primitive, encode the string according to the specified encoding.
'''
# try to encode the string properly and fall back to the default value on failure.
try:
self.rendered = str(self.value).encode(self.encoding)
except:
if version[0] is '3':
self.rendered = str(self.value).encode('latin-1')
else:
self.rendered = self.value
return self.rendered
########################################################################################################################
class bit_field (base_primitive):
def __init__ (self, value, width, max_num=None, endian="<", format="binary", signed=False, full_range=False, fuzzable=True, name=None):
'''
The bit field primitive represents a number of variable length and is used to define all other integer types.
@type value: Integer
@param value: Default integer value
@type width: Integer
@param width: Width of bit fields
@type endian: Character
@param endian: (Optional, def=LITTLE_ENDIAN) Endianess of the bit field (LITTLE_ENDIAN: <, BIG_ENDIAN: >)
@type format: String
@param format: (Optional, def=binary) Output format, "binary" or "ascii"
@type signed: Boolean
@param signed: (Optional, def=False) Make size signed vs. unsigned (applicable only with format="ascii")
@type full_range: Boolean
@param full_range: (Optional, def=False) If enabled the field mutates through *all* possible values.
@type fuzzable: Boolean
@param fuzzable: (Optional, def=True) Enable/disable fuzzing of this primitive
@type name: String
@param name: (Optional, def=None) Specifying a name gives you direct access to a primitive
'''
super(bit_field,self).__init__()
assert isinteger(width)
if isinteger(value) or isinstance(value,(list, tuple)):
self.value = self.original_value = value
else:
raise ValueError("The supplied value must be either an Int, Long, List or Tuple.")
self.width = width
self.max_num = max_num
self.endian = endian
self.format = format
self.signed = signed
self.full_range = full_range
self.fuzzable = fuzzable
self.name = name
self.rendered = b"" # rendered value
self.cyclic_index = 0 # when cycling through non-mutating values
if self.max_num is None:
self.max_num = self.to_decimal("1" + "0" * width)
assert isinteger(self.max_num)
# build the fuzz library.
if self.full_range:
# add all possible values.
for i in range(0, self.max_num):
self.fuzz_library.append(i)
else:
if isinstance(value,(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:
fh = open(".fuzz_ints", "r")
for fuzz_int in fh.readlines():
# convert the line into an integer, continue on failure.
try:
fuzz_int = long(fuzz_int, 16)
except:
continue
if fuzz_int < self.max_num:
self.fuzz_library.append(fuzz_int)
fh.close()
except:
pass
def add_integer_boundaries (self, integer):
'''
Add the supplied integer and border cases to the integer fuzz heuristics library.
@type integer: Int
@param integer: Integer to append to fuzz heuristics
'''
for i in range(-10, 10):
case = integer + i
# ensure the border case falls within the valid range for this field.
if 0 <= case < self.max_num:
if case not in self.fuzz_library:
self.fuzz_library.append(case)
def render (self):
'''
Render the primitive.
'''
#
# binary formatting.
#
if self.format == "binary":
bit_stream = ""
rendered = b""
# pad the bit stream to the next byte boundary.
if self.width % 8 == 0:
bit_stream += self.to_binary()
else:
bit_stream = "0" * (8 - (self.width % 8))
bit_stream += self.to_binary()
# convert the bit stream from a string of bits into raw bytes.
for i in range(len(bit_stream) // 8):
chunk = bit_stream[8*i:8*i+8]
rendered += struct.pack("B", self.to_decimal(chunk))
# if necessary, convert the endianess of the raw bytes.
if self.endian == "<":
rendered = rendered[::-1]
self.rendered = rendered
#
# ascii formatting.
#
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("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 - 1
# toss in the negative sign.
self.rendered = "%d" % ~val
# unsigned integer or positive signed integer.
else:
self.rendered = "%d" % self.value
return self.rendered
def to_binary (self, number=None, bit_count=None):
'''
Convert a number to a binary string.
@type number: Integer
@param number: (Optional, def=self.value) Number to convert
@type bit_count: Integer
@param bit_count: (Optional, def=self.width) Width of bit string
@rtype: String
@return: Bit string
'''
if number is None:
if isinstance(self.value, (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.cyclic_index = 0
number = self.value[self.cyclic_index]
self.cyclic_index += 1
else:
number = self.value
if bit_count is None:
bit_count = self.width
return "".join(map(lambda x:str((number >> x) & 1), range(bit_count -1, -1, -1)))
def to_decimal (self, binary):
'''
Convert a binary string to a decimal number.
@type binary: String
@param binary: Binary string
@rtype: Integer
@return: Converted bit string
'''
return int(binary, 2)
########################################################################################################################
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 not(isinteger(value) or isinstance(value, (list, tuple))):
value = struct.unpack(endian + "B", value)[0]
bit_field.__init__(self, value, 8, None, endian, format, signed, full_range, fuzzable, name)
########################################################################################################################
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 not(isinteger(value) or isinstance(value, (list, tuple))):
value = struct.unpack(endian + "H", value)[0]
bit_field.__init__(self, value, 16, None, endian, format, signed, full_range, fuzzable, name)
########################################################################################################################
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 not(isinteger(value) or isinstance(value, (list, tuple))):
value = struct.unpack(endian + "L", value)[0]
bit_field.__init__(self, value, 32, None, endian, format, signed, full_range, fuzzable, name)
########################################################################################################################
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 not(isinteger(value) or isinstance(value, (list, tuple))):
value = struct.unpack(endian + "Q", value)[0]
bit_field.__init__(self, value, 64, None, endian, format, signed, full_range, fuzzable, name)