Welcome to Kitty’s documentation!¶
Contents:
Introduction¶
Sulley: Boo?Boo: Kitty!
What is Kitty?¶
Kitty is an open-source modular and extensible fuzzing framework written in python, inspired by OpenRCE’s Sulley and Michael Eddington’s (and now Deja Vu Security’s) Peach Fuzzer.
Goal¶
When we started writing Kitty, our goal was to help us fuzz unusual targets — meaning proprietary and esoteric protocols over non-TCP/IP communication channels — without writing everything from scratch each time. A generic and abstract framework that would include the common functionallity of every fuzzing process we could think of, and would allow the user to easily extend and use it to test their specific target.
Features¶
With this goal in mind, the following features were very important to us:
Modularity: | Each part of the fuzzer stands on its own. This means that you can use the same monitoring code for different applications, or the same payload generator (aka Data Model) for testing parsing of the same data that is received over different channels. |
---|---|
Extensibility: | If you need to test something “new”, you will not need to change Kitty’s core code. Most, if not all, features can be implemented in the user code. This includes monitoring, controlling and communicating with the fuzzed target. |
Rich data modeling: | |
The data model core is rich and allows describing advanced data structures, including strings, hashes, lengths, conditions and many more. And, like most of the framework, it is designed to be extended even further as necessary. | |
Stateful: | Support for multi-stage fuzzing tests. Not only you can describe what the payload of an individual message will look like, you can also describe the order of messages, and even perform fuzzing on the sequence’s order. |
Client and Server fuzzing: | |
You can fuzz both servers and clients, assuming you have a matching stack. Sounds like a big requirement, but it isn’t: it just means that you should have the means to communicate with the target, which you should have in most cases anyway. | |
Cross platform: | Runs on Linux, OS X and Windows. We don’t judge ;-) |
What it’s not?¶
Well, Kitty is not a fuzzer. It also contains no implementation of specific protocol or communication channel. You can write your own fuzzer with it, and you can use Kitty-based code of others, but it’s not an out-of-the-box fuzzer.
A good place to get (and add) implementations of Kitty models is Katnip.
Katnip¶
Kitty, as a framework, implements the fuzzer main loop, and provides syntax for modeling data and base classes for each of the elements that are used to create a full fuzzing session. However, specific implementations of classes are not part of the Kitty framework. This means that Kitty defines the interface and base class to perform data transactions with a target, but it doesn’t provide implementations for data transmition over HTTP, TCP or UART.
Implementations of all sorts of classes can be found in the complimentary
repository Katnip, which has the sole porpuse of providing specific
implementaions. As such, Katnip contains different implementations for
targets (such as TcpTarget
and SslTarget
), controllers (such as
TelnetController
and SshController
), monitors and templates. Use
them when you write your fuzzer, as they will save you a lot of time, and
may serve as a reference for your own implementations.
What’s Next?¶
Contribution FAQ¶
- Found a bug?
- Open an issue
- Have a fix?
- Great! please submit a pull request
- Implemented an interesting controller/monitor/target?
- Please submit a pull request in the Katnip repository
- Found an interesting bug using a Kitty-based fuzzer?
- We’d love to hear about it! please drop us a line
Framework Structure¶
This document goes over the main modules that form a fuzzer and explains the relation between them. It also discusses the differences between client and server fuzzing.
Relation Between Modules¶
Note
Need to generate UML, look here... https://build-me-the-docs-please.readthedocs.io/en/latest/Using_Sphinx/UsingGraphicsAndDiagramsInSphinx.html
Fuzzer +--- Model *--- Template *--- Field
|
+--- Target +--- Controller
| |
| *--- Monitor
|
+--- Interface
Modules¶
Data Model¶
The first part of Kitty is the data model. It defines the structure of the messages that will be sent by the fuzzer. This includes a separation of the message into fields (such as header, length and payload), types of those fields (such as string, checksum and hex-encoded 32bit integer) and the relation between them (such as length, checksum and count). The data model also describes the order in which different messages are chained together to form a fuzzing session. This can be useful when trying to fuzz deeper parts of the system (for example, getting past an authentication stage in order to fuzz the parts of the system only reachable by authenticated users). The data model can also specify that the order in which messages are sent itself be fuzzed.
The data model is constructed using the classes defined in the model source folder.
For more information, visit the data model documentation, reference and tutorials.
Target¶
Base Classes: |
|
---|---|
API Reference: |
The target module is in charge of everything that is related to the victim. Its responsibilities are:
- When fuzzing a server — initiating the fuzzing session by sending a request to the server, and handling the response, when such response exists.
- When fuzzing a client — triggering a fuzzing session by causing the client to initiate a request to the server. The server, with the help of the stack (see the client fuzzing tutorial), will send a fuzzed response to the client. Note that in this case the target itself is not involved the client-server-fuzzer communication!
- Managing the monitors and controllers (see below).
The sources for the target classes are located in the targets source folder.
There are two target base classes, ClientTarget
(in targets/client.py
) and
ServerTarget
(in targets/server.py
). These classes define the target APIs
and should be subclassed when implementing a new target class.
A class should be written for every new type of target. For example, if you
want to test a server application that communicates over a serial UART
connection, you will need to create a new class that inherits from
ServerTarget
and is able to send data over the UART. However, many times it will
only require the implementation of the send/receive functions and not much more.
Some targets are already available in the Katnip repository,
so you don’t need to implement them yourself: for example,
TcpTarget
may be used to test HTTP servers and SslTarget
may be used to
test HTTPS servers.
The controller and the monitors (described below) are managed by the target, which queries and uses them as needed.
For each test the target generates a report which contains the reports of the controller and all monitors. This report is passed back to the fuzzer (described below) upon request.
Controller¶
Base Class: | kitty.controllers.base.BaseController |
---|---|
API Reference: | kitty.controllers package |
The controller is in charge of preparing the victim for the test. It should make sure that the victim is in an appropriate state before the target initiates the transfer session. Sometimes it means doing nothing, other times it means starting or reseting a VM, killing a process or performing a hard reset to the victim hardware.
Since the controller is reponsible for the state of the victim, it is expected to perform basic monitoring as well, and report whether the victim is ready for the next test.
Monitor¶
Base Class: | kitty.monitors.base.BaseMonitor |
---|---|
API Reference: | kitty.monitors package |
A monitor object monitors the behavior of the victim. It may monitor the network traffic, memory consumption, serial output or anything else.
Since there might be more than a single behavior to monitor, multiple monitors can be used when fuzzing a victim.
Fuzzer¶
Classes: |
|
---|---|
API Reference: |
A fuzzer drives the whole fuzzing process. Its job is to obtain mutated payloads from the model, initiate the data transaction, receive the report from the target, and perform further processing, if needed. A fuzzer is the top level entity in our test runner, and should not be subclassed in most cases.
Interface¶
Base Class: | kitty.interfaces.base.BaseInterface |
---|---|
API Reference: | kitty.interfaces package |
Interface is a user interface, which allows the user to monitor and check the fuzzer as it goes. The web interface should suffice in most cases.
Tutorials¶
Here you can find some tutorials to help you get started with Kitty.
Server vs. Client Fuzzing¶
Kitty is a framework for fuzzing various kinds of entities. It can fuzz servers — by acting like a web client to initiate requests to the victim server, or by acting like a USB host to send commands to the victim USB device; and it can also fuzz clients, by responding to requests made by them — acting as the web server to respond to requests from the victim browser, or as the USB device responding to commands from the victim USB host.
In Kitty’s terminology, the client is the side initiating the data exchange, and the server is the side reacting to it. By this defintion, a TCP server would be considered a server, as it reacts to requests initiated by the fuzzer; but so would a PDF viewer, if it is started with a given file provided by the fuzzer. Similarly, an IRC client or a web browser would be considered clients, as they initiate requests to the fuzzer and then process its responses; but so would a USB-Host stack, which initiates the data exchange with a fuzzer disguised as the USB device. And in fact, the same entity could be a client in some contexts, and a server in others: a web browser would be considered a client in the context of conversations made to the fuzzer-server, as seen above; but would be considerd a server in the context of being run as a process by the fuzzer, with a command-line argument pointing it at a given (presumably-fuzzed) URL.
There is a big difference between server fuzzing and client fuzzing, and the need to support both was a main driver for the creation of Kitty. This section will list the conceptual, flow and implementation differences between those two modes.
Conceptual Differences¶
Session initiation: | |
---|---|
Since the nature of a server is to handle and respond to requests, it is rather simple to fuzz it on our terms: we need to make sure it is up and ready to accept requests, and then start sending those requests to it. This means that the fuzzer is active, it initiates the data exchange and decides what data the server will handle, assuming that the server starts in the same state for each data exchange session. On the other hand, when fuzzing a client, the mutated data is the response, not the request. The client is the one to initiate the data exchange, the fuzzer acts as a server, and so it is passive and cannot control the timing or even the occurence of the fuzzing session. In order to take control back, the fuzzer needs some way to cause the client to start the data exchange. |
|
Communication Stack: | |
When a server is tested, it is the fuzzer, as we just saw, that initiates the communication. This means that the fuzzer can choose at exactly what layer to begin the communication. So, for example, when testing a TCP-based protocol, the TCP stack may be used, and when testing an HTTP-based protocol, an HTTP stack may be used. Moreover, there usually are readily-available Python or C APIs for initiating the communication starting at any of these layers. When testing a client, on the other hand, the fuzzer is only responding to requests from the client. As such, the fuzzer cannot easily choose at which layer to handle the communication — it must handle the request at whichever layer it was received. Thus, fuzzing a specific layer will very likely require hooking into the stack implementation in order to inject the mutated responses. |
Flow Differences¶
The flow differences are derived from the conceptual differences, the flow for each scenario is described below.
[ TBD - flow charts ]
Implementation Differences¶
The implementation differences are derived from the flow differences and are listed in the table below.
Component | Server Mode | Client Mode |
---|---|---|
Stack | Unmodified in most cases | Hooked in most cases, runs as a separate process |
Target | Responsible for sending and receiving data from victim, uses the stack | Responsible for triggering the data exchange in the victim (using the controller), data is handled directly by the stack |
Controller | Brings the victim to initial state, monitors victim status | Same as in server mode, but additionally responsible for triggering the data exchange |
Fuzzer | Actively polls the data model for more data and triggers the data exhange | Waits in a separate process for requests from the modified stack over IPC, provides stack with mutated data when needed |
Server Fuzzing Tutorial¶
This tutorial will guide you through the steps that are taken to build a fuzzer for your target, we will build such a fuzzer for a tiny HTTP server. It will be a minimal implementation, just to show the basics.
- First, we need to define our data model to let Kitty know how our protocol looks like.
- Then, we need to define how will we communicate with our target.
- After that, we need to find a way to control our target and how to monitor it (TBD).
- And finally, we need to connect all those pieces together.
Data Model¶
We start with a simple example, of fuzzing a simple HTTP GET request. For simplicity, we will not look at the spec to see the format of each message.
A simple “GET” request may look like this:
GET /index.html HTTP/1.1
There are some obvious fields in this request:
- Method - a string with the value “GET”
- Path - a string with the value “/index.html”
- Protocol - a string with the value “HTTP/1.1”
However, there are some other things in this message, which we ignored:
- (1.a) The space between Method and Path
- (2.a) The space between Path and Protocol
- The double “new lines” (“”) at the end of the request
Those are the delimiters, and we should not forget them.
Here’s the translation of this structure to a Kitty model:
Data model, version 1
from kitty.model import *
http_get_v1 = Template(name='HTTP_GET_V1', fields=[
String('GET', name='method'), # 1. Method - a string with the value "GET"
Delimiter(' ', name='space1'), # 1.a The space between Method and Path
String('/index.html', name='path'), # 2. Path - a string with the value "/index.html"
Delimiter(' ', name='space2'), # 2.a. The space between Path and Protocol
String('HTTP/1.1', name='protocol'), # 3. Protocol - a string with the value "HTTP/1.1"
Delimiter('\r\n\r\n', name='eom'), # 4. The double "new lines" ("\r\n\r\n") at the end of the http request
])
We used three new objects here, all declared in kitty/model/__init__.py:
Template
, which is the top most container of the low level data model, it encloses a full message. It received all of its enclosed fields as an array.String('GET', name='method')
creates a newString
object with the default value ‘GET’, and names it ‘method’.Delimiter(' ', name='space1')
creates a newDelimiter
object with the default value ‘ ‘, and names it ‘space1’.
Based on this model, Kitty will generate various mutations of the template, each mutation is constructed from a mutation of one of the fields, and the default values of the rest of them. When a field has no more mutations, it will return it to its default value, and move to the next field.
Even in this simple example, we refine our model even more. We can see that the Protocol field can be divided even more. We can split it to the following fields:
- (3.a) Protocol Name - a string with the value “HTTP”
- (3.b) The ‘/’ after “HTTP”
- (3.c) Major Version - a number with the value 1
- (3.d) The ‘.’ between 1 and 1
- (3.e) Minor Version - a number with the value 1
Now we can replace the protocol
string field with 5 fields.
Data model, version 2
from kitty.model import *
http_get_v2 = Template(name='HTTP_GET_V2', fields=[
String('GET', name='method'), # 1. Method - a string with the value "GET"
Delimiter(' ', name='space1'), # 1.a The space between Method and Path
String('/index.html', name='path'), # 2. Path - a string with the value "/index.html"
Delimiter(' ', name='space2'), # 2.a. The space between Path and Protocol
String('HTTP', name='protocol name'), # 3.a Protocol Name - a string with the value "HTTP"
Delimiter('/', name='fws1'), # 3.b The '/' after "HTTP"
Dword(1, name='major version', # 3.c Major Version - a number with the value 1
encoder=ENC_INT_DEC), # encode the major version as decimal number
Delimiter('.', name='dot1'), # 3.d The '.' between 1 and 1
Dword(1, name='minor version', # 3.e Minor Version - a number with the value 1
encoder=ENC_INT_DEC), # encode the minor version as decimal number
Delimiter('\r\n\r\n', name='eom') # 4. The double "new lines" ("\r\n\r\n") at the end of the request
])
We just met two new objects:
Dword(1, name='major version')
create a 32-bit integer field with default value 1 and name it ‘major version’ENC_INT_DEC
is an encoder that encodes this int as a decimal number. An encoder only affects the representation of the number, not its data nor its mutations
Dword
is part of a family of fields (Byte
, Word
and
Qword
) that provides convenient initialization to the basic field on
BitField
.
The last example shows how we can treat a payload in different ways, and how it affects our data model. It is not always good to give too much details in the model. Sometimes too much details will make the fuzzer miss some weird cases, because it will always be “almost correct” and most of the times it will cause the fuzzing session to be very long. There is a balance that should be reached, and each implementor should find his own (this is a spiritual guide as well).
HTTP_GET_V2 is pretty detailed data model, but while all parts of the template that we want Kitty to send should be represented by fields, there are fields that we don’t want Kitty to mutate. For Instance, the two new lines at the end of the request signals the server that the message has ended, and if they are not sent, the request will probably not be processed at all. Or, if we know there is a “GET” handler function in the target, we might want to always have “GET ” at the start of our template.
The next example achieves both goals, but in two different ways:
Data model, version 3
from kitty.model import *
http_get_v3 = Template(name='HTTP_GET_V3', fields=[
String('GET', name='method', fuzzable=False), # 1. Method - a string with the value "GET"
Delimiter(' ', name='space1', fuzzable=False), # 1.a The space between Method and Path
String('/index.html', name='path'), # 2. Path - a string with the value "/index.html"
Delimiter(' ', name='space2'), # 2.a. The space between Path and Protocol
String('HTTP', name='protocol name'), # 3.a Protocol Name - a string with the value "HTTP"
Delimiter('/', name='fws1'), # 3.b The '/' after "HTTP"
Dword(1, name='major version', # 3.c Major Version - a number with the value 1
encoder=ENC_INT_DEC), # encode the major version as decimal number
Delimiter('.', name='dot1'), # 3.d The '.' between 1 and 1
Dword(1, name='minor version', # 3.e Minor Version - a number with the value 1
encoder=ENC_INT_DEC), # encode the minor version as decimal number
Static('\r\n\r\n', name='eom') # 4. The double "new lines" ("\r\n\r\n") at the end of the request
])
The first method we used is setting the fuzzable
parameter of a
field to False
, as we did for the first two fields, this method lets
us preserve the structure of the model, and change it easily when we do
want to mutate those fields:
String('GET', name='method', fuzzable=False), # 1. Method - a string with the value "GET"
Delimiter(' ', name='space1', fuzzable=False), # 1.a The space between Method and Path
The second method is by using a Static
object, which is immutable,
as we did with the last field, this method improves the readability if
we have a long chunk of data in our template that will never change:
# 4. The double "new lines" ("\r\n\r\n") at the end of the request
Static('\r\n\r\n', name='eom')
Target¶
Now that we have a data model, we need to somehow pass it to our target.
Since we are fuzzing an HTTP server implementation, we need to send our
requests over TCP. There is already a target class to take care of TCP
communication with the server - kitty.targets.tcp.TcpTarget
, but we
will build it here again, step by step, to learn from it.
When fuzzing a server, our target should inherit from ServerTarget
.
Except of two methods - _send_to_target
and
_receive_from_target
, each method that you override should call its
super.
Each fuzzing session goes through the following stages:
1. set up the environment
2. for each mutation:
1. preform pre-test actions
2. do transmition
3. cleanup after the test
4. provide a test report
3. tear down the environment
Each of those steps is reflected in the ServerTarget
API:
Step | Corresponding API |
---|---|
set up the environment | setup() |
perform pre-test actions | pre_test(test_num) |
do transmission | transmit(payload) (calls _send_to_target(payload) and _receive_from_target() ) |
cleanup after test | post_test(test_num) |
provide a test report | get_report() |
tear down the environment | teardown() |
Now let’s implement those methods (the part we need):
class definition and constructor¶
'''
TcpTarget is an implementation of a TCP target
'''
import socket
from kitty.targets.server import ServerTarget
class TcpTarget(ServerTarget):
'''
TcpTarget is implementation of a TCP target for the ServerFuzzer
'''
def __init__(self, name, host, port, timeout=None, logger=None):
'''
:param name: name of the object
:param host: hostname of the target (the TCP server)
:param port: port of the target
:param timeout: socket timeout (default: None)
:param logger: logger for this object (default: None)
'''
## Call ServerTarget constructor
super(TcpTarget, self).__init__(name, logger)
## hostname of the target (the TCP server)
self.host = host
## port of the target
self.port = port
if (host is None) or (port is None):
raise ValueError('host and port may not be None')
## socket timeout (default: None)
self.timeout = timeout
## the TCP socket
self.socket = None
We create a socket at the beginning of each test, and close it at the end
pre_test and post_test¶
def pre_test(self, test_num):
'''
prepare to the test, create a socket
'''
## call the super (report preparation etc.)
super(TcpTarget, self).pre_test(test_num)
## only create a socket if we don't have one
if self.socket is None:
sock = self._get_socket()
## set the timeout
if self.timeout is not None:
sock.settimeout(self.timeout)
## connect to socket
sock.connect((self.host, self.port))
## our TCP socket
self.socket = sock
def _get_socket(self):
'''get a socket object'''
## Create a TCP socket
return socket.socket(socket.AF_INET, socket.SOCK_STREAM)
def post_test(self, test_num):
'''
Called after a test is completed, perform cleanup etc.
'''
## Call super, as it prepares the report
super(TcpTarget, self).post_test(test_num)
## close socket
if self.socket is not None:
self.socket.close()
## set socket to none
self.socket = None
Notice that we called the super in each overriden method. This is important, as the super class perform many tasks that are not target-specific.
The next step is to implement the sending and receiving. It’s pretty
straight forward, we call socket’s send
and receive
methods. We
don’t call super in those methods, as the super is not implemented.
send_to_target + receive_from_target¶
def _send_to_target(self, data):
self.socket.send(data)
def _receive_from_target(self):
return self.socket.recv(10000)
That’s it. We have a target that is able to perform TCP transmissions.
As the final stage of each test is providing a report, you can add fields to the report in your target at each of the methods above.
A basic fuzzer can already created with what we’ve seen so far. Dummy
controller can supply supply the requirement of the base target class,
and we don’t have to use any monitor at all, but if we want to be able
to not only crash the client, but to be able to detect the crash and
restart it once it crashes, we need to implement a Controller
Controller¶
As described in the overview, and in the Controller Documentation, the controller makes sure that our victim is ready to be fuzzed, and if it can’t it reports failure.
In our example, we have an HTTP server that we want to fuzz, for
simplicity, we will run the server locally. We will do it by
implementing LocalProcessController
a class that inherits from
kitty.controllers.base.BaseController
.
The controller is controller by the Target
and it follows pretty
much the same stages as the target (excluding the transmission) Each
fuzzing session goes through the following stages:
1. set up the environment
2. for each mutation:
1. preform pre-test actions
2. cleanup after the test
3. provide a test report
3. tear down the environment
Each of those steps is reflected in the ServerTarget
API:
Step | Corresponding API | Controllers role |
---|---|---|
set up the environment | setup() |
preparations |
perform pre-test actions | pre_test(test_number) |
prepare the victim to the test (make sure its up) |
cleanup after test | post_test() |
check the status of the victim, shut it down if needed |
provide a test report | get_report() |
provide a report |
tear down the environment | teardown() |
perform a cleanup |
class definition and constructor¶
from kitty.controllers.base import BaseController
class LocalProcessController(BaseController):
'''
LocalProcessController a process that was opened using subprocess.Popen.
The process will be created for each test and killed at the end of the test
'''
def __init__(self, name, process_path, process_args, logger=None):
'''
:param name: name of the object
:param process_path: path to the target executable
:param process_args: arguments to pass to the process
:param logger: logger for this object (default: None)
'''
super(LocalProcessController, self).__init__(name, logger)
assert(process_path)
assert(os.path.exists(process_path))
self._process_path = process_path
self._process_name = os.path.basename(process_path)
self._process_args = process_args
self._process = None
Our controller has nothing to do at the setup stage, so we don’t override this method
Before a test starts, we need to make sure that the victim is up
pre_test¶
def pre_test(self, test_number):
'''start the victim'''
## call the super
super(LocalProcessController, self).pre_test(test_num)
## stop the process if it still runs for some reason
if self._process:
self._stop_process()
cmd = [self._process_path] + self._process_args
## start the process
self._process = Popen(cmd, stdout=PIPE, stderr=PIPE)
## add process information to the report
self.report.add('process_name', self._process_name)
self.report.add('process_path', self._process_path)
self.report.add('process_args', self._process_args)
self.report.add('process_id', self._process.pid)
When the test is over, we want to store the output of the process, as well as its exit code (if crashed):
post_test¶
def post_test(self):
'''Called when test is done'''
self._stop_process()
## Make sure process started by us
assert(self._process)
## add process information to the report
self.report.add('stdout', self._process.stdout.read())
self.report.add('stderr', self._process.stderr.read())
self.logger.debug('return code: %d', self._process.returncode)
self.report.add('return_code', self._process.returncode)
## if the process crashed, we will have a different return code
if self._process.returncode != 0:
self.report.failed('return code is not zero: %s' % self._process.returncode)
self._process = None
## call the super
super(LocalProcessController, self).post_test()
When all fuzzing is over, we perform the teardown
:
teardown¶
def teardown(self):
'''
Called at the end of the fuzzing session, override with victim teardown
'''
self._stop_process()
self._process = None
super(LocalProcessController, self).teardown()
Finally, here is the implementation of the _stop_process
method
_stop_process¶
def _stop_process(self):
if self._is_victim_alive():
self._process.terminate()
time.sleep(0.5)
if self._is_victim_alive():
self._process.kill()
time.sleep(0.5)
if self._is_victim_alive():
raise Exception('Failed to kill client process')
def _is_victim_alive(self):
return self._process and (self._process.poll() is None)
Client Fuzzing Tutorial¶
One of the advanteges of kitty is its ability to fuzz client targets. Client targets are targets that we cannot fuzz by sending malformed requests, but by sending malformed responses.
As explained in the Server vs. Client Fuzzing, this is a big difference, for two main reasons. The first reason is that unlike server fuzzing, the communication is started by the target, and not by the fuzzer. The second reason is that in order to fuzz a client we usually need to hook some functions in the server stack.
First we will explain how to fuzz a client target with Kitty. After that, we will explain how to separate the code so the fuzzer will run in a separate process than the stack.
How Does It Work¶
Since in our case the communication is triggered by the target and handled by the stack, the fuzzer is passive. It triggers the client to start the communication from its own context (thread / process) and then does nothing until it is called by the stack to provide a response to the target request.
The stack needs to get a mutation of a response from the fuzzer. The
fuzzer exposes the get_mutation(name, data)
method to the stack to
provide those mutations. The responsibility of the stack is to call
get_mutation
in request handlers. If get_mutation
returns
None
, the stack handles the request aproprietly, otherwise, it
returns the result of get_mutation
to the target.
Here’s an example of (pseudo) client hook:
class StackImplementation:
# ...
def build_get_response(self, request_id):
resp = self.fuzzer.get_mutation(stage='get_response', data={ 'request_id' : request_id })
if resp:
return resp
# build valid response
resp = ...
return resp
self.fuzzer
in the example above is the instance of ClientFuzzer
that we passed to the stack. We call get_mutation
with two
arguments. The first, get_response
is the name of the name of the
scheme (request) that is used for this request, that we create in our
data model. In get_mutation
the fuzzer checks if it currently
fuzzing this scheme, and if so, it will return a mutated response,
otherwise it will return None. The second argument is a dictionary of
data that should be inserted into the DynamicField
s in the scheme,
it is usually a data that is transaction dependant and is not known when
building the scheme, for example, transaction or request id, such as in
the example above.
Building the Fuzzer¶
We will list the different parts of the client fuzzer, in the last section we will give a simple example of such a fuzzer.
Target¶
The target for client fuzzing inherits from
kitty.target.client.ClientTarget
unlike in server fuzzing, it’s
major work is managing the controller and monitors, so you can often
just instantiate kitty.target.client.ClientTarget
directly.
Controller¶
The controller of the client target inherits from
kitty.controllers.client.ClientController
. The most important method
in it is trigger
. This method triggers the client to start the
communication with the server stack. Since this method differ from
target to target, it is not implemented in ClientController
and must
be implemented in a new class.
The other methods are inherited from
kitty.controllers.base.BaseController
and may or may not be
implemented in the new class, based on your needs.
Monitor¶
The monitors of the client target inherit from
kitty.monitors.base.BaseMonitor
there is nothing special in client
monitors.
User Interface¶
The user interface of the client fuzzer inherit from
kitty.interfaces.base.BaseInterface
there is nothing special about
client fuzzer user interface.
Fuzzer¶
When fuzzing a client, you should create a
kitty.fuzzers.client.ClientFuzzer
object, pass it to the stack, and
then start it.
Fuzzer Building Example¶
fuzz_special_stack.py¶
import struct
from kitty.targets import ClientTarget
from kitty.controllers import ClientController
from kitty.interfaces import WebInterface
from kitty.fuzzers import ClientFuzzer
from kitty.model import GraphModel
from kitty.model import Template, Dynamic, String
################# Modified Stack #################
class MySpecialStack(object):
# We only show the relevant methods
def __init__(self):
self.fuzzer = None
self.names = {1: 'Lumpy', 2: 'Cuddles', 3: 'Flaky', 4: 'Petunya'}
def set_fuzzer(self, fuzzer):
self.fuzzer = fuzzer
def handle_GetName(self, name_id):
resp = self.fuzzer.get_mutation(stage='GetName response', data={'name_id': struct.pack('I', name_id)})
if resp:
return resp
name = '' if name_id not in self.names else self.names[name_id]
return struct.pack('I', name_id) + name
################# Data Model #################
get_name_response_template = Template(
name='GetName response',
fields=[
Dynamic(key='name_id', default_value='\x00', name='name id'),
String(value='admin', nane='name')
]
)
################# Controller Implementation #################
class MyClientController(ClientController):
def __init__(self):
super(MyClientController, self).__init__('MyClientController')
def trigger(self):
# trigger transaction start at the client
pass
################# Actual fuzzer code #################
target = ClientTarget('Example Target')
controller = MyClientController()
target.set_controller(controller)
model = GraphModel()
model.connect(get_name_response_template)
fuzzer = ClientFuzzer()
fuzzer.set_model(model)
fuzzer.set_target(target)
fuzzer.set_interface(WebInterface())
my_stack = MySpecialStack()
my_stack.set_fuzzer(fuzzer)
fuzzer.start()
my_stack.start()
Remote Fuzzer¶
The are two big problems with the client fuzzer that we’ve shown in the previous section. The first problem is that it ties us to python2 implementations of the stack. This means that even if you have a stack that you can modify, if it’s not written in python2 you will need to perform major changes to your code, or not use it at all. The second problem is that even when using python2, different threading models and signal handling may cause big issues with kitty, as it uses python threads and uses signal handlers.
To overcome those issue, we have created the kitty.remote
package.
It allows you to separate the stack process from the fuzzer process.
Currently, we only support python2 and python3, using the same python modules (withsix
) support for other languages will be provided in the future.
The idea is pretty simple - on the stack side, we only add
RpcClient
. No data models, monitors, target or anything like that.
On the fuzzer side, we create the fuzzer as before, with all its
classes, and than wrap it with a RpcServer
, which waits for requests
from the agent.
The next example shows how we convert the previous example to use the remote package.
Python2/3 Remote Fuzzer¶
my_stack.py (python3)¶
from kitty.remote import RpcClient
################# Modified Stack #################
class MySpecialStack(object):
# We only show the relevant methods
def __init__(self):
self.fuzzer = None
self.names = {1: 'Lumpy', 2: 'Cuddles', 3: 'Flaky', 4: 'Petunya'}
def set_fuzzer(self, fuzzer):
self.fuzzer = fuzzer
def handle_GetName(self, name_id):
resp = self.fuzzer.get_mutation(stage='GetName response', data={'name_id': struct.pack('I', name_id)})
if resp:
return resp
name = '' if name_id not in self.names else self.names[name_id]
return struct.pack('I', name_id) + name
fuzzer = RpcClient(host='127.0.0.1', port=26010)
my_stack = MySpecialStack()
my_stack.set_fuzzer(fuzzer)
fuzzer.start()
my_stack.start()
my_stack_fuzzer.py (python2)¶
from kitty.targets import ClientTarget
from kitty.controllers import ClientController
from kitty.interfaces import WebInterface
from kitty.fuzzers import ClientFuzzer
from kitty.model import GraphModel
from kitty.model import Template, Dynamic, String
from kitty.remote import RpcServer
################# Data Model #################
get_name_response_template = Template(
name='GetName response',
fields=[
Dynamic(key='name_id', default_value='\x00', name='name id'),
String(value='admin', nane='name')
]
)
################# Controller Implementation #################
class MyClientController(ClientController):
def __init__(self):
super(MyClientController, self).__init__('MyClientController')
def trigger(self):
# trigger transaction start at the client
pass
################# Actual fuzzer code #################
target = ClientTarget('Example Target')
controller = MyClientController()
target.set_controller(controller)
model = GraphModel()
model.connect(get_name_response_template)
fuzzer = ClientFuzzer()
fuzzer.set_model(model)
fuzzer.set_target(target)
fuzzer.set_interface(WebInterface())
remote = RpcServer(host='127.0.0.1', port=26010, impl=fuzzer)
remote.start()
Session Data in Server Fuzzing¶
Overview¶
There are cases where not all of the data is available when writing the data model. For example, when you have a multi-message session with the target, and you need to use a session ID that was returned in the first message from the target.
Kitty provides means to handle these cases. However, since this problem is a little complex, Kitty’s solution is composed of 3 parts.
- Special fields in the template to indicate that a data is session-specific (done by you)
- Callback between messages in the session, providing a hook to parse responses and handle them (done by you)
- Call to Target API to get the session specific data before rendering each template (done by the fuzzer)
We’ll go over the usage of this feature using an example protocol.
Example Protocol¶
Fuzzer (client) Target (server)
||---------------(open_session)----------------->||
||<---------------(session_id)-------------------||
||-------(do_something + session_id)------------>||
||<----------------(ok)--------------------------||
The Templates¶
We need to implement two templates:
open_session
Used to request the server to open a session. The server responds with a session id, which should be used in subsequent calls.
open_session = Template(name='open_session', fields=[ String(name='username', value='user'), Static(': '), String(name='password', value='pass'), ])
do_something
Tells the server to do something, providing the session ID to the open session. Since we don’t know the session id at the time we write the tempalte, we define it as a
Dynamic
field, and assigning a key to it, this key will be used later on.do_something = Template(name='do_something', fields=[ Static('session id: '), # this is the intersting field Dynamic(key='session_id', default_value=''), Static('\naction: '), String(name='the action', value='cry me a river') ])
Great. So at this point we have two templates, and the second one knows that it might be changed (partially) by external entities at runtime. Time to create a connection between the two messages.
The Callback¶
What do we actually want to do? We want to parse the resposnse to the open_session request, extract the session_id from it and provide the session_id to the do_something template.
We’ll do that by using GraphModel
connect()
method’s
third arguemnt - the callback.
g = GraphModel('session-graph')
g.connect(open_session)
g.connect(open_session, do_something, new_session_callback)
new_session_callback
will be called when we get a response to the open_session
and before we render and send do_something
And here’s the implementation of new_session_callback
:
def new_session_callback(fuzzer, edge, resp):
'''
:param fuzzer: the fuzzer object
:param edge: the edge in the graph we currently at.
edge.src is the open_session template
edge.dst is the do_something template
:param resp: the response from the target
'''
# let's say that we have a reference to the target object,
# or this might be a target's method
target.session_data['session_id'] = extract_session_from_response(resp)
Once we set the session_id value in the session_data
dictionary, we are good to go.
From this point, the fuzzer will take the session_data
dictionary from the target
and set it in the template.
Note that you can have multiple key/value pairs in the dictionary,
and that you don’t have to have key/value pairs for all dynamic fields.
Only those who exist in the dictionary will be set in the dynamic fields.
Also, you can have multiple dynamic fields with the same key.
Writing a ServerTarget Class¶
Role¶
A Target object has two roles in a fuzzing session:
- Handle the actual communication with the target (victim).
- Manage and query the Controller and Monitors.
This means that the Target object, in general, doesn’t monitor the behavior of the target, nor does it start / stop the target. It only sends the payloads to the target, and – if needed – waits for response from the target.
This Tutorial¶
In this tutorial, we will implement a ServerTarget class. This target will allow the fuzzer to send and receive data over a serial (UART) data channel.
Hopefully, by the end of the tutorial you will feel comfortable writing your own controller.
Let’s Begin¶
Target Flow¶
The behavior of our target will look like that:
- Open and configure serial connection
- For each test
- For each payload
- Send payloads
- Receive response
- Close serial connection
Overall, not too complicated.
Functions¶
Kitty’s Target
API provides hooks for performing actions
in different points in time.
We will write implementation for each parts of the flow above.
Part 1 - Open and configure serial connection¶
There are two functions we need to implement in this part.
__init__
- to initialize the Target object,
and setup
- which will be called when starting the session.
import serial
from kitty.targets import ServerTarget
class SerialTarget(ServerTarget):
def __init__(self, dev_name, baudrate=115200, name='SerialTarget', logger=None, expect_response=False):
super(SerialTarget, self).__init__(name, logger, expect_response)
self.dev_name = dev_name
self.baudrate = baudrate
self.serial = None
# we set this timeout to simplify the reads
self.timeout = 2
Since each class in the hierarchy of Target
perform some important
initialization in setup
, it is important to call the super function.
def setup(self):
if self.serial:
self.serial.close()
self.serial = serial.Serial(self.dev_name, self.baudrate)
self.serial.timeout = self.timeout
super(SerialTarget, self).setup()
Part 2.1.1 - Send payload¶
Each test might have multiple payloads that should be sent in sequence,
the function _send_to_target
is expected to send the payload to the target,
but not to perform the mutations on it.
The payloads that are passed to this function are already mutated (if needed).
Note
We don’t handle exceptions here
(and in the next function).
The function transmit
,
which is implemented in the parent class,
handles the exceptions.
def _send_to_target(self, payload):
self.serial.write(payload)
Part 2.1.2 - Receive response¶
This function responsible for reading the target’s response.
As in _send_to_target
,
we usually don’t handle exceptions in this function,
but leave transmit
to handle it.
def _receive_from_target(self):
return self.serial.read(1000)
Part 3 - Close serial connection¶
When we end the fuzzing session (i.e. all the tests),
we need to perform a cleanup.
The cleanup function called teardown
.
As with setup
,
the super function of teardown
should be called as well.
def teardown(self):
if self.serial:
self.serial.close()
self.serial = None
super(SerialTarget, self).teardown()
New connection in each test¶
Sometimes, you don’t want to use the same connection in different tests.
This can be acheived by implementing
pre_test
to create the connection,
and post_test
to close it,
instead of implmenting the setup
and teardown
functions.
Note
As with setup
and teardown
,
you should call the super functions from your implementations.
def pre_test(self, test_num):
if self.serial:
self.serial.close()
self.serial = serial.Serial(self.dev_name, self.baudrate)
self.serial.timeout = self.timeout
super(SerialTarget, self).pre_test(test_num)
def post_test(self, test_num):
if self.serial:
self.serial.close()
self.serial = None
super(SerialTarget, self).post_test(test_num)
The Data Model¶
Contents:
Data Model Overview¶
As described in the overview, Kitty is by default a generation-based fuzzer. This means that a model should be created to represent the data exchanged in the fuzzed protocol.
On the high level, we describe and fuzz different sequences of messages, this allows building fuzzing scenarios that are valid until a specific stage but also to fuzz the order of messages as well.
Low Level Model¶
At the low level, there is a signle payload, constructed from multiple fields. Those fields can be encoded, dependant on other fields, be renderened conditionaly and combined with other fields into larger logical units. The top unit of the logical units is Template, and Template is the only low-level interface to the high-level of the data model.
A full documentation of the low level data model can be found in the API reference.
A full list of the fields in the Kitty data model syntax can be found in the Data Model Syntax.
High Level Model¶
The high level model describe how a sequence of messages (Templates)
looks like to the fuzzer. There are two main models for this part,
GraphModel
and StagedSequenceModel
. An additional model -
RandomSequenceModel
is a naive case of StagedSequenceModel
and is
more convenient, when the order of messages really doesn’t matter.
During the fuzzing session, the fuzzer queries the data model for a sequence to transmit. The data model chooses the next sequence according to its strategy and perform internal mutations if needed.
Data Model Syntax¶
A big part of generation based fuzzing is the data modeling. It provides guidance to the fuzzer on how the data is supposed to be structured. This allows the fuzzer to perform a more precise and faster fuzzing, and to keep the state of the payload as similar as possible to a valid while performing mutations on very specific fields.
This chapter is composed of three parts. We start by listing the available fields, these fields compose kitty’s syntax. After that, we list the available encoders. The last part of this chapter provides examples on how to use the fields, containers and encoders that are listed in the first two part to compose a full template.
Fields¶
A basic overview of the data model can be found in the Data Model Overview, and you can also take a look at the API reference.
There are a few types of fields in Kitty’s data model, and all of them are derived from BaseField
.
The field type defines the type of value that will be rendered by this field, as well of the type of mutations that will be performed. The type of field instructs the fuzzer what data to render in the payload, but not how to render it. For that, we have encoders (see Encoders).
example: | If we want to fuzz an integer, we will use a field like This number may be rendered as Little endian binary or a string of ASCII values, or something else. The use of |
---|
Atomic Fields¶
The atomic fields are the very basic building blocks of a data model. Each of this fields is self-contained and discrete.
The following atomic fields are available in Kitty (some of them are aliases for other fields):
Calculated (dependant) Fields¶
Calculated fields are fields that their value is calculated from properties that are outside of their scope by default, for example, the length or the checksum of another fields.
These fields give Kitty’s syntax much of its power. It allows the user to get into deeper layer of the parsing.
These fields can be fuzzed as well, but when they are not fuzzed, they will be calculated each time, to ensure that they render into a valid value.
The following calculated fields are available in Kitty (again, some of them are aliases for other fields):
Containers¶
Containers, as is pretty obvious from their name, contain other fields. There is no limit on how many fields a container can hold, nor on the nesting level of containers.
Containers can be used to perform encoding on multiple fields at once, or to use some property of multiple rendered fields to perform some calculation. But the most important property of containers, IMHO, is the logical separation and grouping of units in the model.
In general, containers treat the contained field in the order they were added and in most cases will mutate each of the contained fields when in turn. However, in addition to the mutations of the internal fields, some container may add some mutations that are a property of the container itself (although they depend on the value of the contained fields)
The following containers are available in Kitty:
Mutation Based Fields¶
While Kitty uses a data model, and expects the user to be familiar with the structure of the payload, in some cases the user might not have this information, only a sample payload.
In this cases, a mutation fuzzing might come in handy, and the Mutation based fields can be used to perform a tests on the target without an accurate data model.
some of the fields are wrappers around other fields, specifically,
MutableField
is a combination of all other mutation based fields.
The following mutation-based fields are available in Kitty:
Encoders¶
The encoders receive data from the field they are assigned to, and return a sequence of bits, which is the rendered data.
There are 4 types of encoders:
- Encoders of strings (python2.7’s str), receiving a string and returning Bits.
- Encoders of Bits, receiving Bits object and returning Bits.
- Encoders of numbers, receiving int value, length in bits and sign and returning Bits.
- Encoders of floating point numbers, receiving float and returning Bits.
Encoders are objects, but since most common encoders are stateless, and don’t change state, the same instance can be used in multple fields, kitty provides many default instances of encoders that can be used directly.
These instances are listed in the API reference.
Using the syntax¶
In this part, we’ll take a look at a few examples on how to use Kitty’s data modeling syntax to model some data or protocol.
It’s important to note that Kitty has no capability of modeling the data automatically. This is really out-of-scope, although, if you developed a tool that can infer the structure of a message from some (or many) samples, or if you know of such a tool, we’d really like to know about that.
Anyway... Kitty’s data model syntax is inspired by the syntax of Construct. In the sense that the entire model of a payload (e.g. template) can be constructed in a single – nested – constructur. Using the standard alignment of python, this results in a very readable data model, although it is 100% python code.
Unlike Construct,
the resulted data model is not designed to pacrse payloads
or be modified from outside (although it is possible to a certain level),
instead, it is designed to generate mutations internally,
(when mutate()
is called)
and provide the resulted payloads (when render()
is called).
Example 1 - Sized String¶
The following template describes an ascii string (not null terminated), prepended by a 16bit int that holds its size (in bytes):
sized_string = Template(name='sized string', fields=[ SizeInBytes(name='size', sized_field='the string', length=16), String(name='the string', value='') ])
Example 2 - Count Elements in a List¶
The following template describe a List of 32bit little endian encoded integers, preprended by the number of element in the list (field of 16bit, little endian encoded).
counted_list = Template(name='counted list', fields=[ ElementCount(name='element count', depends_on='the list', length=16, encoder=ENC_INT_LE), List(name='the list', fields=[ LE32(name='element 1', value=0x00010203), LE32(name='element 2', value=0x0a050607), LE32(name='element 3', value=0x08090a0b), LE32(name='element 4', value=0x0c0d0e0f), LE32(name='element 5', value=0x10111213), LE32(name='element 6', value=0x1a151617), LE32(name='element 7', value=0x18191a1b), LE32(name='element 8', value=0x1c1d1e1f), ]) ])
Here’s the default rendered payload (hex encoded), see how it matchs the description
0800030201000706050a0b0a09080f0e0d0c131211101716151a1b1a19181f1e1d1c 0800 -> element count: 8 (16 bits, little endian encoded) 03020100 -> element 1: 0x00010203 (32 bits, little endian encoded) 0706050a -> element 2: 0x0a050607 (32 bits, little endian encoded) 0b0a0908 -> element 3: 0x08090a0b (32 bits, little endian encoded) 0f0e0d0c -> element 4: 0x0c0d0e0f (32 bits, little endian encoded) 13121110 -> element 5: 0x10111213 (32 bits, little endian encoded) 1716151a -> element 6: 0x1a151617 (32 bits, little endian encoded) 1b1a1918 -> element 7: 0x18191a1b (32 bits, little endian encoded) 1f1e1d1c -> element 8: 0x1c1d1e1f (32 bits, little endian encoded)
Example 3 - Base64 Encoded Container¶
As mentioned, a container may be encoded as a whole,
this comes quite handy in HTTP authorization,
where the clients sends the username and password as:
base64encode(USERNAME:PASSWORD)
.
Of course, encoding each string (USERNAME
, :
and PASSWORD
)
separately will result in a different base64 string
then if they were encoded together.
http_user_pass = Container( name='userpass header', fields=[ String(name='username', value='some user'), String(name='delimiter', value=':'), String(name='password', value='some password') ], encoder=ENC_BITS_BASE64_NO_NL )
The default result of such a container is
'c29tZSB1c2VyOnNvbWUgcGFzc3dvcmQ='
Which is the base64 encoded version of
'some user:some password'
Writing Encoders¶
A Little Bit About Encoders¶
Each field in the data model has two main properties:
- The value of the field - for example, a number may have the value 1, 2 or -1532 and so on.
- The representation of the field - for example, a number with the value 123 may be represented as decimal (
123
), hexadecimal (7B
) or a 32 bit little endian ('\x7b\x00\x00\x00'
) and so on.
The value of the field is decided by the field itself, using the default value or one of the field’s mutations. The representation of the field is decided by it’s encoder.
Kitty has a few encoder classes, used to encode integer values, floats, strings and Bits
objects.
You can see a list of available encoder classes in the Data Model Syntax documentation.
Kitty also provides many encoder objects that may be used directly in your model,
such as ENC_INT_BE
to encode an integer as a big endian,
or ENC_STR_BASE64
to encoder a string in base64.
Since most encoders have no state, the same object may be used by many fields without issue,
thus the use of a global encoder object makes sense.
However, Kitty doesn’t have any possible encoding, and so you might be required to implement your own encoder from time to time.
Implementing Your Own Encoder¶
Example 1 - Aligned String¶
Let’s say that you have a String
field that must be 4 bytes aligned,
and you decide that it makes no sense to have mutations of this field that are not aligned.
Since the alignment is only related to the representation of the string, you should probably just encode it as a 4 byte aligned string, meaning, implement an encoder that encodes the string with padding when needed.
There are several ways to implement such an encoder. Here are two of them.
Straight Forward Implementation¶
We’ll start with a simple one, which might be just enough in some cases,
and will make it more robust.
Since we encode a string, we need to inherit from StrEncoder
and override its encode()
method.
This method receive a string as argument, and returns an encoded string as a Bits
object.
class AlignedStrEncoder(StrEncoder):
def encode(self, value):
pad_len = (4 - len(value) % 4) % 4
new_value = value + '\x00' * pad_len
return Bits(bytes=new_value)
Then, we can instantiate it:
ENC_STR_ALIGN = AlignedStrEncoder()
And use it multiple times:
Template(name='two aligned strings', fields=[
String('foo', encoder=ENC_STR_ALIGN),
String('bar', encoder=ENC_STR_ALIGN),
])
Using StrFuncEncoder
¶
You can also implement it by passing the encoding function
to the constructor of StrFuncEncoder
def align_string(value):
pad_len = (4 - len(value) % 4) % 4
new_value = value + '\x00' * pad_len
return Bits(bytes=new_value)
ENC_STR_ALIGN = StrFuncEncoder(align_string)
Generic Implementation¶
We might want to create a generic aligned string encoder class
and pass the alignment size as a parameter.
In this case, we need to override the __init__
function:
class AlignedStrEncoder(StrEncoder):
def __init__(self, pad_size, pad_char='\x00'):
self._pad_size = pad_size
self._pad_char = pad_char
def encode(self, value):
pad_len = (self._pad_size - len(value) % self._pad_size) % self._pad_size
new_value = value + self._pad_char * pad_len
return Bits(bytes=new_value)
Then, we can instantiate it with different values:
ENC_STR_ALIGN4 = AlignedStrEncoder(4)
ENC_STR_ALIGN8 = AlignedStrEncoder(8)
Example 2 - Reversed Bits¶
The encode()
method returns a Bits
object (from the bitstring
package).
The main difference between the big encoder types is the (type of) value that their
encode()
method accepts.
In the first example, the it accepted a string,
in the case of BitsEncoder
, it accepts a Bits
object.
The encoder below encodes the bits in a reversed order,
e.g. if it receives the bits 10101100
it will return 00110101
.
There two main ways to implement such an encoder.
Using BitsFuncEncoder
¶
As with StrFuncEncoder
in the previous example,
BitsFuncEncoder
allows you to just pass an encode()
function to the constructor,
so you don’t need to create a new class and implement its encode
method.
This comes handy from time to time.
def reverse_bits(value):
return value[::-1]
ENC_BITS_REVERSED = BitsFuncEncoder(reverse_bits)
Subclassing BitsEncoder
¶
However, you may subclass BitsEncoder
directly.
class ReversedBitsEncoder(BitsEncoder):
def encoder(value):
return value[::-1]
And instantiate it
ENC_BITS_REVERSED = ReversedBitsEncoder()
kitty.model package¶
This package contains the entire reference for data and sequence models used by Kitty.
Subpackages¶
kitty.model.high_level package¶
This package contains the high level data model, which represents sequences and transition between messages.
Submodules¶
-
class
kitty.model.high_level.base.
BaseModel
(name='BaseModel')[source]¶ Bases:
kitty.core.kitty_object.KittyObject
This class defines the API that is required to be implemented by any top- level model
Note
This class should not be instantiated directly.
-
get_sequence
()[source]¶ Return type: [Connection] Returns: Sequence of current case
-
get_stages
()[source]¶ Returns: dictionary of information regarding the stages in the fuzzing session Note
structure: { current: [‘stage1’, ‘stage2’, ‘stage3’], ‘stages’: {‘source1’: [‘dest1’, ‘dest2’], ‘source2’: [‘dest1’, ‘dest3’]}}
-
get_template_info
()[source]¶ Returns: dictionary of information regarding the current template Note
by default, we return None, as the current template might not be interesting. Take a look at GraphModel for actual implementation.
-
Model with a graph structure, all paths in the graph will be fuzzed. The last node in each path will be mutated until exhaustion.
-
class
kitty.model.high_level.graph.
GraphModel
(name='GraphModel')[source]¶ Bases:
kitty.model.high_level.base.BaseModel
The GraphModel is built of a simple digraph, where the nodes are templates, and on each edge there’s a callback function. It will provide sequences of edges from this graph, always starting from the root (dummy) node, where the last template in the sequence (the destination of the last edge) is mutated. As such the main target of the GraphModel is to fuzz the handling of the fields in a message.
Assuming we have the templates A, B, C and D and we want to fuzz all templates, but we know that in order to make an impact in fuzzing template D, we first need to send A, then B or C, and only then D, e.g. we have the following template graph:
/==> B / \ A +==> D \ / \==> C
Which translate to the following sequences (* - mutated template):
A* A -> B* A -> B -> D* A -> C* A -> C -> D*
Such a model will be written in Kitty like this:
Example: model = GraphModel('MyGraphModel') model.connect(A) model.connect(A, B) model.connect(B, D) model.connect(A, C) model.connect(C, D)
Note
Make sure there are no loops in your model!!
The callback argument of connect allows monitoring and maintainance during a fuzzing test.
Example: def log_if_empty_response(fuzzer, edge, response): if not response: logger.error('Got an empty response for request %s' % edge.src.get_name()) model.connect(A, B, log_if_empty_response)
-
check_loops_in_grpah
(current=None, visited=[])[source]¶ Parameters: - current – current node to check if visited
- visited – list of visited fields
Raise: KittyException if loop found
-
connect
(src, dst=None, callback=None)[source]¶ Parameters: - src – source node, if dst is None it will be destination node and the root (dummy) node will be source
- dst – destination node (default: None)
- callback (func(fuzzer, edge, response) -> None) – a function to be called after the response for src received and before dst is sent
-
-
class
kitty.model.high_level.random_sequence.
RandomSequenceModel
(name='RandomSequenceModel', seed=None, callback_generator=None, num_mutations=1000, max_sequence=10)[source]¶ Bases:
kitty.model.high_level.staged_sequence.StagedSequenceModel
This class provides random sequences of templates based on the selection strategy. It is like
StagedSequenceModel
with a signle stage.-
__init__
(name='RandomSequenceModel', seed=None, callback_generator=None, num_mutations=1000, max_sequence=10)[source]¶ Parameters: - name – name of the model object (default: ‘RandomSequenceModel’)
- seed (int) – RNG seed (default: None)
- callback_generator (func(from_template, to_template) -> func(fuzzer, edge, response) -> None) – a function that returns callback functions (default: None)
- num_mutations – number of mutations to perform (defualt: 1000)
- max_sequence – maximum sequence length (default: 10)
-
Generate random sequences of messages for a session. Currently, perform no special operation for mutations on the nodes.
-
class
kitty.model.high_level.staged_sequence.
Stage
(name, selection_strategy='1', seed=None)[source]¶ Bases:
kitty.core.kitty_object.KittyObject
Stage supports 4 different sequence length strategy. For all of them, the order of templates will be random. The available strategies are:
- for random length - ‘random’
- for exact length - ‘12’
- for length in range - ‘1-3’
- for all - ‘all’
-
class
kitty.model.high_level.staged_sequence.
StagedSequenceModel
(name='StagedSequenceModel', callback_generator=None, num_mutations=1000)[source]¶ Bases:
kitty.model.high_level.base.BaseModel
The StagedSequenceModel provides sequences that are constructed from multiple stages of sequences. Each such stage provides its part of the sequence based on its strategy. The main goal of the Staged sequence mode is to find flaws in the state handling of a stateful target.
Example: Assuming we have the templates [CreateA .. CreateZ, UseA .. UseZ and DeleteA .. DeleteZ]. A valid sequence is CreateA -> UseA -> DeleteA, so we want to test cases like CreateA -> UseB -> UseA -> DeleteC, or CreateA -> DeleteA -> UseA. And let’s say the common thing to all sequences is that we always want to start by sending one or more of the Create messages.
We can do that with StagedSequenceModel:
stage1 = Stage('create', selection_strategy='1-5') stage1.add_Template(CreateA) # ... stage1.add_Template(CreateZ) stage2 = Stage('use and delete', selection_strategy='3-20') stage2.add_Template(UseA) stage2.add_Template(DeleteA) # ... stage2.add_Template(UseZ) stage2.add_Template(DeleteZ) model = StagedSequenceModel('use and delete mess', num_mutations=10000) model.add_stage(stage1) model.add_stage(stage2)
Each time it is asked, it will provide a sequence that starts with 1-5 random “Create” templates, followed by 3-20 random Use and Delete messages. None of those templates will be mutated, as we try to fuzz the sequence itself, not the message structure.
Since we don’t know the what will be the order of templates in the sequences, we can’t just provide a callback as we do in GraphModel. The solution in our case is to provide the model with a callback generator, which receives the from_template and to_template and returns a callback function as described in GraphModel in runtime.
-
__init__
(name='StagedSequenceModel', callback_generator=None, num_mutations=1000)[source]¶ Parameters: - name – name of the model object (default: ‘StagedSequenceModel’)
- callback_generator (func(from_template, to_template) -> func(fuzzer, edge, response) -> None) – a function that returns callback functions
- num_mutations – number of mutations to perform
-
kitty.model.low_level package¶
This package contains the low level data model, which represents the structure of specific messages in the fuzzed protocol.
Submodules¶
Functions that provide simpler field creation API and name aliasing for convenience
Integer Aliases: | |||||||||||||||||||||||||||||||||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
Since integers the are binary encoded are used in many places, there are many aliases for them. Some are similar to common type conventions, others are simply meant to make the template code smaller. Below is a (hopefully) full list of aliases. However, the safest place to check, as always, is the source.
|
|||||||||||||||||||||||||||||||||||||||||||||||||||
Hash Aliases: | There are multiple functions that return frequently used Hash instances:
Their prototype is the same as Hash, excluding the algorithm argument. |
||||||||||||||||||||||||||||||||||||||||||||||||||
Compare Aliases: | |||||||||||||||||||||||||||||||||||||||||||||||||||
The Compare aliases create a Compare object with a specific comp_type. See table below:
|
-
kitty.model.low_level.aliases.
AtLeast
(field, comp_value)¶ Condition applies if the value of the field is greater than or equals to the comp_value
Return type: Compare
-
kitty.model.low_level.aliases.
AtMost
(field, comp_value)¶ Condition applies if the value of the field is lesser than or equals to the comp_value
Return type: Compare
-
kitty.model.low_level.aliases.
BE16
(value, min_value=None, max_value=None, fuzzable=True, name=None, full_range=False)[source]¶ 16-bit field, Big endian encoded
-
kitty.model.low_level.aliases.
BE32
(value, min_value=None, max_value=None, fuzzable=True, name=None, full_range=False)[source]¶ 32-bit field, Big endian encoded
-
kitty.model.low_level.aliases.
BE64
(value, min_value=None, max_value=None, fuzzable=True, name=None, full_range=False)[source]¶ 64-bit field, Big endian encoded
-
kitty.model.low_level.aliases.
BE8
(value, min_value=None, max_value=None, fuzzable=True, name=None, full_range=False)[source]¶ 8-bit field, Big endian encoded
-
kitty.model.low_level.aliases.
BitMaskNotSet
(field, comp_value)[source]¶ Condition applies if the given bitmask is equal to 0 in the value of the field
Return type: Compare
-
kitty.model.low_level.aliases.
BitMaskSet
(field, comp_value)[source]¶ Condition applies if the given bitmask is set in the value of the field
Return type: Compare
-
kitty.model.low_level.aliases.
Byte
(value, min_value=None, max_value=None, encoder=<kitty.model.low_level.encoder.BitFieldBinEncoder object>, fuzzable=True, name=None, full_range=False)¶ Unsigned 8-bit field
-
kitty.model.low_level.aliases.
Dword
(value, min_value=None, max_value=None, encoder=<kitty.model.low_level.encoder.BitFieldBinEncoder object>, fuzzable=True, name=None, full_range=False)¶ Unsigned 32-bit field
-
kitty.model.low_level.aliases.
DwordBE
(value, min_value=None, max_value=None, fuzzable=True, name=None, full_range=False)¶ 32-bit field, Big endian encoded
-
kitty.model.low_level.aliases.
DwordLE
(value, min_value=None, max_value=None, fuzzable=True, name=None, full_range=False)¶ 32-bit field, Little endian encoded
-
kitty.model.low_level.aliases.
Equal
(field, comp_value)[source]¶ Condition applies if the value of the field is equal to the comp_value
Return type: Compare
-
kitty.model.low_level.aliases.
Greater
(field, comp_value)[source]¶ Condition applies if the value of the field is greater than the comp_value
Return type: Compare
-
kitty.model.low_level.aliases.
GreaterEqual
(field, comp_value)[source]¶ Condition applies if the value of the field is greater than or equals to the comp_value
Return type: Compare
-
kitty.model.low_level.aliases.
LE16
(value, min_value=None, max_value=None, fuzzable=True, name=None, full_range=False)[source]¶ 16-bit field, Little endian encoded
-
kitty.model.low_level.aliases.
LE32
(value, min_value=None, max_value=None, fuzzable=True, name=None, full_range=False)[source]¶ 32-bit field, Little endian encoded
-
kitty.model.low_level.aliases.
LE64
(value, min_value=None, max_value=None, fuzzable=True, name=None, full_range=False)[source]¶ 64-bit field, Little endian encoded
-
kitty.model.low_level.aliases.
LE8
(value, min_value=None, max_value=None, fuzzable=True, name=None, full_range=False)[source]¶ 8-bit field, Little endian encoded
-
kitty.model.low_level.aliases.
Lesser
(field, comp_value)[source]¶ Condition applies if the value of the field is lesser than the comp_value
Return type: Compare
-
kitty.model.low_level.aliases.
LesserEqual
(field, comp_value)[source]¶ Condition applies if the value of the field is lesser than or equals to the comp_value
Return type: Compare
-
kitty.model.low_level.aliases.
Md5
(depends_on, encoder=<kitty.model.low_level.encoder.StrEncoder object>, fuzzable=False, name=None)[source]¶ Return type: Hash
Returns: MD5 hash field
-
kitty.model.low_level.aliases.
NotEqual
(field, comp_value)[source]¶ Condition applies if the value of the field is not equal to the comp_value
Return type: Compare
-
kitty.model.low_level.aliases.
Qword
(value, min_value=None, max_value=None, encoder=<kitty.model.low_level.encoder.BitFieldBinEncoder object>, fuzzable=True, name=None, full_range=False)¶ Unsigned 64-bit field
-
kitty.model.low_level.aliases.
QwordBE
(value, min_value=None, max_value=None, fuzzable=True, name=None, full_range=False)¶ 64-bit field, Big endian encoded
-
kitty.model.low_level.aliases.
QwordLE
(value, min_value=None, max_value=None, fuzzable=True, name=None, full_range=False)¶ 64-bit field, Little endian encoded
-
kitty.model.low_level.aliases.
S16
(value, min_value=None, max_value=None, encoder=<kitty.model.low_level.encoder.BitFieldBinEncoder object>, fuzzable=True, name=None, full_range=False)¶ Signed 16-bit field
-
kitty.model.low_level.aliases.
S32
(value, min_value=None, max_value=None, encoder=<kitty.model.low_level.encoder.BitFieldBinEncoder object>, fuzzable=True, name=None, full_range=False)¶ Signed 32-bit field
-
kitty.model.low_level.aliases.
S64
(value, min_value=None, max_value=None, encoder=<kitty.model.low_level.encoder.BitFieldBinEncoder object>, fuzzable=True, name=None, full_range=False)¶ Signed 64-bit field
-
kitty.model.low_level.aliases.
S8
(value, min_value=None, max_value=None, encoder=<kitty.model.low_level.encoder.BitFieldBinEncoder object>, fuzzable=True, name=None, full_range=False)¶ Signed 8-bit field
-
kitty.model.low_level.aliases.
SInt16
(value, min_value=None, max_value=None, encoder=<kitty.model.low_level.encoder.BitFieldBinEncoder object>, fuzzable=True, name=None, full_range=False)[source]¶ Signed 16-bit field
-
kitty.model.low_level.aliases.
SInt32
(value, min_value=None, max_value=None, encoder=<kitty.model.low_level.encoder.BitFieldBinEncoder object>, fuzzable=True, name=None, full_range=False)[source]¶ Signed 32-bit field
-
kitty.model.low_level.aliases.
SInt64
(value, min_value=None, max_value=None, encoder=<kitty.model.low_level.encoder.BitFieldBinEncoder object>, fuzzable=True, name=None, full_range=False)[source]¶ Signed 64-bit field
-
kitty.model.low_level.aliases.
SInt8
(value, min_value=None, max_value=None, encoder=<kitty.model.low_level.encoder.BitFieldBinEncoder object>, fuzzable=True, name=None, full_range=False)[source]¶ Signed 8-bit field
-
kitty.model.low_level.aliases.
Sha1
(depends_on, encoder=<kitty.model.low_level.encoder.StrEncoder object>, fuzzable=False, name=None)[source]¶ Return type: Hash
Returns: SHA1 hash field
-
kitty.model.low_level.aliases.
Sha224
(depends_on, encoder=<kitty.model.low_level.encoder.StrEncoder object>, fuzzable=False, name=None)[source]¶ Return type: Hash
Returns: SHA224 hash field
-
kitty.model.low_level.aliases.
Sha256
(depends_on, encoder=<kitty.model.low_level.encoder.StrEncoder object>, fuzzable=False, name=None)[source]¶ Return type: Hash
Returns: SHA256 hash field
-
kitty.model.low_level.aliases.
Sha384
(depends_on, encoder=<kitty.model.low_level.encoder.StrEncoder object>, fuzzable=False, name=None)[source]¶ Return type: Hash
Returns: SHA384 hash field
-
kitty.model.low_level.aliases.
Sha512
(depends_on, encoder=<kitty.model.low_level.encoder.StrEncoder object>, fuzzable=False, name=None)[source]¶ Return type: Hash
Returns: SHA512 hash field
-
kitty.model.low_level.aliases.
SizeInBytes
(sized_field, length, encoder=<kitty.model.low_level.encoder.BitFieldBinEncoder object>, fuzzable=False, name=None)[source]¶ Return type: Size
Returns: Size field, which holds the size in bytes of the sized field
-
kitty.model.low_level.aliases.
U16
(value, min_value=None, max_value=None, encoder=<kitty.model.low_level.encoder.BitFieldBinEncoder object>, fuzzable=True, name=None, full_range=False)¶ Unsigned 16-bit field
-
kitty.model.low_level.aliases.
U32
(value, min_value=None, max_value=None, encoder=<kitty.model.low_level.encoder.BitFieldBinEncoder object>, fuzzable=True, name=None, full_range=False)¶ Unsigned 32-bit field
-
kitty.model.low_level.aliases.
U64
(value, min_value=None, max_value=None, encoder=<kitty.model.low_level.encoder.BitFieldBinEncoder object>, fuzzable=True, name=None, full_range=False)¶ Unsigned 64-bit field
-
kitty.model.low_level.aliases.
U8
(value, min_value=None, max_value=None, encoder=<kitty.model.low_level.encoder.BitFieldBinEncoder object>, fuzzable=True, name=None, full_range=False)¶ Unsigned 8-bit field
-
kitty.model.low_level.aliases.
UInt16
(value, min_value=None, max_value=None, encoder=<kitty.model.low_level.encoder.BitFieldBinEncoder object>, fuzzable=True, name=None, full_range=False)[source]¶ Unsigned 16-bit field
-
kitty.model.low_level.aliases.
UInt32
(value, min_value=None, max_value=None, encoder=<kitty.model.low_level.encoder.BitFieldBinEncoder object>, fuzzable=True, name=None, full_range=False)[source]¶ Unsigned 32-bit field
-
kitty.model.low_level.aliases.
UInt64
(value, min_value=None, max_value=None, encoder=<kitty.model.low_level.encoder.BitFieldBinEncoder object>, fuzzable=True, name=None, full_range=False)[source]¶ Unsigned 64-bit field
-
kitty.model.low_level.aliases.
UInt8
(value, min_value=None, max_value=None, encoder=<kitty.model.low_level.encoder.BitFieldBinEncoder object>, fuzzable=True, name=None, full_range=False)[source]¶ Unsigned 8-bit field
-
kitty.model.low_level.aliases.
Word
(value, min_value=None, max_value=None, encoder=<kitty.model.low_level.encoder.BitFieldBinEncoder object>, fuzzable=True, name=None, full_range=False)¶ Unsigned 16-bit field
-
kitty.model.low_level.aliases.
WordBE
(value, min_value=None, max_value=None, fuzzable=True, name=None, full_range=False)¶ 16-bit field, Big endian encoded
-
kitty.model.low_level.aliases.
WordLE
(value, min_value=None, max_value=None, fuzzable=True, name=None, full_range=False)¶ 16-bit field, Little endian encoded
Fields that are dependant on other fields - Size, Checksum etc.
-
class
kitty.model.low_level.calculated.
AbsoluteOffset
(target_field, length, correction=None, encoder=<kitty.model.low_level.encoder.BitFieldBinEncoder object>, fuzzable=True, name=None)[source]¶ Bases:
kitty.model.low_level.calculated.Offset
An absolute offset of a field from the beginning of the payload
-
__init__
(target_field, length, correction=None, encoder=<kitty.model.low_level.encoder.BitFieldBinEncoder object>, fuzzable=True, name=None)[source]¶ Parameters: - target_field – (name of) field to calculate offset to
- length – length of the AbsoluteOffset field (in bits)
- correction – correction function, or value for the index, (default: divide by 8 (bytes))
- encoder (
BitFieldEncoder
) – encoder for the field (default: ENC_INT_DEFAULT) - fuzzable – is container fuzzable
- name – (unique) name of the container (default: None)
Example: Container(fields=[ String(name='A', value='base string'), String(name='B', value='bar'), String(name='C', value='target string'), AbsoluteOffset( target_field='C', length=32, ) ])
-
-
class
kitty.model.low_level.calculated.
Calculated
(depends_on, encoder=<kitty.model.low_level.encoder.BitsEncoder object>, fuzzable=True, name=None)[source]¶ Bases:
kitty.model.low_level.field.BaseField
A base type for fields that are calculated based on other fields
-
FIELD_PROP_BASED
= 'field property'¶
-
LENGTH_BASED
= 'length'¶
-
VALUE_BASED
= 'value'¶
-
__init__
(depends_on, encoder=<kitty.model.low_level.encoder.BitsEncoder object>, fuzzable=True, name=None)[source]¶ Parameters: - depends_on – (name of) field we depend on
- encoder (
BitsEncoder
) – encoder for the field - fuzzable – is container fuzzable
- name – (unique) name of the container
-
-
class
kitty.model.low_level.calculated.
CalculatedBits
(depends_on, func, encoder=<kitty.model.low_level.encoder.BitsEncoder object>, fuzzable=True, name=None)[source]¶ Bases:
kitty.model.low_level.calculated.Calculated
field that depends on the rendered value of a field, and rendered into Bits() object
-
__init__
(depends_on, func, encoder=<kitty.model.low_level.encoder.BitsEncoder object>, fuzzable=True, name=None)[source]¶ Parameters: - depends_on – (name of) field we depend on
- func – function for processing of the dependant data. func(Bits)->Bits
- encoder (
BitsEncoder
) – encoder for the field - fuzzable – is container fuzzable
- name – (unique) name of the container
-
-
class
kitty.model.low_level.calculated.
CalculatedInt
(depends_on, bit_field, calc_func, encoder=<kitty.model.low_level.encoder.BitsEncoder object>, fuzzable=False, name=None)[source]¶ Bases:
kitty.model.low_level.calculated.Calculated
field that depends on the rendered value of another field and is rendered to (int, length, signed) tuple
-
__init__
(depends_on, bit_field, calc_func, encoder=<kitty.model.low_level.encoder.BitsEncoder object>, fuzzable=False, name=None)[source]¶ Parameters: - depends_on – (name of) field we depend on
- bit_field – a BitField to be used for holding the value
- calc_func (func(bits) -> int) – function to calculate the value of the field
- encoder (
BitsEncoder
) – encoder for the field (default: ENC_BITS_DEFAULT) - fuzzable – is container fuzzable
- name – (unique) name of the container
-
-
class
kitty.model.low_level.calculated.
CalculatedStr
(depends_on, func, encoder=<kitty.model.low_level.encoder.StrEncoder object>, fuzzable=False, name=None)[source]¶ Bases:
kitty.model.low_level.calculated.Calculated
field that depends on the rendered value of a byte-aligned field and rendered to a byte aligned Bits() object
-
__init__
(depends_on, func, encoder=<kitty.model.low_level.encoder.StrEncoder object>, fuzzable=False, name=None)[source]¶ Parameters: - depends_on – (name of) field we depend on
- func – function for processing of the dependant data. func(str)->str
- encoder (
StrEncoder
) – encoder for the field (default: ENC_STR_DEFAULT) - fuzzable – is container fuzzable
- name – (unique) name of the container
-
-
class
kitty.model.low_level.calculated.
Checksum
(depends_on, length, algorithm='crc32', encoder=<kitty.model.low_level.encoder.BitFieldBinEncoder object>, fuzzable=False, name=None)[source]¶ Bases:
kitty.model.low_level.calculated.CalculatedInt
Checksum of another container.
-
__init__
(depends_on, length, algorithm='crc32', encoder=<kitty.model.low_level.encoder.BitFieldBinEncoder object>, fuzzable=False, name=None)[source]¶ Parameters: - depends_on – (name of) field to be checksummed
- length – length of the checksum field (in bits)
- algorithm – checksum algorithm name (from Checksum._algos) or a function to calculate the value of the field. func(Bits) -> int
- encoder (
BitFieldEncoder
) – encoder for the field (default: ENC_INT_DEFAULT) - fuzzable – is field fuzzable (default: False)
- name – (unique) name of the field (default: None)
Example: Container(name='checksummed chunk', fields=[ RandomBytes(name='chunk', value='1234', min_length=0, max_length=75), Checksum(name='CRC', depends_on='chunk', length=32) ])
-
-
class
kitty.model.low_level.calculated.
Clone
(depends_on, encoder=<kitty.model.low_level.encoder.BitsEncoder object>, fuzzable=False, name=None)[source]¶ Bases:
kitty.model.low_level.calculated.CalculatedBits
rendered the same as the field it depends on
-
__init__
(depends_on, encoder=<kitty.model.low_level.encoder.BitsEncoder object>, fuzzable=False, name=None)[source]¶ Parameters: - depends_on – (name of) field we depend on
- encoder (
BitsEncoder
) – encoder for the field (default: ENC_BITS_DEFAULT) - fuzzable – is container fuzzable
- name – (unique) name of the container
Example: Container(name='empty HTML body', fields=[ Static('<'), String(name='opening tag', value='body'), Static('>'), Static('</'), Clone(name='closing tag', depends_on='opening tag'), Static('>'), ])
-
-
class
kitty.model.low_level.calculated.
ElementCount
(depends_on, length, correction=None, encoder=<kitty.model.low_level.encoder.BitFieldBinEncoder object>, fuzzable=False, name=None)[source]¶ Bases:
kitty.model.low_level.calculated.FieldIntProperty
Number of elements inside another field. The value depends on the number of fields in the field it depends on.
Example: Container(name='list with count', fields=[ ElementCount( # will be rendered to '3' name='element count', depends_on='list of items', length=32, encoder=ENC_INT_DEC ), Container(name='list of items', fields=[ Static('element 1'), Static('element 2'), Static('element 3'), ]) ])
-
class
kitty.model.low_level.calculated.
FieldIntProperty
(depends_on, length, correction=None, encoder=<kitty.model.low_level.encoder.BitFieldBinEncoder object>, fuzzable=False, name=None)[source]¶ Bases:
kitty.model.low_level.calculated.CalculatedInt
Calculate an int value based on some field property. The main difference from
CalculatedInt
is that it provides the field itself to the calculation function, not its rendered value.-
__init__
(depends_on, length, correction=None, encoder=<kitty.model.low_level.encoder.BitFieldBinEncoder object>, fuzzable=False, name=None)[source]¶ Parameters: - depends_on – (name of) field we depend on
- length – length of the FieldIntProperty field (in bits)
- correction – correction function, or value for the index
- encoder (
BitFieldEncoder
) – encoder for the field (default: ENC_INT_DEFAULT) - fuzzable – is container fuzzable
- name – (unique) name of the container (default: None)
-
-
class
kitty.model.low_level.calculated.
Hash
(depends_on, algorithm, encoder=<kitty.model.low_level.encoder.StrEncoder object>, fuzzable=False, name=None)[source]¶ Bases:
kitty.model.low_level.calculated.CalculatedStr
Hash of a field.
Note
To make it more convenient, there are multiple aliases for various hashes. Take a look at
aliases
.-
__init__
(depends_on, algorithm, encoder=<kitty.model.low_level.encoder.StrEncoder object>, fuzzable=False, name=None)[source]¶ Parameters: - depends_on – (name of) field to be hashed
- algorithm – hash algorithm name (from Hash._algos) or a function to calculate the value of the field. func(str) -> str
- encoder (
StrEncoder
) – encoder for the field (default: ENC_STR_DEFAULT) - fuzzable – is field fuzzable (default: False)
- name – (unique) name of the field (default: None)
Example: Container(name='SHA1 hashed string', fields=[ Meta(String(name='secret', value='s3cr3t')), Hash(name='secret_hash', algorithm='sha1', depends_on='secret') ])
-
-
class
kitty.model.low_level.calculated.
IndexOf
(depends_on, length, correction=None, encoder=<kitty.model.low_level.encoder.BitFieldBinEncoder object>, fuzzable=False, name=None)[source]¶ Bases:
kitty.model.low_level.calculated.FieldIntProperty
Index of a field in its container.
Edge case behavior:
- If field has no encloser - return 0
- If field is not rendered - return len(rendered element list) as index/
Example: Container(name='indexed list', fields=[ IndexOf( # will be rendered to '2' name='index of second', depends_on='second', length=32, encoder=ENC_INT_DEC ), Container(name='list of items', fields=[ Static(name='first', value='A'), Static(name='second', value='B'), Static(name='third', value='C'), ]) ])
-
class
kitty.model.low_level.calculated.
Offset
(base_field, target_field, length, correction=None, encoder=<kitty.model.low_level.encoder.BitFieldBinEncoder object>, fuzzable=True, name=None)[source]¶ Bases:
kitty.model.low_level.calculated.FieldIntProperty
A relative offset of a field from another field
-
__init__
(base_field, target_field, length, correction=None, encoder=<kitty.model.low_level.encoder.BitFieldBinEncoder object>, fuzzable=True, name=None)[source]¶ Parameters: - base_field – (name of) field to calculate offset from
- target_field – (name of) field to calculate offset to
- length – length of the Offset field (in bits)
- correction – correction function, or value for the index, (default: divide by 8 (bytes))
- encoder (
BitFieldEncoder
) – encoder for the field (default: ENC_INT_DEFAULT) - fuzzable – is container fuzzable
- name – (unique) name of the container (default: None)
Examples: Calculate the offset of field C from field B, in bits
Container(fields=[ String(name='A', value='base string'), String(name='B', value='bar'), String(name='C', value='target string'), Offset( base_field='B', target_field='C', length=32, correction=lambda x: x, ) ])
Calculate the absolute offset of field C from the beginning of the payload (also, see
AbsoluteOffset
)Container(fields=[ String(name='A', value='base string'), String(name='B', value='bar'), String(name='C', value='target string'), Offset( base_field=None, target_field='C', length=32, ) ])
-
-
class
kitty.model.low_level.calculated.
Size
(sized_field, length, calc_func=<function <lambda>>, encoder=<kitty.model.low_level.encoder.BitFieldBinEncoder object>, fuzzable=False, name=None)[source]¶ Bases:
kitty.model.low_level.calculated.CalculatedInt
Size of another container. Calculated in each render call
Note
In most cases you can use the function
SizeInBytes()
instead, which receives the same arguments except of calc_func-
__init__
(sized_field, length, calc_func=<function <lambda>>, encoder=<kitty.model.low_level.encoder.BitFieldBinEncoder object>, fuzzable=False, name=None)[source]¶ Parameters: - sized_field – (name of) field to be sized
- length – length of the size field (in bits)
- calc_func – function to calculate the value of the field. func(bits) -> int (default: length in bytes)
- encoder (
BitFieldEncoder
) – encoder for the field (default: ENC_INT_DEFAULT) - fuzzable – is field fuzzable (default: False)
- name – (unique) name of the field (default: None)
Examples: Calculate the size of a field/container in bits
Container(name='sized chunk', fields=[ RandomBytes(name='chunk', value='1234', min_length=0, max_length=75), Size( name='size in bits', sized_field='chunk', length=32, calc_func=lambda x: len(x) ) ])
Calculate the size of a field/container in bytes, and add 5 to the result
Container(name='sized chunk', fields=[ RandomBytes(name='chunk', value='1234', min_length=0, max_length=75), Size( name='size in bytes plus 5', sized_field='chunk', length=32, calc_func=lambda x: len(x) / 8 + 5 ) ])
-
conditon object has one mandatory function - applies, which receives a Container as the single argument.
Conditions are used by the If and IfNot fields to decide wether to render their content or not. In many cases the decision is made based on a value of a specific field, but you can create whatever condition you want. In future versions, they will probably be used to make other decisions, not only basic rendering decisions.
-
class
kitty.model.low_level.condition.
Compare
(field, comp_type, comp_value)[source]¶ Bases:
kitty.model.low_level.condition.FieldCondition
Condition applies if the comparison between the value of the field and the comp_value evaluates to True
Note
There are some functions for creating specific Compare conditions that you can see below.
-
__init__
(field, comp_type, comp_value)[source]¶ Parameters: - field – (name of) field that should meet the condition
- comp_type – how to compare the values. One of (‘>’, ‘<’, ‘>=’, ‘<=’, ‘==’, ‘!=’)
- comp_value – value to compare the field to
Example: Render the content of the If container only if the value in the field called “name” is not ‘kitty’
Template([ String('kitty', name='name'), If(Compare('name', '!=', 'kitty'), [ String('current name is not kitty') ]) ])
-
-
class
kitty.model.low_level.condition.
Condition
[source]¶ Bases:
object
-
class
kitty.model.low_level.condition.
FieldCondition
(field)[source]¶ Bases:
kitty.model.low_level.condition.Condition
Base class for field-based conditions (if field meets condition return true)
-
__init__
(field)[source]¶ Parameters: field ( BaseField
orstr
) – (name of, or) field that should meet the condition
-
applies
(container, ctx)[source]¶ Subclasses should not override applies, but instead they should override _applies, which has the same syntax as applies. In the _applies method the condition is guaranteed to have a reference to the desired field, as self._field.
Parameters: - container (
Container
) – the caller - ctx – rendering context in which applies was called
Returns: True if condition applies, False otherwise
- container (
-
-
class
kitty.model.low_level.condition.
FieldMutating
(field)[source]¶ Bases:
kitty.model.low_level.condition.FieldCondition
Condition applies if the field is currently mutating
Example: Render the content of the If container only if ‘kittyfield’ is currently mutating.
Template([ String('kitty', name='kittyfield'), String('fritty', name='frittyfield'), If(FieldMutating('kittyfield'), [ String('kitty is now mutating') ]) ])
-
class
kitty.model.low_level.condition.
InList
(field, value_list)[source]¶ Bases:
kitty.model.low_level.condition.ListCondition
Condition applies if the value of the field appears in the value list
Example: Render the content of the If container only if the current rendered value of the ‘numbers’ field is ‘1’, ‘5’ or ‘7’.
Template([ Group(['1', '2', '3'], name='numbers'), If(InList('numbers', ['1', '5', '7']), [ String('123') ]) ])
-
class
kitty.model.low_level.condition.
ListCondition
(field, value_list)[source]¶ Bases:
kitty.model.low_level.condition.FieldCondition
Base class for comparison between field and list. can’t be instantiated
Containers are fields that group multiple fields into a single logical unit,
they all inherit from Container
, which inherits from
BaseField
.
-
class
kitty.model.low_level.container.
Conditional
(condition, fields=[], encoder=<kitty.model.low_level.encoder.BitsEncoder object>, fuzzable=True, name=None)[source]¶ Bases:
kitty.model.low_level.container.Container
Container that its rendering is dependant on a condition
-
__init__
(condition, fields=[], encoder=<kitty.model.low_level.encoder.BitsEncoder object>, fuzzable=True, name=None)[source]¶ Parameters: - condition (an object that has a function applies(self, Container) -> Boolean) – condition to evaluate
- fields – enclosed field(s) (default: [])
- encoder (BitsEncoder) – encoder for the container (default: ENC_BITS_DEFAULT)
- fuzzable – is container fuzzable (default: True)
- name – (unique) name of the container (default: None)
Example: Template([ Group(['a', 'b', 'c'], name='letters'), If(ConditionCompare('letters', '==', 'a'), [ Static('dvil') ]) ]) # results in the mutations: advil, b, c
-
get_rendered_fields
(ctx=None)[source]¶ Parameters: ctx – rendering context in which the method was called Returns: ordered list of the fields that will be rendered
-
-
class
kitty.model.low_level.container.
Container
(fields=[], encoder=<kitty.model.low_level.encoder.BitsEncoder object>, fuzzable=True, name=None)[source]¶ Bases:
kitty.model.low_level.field.BaseField
A logical unit to group multiple fields together
-
__init__
(fields=[], encoder=<kitty.model.low_level.encoder.BitsEncoder object>, fuzzable=True, name=None)[source]¶ Parameters: - fields (field or iterable of fields) – enclosed field(s) (default: [])
- encoder (BitsEncoder) – encoder for the container (default: ENC_BITS_DEFAULT)
- fuzzable – is container fuzzable (default: True)
- name – (unique) name of the container (default: None)
Example: Container([ String('header_name'), Delimiter('='), String('header_value') ])
-
append_fields
(new_fields)[source]¶ Add fields to the container
Parameters: new_fields – fields to append
-
get_field_by_name
(name)[source]¶ Parameters: name – name of field to get Returns: direct sub-field with the given name Raises: KittyException
if no direct subfield with this name
-
get_rendered_fields
(ctx=None)[source]¶ Parameters: ctx – rendering context in which the method was called Returns: ordered list of the fields that will be rendered
-
is_default
()[source]¶ Checks if the field is in its default form
Returns: True if field is in default form
-
push
(field)[source]¶ Add a field to the container, if the field is a Container itself, it should be poped() when done pushing into it
Parameters: field – BaseField to push
-
render
(ctx=None)[source]¶ Parameters: ctx – rendering context in which the method was called Return type: Bits Returns: rendered value of the container
-
replace_fields
(new_fields)[source]¶ Remove all fields from the container and add new fields
Parameters: new_fields – fields to add to the container
-
scan_for_field
(field_key)[source]¶ Scan for a field in the container and its enclosed fields
Parameters: field_key – name of field to look for Returns: field with name that matches field_key, None if not found
-
-
class
kitty.model.low_level.container.
ForEach
(mutated_field, fields=[], encoder=<kitty.model.low_level.encoder.BitsEncoder object>, fuzzable=True, name=None)[source]¶ Bases:
kitty.model.low_level.container.Container
Perform all mutations of enclosed fields for each mutation of mutated_field
-
__init__
(mutated_field, fields=[], encoder=<kitty.model.low_level.encoder.BitsEncoder object>, fuzzable=True, name=None)[source]¶ Parameters: - mutated_field – (name of) field to perform mutations for each of its mutations
- fields – enclosed field(s) (default: [])
- encoder (BitsEncoder) – encoder for the container (default: ENC_BITS_DEFAULT)
- fuzzable – is container fuzzable (default: True)
- name – (unique) name of the container (default: None)
Example: Template([ Group(['a', 'b', 'c'], name='letters'), ForEach('letters', [ Group(['1', '2', '3']) ]) ]) # results in the mutations: a1, a2, a3, b1, b2, b3, c1, c2, c3
-
-
class
kitty.model.low_level.container.
If
(condition, fields=[], encoder=<kitty.model.low_level.encoder.BitsEncoder object>, fuzzable=True, name=None)[source]¶ Bases:
kitty.model.low_level.container.Conditional
Render only if condition evalutes to True
-
class
kitty.model.low_level.container.
IfNot
(condition, fields=[], encoder=<kitty.model.low_level.encoder.BitsEncoder object>, fuzzable=True, name=None)[source]¶ Bases:
kitty.model.low_level.container.Conditional
Render only if condition evalutes to False
-
class
kitty.model.low_level.container.
Meta
(fields=[], encoder=<kitty.model.low_level.encoder.BitsEncoder object>, fuzzable=True, name=None)[source]¶ Bases:
kitty.model.low_level.container.Container
Don’t render enclosed fields
Example: Container([ Static('no sp'), Meta([Static(' ')]), Static('ace') ]) # will render to: 'no space'
-
class
kitty.model.low_level.container.
OneOf
(fields=[], encoder=<kitty.model.low_level.encoder.BitsEncoder object>, fuzzable=True, name=None)[source]¶ Bases:
kitty.model.low_level.container.Container
Render a single field from the fields (also mutates only one field each time)
Example: OneOf(fields=[ String('A'), String('B'), String('C'), String('D'), ]) # 'A', 'AAAA', '%s' ... 'B', 'BBBB' ... 'C' .. 'D'
-
class
kitty.model.low_level.container.
Pad
(pad_length, pad_data='x00', fields=[], fuzzable=True, name=None)[source]¶ Bases:
kitty.model.low_level.container.Container
Pad the rendered value of the enclosed fields
-
__init__
(pad_length, pad_data='\x00', fields=[], fuzzable=True, name=None)[source]¶ Parameters: - pad_length – length to pad up to (in bits)
- pad_data – data to pad with (default: ‘’)
- fields – enclosed field(s) (default: [])
- fuzzable – is fuzzable (default: True)
- name – (unique) name of the template (default: None)
Example: Pad a string with ‘ ‘s so it is at least 20 bytes
Pad(fields=String('padded'), pad_data=' ', pad_length=20) # default result will be: 'padded '
-
-
class
kitty.model.low_level.container.
PseudoTemplate
(name)[source]¶ Bases:
kitty.model.low_level.container.Template
A pseudo template is an empty, immutable template, that can be created with any name. Pseudo templates are useful when fuzzing clients and we want to fuzz a template at differemt stages.
Example: Let’s say you have a protocol in which a given request is performed twice and you don’t only want to check the handling of the response for each request, but what happens when those responses are different.
This use case happens in various protocols. For example, some USB hosts may ask the same descriptor twice. In the first time, they will only read its length and allocate enough memory for it, and in the second time it will copy the descriptor to the allocated buffer. Providing different descriptors in each response may expose bugs that are similar to time-of-check time-of-use.
When working with a graph model, this can be a problem, as you need to connect the same template to itself to match the stages of the stack, but you cannot do that, as it creates a cycle inside the GraphModel.
The solution is to create
PseudoTemplates
s with the same name.Example: g = GraphModel() stage1 = PseudoTemplate(original.get_name()) stage2 = PseudoTemplate(original.get_name()) g.connect(original) g.connect(stage1) g.connect(stage1, original) g.connect(stage1, stage2) g.connect(stage2, original)
This will result in the following (interesting) sequences:
original
,stage1 -> original
andstage1 -> stage2 -> original
-
class
kitty.model.low_level.container.
Repeat
(fields=[], min_times=1, max_times=1, step=1, encoder=<kitty.model.low_level.encoder.BitsEncoder object>, fuzzable=True, name=None)[source]¶ Bases:
kitty.model.low_level.container.Container
Repeat the enclosed fields. When not mutated, the repeat count is min_times
-
__init__
(fields=[], min_times=1, max_times=1, step=1, encoder=<kitty.model.low_level.encoder.BitsEncoder object>, fuzzable=True, name=None)[source]¶ Parameters: - fields – enclosed field(s) (default: [])
- min_times – minimum number of repetitions (default: 1)
- max_times – maximum number of repetitions (default: 1)
- step – how many repetitions to add each mutation (default: 1)
- encoder (BitsEncoder) – encoder for the container (default: ENC_BITS_DEFAULT)
- fuzzable – is container fuzzable (default: True)
- name – (unique) name of the container (default: None)
Examples: Repeat([Static('a')], min_times=5, fuzzable=False) # will render to: 'aaaaa' Repeat([Static('a')], min_times=5, max_times=10, step=5) # will render to: 'aaaaa', 'aaaaaaaaaa'
-
-
class
kitty.model.low_level.container.
Switch
(field_dict, key_field, default_key, encoder=<kitty.model.low_level.encoder.BitsEncoder object>, fuzzable=True, name=None)[source]¶ Bases:
kitty.model.low_level.container.OneOf
When switch is not mutating, it will render one of the fields, choosing the one with a key that matchs the value of key_field. When switch is mutating, it will mutate and render one of its fields each time, setting the value of the key_field field to the mutated field key.
-
__init__
(field_dict, key_field, default_key, encoder=<kitty.model.low_level.encoder.BitsEncoder object>, fuzzable=True, name=None)[source]¶ Parameters: - field_dict – dictionary of key:field
- key_field – (name of) field that switch takes the key from
- default_key – key to use if the key_field’s value doesn’t match any key
- encoder (BitsEncoder) – encoder for the container (default: ENC_BITS_DEFAULT)
- fuzzable – is container fuzzable (default: True)
- name – (unique) name of the container (default: None)
Example: Container(fields=[ BE16(name='opcode', value=1), Switch(name='opcode_params', key_field='opcode', default_key=1, field_dict={ 1: Container(name='opcode_1_params', fields=[ BE32(name='param1', value=3), ]), 2: Container(name='opcode_2_params', fields=[ BE32(name='param1', value=4), BE32(name='param2', value=5), ]), }) ])
-
-
class
kitty.model.low_level.container.
TakeFrom
(fields=[], min_elements=1, max_elements=None, encoder=<kitty.model.low_level.encoder.BitsEncoder object>, fuzzable=True, name=None)[source]¶ Bases:
kitty.model.low_level.container.OneOf
Render to only part of the enclosed fields, performing all mutations on them
-
__init__
(fields=[], min_elements=1, max_elements=None, encoder=<kitty.model.low_level.encoder.BitsEncoder object>, fuzzable=True, name=None)[source]¶ Parameters: - fields (field or iterable of fields) – enclosed field(s) (default: [])
- min_elements – minimum number of elements in the sub set
- max_elements – maximum number of elements in the sub set
- encoder (BitsEncoder) – encoder for the container (default: ENC_BITS_DEFAULT)
- fuzzable – is container fuzzable (default: True)
- name – (unique) name of the container (default: None)
Example: TakeFrom(fields=[ Static('A'), Static('B'), Static('C'), Static('D'), Static('E'), Static('F'), ]) # 'E', 'B', 'D', 'F', 'C', 'A', 'CE', 'FC', 'CF', 'BD', 'AF', 'BED', # 'EBC', 'CDB', 'DCA', 'BFAD', 'FCBD', 'DBCF', 'BFACD' ...
-
-
class
kitty.model.low_level.container.
Template
(fields=[], encoder=<kitty.model.low_level.encoder.ByteAlignedBitsEncoder object>, fuzzable=True, name=None)[source]¶ Bases:
kitty.model.low_level.container.Container
Top most container of a message, serves a the only interface to the high level model
-
__init__
(fields=[], encoder=<kitty.model.low_level.encoder.ByteAlignedBitsEncoder object>, fuzzable=True, name=None)[source]¶ Parameters: - fields – enclosed field(s) (default: [])
- encoder (BitsEncoder) – encoder for the container (default: ENC_BITS_BYTE_ALIGNED)
- fuzzable – is fuzzable (default: True)
- name – (unique) name of the template (default: None)
Example: Template([ Group(['a', 'b', 'c']), Group(['1', '2', '3']) ]) # the mutations are: a1, b1, c1, a1, a2, a3
-
copy
()[source]¶ We might want to change it in the future, but for now...
Raises: KittyException
, as it should not be copied
-
-
class
kitty.model.low_level.container.
Trunc
(max_size, fields=[], fuzzable=True, name=None)[source]¶ Bases:
kitty.model.low_level.container.Container
Truncate the size of the enclosed fields
Container mutators treat fields of the container as atomic blocks, and perform mutations over the collection of the field.
- Examples of such mutations are:
- remove fields from the rendered payload repeat fields in the rendered payload change the order of fields etc.
-
class
kitty.model.low_level.container_mutator.
DuplicateMutator
(field_count, dup_num, fields=[], delim=None, encoder=<kitty.model.low_level.encoder.BitsEncoder object>, fuzzable=True, name=None)[source]¶ Bases:
kitty.model.low_level.container_mutator.FieldRangeMutator
Duplicate X fields Y times in the final payload
-
__init__
(field_count, dup_num, fields=[], delim=None, encoder=<kitty.model.low_level.encoder.BitsEncoder object>, fuzzable=True, name=None)[source]¶ Parameters: - field_count – how many (sequential) fields to duplicate
- dup_num – how many times to duplicate each of the field
- fields (field or iterable of fields) – enclosed field(s) (default: [])
- delim (field) – delimiter between elements in the list (default: None)
- encoder (BitsEncoder) – encoder for the container (default: ENC_BITS_DEFAULT)
- fuzzable – is container fuzzable (default: True)
- name – (unique) name of the container (default: None)
Example: DuplicateMutator(field_count=2, dup_num=2, fields=[ Static('A'), Static('B'), Static('C'), Static('D'), ])
will result in: AABBCD, ABBCCD, ABCCDD
-
-
class
kitty.model.low_level.container_mutator.
FieldRangeMutator
(field_count, fields=[], delim=None, encoder=<kitty.model.low_level.encoder.BitsEncoder object>, fuzzable=True, name=None)[source]¶ Bases:
kitty.model.low_level.container.Container
Base class for mutating a field range, it should not be instantiated. Mutators are intended to be used internally in the framework, not in the template declaration directly, and as such, they provide empty response when not mutated.
-
__init__
(field_count, fields=[], delim=None, encoder=<kitty.model.low_level.encoder.BitsEncoder object>, fuzzable=True, name=None)[source]¶ Parameters: - field_count – how many fields to omit in each mutation
- fields (field or iterable of fields) – enclosed field(s) (default: [])
- delim (field) – delimiter between elements in the list (default: None)
- encoder (BitsEncoder) – encoder for the container (default: ENC_BITS_DEFAULT)
- fuzzable – is container fuzzable (default: True)
- name – (unique) name of the container (default: None)
-
-
class
kitty.model.low_level.container_mutator.
List
(fields=[], delim=None, encoder=<kitty.model.low_level.encoder.BitsEncoder object>, fuzzable=True, name=None)[source]¶ Bases:
kitty.model.low_level.container.OneOf
Describe a list of elements in the template. In addition to the standard mutations of its element, a List also performs mutation of full elements, by reordering, duplicating and omitting them.
-
__init__
(fields=[], delim=None, encoder=<kitty.model.low_level.encoder.BitsEncoder object>, fuzzable=True, name=None)[source]¶ Parameters: - fields (field or iterable of fields) – enclosed field(s) (default: [])
- delim (field) – delimiter between elements in the list (default: None)
- encoder (BitsEncoder) – encoder for the container (default: ENC_BITS_DEFAULT)
- fuzzable – is container fuzzable (default: True)
- name – (unique) name of the container (default: None)
Example: List([ BE32(name='id1', value=1), BE32(name='id2', value=2), BE32(name='id3', value=10), BE32(name='id4', value=50), ])
-
-
class
kitty.model.low_level.container_mutator.
OmitMutator
(field_count, fields=[], delim=None, encoder=<kitty.model.low_level.encoder.BitsEncoder object>, fuzzable=True, name=None)[source]¶ Bases:
kitty.model.low_level.container_mutator.FieldRangeMutator
Omit X fields from the final payload
Example: OmitMutator(field_count=1, fields=[ Static('A'), Static('B'), Static('C'), Static('D'), ])
will result in: BCD, ACD, ABD, ABC
-
class
kitty.model.low_level.container_mutator.
RotateMutator
(field_count, fields=[], delim=None, encoder=<kitty.model.low_level.encoder.BitsEncoder object>, fuzzable=True, name=None)[source]¶ Bases:
kitty.model.low_level.container_mutator.FieldRangeMutator
Perform rotation of X fields in the final payload
-
__init__
(field_count, fields=[], delim=None, encoder=<kitty.model.low_level.encoder.BitsEncoder object>, fuzzable=True, name=None)[source]¶ Parameters: - field_count – how many fields to omit in each mutation
- fields (field or iterable of fields) – enclosed field(s) (default: [])
- delim (field) – delimiter between elements in the list (default: None)
- encoder (BitsEncoder) – encoder for the container (default: ENC_BITS_DEFAULT)
- fuzzable – is container fuzzable (default: True)
- name – (unique) name of the container (default: None)
Example: RotateMutator(field_count=3, fields=[ Static('A'), Static('B'), Static('C'), Static('D'), ])
will result in: BCAD, CABD, ACDB, ADBC
-
Encoders are used for encoding fields and containers. The encoders are passed as an argument to the fields/container, during the field rendering, the encoder’s encode method is called.
There are four families of encoders:
Bits Encoders: | Used to encode fields/containers that their value is of type Bits (Container, ForEach etc.) |
---|---|
String Encoders: | |
Used to encode fields that their value is of type str (String, Delimiter, RandomBytes etc.) | |
BitField Encoders: | |
Used to encode fields that inherit from BitField or contain BitField (UInt8, Size, Checksum etc.) Those encoders are also refferred to as Int Encoders. | |
FloatingPoint Encoders: | |
Used to encode fields that inherit from FloatingPoint field (Float, Double) Those encoders are also refferred to as Float Encoders |
-
class
kitty.model.low_level.encoder.
BitFieldAsciiEncoder
(fmt)[source]¶ Bases:
kitty.model.low_level.encoder.BitFieldEncoder
Encode int as ascii
-
formats
= ['%d', '%x', '%X', '%#x', '%#X']¶
-
-
class
kitty.model.low_level.encoder.
BitFieldBinEncoder
(mode)[source]¶ Bases:
kitty.model.low_level.encoder.BitFieldEncoder
Encode int as binary
-
class
kitty.model.low_level.encoder.
BitFieldEncoder
[source]¶ Bases:
object
Base encoder class for BitField values
Singleton Name Encoding Class ENC_INT_BIN Encode as binary bits BitFieldBinEncoder ENC_INT_LE Encode as a little endian binary bits ENC_INT_BE Encode as a big endian binary bits ENC_INT_DEC Encode as a decimal value BitFieldAsciiEncoder ENC_INT_HEX Encode as a hex value ENC_INT_HEX_UPPER Encode as an upper case hex value ENC_INT_DEFAULT Same as ENC_INT_BIN BitFieldBinEncoder
-
class
kitty.model.low_level.encoder.
BitFieldMultiByteEncoder
(mode='be')[source]¶ Bases:
kitty.model.low_level.encoder.BitFieldEncoder
Encode int as multi-byte (used in WBXML format)
-
class
kitty.model.low_level.encoder.
BitsEncoder
[source]¶ Bases:
object
Base encoder class for Bits values
The Bits encoders encode function receives a Bits object as an argument and returns an encoded Bits object.
Singleton Name Encoding Class ENC_BITS_NONE None, returns the same value received BitsEncoder ENC_BITS_BYTE_ALIGNED Appends bits to the received object to make it byte aligned ByteAlignedBitsEncoder ENC_BITS_REVERSE Reverse the order of bits ReverseBitsEncoder ENC_BITS_BASE64 Encode a Byte aligned bits in base64 StrEncoderWrapper ENC_BITS_BASE64_NO_NL Encode a Byte aligned bits in base64, but removes the new line from the end ENC_BITS_UTF8 Encode a Byte aligned bits in UTF-8 ENC_BITS_HEX Encode a Byte aligned bits in hex ENC_BITS_DEFAULT Same as ENC_BITS_NONE
-
class
kitty.model.low_level.encoder.
BitsFuncEncoder
(func)[source]¶ Bases:
kitty.model.low_level.encoder.BitsEncoder
Encode bits using a given function
-
class
kitty.model.low_level.encoder.
ByteAlignedBitsEncoder
[source]¶ Bases:
kitty.model.low_level.encoder.BitsEncoder
Stuff bits for byte alignment
-
class
kitty.model.low_level.encoder.
FloatAsciiEncoder
(fmt)[source]¶ Bases:
kitty.model.low_level.encoder.FloatEncoder
Encode a floating point number in ascii as described by IEEE 754 (decimal*)
-
class
kitty.model.low_level.encoder.
FloatBinEncoder
(fmt)[source]¶ Bases:
kitty.model.low_level.encoder.FloatEncoder
Encode a floating point number in binary format as described by IEEE 754 (binary32 and binary64)
-
class
kitty.model.low_level.encoder.
FloatEncoder
[source]¶ Bases:
object
Base encoder class for FloatingPoint values
Singleton Name Encoding Class ENC_FLT_LE Encode as a little endian 32 bit FloatBinEncoder
ENC_FLT_BE Encode as a big endian 32 bit ENC_DBL_LE Encode as a little endian 64 bit ENC_DBL_BE Encode as a big endian 64 bit ENC_FLT_FP Fixed point FloatAsciiEncoder
ENC_FLT_EXP Exponent notation ENC_FLT_EXP_UPPER Exponent notation, with upper case E ENC_FLT_GEN General format ENC_FLT_GEN_UPPER General format, with upper case ENC_FLT_DEFAULT Same as ENC_FLT_BE FloatBinEncoder
-
class
kitty.model.low_level.encoder.
ReverseBitsEncoder
[source]¶ Bases:
kitty.model.low_level.encoder.BitsEncoder
Reverse the order of bits
-
class
kitty.model.low_level.encoder.
StrBase64NoNewLineEncoder
[source]¶ Bases:
kitty.model.low_level.encoder.StrEncoder
Encode the string as base64, but without the new line at the end
-
class
kitty.model.low_level.encoder.
StrEncodeEncoder
(encoding)[source]¶ Bases:
kitty.model.low_level.encoder.StrEncoder
Encode the string using str.encode function
-
class
kitty.model.low_level.encoder.
StrEncoder
[source]¶ Bases:
object
Base encoder class for str values The String encoders encode function receives a str object as an argument and returns an encoded Bits object.
Singleton Name Encoding Class ENC_STR_UTF8 Encode the str in UTF-8 StrEncodeEncoder ENC_STR_HEX Encode the str in hex ENC_STR_BASE64 Encode the str in base64 ENC_STR_BASE64_NO_NL Encode the str in base64 but remove the new line from the end StrBase64NoNewLineEncoder ENC_STR_DEFAULT Do nothing, just convert the str to Bits object StrEncoder
-
class
kitty.model.low_level.encoder.
StrEncoderWrapper
(encoder)[source]¶ Bases:
kitty.model.low_level.encoder.ByteAlignedBitsEncoder
Encode the data using str.encode function
-
__init__
(encoder)[source]¶ Parameters: encoding (StrEncoder) – encoder to wrap
-
-
class
kitty.model.low_level.encoder.
StrFuncEncoder
(func)[source]¶ Bases:
kitty.model.low_level.encoder.StrEncoder
Encode string using a given function
-
class
kitty.model.low_level.encoder.
StrNullTerminatedEncoder
[source]¶ Bases:
kitty.model.low_level.encoder.StrEncoder
Encode the string as c-string, with null at the end
This module is the “Heart” of the data model. It contains all the basic building blocks for a Template. Each “field” type is a discrete component in the full Template.
-
class
kitty.model.low_level.field.
BaseField
(value, encoder=<kitty.model.low_level.encoder.BitsEncoder object>, fuzzable=True, name=None)[source]¶ Bases:
kitty.core.kitty_object.KittyObject
Basic type for all fields and containers, it contains the common logic. This class should never be used directly.
-
__init__
(value, encoder=<kitty.model.low_level.encoder.BitsEncoder object>, fuzzable=True, name=None)[source]¶ Parameters: - value – default value
- encoder (
BaseEncoder
) – encoder for the field - fuzzable – is field fuzzable (default: True)
- name – name of the object (default: None)
-
get_field_by_name
(name)[source]¶ Parameters: name – name of field to get Raises: KittyException
if no direct subfield with this name
-
is_default
()[source]¶ Checks if the field is in its default form
Returns: True if field is in default form
-
render
(ctx=None)[source]¶ Render the current value of the field
Return type: Bits Returns: rendered value
-
resolve_absolute_name
(name)[source]¶ Resolve a field from an absolute name. An absolute name is just like unix absolute path, starts with ‘/’ and each name component is separated by ‘/’.
Parameters: name – absolute name, e.g. “/container/subcontainer/field” Returns: field with this absolute name Raises: KittyException if field could not be resolved
-
resolve_field
(field)[source]¶ Resolve a field from name
Parameters: field – name of the field to resolve Return type: BaseField Returns: the resolved field Raises: KittyException if field could not be resolved
-
scan_for_field
(field_name)[source]¶ Scan for field field with given name
Parameters: field_name – field name to look for Returns: None
-
-
kitty.model.low_level.field.
BitField
(value, length, signed=False, min_value=None, max_value=None, encoder=<kitty.model.low_level.encoder.BitFieldBinEncoder object>, fuzzable=True, name=None, full_range=False)[source]¶ Returns an instance of some BitField class .. note:
Since BitField is frequently used in binary format, multiple aliases were created for it. See aliases.py for more details.
-
class
kitty.model.low_level.field.
Delimiter
(value, max_size=None, encoder=<kitty.model.low_level.encoder.StrEncoder object>, fuzzable=True, name=None)[source]¶ Bases:
kitty.model.low_level.field.String
Represent a text delimiter, the mutations target common delimiter-related vulnerabilities
-
__init__
(value, max_size=None, encoder=<kitty.model.low_level.encoder.StrEncoder object>, fuzzable=True, name=None)[source]¶ Parameters: - value (str) – default value
- max_size – maximal size of the string before encoding (default: None)
- encoder (
StrEncoder
) – encoder for the field (default: ENC_STR_DEFAULT) - fuzzable – is field fuzzable (default: True)
- name – name of the object (default: None)
Example: Delimiter('=', max_size=30, encoder=ENC_STR_BASE64)
-
lib
= None¶
-
-
class
kitty.model.low_level.field.
Dynamic
(key, default_value, length=None, encoder=<kitty.model.low_level.encoder.StrEncoder object>, fuzzable=False, name=None)[source]¶ Bases:
kitty.model.low_level.field.BaseField
A field that gets its value from the fuzzer at runtime
-
__init__
(key, default_value, length=None, encoder=<kitty.model.low_level.encoder.StrEncoder object>, fuzzable=False, name=None)[source]¶ Parameters: - key (str) – key for the data in the session_data dictionary
- default_value (str) – default value of the field
- length – length of the field in bytes. must be set if fuzzable=True (default: None)
- encoder (
StrEncoder
) – encoder for the field (default: ENC_STR_DEFAULT) - fuzzable – is field fuzzable (default: False)
- name – name of the object (default: None)
Examples: Dynamic(key='session id', default_value='') Dynamic(key='session id', default_value='', length=4, fuzzable=True)
-
-
class
kitty.model.low_level.field.
Float
(value, encoder=<kitty.model.low_level.encoder.FloatBinEncoder object>, fuzzable=True, name=None)[source]¶ Bases:
kitty.model.low_level.field._LibraryField
Represent a floating point number. The mutations target edge cases and invalid floating point numbers.
-
__init__
(value, encoder=<kitty.model.low_level.encoder.FloatBinEncoder object>, fuzzable=True, name=None)[source]¶ Parameters: - value (float) – default value
- encoder (
FloatEncoder
) – encoder for the field (default: ENC_FLT_DEFAULT) - fuzzable – is field fuzzable (default: True)
- name – name of the object (default: None)
Example: Float(0.3)
-
lib
= None¶
-
-
class
kitty.model.low_level.field.
Group
(values, encoder=<kitty.model.low_level.encoder.StrEncoder object>, fuzzable=True, name=None)[source]¶ Bases:
kitty.model.low_level.field._LibraryField
A field with fixed set of possible mutations
-
__init__
(values, encoder=<kitty.model.low_level.encoder.StrEncoder object>, fuzzable=True, name=None)[source]¶ Parameters: - values (list of strings) – possible values for the field
- encoder (
StrEncoder
) – encoder for the field (default: ENC_STR_DEFAULT) - fuzzable – is field fuzzable (default: True)
- name – name of the object (default: None)
Example: This field will generate exactly 3 mutations: ‘GET’, ‘PUT’ and ‘POST’
Group(['GET', 'PUT', 'POST'], name='http methods')
-
lib
= None¶
-
-
class
kitty.model.low_level.field.
RandomBits
(value, min_length, max_length, unused_bits=0, seed=1235, num_mutations=25, step=None, encoder=<kitty.model.low_level.encoder.BitsEncoder object>, fuzzable=True, name=None)[source]¶ Bases:
kitty.model.low_level.field.BaseField
A random sequence of bits. The length of the sequence is between min_length and max_length, and decided either randomally (if step is None) or starts from min_length and inreased by step bits (if step has a value).
-
__init__
(value, min_length, max_length, unused_bits=0, seed=1235, num_mutations=25, step=None, encoder=<kitty.model.low_level.encoder.BitsEncoder object>, fuzzable=True, name=None)[source]¶ Parameters: - value (str) – default value, the last unsused_bits will be removed from the value
- min_length – minimal length of the field (in bits)
- max_length – maximal length of the field (in bits)
- unused_bits – how many bits from the value are not used (default: 0)
- seed – seed for the random number generator, to allow consistency between runs (default: 1235)
- num_mutations – number of mutations to perform (if step is None) (default:25)
- step (int) – step between lengths of each mutation (default: None)
- encoder (
BitsEncoder
) – encoder for the field (default: ENC_BITS_DEFAULT) - fuzzable – is field fuzzable (default: True)
- name – name of the object (default: None)
Examples: RandomBits(value='1234', min_length=0, max_length=75, unused_bits=0, step=15) RandomBits(value='1234', min_length=0, max_length=75, unused_bits=3, num_mutations=80)
-
-
class
kitty.model.low_level.field.
RandomBytes
(value, min_length, max_length, seed=1234, num_mutations=25, step=None, encoder=<kitty.model.low_level.encoder.StrEncoder object>, fuzzable=True, name=None)[source]¶ Bases:
kitty.model.low_level.field.BaseField
A random sequence of bytes The length of the sequence is between min_length and max_length, and decided either randomally (if step is None) or starts from min_length and inreased by step bytes (if step has a value).
-
__init__
(value, min_length, max_length, seed=1234, num_mutations=25, step=None, encoder=<kitty.model.low_level.encoder.StrEncoder object>, fuzzable=True, name=None)[source]¶ Parameters: - value (str) – default value
- min_length – minimal length of the field (in bytes)
- max_length – maximal length of the field (in bytes)
- seed – seed for the random number generator, to allow consistency between runs (default: 1234)
- num_mutations – number of mutations to perform (if step is None) (default:25)
- step (int) – step between lengths of each mutation (default: None)
- encoder (
StrEncoder
) – encoder for the field (default: ENC_STR_DEFAULT) - fuzzable – is field fuzzable (default: True)
- name – name of the object (default: None)
Examples: RandomBytes(value='1234', min_length=0, max_length=75, step=15) RandomBytes(value='1234', min_length=0, max_length=75, num_mutations=80)
-
-
class
kitty.model.low_level.field.
Static
(value, encoder=<kitty.model.low_level.encoder.StrEncoder object>, name=None)[source]¶ Bases:
kitty.model.low_level.field.BaseField
A static field does not mutate. It is used for constant parts of the model
-
__init__
(value, encoder=<kitty.model.low_level.encoder.StrEncoder object>, name=None)[source]¶ Parameters: - value (str) – default value
- encoder (
StrEncoder
) – encoder for the field (default: ENC_STR_DEFAULT) - name – name of the object (default: None)
Example: Static('this will never change')
-
-
class
kitty.model.low_level.field.
String
(value, max_size=None, encoder=<kitty.model.low_level.encoder.StrEncoder object>, fuzzable=True, name=None)[source]¶ Bases:
kitty.model.low_level.field._LibraryField
Represent a string, the mutation target common string-related vulnerabilities
-
__init__
(value, max_size=None, encoder=<kitty.model.low_level.encoder.StrEncoder object>, fuzzable=True, name=None)[source]¶ Parameters: - value (str) – default value
- max_size – maximal size of the string before encoding (default: None)
- encoder (
StrEncoder
) – encoder for the field - fuzzable – is field fuzzable (default: True)
- name – name of the object (default: None)
Example: String('this is the default value', max_size=5)
-
lib
= None¶
-
Fields to perform mutation fuzzing
In some cases, you might not know the details and structure of the fuzzed protocol (or might just be too lazy) but you do have some examples of valid messages. In these cases, it makes sense to perform mutation fuzzing. The strategy of mutation fuzzing is to take some valid messages and mutate them in various ways. Kitty supports the following strategies described below. The last one, MutableField, combines all strategies, with reasonable parameters, together.
Currently all strategies are inspired by this article: http://lcamtuf.blogspot.com/2014/08/binary-fuzzing-strategies-what-works.html
-
class
kitty.model.low_level.mutated_field.
BitFlip
(value, num_bits=1, fuzzable=True, name=None)[source]¶ Bases:
kitty.model.low_level.field.BaseField
Perform bit-flip mutations of N sequential bits on the value
Example: BitFlip('\x01', 3) Results in: '\xe1', '\x71', '\x39', '\x1d', '\x0f', '\x06'
-
__init__
(value, num_bits=1, fuzzable=True, name=None)[source]¶ Parameters: - value – value to mutate (str)
- num_bits – number of consequtive bits to flip (invert)
- fuzzable – is field fuzzable (default: True)
- name – name of the object (default: None)
Raises: KittyException
if num_bits is bigger than the value length in bitsRaises: KittyException
if num_bits is not positive
-
-
class
kitty.model.low_level.mutated_field.
BitFlips
(value, bits_range=[1, 2, 3, 4], fuzzable=True, name=None)[source]¶ Bases:
kitty.model.low_level.container.OneOf
Perform bit-flip mutations of (N..) sequential bits on the value
-
__init__
(value, bits_range=[1, 2, 3, 4], fuzzable=True, name=None)[source]¶ Parameters: - value (str) – value to mutate
- bits_range – range of number of consequtive bits to flip (default: range(1, 5))
- fuzzable – is field fuzzable (default: True)
- name – name of the object (default: None)
Example: BitFlips('\x00', (3, 5)) Results in: '\xe0', '\x70', '\x38', '\x1c', '\x0e', '\x07' - 3 bits flipped each time '\xf8', '\x7c', '\x3e', '\x1f' - 5 bits flipped each time
-
-
class
kitty.model.low_level.mutated_field.
BlockDuplicate
(value, block_size, num_dups=2, fuzzable=True, name=None)[source]¶ Bases:
kitty.model.low_level.mutated_field.BlockOperation
Duplicate a block of bytes from the default value, each mutation moving one byte forward.
-
__init__
(value, block_size, num_dups=2, fuzzable=True, name=None)[source]¶ Parameters: - value (str) – value to mutate
- block_size – number of consequtive bytes to duplicate
- num_dups – number of times to duplicate the block (default: 1)
- fuzzable – is field fuzzable (default: True)
- name – name of the object (default: None)
Raises: KittyException
if block_size is bigger than the value length in bytesRaises: KittyException
if block_size is not positive
-
-
class
kitty.model.low_level.mutated_field.
BlockDuplicates
(value, block_size, num_dups_range=(2, 5, 10, 50, 200), fuzzable=True, name=None)[source]¶ Bases:
kitty.model.low_level.container.OneOf
Perform block duplication with multiple number of duplications
-
class
kitty.model.low_level.mutated_field.
BlockOperation
(value, block_size, fuzzable=True, name=None)[source]¶ Bases:
kitty.model.low_level.field.BaseField
Base class for performing block-level mutations
-
__init__
(value, block_size, fuzzable=True, name=None)[source]¶ Parameters: - value (str) – value to mutate
- block_size – number of consequtive bytes to operate on
- fuzzable – is field fuzzable (default: True)
- name – name of the object (default: None)
Raises: KittyException
if block_size is bigger than the value length in bytesRaises: KittyException
if block_size is not positive
-
-
class
kitty.model.low_level.mutated_field.
BlockRemove
(value, block_size, fuzzable=True, name=None)[source]¶ Bases:
kitty.model.low_level.mutated_field.BlockOperation
Remove a block of bytes from the default value, each mutation moving one byte forward.
-
__init__
(value, block_size, fuzzable=True, name=None)[source]¶ Parameters: - value (str) – value to mutate
- block_size – number of consequtive bytes to remove
- fuzzable – is field fuzzable (default: True)
- name – name of the object (default: None)
Raises: KittyException
if block_size is bigger than the value length in bytesRaises: KittyException
if block_size is not positive
-
-
class
kitty.model.low_level.mutated_field.
BlockSet
(value, block_size, set_chr, fuzzable=True, name=None)[source]¶ Bases:
kitty.model.low_level.mutated_field.BlockOperation
Set a block of bytes from the default value to a specific value, each mutation moving one byte forward.
-
__init__
(value, block_size, set_chr, fuzzable=True, name=None)[source]¶ Parameters: - value (str) – value to mutate
- block_size – number of consequtive bytes to duplicate
- set_chr – char to set in the blocks
- fuzzable – is field fuzzable (default: True)
- name – name of the object (default: None)
Raises: KittyException
if block_size is bigger than the value length in bytesRaises: KittyException
if block_size is not positive
-
-
class
kitty.model.low_level.mutated_field.
ByteFlip
(value, num_bytes=1, fuzzable=True, name=None)[source]¶ Bases:
kitty.model.low_level.field.BaseField
Flip number of sequential bytes in the message, each mutation moving one byte forward.
Example: ByteFlip('\x00\x00\x00\x00', 2) # results in: '\xff\xff\x00\x00' '\x00\xff\xff\x00' '\x00\x00\xff\xff'
-
__init__
(value, num_bytes=1, fuzzable=True, name=None)[source]¶ Parameters: - value (str) – value to mutate
- num_bytes – number of consequtive bytes to flip (invert)
- fuzzable – is field fuzzable (default: True)
- name – name of the object (default: None)
Raises: KittyException
if num_bytes is bigger than the value lengthRaises: KittyException
if num_bytes is not positive
-
-
class
kitty.model.low_level.mutated_field.
ByteFlips
(value, bytes_range=(1, 2, 4), fuzzable=True, name=None)[source]¶ Bases:
kitty.model.low_level.container.OneOf
Perform byte-flip mutations of (N..) sequential bytes on the value
-
__init__
(value, bytes_range=(1, 2, 4), fuzzable=True, name=None)[source]¶ Parameters: - value (str) – value to mutate
- bytes_range – range of number of consequtive bytes to flip (default: (1, 2, 4))
- fuzzable – is field fuzzable (default: True)
- name – name of the object (default: None)
Example: ByteFlips('\x00\x00\x00\x00', (2,3)) Results in: '\xff\xff\x00\x00', '\x00\xff\xff\x00', '\x00\x00\xff\xff' # 2 bytes flipped each time '\xff\xff\xff\x00', '\x00\xff\xff\xff' # 3 bytes flipped each time
-
-
class
kitty.model.low_level.mutated_field.
MutableField
(value, encoder=<kitty.model.low_level.encoder.ByteAlignedBitsEncoder object>, fuzzable=True, name=None)[source]¶ Bases:
kitty.model.low_level.container.OneOf
Container to perform mutation fuzzing on a value ByteFlips, BitFlips and block operations
-
__init__
(value, encoder=<kitty.model.low_level.encoder.ByteAlignedBitsEncoder object>, fuzzable=True, name=None)[source]¶ Parameters: - value (str) – value to mutate
- encoder (BitsEncoder) – encoder for the container (default: ENC_BITS_BYTE_ALIGNED)
- fuzzable – is fuzzable (default: True)
- name – (unique) name of the template (default: None)
-
Indices and tables¶
Kitty Tools¶
When installing Kitty using setup.py or pip, it will install several tools:
Template Tester¶
Usage:
kitty-template-tester [--fast] [--tree] [--verbose] <FILE> ...
This tool mutates and renders templates in a file, making sure there are no
syntax issues in the templates.
It doesn't prove that the data model is correct, only checks that the it is
a valid model
Options:
<FILE> python file that contains templates in dictionaries, lists or globals
--fast only import, don't run all mutations
--tree print fields tree of the template instead of mutating it
--verbose print full call stack upon exception
Kitty Tools¶
kitty-tool will replace kitty-template tester and provide extended functionality
Tools for testing and manipulating kitty templates.
Usage:
kitty-tool generate [--verbose] [-s SKIP] [-c COUNT] [-o OUTDIR] [-f FORMAT] <FILE> <TEMPLATE> ...
kitty-tool list <FILE>
kitty-tool --version
Commands:
generate generate files with mutated payload
list list templates in a file
Options:
<FILE> python file that contains the template
<TEMPLATE> template name(s) to generate files from
--out -o OUTDIR output directory for the generated mutations [default: out]
--skip -s SKIP how many mutations to skip [default: 0]
--count -c COUNT end index to generate
--verbose -v verbose output
--filename-format -f FORMAT format for generated file names [default: %(template)s.%(index)s.bin]
--version print version and exit
--help -h print this help and exit
File name formats:
You can control the name of an output file by giving a filename format,
it follows python's dictionary format string.
The available keywords are:
template - the template name
index - the template index
CLI Web Client¶
Usage:
kitty-web-client (info [-v]|pause|resume) [--host <hostname>] [--port <port>]
kitty_web_client.py reports store <folder> [--host <hostname>] [--port <port>]
kitty_web_client.py reports show <file> ...
Retrieve and parse kitty status and reports from a kitty web server
Options:
-v --verbose verbose information
-h --host <hostname> kitty web server host [default: localhost]
-p --port <port> kitty web server port [default: 26000]
API Reference¶
Kitty API Reference¶
Subpackages¶
kitty.controllers package¶
The kitty.controllers package provide the basic controller classes. The controller role is to provide means to start/stop the victim and put it in the appropriate state for a test.
BaseController
should not be instantiated. It
should be extended when performing server fuzzing.
ClientController
is used for client fuzzing.
It should be extended with an implementation for the
trigger()
function to trigger
the victim to start the communications.
EmptyController
is used when no actual work
should be done by the controller.
Submodules¶
The controller is in charge of preparing the victim for the test. It should make sure that the victim is in an appropriate state before the target initiates the transfer session. Sometimes it means doing nothing, other times it means starting or reseting a VM, killing a process or performing a hard reset to the victim hardware. Since the controller is reponsible for the state of the victim, it is expected to perform a basic monitoring as well, and report whether the victim is ready for the next test.
-
class
kitty.controllers.base.
BaseController
(name, logger=None, victim_alive_check_delay=0.3)[source]¶ Bases:
kitty.core.actor.KittyActorInterface
Base class for controllers. Defines basic variables and implements basic behavior.
ClientController
is a controller for victim in client mode,
it inherits from BaseController
,
and implements one additional method:
trigger()
.
-
class
kitty.controllers.client.
ClientController
(name, logger=None, victim_alive_check_delay=0.3)[source]¶ Bases:
kitty.controllers.base.BaseController
Base class for client controllers.
EmptyController does nothing, implements both client and server controller API
-
class
kitty.controllers.empty.
EmptyController
(name='EmptyController', logger=None)[source]¶ Bases:
kitty.controllers.client.ClientController
EmptyController does nothing, implements both client and server controller API
kitty.core package¶
The classes in this module has very little to do with the fuzzing process, however, those classes and functions are used all over kitty.
-
exception
kitty.core.
KittyException
[source]¶ Bases:
exceptions.Exception
Simple exception, used mainly to make tests better, and identify exceptions that were thrown by kitty directly.
-
kitty.core.
khash
(*args)[source]¶ hash arguments. khash handles None in the same way accross runs (which is good :))
Submodules¶
This module contains KittyActorInterface
which is the base class for both monitors and controllers.
-
class
kitty.core.actor.
KittyActorInterface
(name, logger=None, victim_alive_check_delay=0.3)[source]¶ Bases:
kitty.core.kitty_object.KittyObject
Base class for monitors and controllers, its defines (and partially implements) the Kitty Actor API:
-
__init__
(name, logger=None, victim_alive_check_delay=0.3)[source]¶ Parameters: - name – name of the actor
- logger – logger for the actor (default: None)
- victim_alive_check_delay – delay between checks if alive (default: 0.3)
-
get_report
()[source]¶ Return type: Report
Returns: a report about the victim since last call to pre_test
-
is_victim_alive
()[source]¶ Called during pre_test in loop until the target becomes alive
Returns: whether target is alive (ready for test) or not Note
by default, it returns true, override if you have a way to check in your actor
-
pre_test
(test_number)[source]¶ Called before a test is started. Call super if overriden.
Parameters: test_number – current test number
-
This module provides various assertion functions used by kitty,
not that important, but makes the life easier.
Useful for making assertions that throw KittyException
-
kitty.core.kassert.
is_in
(obj, it)[source]¶ Parameters: - obj – object to assert
- it – iterable of elements we assert obj is in
Raise: an exception if obj is in an iterable
-
kitty.core.kassert.
is_int
(obj)[source]¶ Parameters: obj – object to assert Raise: an exception if obj is not an int type
KittyObject is subclassed by most of Kitty’s objects.
It provides logging, naming, and description of the object.
-
class
kitty.core.kitty_object.
KittyObject
(name, logger=None)[source]¶ Bases:
object
Basic class to ease logging and description of objects.
-
get_description
()[source]¶ Return type: str Returns: the description of the object. by default only prints the object type.
-
log_file_name
= './kittylogs/kitty_20170613-222149.log'¶
-
Threading Utils
-
class
kitty.core.threading_utils.
FuncThread
(func, *args)[source]¶ Bases:
threading.Thread
FuncThread is a thread wrapper to create thread from a function
-
class
kitty.core.threading_utils.
LoopFuncThread
(func, *args)[source]¶ Bases:
threading.Thread
FuncThread is a thread wrapper to create thread from a function
-
__init__
(func, *args)[source]¶ Parameters: - func – function to be call in a loop in this thread
- args – arguments for the function
-
kitty.data package¶
This package contains class for managing data related to the fuzzing session.
Submodules¶
This module is usde to store the fuzzing session related data. It provides both means of communications between the fuzzer and the user interface, and persistent storage of the fuzzing session results.
-
class
kitty.data.data_manager.
DataManager
(dbname)[source]¶ Bases:
threading.Thread
Manages data on a dedicated thread. All calls to it should be done by submitting DataManagerTask
Example: dataman = DataManager('fuzz_session.sqlite`) dataman.start() def get_session_info(manager): return manager.get_session_info_manager().get_session_info() get_info_task = DataManagerTask(get_session_info) dataman.submit_task(get_info_task) session_info = get_info_task.get_results()
-
submit_task
(task)[source]¶ submit a task to the data manager, to be proccessed in the DataManager context
Parameters: task ( DataManagerTask
) – task to perform
-
-
class
kitty.data.data_manager.
DataManagerTask
(task, *args)[source]¶ Bases:
object
Task to be performed in the
DataManager
context-
__init__
(task, *args)[source]¶ Parameters: task (function( DataManager
) -> object) – task to be performed
-
execute
(dataman)[source]¶ run the task
Parameters: dataman ( DataManager
) – the executing data manager
-
-
class
kitty.data.data_manager.
ReportsTable
(connection, cursor)[source]¶ Bases:
kitty.data.data_manager.Table
Table for storing the reports
-
class
kitty.data.data_manager.
SessionInfo
(orig=None)[source]¶ Bases:
object
session information manager
-
copy
(orig)[source]¶ Parameters: orig – SessionInfo object to copy Returns: True if changed, false otherwise
-
fields
= ['start_time', 'start_index', 'end_index', 'current_index', 'failure_count', 'kitty_version', 'data_model_hash', 'test_list_str']¶
-
classmethod
from_dict
(info_d)[source]¶ Parameters: info_d – the info dictionary Return type: SessionInfo
Returns: object that corresponds to the info dictionary
-
i
= ('test_list_str', 'BLOB')¶
-
-
class
kitty.data.data_manager.
SessionInfoTable
(connection, cursor)[source]¶ Bases:
kitty.data.data_manager.Table
Table for storing the session info
-
__init__
(connection, cursor)[source]¶ Parameters: - connection – the database connection
- cursor – the cursor for the database
-
get_session_info
()[source]¶ Return type: SessionInfo
Returns: current session info
-
read_info
()[source]¶ Return type: SessionInfo
Returns: current session info
-
set_session_info
(info)[source]¶ Parameters: info ( SessionInfo
) – info to set
-
-
class
kitty.data.data_manager.
Table
(connection, cursor)[source]¶ Bases:
object
Base class for data manager tables
-
__init__
(connection, cursor)[source]¶ Parameters: - connection – the database connection
- cursor – the cursor for the database
-
insert
(fields, values)[source]¶ insert new db entry
Parameters: - fields – list of fields to insert
- values – list of values to insert
Returns: row id of the new row
-
row_to_dict
(row)[source]¶ translate a row of the current table to dictionary
Parameters: row – a row of the current table (selected with *) Returns: dictionary of all fields
-
This module defines the Report
class
-
class
kitty.data.report.
Report
(name, default_failed=False)[source]¶ Bases:
object
This class represent a report for a single test. This report may contain subreports from nested entities.
Example: In this example, the report, generated by the controller, indicates failure.
report = Report('Controller') report.add('generation time', 0) report.failed('target does not respond')
-
ERROR
= 'error'¶
-
FAILED
= 'failed'¶
-
PASSED
= 'passed'¶
-
__init__
(name, default_failed=False)[source]¶ Parameters: - name – name of the report (or the issuer)
- default_failed – is the default status of the report failed (default: False)
-
add
(key, value)[source]¶ Add an entry to the report
Parameters: - key – entry’s key
- value – the actual value
Example: my_report.add('retry count', 3)
-
allowed_statuses
= ['passed', 'failed', 'error']¶
-
clear
()[source]¶ Set the report to its defaults. This will clear the report, keeping only the name and setting the failure status to the default.
-
error
(reason=None)[source]¶ Set the test status to Report.ERROR, and set the error reason
Parameters: reason – error reason (default: None)
-
failed
(reason=None)[source]¶ Set the test status to Report.FAILED, and set the failure reason
Parameters: reason – failure reason (default: None)
-
classmethod
from_dict
(d, encoding='base64')[source]¶ Construct a
Report
object from dictionary.Parameters: - d (dictionary) – dictionary representing the report
- encoding – encoding of strings in the dictionary (default: ‘base64’)
Returns: Report object
-
get
(key)[source]¶ Get a value for a given key
Parameters: key – entry’s key Returns: corresponding value
-
get_status
()[source]¶ Get the status of the report and its sub-reports.
Return type: str Returns: report status (‘passed’, ‘failed’ or ‘error’)
-
is_failed
()[source]¶ Deprecated since version 0.6.7: use
get_status()
-
reserved_keys
= {'status': 'set_status(status), success(), failed(reason) or error(reason)', 'failed': 'failed(reason)'}¶
-
kitty.fuzzers package¶
The kitty.fuzzers module provides fuzzer classes. In most cases, there is no need to extend those classes, and they may be used as is.
BaseFuzzer
should not be instantiated, and only
serves as a common parent for ClientFuzzer
and
ServerFuzzer
.
ClientFuzzer
should be used when the fuzzer
provides payloads to some server stack in a client fuzzing session.
ServerFuzzer
should be used when the fuzzer
instantiates the communication, in cases such as fuzzing a server of some sort
or when writing payloads to files.
Submodules¶
This module contains BaseFuzzer, which implements most of the fuzzing logic for both Server and Client fuzzing.
This module should not be overriden/referenced by entities outside of kitty, as it is tightly coupled to the implementation of the Client and Server fuzzer, and will probably be changed in the future.
-
class
kitty.fuzzers.base.
BaseFuzzer
(name='', logger=None, option_line=None)[source]¶ Bases:
kitty.core.kitty_object.KittyObject
Common members and logic for client and server fuzzers. This class should not be instantiated, only subclassed.
-
__init__
(name='', logger=None, option_line=None)[source]¶ Parameters: - name – name of the object
- logger – logger for the object (default: None)
- option_line – cmd line options to the fuzzer (dafult: None)
-
handle_stage_changed
(model)[source]¶ handle a stage change in the data model
Parameters: model – the data model that was changed
-
set_delay_between_tests
(delay_secs)[source]¶ Set duration between tests
Parameters: delay_secs – delay between tests (in seconds)
-
set_delay_duration
(delay_duration)[source]¶ Deprecated since version use:
set_delay_between_tests()
-
set_max_failures
(max_failures)[source]¶ Parameters: max_failures – maximum failures before stopping the fuzzing session
-
set_model
(model)[source]¶ Set the model to fuzz
Parameters: model ( BaseModel
or a subclass) – Model object to fuzz
-
set_range
(start_index=0, end_index=None)[source]¶ Set range of tests to run
Deprecated since version use:
set_test_list()
Parameters: - start_index – index to start at (default=0)
- end_index – index to end at(default=None)
-
set_session_file
(filename)[source]¶ Set session file name, to keep state between runs
Parameters: filename – session file name
-
set_skip_env_test
(skip_env_test=True)[source]¶ Set whether to skip the environment test. Call this if the environment test cannot pass and you prefer to start the tests without it.
Parameters: skip_env_test – skip the environment test (default: True)
-
set_store_all_reports
(store_all_reports)[source]¶ Parameters: store_all_reports – should all reports be stored
-
set_test_list
(test_list_str='')[source]¶ Parameters: test_list_str – listing of the test to execute The test list should be a comma-delimited string, and each element should be one of the following forms:
‘-x’ - run from test 0 to test x ‘x-‘ - run from test x to the end ‘x’ - run test x ‘x-y’ - run from test x to test y
To execute all tests, pass None or an empty string
-
This module contains the ClientFuzzer
class.
-
class
kitty.fuzzers.client.
ClientFuzzer
(name='ClientFuzzer', logger=None, option_line=None)[source]¶ Bases:
kitty.fuzzers.base.BaseFuzzer
ClientFuzzer is designed for fuzzing clients. It does not preform an active fuzzing, but rather returns a mutation of a response when in the right state. It is designed to be a module that is integrated into different stacks.
You can see its usahe examples in the following places:
- examples/02_client_fuzzer_browser_remote
- examples/03_client_fuzzer_browser
-
STAGE_ANY
= '******************'¶
-
__init__
(name='ClientFuzzer', logger=None, option_line=None)[source]¶ Parameters: - name – name of the object
- logger – logger for the object (default: None)
- option_line – cmd line options to the fuzzer
-
class
kitty.fuzzers.server.
ServerFuzzer
(name='ServerFuzzer', logger=None, option_line=None)[source]¶ Bases:
kitty.fuzzers.base.BaseFuzzer
ServerFuzzer is a class that is designed to fuzz servers. It does not create the mutations, as those are created by the Session object. The idea is to go through every path in the model, execute all requsets in the path, and mutating the last request.
kitty.interfaces package¶
This package provides User Interface classes
BaseInterface
is not well-maintained, and
should not be used.
WebInterface
starts a web server that provides
information about the fuzzing state, as well as reports.
Submodules¶
-
class
kitty.interfaces.base.
BaseInterface
(name='BaseInterface', logger=None)[source]¶ Bases:
kitty.core.kitty_object.KittyObject
User interface API
-
class
kitty.interfaces.base.
EmptyInterface
(name='EmptyInterface', logger=None)[source]¶ Bases:
kitty.interfaces.base.BaseInterface
This interface may be used when there is no need for user interface
kitty.monitors package¶
The kitty.monitors
package provides the basic monitor class
BaseMonitor should not be instantiated, only extended.
By default, it runs a separate thread and calls _monitor_func in a loop.
If a non-threaded monitor is required, one should re-implement multiple parts of the BaseMonitor class.
Submodules¶
This module defines BaseMonitor - the base (abstract) monitor class
-
class
kitty.monitors.base.
BaseMonitor
(name, logger=None, victim_alive_check_delay=0.3)[source]¶ Bases:
kitty.core.actor.KittyActorInterface
Base (abstract) monitor class
-
__init__
(name, logger=None, victim_alive_check_delay=0.3)[source]¶ Parameters: - name – name of the actor
- logger – logger for the actor (default: None)
- victim_alive_check_delay – delay between checks if alive (default: 0.3)
-
kitty.remote package¶
kitty.remote The remote package provides RPC mechanism for kitty Currently, all RPC communication is done over TCP
Submodules¶
-
class
kitty.remote.actor.
RemoteActor
(host, port)[source]¶ Bases:
kitty.remote.rpc.RpcClient
-
class
kitty.remote.actor.
RemoteActorServer
(host, port, impl)[source]¶ Bases:
kitty.remote.rpc.RpcServer
RPC implementation, based on jsonrpc https://json-rpc.readthedocs.io/
-
class
kitty.remote.rpc.
RpcHandler
(request, client_address, server)[source]¶ Bases:
BaseHTTPServer.BaseHTTPRequestHandler
-
error_response
(code, msg)[source]¶ Send an error response
Parameters: - code – error code
- msg – error message
-
-
class
kitty.remote.rpc.
RpcHttpServer
(server_address, handler, impl, meta)[source]¶ Bases:
BaseHTTPServer.HTTPServer
-
class
kitty.remote.rpc.
RpcServer
(host, port, impl)[source]¶ Bases:
object
-
__init__
(host, port, impl)[source]¶ Parameters: - host – listening address
- port – listening port
- impl – implementation class
-
-
kitty.remote.rpc.
decode_data
(data)[source]¶ Decode data - list, dict, string, bool or int (and nested)
Parameters: - data – data to decode
- encoding – encoding to use (default: ‘hex’)
Returns: decoded object of the same type
-
kitty.remote.rpc.
decode_string
(data, encoding='hex')[source]¶ Decode string
Parameters: - data – string to decode
- encoding – encoding to use (default: ‘hex’)
Returns: decoded string
kitty.targets package¶
The kitty.target package provides basic target classes. The target classes should be extended in most cases.
BaseTarget
should not be instantiated, and only
serves as a common parent for ClientTarget
,
EmptyTarget
and
ServerTarget
.
ClientTarget
should be used when fuzzing a
client. In most cases it should not be extended, as the special functionality
(triggering the victim) is done by its ClientController
EmptyTarget
can be used in server-like fuzzing
when no communication should be done.
ServerTarget
should be used when fuzzing a
server. In most cases it should be extended to provide the appropriate
communication means with the server.
Submodules¶
This module defines BaseTarget - the basic target
-
class
kitty.targets.base.
BaseTarget
(name='BaseTarget', logger=None)[source]¶ Bases:
kitty.core.kitty_object.KittyObject
BaseTarget contains the common logic and behaviour of all target.
-
class
kitty.targets.client.
ClientTarget
(name, logger=None, mutation_server_timeout=3)[source]¶ Bases:
kitty.targets.base.BaseTarget
This class represents a target when fuzzing a client.
-
__init__
(name, logger=None, mutation_server_timeout=3)[source]¶ Parameters: - name – name of the target
- logger – logger for this object (default: None)
- mutation_server_timeout – timeout for receiving mutation request from the server stack
-
set_mutation_server_timeout
(mutation_server_timeout)[source]¶ Set timeout for receiving mutation request from the server stack.
Parameters: mutation_server_timeout – timeout for receiving mutation request from the server stack
-
-
class
kitty.targets.empty.
EmptyTarget
(name, logger=None)[source]¶ Bases:
kitty.targets.server.ServerTarget
Target that does nothing. Weird, but sometimes it is required.
-
class
kitty.targets.server.
ServerTarget
(name, logger=None, expect_response=False)[source]¶ Bases:
kitty.targets.base.BaseTarget
This class represents a target when fuzzing a server. Its main job, beside using the Controller and Monitors, is to send and receive data from/to the target.
-
__init__
(name, logger=None, expect_response=False)[source]¶ Parameters: - name – name of the target
- logger – logger for this object (default: None)
- expect_response – should wait for response from the victim (default: False)
-
Controllers¶
The controller is in charge of preparing the victim for the test. It should make sure that the victim is in an appropriate state before the target initiates the transfer session. Sometimes it means doing nothing, other times it means starting or reseting a VM, killing a process or performing a hard reset to the victim hardware. Since the controller is reponsible for the state of the victim, it is expected to perform a basic monitoring as well, and report whether the victim is ready for the next test.
Core Classes¶
BaseController¶
kitty.controllers.base.BaseController
(kitty.core.kitty_object.KittyObject
)
setup(self)
Called at the beginning of the fuzzing session, override with victim setup
teardown(self)
Called at the end of the fuzzing session, override with victim teardown
pre_test(self, test_number)
Called before a test is started Call super if overriden
post_test(self)
Called when test is done Call super if overriden
get_report(self)
Returns a report about the victim since last call to pre_test
ClientController¶
kitty.controllers.client.ClientController
(kitty.controllers.base.BaseController
)
ClientController is a controller for victim in client mode
trigger(self)
Trigger a data exchange from the tested client
EmptyController¶
kitty.controllers.empty.EmptyController
(kitty.controllers.client.ClientController
) EmptyController does
nothing, implements both client and server controller API
Implementation Classes¶
Implemented controllers for different victim types.
ClientGDBController¶
kitty.controllers.client_gdb.ClientGDBController
(kitty.controllers.client.ClientController
)
ClientGDBController runs a client target in gdb to allow further monitoring and crash detection.
Requires pygdb
__init__(self, name, gdb_path, process_path, process_args, max_run_time, logger=None)
ClientUSBController¶
kitty.controllers.client_usb.ClientUSBController
(kitty.controllers.client.ClientController
)
ClientUSBController is a controller that triggers USB device connection by switching its Vbus signal. It is done by controlling EL7156 from arduino. The arduino is loaded with firmata, which allows remote control over serial from the PC, using pyduino.
__init__(self, name, controller_port, connect_delay, logger=None)
controller_port
: tty port of the controllerconnect_delay
: delay between disconnecting and reconnecting the USB, in seconds
ClientProcessController¶
kitty.controllers.client_process.ClientProcessController
(kitty.controllers.client.ClientController
)
Starts the client with subprocess.Popen
, collects stdout and
stderr.
__init__(self, name, process_path, process_args, logger=None)
TcpSystemController¶
kitty.controllers.tcp_system.TcpSystemController
(kitty.controllers.base.BaseController
)
this controller controls a process on a remote machine by sending tcp commands over the network to a local agent on the remote machine to execute using popen
__init__(self, name, logger, proc_name, host, port)
proc_name
: name of victim processhost
: hostname of agentport
: port of agent