Awesome
dizzy
dizzy is a fuzzing framework, written in python and capable of state-full and state-less fuzzing with a lot of output options.
usage
$dizzy_cmd version 2.0 running on Linux
usage: dizzy_cmd [-h] [-s START_AT] [-d SEC] [-l] [-o OPTIONS] [jobfile]
positional arguments:
jobfile
optional arguments:
-h, --help show this help message and exit
-s START_AT Start at the given step
-d SEC Output status every SEC seconds
-l List all loaded modules and their contents.
-o OPTIONS Overwrite a config option
A jobfile is all that is needed. The -s START_AT
option can be used to start the fuzzing process at a given step and skip all previous steps. The -d SEC
option configures the output interval of progress messages and using the -o OPTIONS
parameter, config values from the jobfile can be overwritten.
jobfile
The Job Configfile contains all information necessary to start a fuzzing job
The [job]
section defines which interaction file (state-full fuzzing) or which dizz file (state-less fuzzing) to use, the fuzzing mode and other parameters, like verbosity or the delay between mutations.
The [output]
section defines were to send the generated data. The only common parameter is the type
parameter, defining which session module to use. All other parameters in this section are session module dependent.
In the optional [probe]
section a target probe can be defined. The probe runs after each complete fuzzing step and checks if the target is still available.
In the optional [value]
section, generic values can be defined. Those values will be available to .dizz and .act files via the config_value
function.
[job]
file = smb2/act/smb2_tree_connect.act
mode = none
delay = 0
verbose = 4
[output]
type = session.tcp
server = False
auto_reopen = False
session_reopen = True
timeout = 5
target_host = 127.0.0.1
target_port = 445
#[probe]
#type = icmp
#timeout = 1
#pkg_size = 64
#target_host = 192.168.2.1
[values]
creds_file = ./creds
share_path = \\127.0.0.1\test
module structure
Modules are created from the modules_src folder. A Makefile is provided that generates the __init__.py index files and packs the module zip files. To update the modules run make in the modules_src folder:
~/dizzy/modules_src# make
to the generated index files, run make clean in the modules_src folder:
~/dizzy/modules_src# make clean
In the modules_src folder, every module has an own folder, named the same as the module. Each module folder has the following structure:
- config.py -- in here, values such as the modules name, the modules external dependecies and the version number are defined.
- a folder named like the module (smb2 in this example).
- a folder named dizz, containing the module's .dizz files. (See DizzFile)
- a folder named act, containing the module's .act files. (See ActFile)
- a folder named job, containing the module's job files. (See JobFile)
- a folder named probe, containing the module's probes. (See Probe)
- a folder named session, containing the module's sessions. (See Session)
- a folder named deps, containing the module's internal dependencies. The folder will be added to pythons include path and its contents can be import ed in .dizz and .act files.
As a example the structure of the smb2 module is shown:
~/dizzy/modules_src# tree smb2
smb2
├── config.py
├── __init__.py
└── smb2
├── act
│ ├── smb2_file_access_read.act
│ ├── smb2_file_access_write.act
│ ├── smb2_neg_setup_auth.act
│ └── smb2_tree_connect.act
├── deps
│ ├── nmb
│ │ ├── ...
│ ├── pyasn1
│ │ └── ...
│ └── smb
│ ├── ...
├── dizz
│ ├── smb2_close_request.dizz
│ ├── smb2_create_request_read.dizz
│ ├── smb2_create_request_write.dizz
│ ├── smb2_negotiate_req.dizz
│ ├── smb2_read_request.dizz
│ ├── smb2_session_setup_req.dizz
│ ├── smb2_tree_connect_req.dizz
│ ├── smb2_write_request.dizz
│ └── smb_com_negotiate_req.dizz
├── job
│ ├── smb2_file_access_read.conf
│ ├── smb2_file_access_write.conf
│ └── smb2_tree_connect.conf
└── probe
└── smb2.py
wireshark plugin
In the wireshark folder, there is a lua plugin to export parsed packet in .dizz
files.
Note, that it requires a lua version of 5.2.0 or newer.
To use it, run
$ cwd
~/dizzy/wireshark
$ wireshark -X lua_script:dizzy.lua
dizz file
So, now a short introduction to the dizzy packet and protocol specifications:
A single packet is described by a so called dizz file. Some example files can be found in the demo module folder that comes with dizzy. These files are python code so you need to write them in python syntax and format rules. They consist of 2 variables which need to be defined.
The first var is called objects
and describes the fields of the packet.
Its a python array of objects with a specific interface:
objects = [
...
]
The following list show the pre-defined classes which is has implemented the interface already and are imported in a dizz file:
Field()
is the standard class to defined a field. The class takes 6 arguments, which are:name
(REQUIRED) have to be astr()
, which is the name of the field (it to be unique in the packet).default
(=b''
) have to be abytes
,int
orstr()
, which is the default value of the field.size
(=None
) have to be aint()
orslice()
(in bits).
If it isNone
than the field has the same size as the default value. Withslice()
a variable size can be defined.fuzz
(="none"
) have to be astr()
, which is defined the fuzzing mode for the field.
The fuzzing mode for that field can be"none"
for not fuzzing that field at all,"std"
for fuzzing some values on the upper and lower value border, and"full"
for fuzzing all possible values.endian
(="!") have to be astr()
, which is defined the endianness of the field when the default value is aint()
The endianss for that argument can be"!"
for network (=big-endian),"<"
for little-endian or">"
for big-endian.encoding
(=CONFIG["GLOBALS"]["CODEC"]
) have to be astr
, which defined the encoding schema of the default value if it is astr()
... objects = [ Field("length_field", b"\x00" * 8), # 8 byte length filed Field("variable_sized_field", b"\x00" * 8, slice(2, 20, 2)) ... ] ...
List()
is a field, which have a list of value from a file. The class takes 4 arguments, which are:name
(REQUIRED) have to be astr()
, which is the name of the field (it to be unique in the packet).path
(=CONFIG["GLOBALS"]["DEFAULT_STR_LIB"]
) have to be astr()
, which defined the path to the file (one value per line, all values will be inserted while fuzzing)encoding
(=CONFIG["GLOBALS"]["CODEC"]
) have to be astr
, which defined the encoding list.default
(=None
) have to bebytes()
, which defined the first value of the list.
... objects = [ Field("length_field", b"\x00" * 8), # 8 byte length filed List("list_of_values", "/lists/test.txt", default=b"First value"), # new line separated list ... ]
The second var is called functions
and is for execute some code to change the value of the fields.
Its a python array of functions with a specific interface:
functions = [
...
]
The following list show the pre-defined functions which is has the interface already and are imported in a dizz file:
-
link
is a function to copy the value of a field into another field per mutation. So that target field has the same value as the source field during the fuzzing. The function takes 2 arguments, which are:source
(REQUIRED) have to be astr()
, which defined the name of the source field.target
(REQUIRED) have to be astr()
, which defined the name of the target field.
Example:
... objects = [ Field("source_field", b"\xaa", fuzz="full"), Field("target_field", b"\x00", fuzz="none"), # this field has always the same value as source_field ... ] functions = [ link("source_field", "target_field"), ... ]
-
length
is a function to save the current size(in bits) of fields in a dedicated length field. This function is useful for example when the packet has a Type Length Value structure. The function takes 3s arguments, which are:target
(REQUIRED) have to be astr()
, which defined the name of the field to save the length.start
(=START
) have to be astr()
, which defined where the function should start to count the length. The default parameter of this argument(=START
) means to start at the beginning of the packet.stop
(=STOP
) have to be astr()
, which defined where the function should stop(inclusive) to count the length. The default parameter of this argument(=STOP
) means to start at the end of the packet.
Example:
objects = [ Field("field0", b"\xaa" * 2, fuzz="full"), Field("field1", b"\xaa" * 2, slice(4, 9), fuzz="full"), # calculate the current size of the field0 und field1 in bits and saves the size in length_field Field("length_field", b"\x00\x00", endian="<", fuzz="none"), ... ] functions = [ length("length_field", "field0", "field1"), ... ]
-
length_bytes
is a function same aslength
but the length is saved in bytes. The function takes 3 arguments, which are:target
(REQUIRED) have to be astr()
, which defined the name of the field to save the length.start
(=START
) have to be astr()
, which defined where the function should start to count the length. The default parameter of this argument(=START
) means to start at the beginning of the packet.stop
(=STOP
) have to be astr()
, which defined where the function should stop(inclusive) to count the length. The default parameter of this argument(=STOP
) means to start at the end of the packet.
Example:
objects = [ Field("field0", b"\xaa" * 2, fuzz="full"), Field("field1", b"\xaa" * 2, slice(4, 9), fuzz="full"), # calculate the current size of the field0 und field1 in bytes and saves the size in length_field Field("length_field", b"\x00\x00", endian="<", fuzz="none"), ... ] functions = [ length_bytes("length_field", "field0", "field1"), ... ]
-
length_string_bytes
is a function same aslength_bytes
but the length is saved in bytes and asstr()
. The function takes 4 arguments, which are:target
(REQUIRED) have to be astr()
, which defined the name of the field to save the length.start
(=START
) have to be astr()
, which defined where the function should start to count the length. The default parameter of this argument(=START
) means to start at the beginning of the packet.stop
(=STOP
) have to be astr()
, which defined where the function should stop(inclusive) to count the length. The default parameter of this argument(=STOP
) means to start at the end of the packet.encoding
(=CONFIG["GLOBALS"]["CODEC"]
) have to be astr
, which defined the encoding of string.
Example:
objects = [ Field("field0", b"\xaa" * 2, fuzz="full"), Field("field1", b"\xaa" * 2, slice(4, 9), fuzz="full"), # calculate the current size of the field0 und field1 in bytes as and saves the size as string in length_field Field("length_field", b"\x00\x00", endian="<", fuzz="none"), ... ] functions = [ length_string_bytes("length_field", "field0", "field1"), ... ]
-
length_lambda
is a function same aslength
but the length is pass to a function and the return value of the function is saved in a dedicated length field. The function takes 4 arguments, which are:lam
(REQUIRED) have to be a function(mostly a lambda function), to pass the size(so the function has 1 argument).target
(REQUIRED) have to be astr()
, which defined the name of the field to save the length.start
(=START
) have to be astr()
, which defined where the function should start to count the length. The default parameter of this argument(=START
) means to start at the beginning of the packet.stop
(=STOP
) have to be astr()
, which defined where the function should stop(inclusive) to count the length. The default parameter of this argument(=STOP
) means to start at the end of the packet.encoding
(=CONFIG["GLOBALS"]["CODEC"]
) have to be astr
, which defined the encoding of string.
Example:
objects = [ Field("field0", b"\xaa" * 2, fuzz="full"), Field("field1", b"\xaa" * 2, slice(4, 9), fuzz="full"), # calculate the current size of the field0 und field1 in bits and pass it to the lambda function and saves the # return value into the length_field Field("length_field", b"\x00\x00", endian="<", fuzz="none"), ... ] functions = [ length_lambda(lambda x: x + 1, "length_field", "field0", "field1"), ... ]
-
chechsum
is a function that calculate a checksum over fields and save the value to a dedicate field. The function takes 4 arguments, which are:target
(REQUIRED) have to be astr()
, which defined where to save the checksum.start
(REQUIRED) have to be astr()
, which defined where the function should start to calculate the checksum.stop
(REQUIRED) have to be astr()
, which defined where the function should stop(inclusive) to calculate the checksum.algorithm
(REQUIRED) have to be astr()
, which defined the algorithm to use for the checksum. The possible value for this argument are:"md5"
for Message-Digest Algorithm 5"sha1"
for Secure Hash Algorithm 1"sha224"
for Secure Hash Algorithm 2 with a hash length of 224 bits"sha256"
for Secure Hash Algorithm 2 with a hash length of 256 bits"sha384"
for Secure Hash Algorithm 2 with a hash length of 384 bits"sha512"
for Secure Hash Algorithm 2 with a hash length of 512 bits
objects = [ Field("field0", b"\xaa" * 2, fuzz="full"), Field("field1", b"\xaa" * 2, slice(4, 9), fuzz="full"), # calculate the checksum of field0 and field1 and save it to checksum_field (the size of checksum_field is the # same, if the checksum value is bigger than the size of the field then it would be truncate) Field("checksum_field", b"\x00\x00\x00\x00"), ... ] functions = [ chechsum("checksum_field", "field0", "field1", "md5"), ... ]
-
checksum_inet
is a function that calculate a checksum over field by using the algorithm 'inet' (rfc1071) and save the to a dedicate field. The function take 3 arhuments, which are:-
target
(REQUIRED) have to be astr()
, which defined where to save the checksum. -
start
(REQUIRED) have to be astr()
, which defined where the function should start to calculate the checksum. -
stop
(REQUIRED) have to be astr()
, which defined where the function should stop(inclusive) to calculate the checksum.Example:
objects = [ Field("field0", b"\xaa" * 2, fuzz="full"), Field("field1", b"\xaa" * 2, slice(4, 9), fuzz="full"), # calculate the checksum of field0 and field1 and save it to checksum_field Field("checksum_field", b"\x00\x00\x00\x00"), ... ] functions = [ checksum_inet("checksum_field", "field0", "field1", "md5"), ... ]
-
act file
Ok, that are the packet descriptions so far. Ones you want to get state full, you need to write interaction in act files. Some prepared can be found in the interactions folder that comes with dizzy. These file are python code as well. They consist of 2 variables which need to be defined.
The first var is called objects
which is a python list of Dizz()
.
Dict()
is a class, which have 6 arguments:
name
(REQUIRED) have to be astr()
, which is the name of the packet (it to be unique in the interaction).filename
(REQUIRED) have to be astr()
, which is the path of the dizz file.readback
(=0
) have to be aint()
, which defined how many bytes at least to read after the packet was send.fuzz
(="std"
) have to be astr()
, which define the fuzzing mode for this packet. The fuzzing mode for that packet can be"none"
for not fuzzing that packet at all,"std"
for fuzzing each field at once, and"full"
for fuzzing all fields by using the cross product.start_at
(=0
) have to be aint()
, which defined at which iteration the packet should start.config_values
(={}
) have to be adict()
, which defined the configuration values for the packet.
objects = [
# define the first packet of the interaction and after the packet is send it have read at least 100 bytes from the
# session
Dizz("first_packet", "module_name/dizzes/first_packet.dizz", 100, fuzz="full"),
Dizz("second_packet", "module_name/dizzes/second_packet.dizz"),
...
]
The second var is called functions
, which is this case a python dict()
.
For more information about act functions
see Interface.
The following example takes the value of the field0
dizz object of the dizz packet Dizz0
and assigned it to the
field0
of the dizz packet Dizz3
:
def test(interaction_iterator, dizzy_iterator, response):
buf0 = response[0:10]
dizzy_iterator["field4"] = buf0
buf1 = interaction_iterator["Dizz0"]["field0"]
interaction_iterator["Dizz3"]["field0"] = buf1
Interface of Probe
This section describes the interface of the probe objects. A field object have mandatory attributes.
Mandatory
__init__(self, section_proxy)
open(self)
have to be afunction
, which open the probe.close(self)
have to be afunction
, which close the probe.probe(self)
have to be afunction
, which runs the probe check and returnsTrue
orFalse
.
Example
A probe always succeeding:
class DizzyProbe(object):
def __init__(self, section_proxy):
pass
def open(self):
pass
def probe(self):
return True
def close(self):
pass
Interface of Session
This section describes the interface of the session objects. A field object have mandatory attributes.
Mandatory
__init__(self, section_proxy)
open(self)
have to be afunction
, which open the session.close(self)
have to be afunction
, which close the session.send(self, data)
have to be afunction
, which send data through the session.data
have to be a bytes.
recv(self)
have to be afunction
, which receive data from the session.
Example
The following example takes the creates a session as stdin
for input and stdout
for output:
class DizzySession(object):
def __init__(self, section_proxy):
pass
def open(self):
pass
def send(self, data):
self.f.write(data + b"\n")
def recv(self):
return stdin.readline()
def close(self):
pass