Skip to content

Commit 51a8204

Browse files
authored
Merge pull request #205 from jtpereyda/webuix
Add Web UI Shinies Including Streaming Test Case Logs
2 parents a00c64f + 04938a4 commit 51a8204

17 files changed

+572
-186
lines changed

CHANGELOG.rst

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,19 @@
1+
v0.1.0
2+
======
3+
Features
4+
--------
5+
- Web UI
6+
- Statistics now auto-update.
7+
- Test case logs now stream on the main page.
8+
- Cool left & right arrow buttons to move through test case
9+
- New ``Session`` parameter ``receive_data_after_fuzz``. Controls whether to execute a receive step after sending
10+
fuzz messages. Defaults to False. This significantly speeds up tests in which the target tends not to respond to
11+
invalid messages.
12+
13+
Fixes
14+
-----
15+
- Text log output would include double titles, e.g. "Test Step: Test Step: ..."
16+
117
v0.0.13
218
=======
319
Features

MANIFEST.in

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ include *.rst
33
include *.txt
44
recursive-include boofuzz *.md
55
recursive-include boofuzz *.py
6+
recursive-include boofuzz *.js
67
recursive-include examples *.py
78
recursive-include examples *.md
89
recursive-include requests *.html

README.rst

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -86,12 +86,12 @@ here on GitHub.
8686

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

89-
For updates, follow `@fuzztheplanet`_ on Twitter.
89+
For updates, follow `@b00fuzz`_ on Twitter.
9090

9191
.. _Sulley: https://github.com/OpenRCE/sulley
9292
.. _Google Groups: https://groups.google.com/d/forum/boofuzz
9393
.. _gitter: https://gitter.im/jtpereyda/boofuzz
94-
.. _@fuzztheplanet: https://twitter.com/fuzztheplanet
94+
.. _@b00fuzz:: https://twitter.com/b00fuzz
9595
.. _documentation: http://boofuzz.readthedocs.io/
9696
.. _INSTALL.rst: INSTALL.rst
9797
.. _CONTRIBUTING.rst: CONTRIBUTING.rst

boofuzz/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@
2828
from .sex import SullyRuntimeError, SizerNotUtilizedError, MustImplementException
2929
from .socket_connection import SocketConnection
3030

31-
__version__ = '0.0.13'
31+
__version__ = '0.1.0'
3232

3333
DEFAULT_PROCMON_PORT = 26002
3434

boofuzz/test_case_data.py renamed to boofuzz/data_test_case.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66

77

88
@attr.s
9-
class TestCaseData(object):
9+
class DataTestCase(object):
1010
name = attr.ib()
1111
index = attr.ib()
1212
timestamp = attr.ib()

boofuzz/test_step_data.py renamed to boofuzz/data_test_step.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66

77

88
@attr.s
9-
class TestStepData(object):
9+
class DataTestStep(object):
1010
type = attr.ib()
1111
description = attr.ib()
1212
data = attr.ib()

boofuzz/fuzz_logger_db.py

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,8 @@
44

55
from . import helpers
66
from . import ifuzz_logger_backend
7-
from . import test_case_data
8-
from . import test_step_data
7+
from . import data_test_case
8+
from . import data_test_step
99

1010

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

6868
def open_test_case(self, test_case_id, name, index, *args, **kwargs):
@@ -139,6 +139,6 @@ def get_test_case_data(self, index):
139139
pass
140140
else:
141141
raise
142-
steps.append(test_step_data.TestStepData(type=row[1], description=row[2], data=data, timestamp=row[4]))
143-
return test_case_data.TestCaseData(name=test_case_row[0], index=test_case_row[1], timestamp=test_case_row[2],
142+
steps.append(data_test_step.DataTestStep(type=row[1], description=row[2], data=data, timestamp=row[4]))
143+
return data_test_case.DataTestCase(name=test_case_row[0], index=test_case_row[1], timestamp=test_case_row[2],
144144
steps=steps)

boofuzz/fuzz_logger_text.py

Lines changed: 11 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -18,16 +18,6 @@ class FuzzLoggerText(ifuzz_logger_backend.IFuzzLoggerBackend):
1818
Using two FuzzLoggerTexts, a FuzzLogger instance can be configured to output to
1919
both console and file.
2020
"""
21-
TEST_CASE_FORMAT = Fore.YELLOW + Style.BRIGHT + "Test Case: {0}" + Style.RESET_ALL
22-
TEST_STEP_FORMAT = Fore.MAGENTA + Style.BRIGHT + "Test Step: {0}" + Style.RESET_ALL
23-
LOG_ERROR_FORMAT = Back.RED + Style.BRIGHT + "Error!!!! {0}" + Style.RESET_ALL
24-
LOG_CHECK_FORMAT = "Check: {0}"
25-
LOG_INFO_FORMAT = "Info: {0}"
26-
LOG_PASS_FORMAT = Fore.GREEN + Style.BRIGHT + "Check OK: {0}" + Style.RESET_ALL
27-
LOG_FAIL_FORMAT = Fore.RED + Style.BRIGHT + "Check Failed: {0}" + Style.RESET_ALL
28-
LOG_RECV_FORMAT = Fore.CYAN + "Received: {0}" + Style.RESET_ALL
29-
LOG_SEND_FORMAT = Fore.CYAN + "Transmitting {0} bytes: {1}" + Style.RESET_ALL
30-
DEFAULT_TEST_CASE_ID = "DefaultTestCase"
3121
INDENT_SIZE = 2
3222

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

4434
def open_test_step(self, description):
45-
self._print_log_msg(self.TEST_STEP_FORMAT.format(description),
35+
self._print_log_msg(msg=description,
4636
msg_type='step')
4737

4838
def log_check(self, description):
49-
self._print_log_msg(self.LOG_CHECK_FORMAT.format(description),
39+
self._print_log_msg(msg=description,
5040
msg_type='check')
5141

5242
def log_error(self, description):
53-
self._print_log_msg(self.LOG_ERROR_FORMAT.format(description),
43+
self._print_log_msg(msg=description,
5444
msg_type='error')
5545

5646
def log_recv(self, data):
57-
self._print_log_msg(self.LOG_RECV_FORMAT.format(self._format_raw_bytes(data)),
47+
self._print_log_msg(data=data,
5848
msg_type='receive')
5949

6050
def log_send(self, data):
6151
self._print_log_msg(
62-
self.LOG_SEND_FORMAT.format(len(data), self._format_raw_bytes(data)),
52+
data=data,
6353
msg_type='send')
6454

6555
def log_info(self, description):
66-
self._print_log_msg(self.LOG_INFO_FORMAT.format(description),
56+
self._print_log_msg(msg=description,
6757
msg_type='info')
6858

6959
def open_test_case(self, test_case_id, name, index, *args, **kwargs):
70-
self._print_log_msg(self.TEST_CASE_FORMAT.format(test_case_id),
60+
self._print_log_msg(msg=test_case_id,
7161
msg_type='test_case')
7262

7363
def log_fail(self, description=""):
74-
self._print_log_msg(self.LOG_FAIL_FORMAT.format(description),
64+
self._print_log_msg(msg=description,
7565
msg_type='fail')
7666

7767
def log_pass(self, description=""):
78-
self._print_log_msg(self.LOG_PASS_FORMAT.format(description),
68+
self._print_log_msg(msg=description,
7969
msg_type='pass')
8070

81-
def _print_log_msg(self, msg, msg_type):
82-
print(helpers.format_log_msg(msg_type=msg_type, description=msg, indent_size=self.INDENT_SIZE),
71+
def _print_log_msg(self, msg_type, msg=None, data=None):
72+
print(helpers.format_log_msg(msg_type=msg_type, description=msg, data=data, indent_size=self.INDENT_SIZE),
8373
file=self._file_handle)

boofuzz/sessions.py

Lines changed: 13 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -277,12 +277,14 @@ class Session(pgraph.Graph):
277277
fuzz_data_logger (fuzz_logger.FuzzLogger): DEPRECATED. Use fuzz_loggers instead.
278278
fuzz_loggers (list of ifuzz_logger.IFuzzLogger): For saving test data and results.. Default Log to STDOUT.
279279
receive_data_after_each_request (bool): If True, Session will attempt to receive a reply after transmitting
280-
each node. Default True.
280+
each non-fuzzed node. Default True.
281281
check_data_received_each_request (bool): If True, Session will verify that some data has
282-
been received after transmitting each node, and if not, register a
283-
failure. If False, this check will not be performed. Default False.
284-
A receive attempt is still made unless receive_data_after_each_request
285-
is False.
282+
been received after transmitting each non-fuzzed node, and if not,
283+
register a failure. If False, this check will not be performed. Default
284+
False. A receive attempt is still made unless
285+
receive_data_after_each_request is False.
286+
receive_data_after_fuzz (bool): If True, Session will attempt to receive a reply after transmitting
287+
a fuzzed message. Default False.
286288
ignore_connection_reset (bool): Log ECONNRESET errors ("Target connection reset") as "info" instead of
287289
failures.
288290
ignore_connection_aborted (bool): Log ECONNABORTED errors as "info" instead of failures.
@@ -309,6 +311,7 @@ def __init__(self, session_filename=None, index_start=1, index_end=None, sleep_t
309311
fuzz_loggers=None,
310312
receive_data_after_each_request=True,
311313
check_data_received_each_request=False,
314+
receive_data_after_fuzz=False,
312315
log_level=logging.INFO, logfile=None, logfile_level=logging.DEBUG,
313316
ignore_connection_reset=False,
314317
ignore_connection_aborted=False,
@@ -341,6 +344,7 @@ def __init__(self, session_filename=None, index_start=1, index_end=None, sleep_t
341344
self._fuzz_data_logger = fuzz_logger.FuzzLogger(fuzz_loggers=[self._db_logger] + fuzz_loggers)
342345
self._check_data_received_each_request = check_data_received_each_request
343346
self._receive_data_after_each_request = receive_data_after_each_request
347+
self._receive_data_after_fuzz = receive_data_after_fuzz
344348
self._skip_current_node_after_current_test_case = False
345349
self._skip_current_element_after_current_test_case = False
346350

@@ -794,8 +798,8 @@ def _process_failures(self, target):
794798
synopsis = "({0} reports) {1}".format(len(crash_synopses), "\n".join(crash_synopses))
795799
else:
796800
synopsis = "\n".join(crash_synopses)
797-
self.procmon_results[self.total_mutant_index] = synopsis
798-
self._fuzz_data_logger.log_info(self.procmon_results[self.total_mutant_index].split("\n")[0])
801+
self.procmon_results[self.total_mutant_index] = crash_synopses
802+
self._fuzz_data_logger.log_info(synopsis)
799803

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

10331037
try: # recv
1034-
if self._receive_data_after_each_request:
1038+
if self._receive_data_after_fuzz:
10351039
self.last_recv = self.targets[0].recv(10000) # TODO: Remove magic number (10000)
1036-
1037-
if self._check_data_received_each_request:
1038-
self._fuzz_data_logger.log_check("Verify some data was received from the target.")
1039-
if not self.last_recv:
1040-
# Assume a crash?
1041-
self._fuzz_data_logger.log_fail("Nothing received from target.")
1042-
else:
1043-
self._fuzz_data_logger.log_pass("Some data received from target.")
10441040
except sex.BoofuzzTargetConnectionReset:
10451041
if self._check_data_received_each_request:
10461042
self._fuzz_data_logger.log_fail("Target connection reset.")
@@ -1455,6 +1451,6 @@ def test_case_data(self, index):
14551451
index (int): Test case index
14561452
14571453
Returns:
1458-
Test case data object
1454+
DataTestCase: Test case data object
14591455
"""
14601456
return self._db_logger.get_test_case_data(index=index)

boofuzz/web/app.py

Lines changed: 74 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22
from flask import Flask, render_template, redirect
33
import re
44

5+
MAX_LOG_LINE_LEN = 1500
6+
57
app = Flask(__name__)
68
app.session = None
79

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

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

2932

30-
@app.route("/")
31-
def index():
32-
crashes = []
33-
procmon_result_keys = app.session.procmon_results.keys()
34-
procmon_result_keys.sort()
33+
@app.route('/api/current-test-case')
34+
def current_test_case_update():
35+
data = {
36+
'index': app.session.total_mutant_index,
37+
'log_data': _get_log_data(app.session.total_mutant_index),
38+
}
39+
return flask.jsonify(data)
3540

36-
for key in procmon_result_keys:
37-
val = app.session.procmon_results[key]
38-
status_bytes = "&nbsp;"
3941

40-
if key in app.session.netmon_results:
41-
status_bytes = commify(app.session.netmon_results[key])
42+
@app.route('/api/test-case/<int:test_case_index>')
43+
def api_test_case(test_case_index):
44+
data = {
45+
'index': test_case_index,
46+
'log_data': _get_log_data(test_case_id=test_case_index),
47+
}
48+
return flask.jsonify(data)
49+
50+
51+
def _get_log_data(test_case_id):
52+
results = []
53+
case = app.session.test_case_data(test_case_id)
54+
if case is not None:
55+
results.append({'css_class': case.css_class, 'log_line': case.html_log_line})
56+
for step in case.steps:
57+
line = step.html_log_line
58+
if len(line) > MAX_LOG_LINE_LEN:
59+
line = line[:MAX_LOG_LINE_LEN] + ' (truncated)'
60+
results.append({'css_class':step.css_class, 'log_line': line})
61+
return results
62+
63+
64+
@app.route('/api/current-run')
65+
def index_update():
66+
data = {
67+
'session_info': {
68+
'is_paused': app.session.is_paused,
69+
'current_index': app.session.total_mutant_index,
70+
'num_mutations': app.session.total_num_mutations,
71+
'current_index_element': app.session.fuzz_node.mutant_index,
72+
'num_mutations_element': app.session.fuzz_node.num_mutations(),
73+
'current_element': app.session.fuzz_node.name,
74+
'crashes': _crash_summary_info(),
75+
},
76+
}
77+
78+
return flask.jsonify(data)
4279

43-
crash = {
44-
"key": key,
45-
"value": val.split("\n")[0],
46-
"status_bytes": status_bytes
47-
}
48-
crashes.append(crash)
80+
81+
@app.route("/")
82+
def index():
83+
crashes = _crash_summary_info()
4984

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

74109
total_mutant_index = float(app.session.total_mutant_index)
75110
total_num_mutations = float(app.session.total_num_mutations)
@@ -95,4 +130,24 @@ def index():
95130
"total_num_mutations": commify(int(total_num_mutations)),
96131
}
97132

98-
return render_template('index.html', state=state, crashes=crashes)
133+
return render_template('index.html', state=state, crashes=crashes)
134+
135+
136+
def _crash_summary_info():
137+
crashes = []
138+
procmon_result_keys = app.session.procmon_results.keys()
139+
procmon_result_keys.sort()
140+
for key in procmon_result_keys:
141+
val = app.session.procmon_results[key]
142+
status_bytes = "&nbsp;"
143+
144+
if key in app.session.netmon_results:
145+
status_bytes = commify(app.session.netmon_results[key])
146+
147+
crash = {
148+
"key": key,
149+
"reasons": val,
150+
"status_bytes": status_bytes
151+
}
152+
crashes.append(crash)
153+
return crashes

0 commit comments

Comments
 (0)