Skip to content

Commit

Permalink
Merge pull request #205 from jtpereyda/webuix
Browse files Browse the repository at this point in the history
Add Web UI Shinies Including Streaming Test Case Logs
  • Loading branch information
jtpereyda authored Aug 8, 2018
2 parents a00c64f + 04938a4 commit 51a8204
Show file tree
Hide file tree
Showing 17 changed files with 572 additions and 186 deletions.
16 changes: 16 additions & 0 deletions CHANGELOG.rst
Original file line number Diff line number Diff line change
@@ -1,3 +1,19 @@
v0.1.0
======
Features
--------
- Web UI
- Statistics now auto-update.
- Test case logs now stream on the main page.
- Cool left & right arrow buttons to move through test case
- New ``Session`` parameter ``receive_data_after_fuzz``. Controls whether to execute a receive step after sending
fuzz messages. Defaults to False. This significantly speeds up tests in which the target tends not to respond to
invalid messages.

Fixes
-----
- Text log output would include double titles, e.g. "Test Step: Test Step: ..."

v0.0.13
=======
Features
Expand Down
1 change: 1 addition & 0 deletions MANIFEST.in
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ include *.rst
include *.txt
recursive-include boofuzz *.md
recursive-include boofuzz *.py
recursive-include boofuzz *.js
recursive-include examples *.py
recursive-include examples *.md
recursive-include requests *.html
Expand Down
4 changes: 2 additions & 2 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -86,12 +86,12 @@ here on GitHub.

For other questions, check out boofuzz on `gitter`_ or `Google Groups`_.

For updates, follow `@fuzztheplanet`_ on Twitter.
For updates, follow `@b00fuzz`_ on Twitter.

.. _Sulley: https://github.com/OpenRCE/sulley
.. _Google Groups: https://groups.google.com/d/forum/boofuzz
.. _gitter: https://gitter.im/jtpereyda/boofuzz
.. _@fuzztheplanet: https://twitter.com/fuzztheplanet
.. _@b00fuzz:: https://twitter.com/b00fuzz
.. _documentation: http://boofuzz.readthedocs.io/
.. _INSTALL.rst: INSTALL.rst
.. _CONTRIBUTING.rst: CONTRIBUTING.rst
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.13'
__version__ = '0.1.0'

DEFAULT_PROCMON_PORT = 26002

Expand Down
2 changes: 1 addition & 1 deletion boofuzz/test_case_data.py → boofuzz/data_test_case.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@


@attr.s
class TestCaseData(object):
class DataTestCase(object):
name = attr.ib()
index = attr.ib()
timestamp = attr.ib()
Expand Down
2 changes: 1 addition & 1 deletion boofuzz/test_step_data.py → boofuzz/data_test_step.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@


@attr.s
class TestStepData(object):
class DataTestStep(object):
type = attr.ib()
description = attr.ib()
data = attr.ib()
Expand Down
12 changes: 6 additions & 6 deletions boofuzz/fuzz_logger_db.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@

from . import helpers
from . import ifuzz_logger_backend
from . import test_case_data
from . import test_step_data
from . import data_test_case
from . import data_test_step


def hex_to_hexstr(input_bytes):
Expand Down Expand Up @@ -61,8 +61,8 @@ def get_test_case_data(self, index):
pass
else:
raise
steps.append(test_step_data.TestStepData(type=row[1], description=row[2], data=data, timestamp=row[4]))
return test_case_data.TestCaseData(name=test_case_row[0], index=test_case_row[1], timestamp=test_case_row[2],
steps.append(data_test_step.DataTestStep(type=row[1], description=row[2], data=data, timestamp=row[4]))
return data_test_case.DataTestCase(name=test_case_row[0], index=test_case_row[1], timestamp=test_case_row[2],
steps=steps)

def open_test_case(self, test_case_id, name, index, *args, **kwargs):
Expand Down Expand Up @@ -139,6 +139,6 @@ def get_test_case_data(self, index):
pass
else:
raise
steps.append(test_step_data.TestStepData(type=row[1], description=row[2], data=data, timestamp=row[4]))
return test_case_data.TestCaseData(name=test_case_row[0], index=test_case_row[1], timestamp=test_case_row[2],
steps.append(data_test_step.DataTestStep(type=row[1], description=row[2], data=data, timestamp=row[4]))
return data_test_case.DataTestCase(name=test_case_row[0], index=test_case_row[1], timestamp=test_case_row[2],
steps=steps)
32 changes: 11 additions & 21 deletions boofuzz/fuzz_logger_text.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,16 +18,6 @@ class FuzzLoggerText(ifuzz_logger_backend.IFuzzLoggerBackend):
Using two FuzzLoggerTexts, a FuzzLogger instance can be configured to output to
both console and file.
"""
TEST_CASE_FORMAT = Fore.YELLOW + Style.BRIGHT + "Test Case: {0}" + Style.RESET_ALL
TEST_STEP_FORMAT = Fore.MAGENTA + Style.BRIGHT + "Test Step: {0}" + Style.RESET_ALL
LOG_ERROR_FORMAT = Back.RED + Style.BRIGHT + "Error!!!! {0}" + Style.RESET_ALL
LOG_CHECK_FORMAT = "Check: {0}"
LOG_INFO_FORMAT = "Info: {0}"
LOG_PASS_FORMAT = Fore.GREEN + Style.BRIGHT + "Check OK: {0}" + Style.RESET_ALL
LOG_FAIL_FORMAT = Fore.RED + Style.BRIGHT + "Check Failed: {0}" + Style.RESET_ALL
LOG_RECV_FORMAT = Fore.CYAN + "Received: {0}" + Style.RESET_ALL
LOG_SEND_FORMAT = Fore.CYAN + "Transmitting {0} bytes: {1}" + Style.RESET_ALL
DEFAULT_TEST_CASE_ID = "DefaultTestCase"
INDENT_SIZE = 2

def __init__(self, file_handle=sys.stdout, bytes_to_str=DEFAULT_HEX_TO_STR):
Expand All @@ -42,42 +32,42 @@ def __init__(self, file_handle=sys.stdout, bytes_to_str=DEFAULT_HEX_TO_STR):
self._format_raw_bytes = bytes_to_str

def open_test_step(self, description):
self._print_log_msg(self.TEST_STEP_FORMAT.format(description),
self._print_log_msg(msg=description,
msg_type='step')

def log_check(self, description):
self._print_log_msg(self.LOG_CHECK_FORMAT.format(description),
self._print_log_msg(msg=description,
msg_type='check')

def log_error(self, description):
self._print_log_msg(self.LOG_ERROR_FORMAT.format(description),
self._print_log_msg(msg=description,
msg_type='error')

def log_recv(self, data):
self._print_log_msg(self.LOG_RECV_FORMAT.format(self._format_raw_bytes(data)),
self._print_log_msg(data=data,
msg_type='receive')

def log_send(self, data):
self._print_log_msg(
self.LOG_SEND_FORMAT.format(len(data), self._format_raw_bytes(data)),
data=data,
msg_type='send')

def log_info(self, description):
self._print_log_msg(self.LOG_INFO_FORMAT.format(description),
self._print_log_msg(msg=description,
msg_type='info')

def open_test_case(self, test_case_id, name, index, *args, **kwargs):
self._print_log_msg(self.TEST_CASE_FORMAT.format(test_case_id),
self._print_log_msg(msg=test_case_id,
msg_type='test_case')

def log_fail(self, description=""):
self._print_log_msg(self.LOG_FAIL_FORMAT.format(description),
self._print_log_msg(msg=description,
msg_type='fail')

def log_pass(self, description=""):
self._print_log_msg(self.LOG_PASS_FORMAT.format(description),
self._print_log_msg(msg=description,
msg_type='pass')

def _print_log_msg(self, msg, msg_type):
print(helpers.format_log_msg(msg_type=msg_type, description=msg, indent_size=self.INDENT_SIZE),
def _print_log_msg(self, msg_type, msg=None, data=None):
print(helpers.format_log_msg(msg_type=msg_type, description=msg, data=data, indent_size=self.INDENT_SIZE),
file=self._file_handle)
30 changes: 13 additions & 17 deletions boofuzz/sessions.py
Original file line number Diff line number Diff line change
Expand Up @@ -277,12 +277,14 @@ class Session(pgraph.Graph):
fuzz_data_logger (fuzz_logger.FuzzLogger): DEPRECATED. Use fuzz_loggers instead.
fuzz_loggers (list of ifuzz_logger.IFuzzLogger): For saving test data and results.. Default Log to STDOUT.
receive_data_after_each_request (bool): If True, Session will attempt to receive a reply after transmitting
each node. Default True.
each non-fuzzed node. Default True.
check_data_received_each_request (bool): If True, Session will verify that some data has
been received after transmitting each node, and if not, register a
failure. If False, this check will not be performed. Default False.
A receive attempt is still made unless receive_data_after_each_request
is False.
been received after transmitting each non-fuzzed node, and if not,
register a failure. If False, this check will not be performed. Default
False. A receive attempt is still made unless
receive_data_after_each_request is False.
receive_data_after_fuzz (bool): If True, Session will attempt to receive a reply after transmitting
a fuzzed message. Default False.
ignore_connection_reset (bool): Log ECONNRESET errors ("Target connection reset") as "info" instead of
failures.
ignore_connection_aborted (bool): Log ECONNABORTED errors as "info" instead of failures.
Expand All @@ -309,6 +311,7 @@ def __init__(self, session_filename=None, index_start=1, index_end=None, sleep_t
fuzz_loggers=None,
receive_data_after_each_request=True,
check_data_received_each_request=False,
receive_data_after_fuzz=False,
log_level=logging.INFO, logfile=None, logfile_level=logging.DEBUG,
ignore_connection_reset=False,
ignore_connection_aborted=False,
Expand Down Expand Up @@ -341,6 +344,7 @@ def __init__(self, session_filename=None, index_start=1, index_end=None, sleep_t
self._fuzz_data_logger = fuzz_logger.FuzzLogger(fuzz_loggers=[self._db_logger] + fuzz_loggers)
self._check_data_received_each_request = check_data_received_each_request
self._receive_data_after_each_request = receive_data_after_each_request
self._receive_data_after_fuzz = receive_data_after_fuzz
self._skip_current_node_after_current_test_case = False
self._skip_current_element_after_current_test_case = False

Expand Down Expand Up @@ -794,8 +798,8 @@ def _process_failures(self, target):
synopsis = "({0} reports) {1}".format(len(crash_synopses), "\n".join(crash_synopses))
else:
synopsis = "\n".join(crash_synopses)
self.procmon_results[self.total_mutant_index] = synopsis
self._fuzz_data_logger.log_info(self.procmon_results[self.total_mutant_index].split("\n")[0])
self.procmon_results[self.total_mutant_index] = crash_synopses
self._fuzz_data_logger.log_info(synopsis)

if self.fuzz_node.mutant is not None and \
self.crashing_primitives[self.fuzz_node] >= self._crash_threshold_node:
Expand Down Expand Up @@ -1031,16 +1035,8 @@ def transmit_fuzz(self, sock, node, edge, callback_data):
.format(e.socket_errno, e.socket_errmsg))

try: # recv
if self._receive_data_after_each_request:
if self._receive_data_after_fuzz:
self.last_recv = self.targets[0].recv(10000) # TODO: Remove magic number (10000)

if self._check_data_received_each_request:
self._fuzz_data_logger.log_check("Verify some data was received from the target.")
if not self.last_recv:
# Assume a crash?
self._fuzz_data_logger.log_fail("Nothing received from target.")
else:
self._fuzz_data_logger.log_pass("Some data received from target.")
except sex.BoofuzzTargetConnectionReset:
if self._check_data_received_each_request:
self._fuzz_data_logger.log_fail("Target connection reset.")
Expand Down Expand Up @@ -1455,6 +1451,6 @@ def test_case_data(self, index):
index (int): Test case index
Returns:
Test case data object
DataTestCase: Test case data object
"""
return self._db_logger.get_test_case_data(index=index)
93 changes: 74 additions & 19 deletions boofuzz/web/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
from flask import Flask, render_template, redirect
import re

MAX_LOG_LINE_LEN = 1500

app = Flask(__name__)
app.session = None

Expand All @@ -24,28 +26,61 @@ def pause():

@app.route('/test-case/<int:crash_id>')
def test_case(crash_id):
return render_template("test-case.html", crashinfo=app.session.procmon_results.get(crash_id, None), test_case=app.session.test_case_data(crash_id))
return render_template("test-case.html", crashinfo=app.session.procmon_results.get(crash_id, None),
test_case=app.session.test_case_data(crash_id))


@app.route("/")
def index():
crashes = []
procmon_result_keys = app.session.procmon_results.keys()
procmon_result_keys.sort()
@app.route('/api/current-test-case')
def current_test_case_update():
data = {
'index': app.session.total_mutant_index,
'log_data': _get_log_data(app.session.total_mutant_index),
}
return flask.jsonify(data)

for key in procmon_result_keys:
val = app.session.procmon_results[key]
status_bytes = "&nbsp;"

if key in app.session.netmon_results:
status_bytes = commify(app.session.netmon_results[key])
@app.route('/api/test-case/<int:test_case_index>')
def api_test_case(test_case_index):
data = {
'index': test_case_index,
'log_data': _get_log_data(test_case_id=test_case_index),
}
return flask.jsonify(data)


def _get_log_data(test_case_id):
results = []
case = app.session.test_case_data(test_case_id)
if case is not None:
results.append({'css_class': case.css_class, 'log_line': case.html_log_line})
for step in case.steps:
line = step.html_log_line
if len(line) > MAX_LOG_LINE_LEN:
line = line[:MAX_LOG_LINE_LEN] + ' (truncated)'
results.append({'css_class':step.css_class, 'log_line': line})
return results


@app.route('/api/current-run')
def index_update():
data = {
'session_info': {
'is_paused': app.session.is_paused,
'current_index': app.session.total_mutant_index,
'num_mutations': app.session.total_num_mutations,
'current_index_element': app.session.fuzz_node.mutant_index,
'num_mutations_element': app.session.fuzz_node.num_mutations(),
'current_element': app.session.fuzz_node.name,
'crashes': _crash_summary_info(),
},
}

return flask.jsonify(data)

crash = {
"key": key,
"value": val.split("\n")[0],
"status_bytes": status_bytes
}
crashes.append(crash)

@app.route("/")
def index():
crashes = _crash_summary_info()

# which node (request) are we currently fuzzing.
if app.session.fuzz_node is not None and app.session.fuzz_node.name:
Expand All @@ -69,7 +104,7 @@ def index():
progress_current = 0
progress_current_bar = ''
mutant_index = 0
num_mutations = 100 # TODO improve template instead of hard coding fake values
num_mutations = 100 # TODO improve template instead of hard coding fake values

total_mutant_index = float(app.session.total_mutant_index)
total_num_mutations = float(app.session.total_num_mutations)
Expand All @@ -95,4 +130,24 @@ def index():
"total_num_mutations": commify(int(total_num_mutations)),
}

return render_template('index.html', state=state, crashes=crashes)
return render_template('index.html', state=state, crashes=crashes)


def _crash_summary_info():
crashes = []
procmon_result_keys = app.session.procmon_results.keys()
procmon_result_keys.sort()
for key in procmon_result_keys:
val = app.session.procmon_results[key]
status_bytes = "&nbsp;"

if key in app.session.netmon_results:
status_bytes = commify(app.session.netmon_results[key])

crash = {
"key": key,
"reasons": val,
"status_bytes": status_bytes
}
crashes.append(crash)
return crashes
Loading

0 comments on commit 51a8204

Please sign in to comment.