Skip to content

Commit

Permalink
Merge pull request #185 from jtpereyda/fuzz-case-names
Browse files Browse the repository at this point in the history
Fuzz case names
  • Loading branch information
jtpereyda authored Apr 16, 2018
2 parents 90f1014 + 1a6de5d commit b3c3c68
Show file tree
Hide file tree
Showing 3 changed files with 176 additions and 93 deletions.
11 changes: 11 additions & 0 deletions CHANGELOG.rst
Original file line number Diff line number Diff line change
@@ -1,3 +1,14 @@
0.0.12
======
Features
--------
- Test cases now have descriptive names
- Added Session methods to fuzz a test cae by name: `fuzz_by_name` and `fuzz_single_node_by_path`

Fixes
-----
- Fixed test case numbers when using `fuzz_single_case`

0.0.11
======
Features
Expand Down
2 changes: 1 addition & 1 deletion boofuzz/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@
from .sex import SullyRuntimeError, SizerNotUtilizedError, MustImplementException
from .socket_connection import SocketConnection

__version__ = '0.0.11'
__version__ = '0.0.12'


# REQUEST MANAGEMENT
Expand Down
256 changes: 164 additions & 92 deletions boofuzz/sessions.py
Original file line number Diff line number Diff line change
@@ -1,24 +1,25 @@
from __future__ import absolute_import
import zlib
import time

import cPickle
import threading
import logging
from tornado.wsgi import WSGIContainer
import re
import sys
import threading
import time
import zlib

from tornado.httpserver import HTTPServer
from tornado.ioloop import IOLoop

import sys
from tornado.wsgi import WSGIContainer

from . import blocks
from . import pgraph
from . import sex
from . import primitives
from . import ifuzz_logger
from . import fuzz_logger
from . import event_hook
from . import fuzz_logger
from . import fuzz_logger_text

from . import ifuzz_logger
from . import pgraph
from . import primitives
from . import sex
from .web.app import app


Expand Down Expand Up @@ -202,6 +203,7 @@ class Session(pgraph.Graph):
logfile_level (int): DEPRECATED Unused. Logger settings are now configured in fuzz_data_logger.
Was once used to set the log level for the logfile. Default logger.INFO.
"""

def __init__(self, session_filename=None, skip=0, sleep_time=0.0, restart_interval=0, web_port=26000,
crash_threshold=3, restart_sleep_time=5, fuzz_data_logger=None,
check_data_received_each_request=True,
Expand Down Expand Up @@ -383,23 +385,77 @@ def export_file(self):
fh.close()

def fuzz(self):
"""
Call this routine to get the ball rolling.
"""Fuzz the entire protocol tree.
Iterates through and fuzzes all fuzz cases, skipping according to
self.skip and restarting based on self.restart_interval.
If you want the web server to be available, your program must persist
after calling this method. helpers.pause_for_signal() is
available to this end.
Returns:
None
"""
self.total_mutant_index = 0
self.total_num_mutations = self.num_mutations()

self._main_fuzz_loop(self._iterate_protocol())

def fuzz_single_node_by_path(self, node_names):
"""Fuzz a particular node via the path in node_names.
Args:
node_names (list of str): List of node names leading to target.
"""
node_edges = self._path_names_to_edges(node_names=node_names)

self.total_mutant_index = 0
self.total_num_mutations = self.nodes[node_edges[-1].dst].num_mutations()

self._main_fuzz_loop(self._iterate_single_node(node_edges))

def fuzz_by_name(self, name):
"""Fuzz a particular test case or node by name.
Args:
name (str): Name of node.
"""
self.fuzz_single_node_by_path(re.split('->', name))

def fuzz_single_case(self, mutant_index):
"""Fuzz a test case by mutant_index.
Args:
mutant_index (int): Positive non-zero integer.
Returns:
None
Raises:
sex.SulleyRuntimeError: If any error is encountered while executing the test case.
"""
self.total_mutant_index = 0
self.total_num_mutations = 1

self._main_fuzz_loop(self._iterate_single_case_by_index(mutant_index))

def _main_fuzz_loop(self, fuzz_case_iterator):
"""Execute main fuzz logic; takes an iterator of test cases.
Preconditions: `self.total_mutant_index` and `self.total_num_mutations` are set properly.
Args:
fuzz_case_iterator (Iterable): An iterator that walks through fuzz cases.
Returns:
None
"""
self.server_init()

try:
num_cases_actually_fuzzed = 0
for fuzz_args in self._fuzz_case_iterator():
for fuzz_args in fuzz_case_iterator:
# skip until we pass self.skip
if self.total_mutant_index <= self.skip:
continue
Expand Down Expand Up @@ -431,26 +487,6 @@ def fuzz(self):
" detection is not working.")
self.export_file()

def fuzz_single_case(self, mutant_index):
"""
Fuzz a test case by mutant_index.
Args:
mutant_index (int): Non-negative integer.
Returns:
None
Raises:
sex.SulleyRuntimeError: If any error is encountered while executing the test case.
"""
fuzz_index = 1
for fuzz_args in self._fuzz_case_iterator():
if fuzz_index == mutant_index:
self._fuzz_current_case(*fuzz_args)
break
fuzz_index += 1

def import_file(self):
"""
Load various object values from disk.
Expand Down Expand Up @@ -696,9 +732,6 @@ def server_init(self):
"""Called by fuzz() to initialize variables, web interface, etc.
"""
if not self.web_interface_thread.isAlive():
self.total_mutant_index = 0
self.total_num_mutations = self.num_mutations()

# spawn the web interface.
self.web_interface_thread.start()

Expand Down Expand Up @@ -764,99 +797,138 @@ def build_webapp_thread(self, port=26000):
flask_thread.daemon = True
return flask_thread

def _fuzz_case_iterator(self, this_node=None, path=()):
def _iterate_protocol(self):
"""
Iterates over fuzz cases and mutates appropriately.
On each iteration, one may call fuzz_current_case to do the
actual fuzzing.
No arguments are necessary as they are both utilized internally
during the recursive traversal of the session graph.
Args:
this_node (node.Node, optional): Current node that is being fuzzed. Default None.
path (list, optional): Nodes along the path to the current one being fuzzed. Default [].
:raise sex.SullyRuntimeError:
"""
# if no node is specified, then we start from the root node..
if this_node is None:
# we can't fuzz if we don't have at least one target and one request.
if not self.targets:
raise sex.SullyRuntimeError("No targets specified in session")
# we can't fuzz if we don't have at least one target and one request.
if not self.targets:
raise sex.SullyRuntimeError("No targets specified in session")

if not self.edges_from(self.root.id):
raise sex.SullyRuntimeError("No requests specified in session")
if not self.edges_from(self.root.id):
raise sex.SullyRuntimeError("No requests specified in session")

self._reset_fuzz_state()
self._reset_fuzz_state()

this_node = self.root
for x in self._iterate_protocol_recursive(this_node=self.root, path=[]):
yield x

if isinstance(path, tuple):
path = list(path)
def _iterate_protocol_recursive(self, this_node, path):
"""
Recursively iterates over fuzz nodes. Used by _fuzz_case_iterator.
Args:
this_node (node.Node): Current node that is being fuzzed.
path (list of Connection): List of edges along the path to the current one being fuzzed.
:raise sex.SullyRuntimeError:
"""
# step through every edge from the current node.
for edge in self.edges_from(this_node.id):
# the destination node is the one actually being fuzzed.
self.fuzz_node = self.nodes[edge.dst]

# keep track of the path as we fuzz through it, don't count the root node.
# we keep track of edges as opposed to nodes because if there is more then one path through a set of
# given nodes we don't want any ambiguity.
path.append(edge)

current_path = " -> ".join([self.nodes[e.src].name for e in path[1:]])
current_path += " -> %s" % self.fuzz_node.name

self._fuzz_data_logger.log_info("current fuzz path: %s" % current_path)

# Loop through and yield all possible mutations of the fuzz node.
# Note: when mutate() returns False, the node has been reverted to the default (valid) state.
while self.fuzz_node.mutate():
self.total_mutant_index += 1
yield (edge, path)

if self._skip_after_cur_test_case:
self._skip_after_cur_test_case = False
break
self.fuzz_node.reset()
for x in self._iterate_single_node(path):
yield x

# recursively fuzz the remainder of the nodes in the session graph.
for x in self._fuzz_case_iterator(self.fuzz_node, path):
for x in self._iterate_protocol_recursive(self.fuzz_node, path):
yield x

# finished with the last node on the path, pop it off the path stack.
if path:
path.pop()

def _fuzz_current_case(self, edge, path):
def _iterate_single_node(self, path):
"""Iterate fuzz cases for the last node in path.
Args:
path (list of Connection): Nodes along the path to the current one being fuzzed.
Raises:
sex.SullyRuntimeError:
"""
self.fuzz_node = self.nodes[path[-1].dst]
# Loop through and yield all possible mutations of the fuzz node.
# Note: when mutate() returns False, the node has been reverted to the default (valid) state.
while self.fuzz_node.mutate():
self.total_mutant_index += 1
yield (path,)

if self._skip_after_cur_test_case:
self._skip_after_cur_test_case = False
break
self.fuzz_node.reset()

def _iterate_single_case_by_index(self, test_case_index):
fuzz_index = 1
for fuzz_args in self._iterate_protocol():
if fuzz_index >= test_case_index:
self.total_mutant_index = 1
yield fuzz_args
break
fuzz_index += 1

def _path_names_to_edges(self, node_names):
"""Take a list of node names and return a list of edges describing that path.
Args:
node_names (list of str): List of node names describing a path.
Returns:
list of Connection: List of edges describing the path in node_names.
"""
cur_node = self.root
edge_path = []
for node_name in node_names:
next_node = None
for edge in self.edges_from(cur_node.id):
if self.nodes[edge.dst].name == node_name:
edge_path.append(edge)
next_node = self.nodes[edge.dst]
break
if next_node is None:
raise Exception("No edge found from {0} to {1}".format(cur_node.name, node_name))
else:
cur_node = next_node
return edge_path

def _fuzz_current_case(self, path):
"""
Fuzzes the current test case. Current test case is controlled by
fuzz_case_iterator().
:param edge:
:param path:
:return:
Args:
path(list of Connection): Path to take to get to the target node.
"""
target = self.targets[0]

self.pause() # only pauses conditionally

self._fuzz_data_logger.open_test_case(self.total_mutant_index)
message_path = "->".join([self.nodes[e.dst].name for e in path])

if self.fuzz_node.mutant.name:
msg = "primitive name: \"%s\", " % self.fuzz_node.mutant.name
primitive_under_test = self.fuzz_node.mutant.name
else:
msg = "primitive name: None, "
primitive_under_test = 'no-name'

test_case_name = "{0}.{1}.{2}".format(message_path, primitive_under_test, self.fuzz_node.mutant_index)

self._fuzz_data_logger.open_test_case("{0}: {1}".format(self.total_mutant_index, test_case_name))

msg += "type: %s, default value: %s" % (
type(self.fuzz_node.mutant).__name__, self.fuzz_node.mutant.original_value
)
self._fuzz_data_logger.log_info(msg)
self._fuzz_data_logger.log_info(
"Test case %d of %d for this node. %d of %d overall." % (self.fuzz_node.mutant_index,
self.fuzz_node.num_mutations(),
self.total_mutant_index,
self.total_num_mutations))
"Type: %s. Default value: %s. Case %d of %d overall." % (
type(self.fuzz_node.mutant).__name__,
self.fuzz_node.mutant.original_value,
self.total_mutant_index,
self.total_num_mutations))

if target.procmon:
target.procmon.pre_send(self.total_mutant_index)
Expand All @@ -874,7 +946,7 @@ def _fuzz_current_case(self, edge, path):
self.transmit(target, node, e)

self._fuzz_data_logger.open_test_step("Fuzzing Node '{0}'".format(self.fuzz_node.name))
self.transmit(target, self.fuzz_node, edge)
self.transmit(target, self.fuzz_node, path[-1])

self._fuzz_data_logger.open_test_step("Calling post_send function:")
try:
Expand Down

0 comments on commit b3c3c68

Please sign in to comment.