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)