diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 3ac9d7f8..da23dca2 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -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 diff --git a/MANIFEST.in b/MANIFEST.in index 930b9719..7c91e497 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -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 diff --git a/README.rst b/README.rst index 863bcac4..af9b0d92 100644 --- a/README.rst +++ b/README.rst @@ -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 diff --git a/boofuzz/__init__.py b/boofuzz/__init__.py index b8568e89..3eb4f7b3 100644 --- a/boofuzz/__init__.py +++ b/boofuzz/__init__.py @@ -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 diff --git a/boofuzz/test_case_data.py b/boofuzz/data_test_case.py similarity index 97% rename from boofuzz/test_case_data.py rename to boofuzz/data_test_case.py index 45bb98b9..e1b7fb33 100644 --- a/boofuzz/test_case_data.py +++ b/boofuzz/data_test_case.py @@ -6,7 +6,7 @@ @attr.s -class TestCaseData(object): +class DataTestCase(object): name = attr.ib() index = attr.ib() timestamp = attr.ib() diff --git a/boofuzz/test_step_data.py b/boofuzz/data_test_step.py similarity index 96% rename from boofuzz/test_step_data.py rename to boofuzz/data_test_step.py index 217030eb..74720cf6 100644 --- a/boofuzz/test_step_data.py +++ b/boofuzz/data_test_step.py @@ -6,7 +6,7 @@ @attr.s -class TestStepData(object): +class DataTestStep(object): type = attr.ib() description = attr.ib() data = attr.ib() diff --git a/boofuzz/fuzz_logger_db.py b/boofuzz/fuzz_logger_db.py index 63be06d2..faaa574b 100644 --- a/boofuzz/fuzz_logger_db.py +++ b/boofuzz/fuzz_logger_db.py @@ -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): @@ -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): @@ -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) diff --git a/boofuzz/fuzz_logger_text.py b/boofuzz/fuzz_logger_text.py index ca5fa05d..b9fe4c24 100644 --- a/boofuzz/fuzz_logger_text.py +++ b/boofuzz/fuzz_logger_text.py @@ -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): @@ -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) diff --git a/boofuzz/sessions.py b/boofuzz/sessions.py index ce8a923a..e353e44b 100644 --- a/boofuzz/sessions.py +++ b/boofuzz/sessions.py @@ -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. @@ -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, @@ -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 @@ -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: @@ -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.") @@ -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) diff --git a/boofuzz/web/app.py b/boofuzz/web/app.py index 7b05dc9d..4acdb230 100644 --- a/boofuzz/web/app.py +++ b/boofuzz/web/app.py @@ -2,6 +2,8 @@ from flask import Flask, render_template, redirect import re +MAX_LOG_LINE_LEN = 1500 + app = Flask(__name__) app.session = None @@ -24,28 +26,61 @@ def pause(): @app.route('/test-case/') 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 = " " - if key in app.session.netmon_results: - status_bytes = commify(app.session.netmon_results[key]) +@app.route('/api/test-case/') +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: @@ -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) @@ -95,4 +130,24 @@ def index(): "total_num_mutations": commify(int(total_num_mutations)), } - return render_template('index.html', state=state, crashes=crashes) \ No newline at end of file + 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 = " " + + 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 diff --git a/boofuzz/web/static/css/boofuzz.css b/boofuzz/web/static/css/boofuzz.css index 7b5ba9e2..c247da56 100644 --- a/boofuzz/web/static/css/boofuzz.css +++ b/boofuzz/web/static/css/boofuzz.css @@ -1,18 +1,25 @@ +:root { + --foreground-color:#A0B0B0; +} a:link {color: #FF8200; text-decoration: none;} a:visited {color: #FF8200; text-decoration: none;} a:hover {color: #C5C5C5; text-decoration: none;} +.link { + cursor: pointer; + color: #FF8200; +} + body { background-color: #000000; font-family: Arial, Helvetica, sans-serif; font-size: 12px; - color: #A0B0B0; + color: var(--foreground-color); } .fixed { font-family: Courier New, sans-serif; font-size: 12px; - color: #A0B0B0; } .input { @@ -24,22 +31,134 @@ body { height: 20px; } +button.input { + padding: 7px; + border: 2px; + text-align: center; +} +button.input div { +} +.input i { + border: solid; + border-color: var(--foreground-color); + border-width: 0 3px 3px 0; + display: inline-block; + padding: 3px; + margin: auto; +} + +.right { + transform: rotate(-45deg); + -webkit-transform: rotate(-45deg); +} + +.left { + transform: rotate(135deg); + -webkit-transform: rotate(135deg); +} + +.up { + transform: rotate(-135deg); + -webkit-transform: rotate(-135deg); +} + +.down { + transform: rotate(45deg); + -webkit-transform: rotate(45deg); +} + + .running { color: green; + text-transform: uppercase; + font-weight: bold; + font-size: 20px; } .paused { color: red; + text-transform: uppercase; + font-weight: bold; + font-size: 20px; } .main-wrapper { + display: table; + float: left; + margin-left: auto; + margin-right: auto; +} + +.fixed-width-wrapper { + display: table; + float: left; + margin-left: auto; + margin-right: auto; +} + +.log-wrapper { + display: table; + clear: both; margin-left: auto; margin-right: auto; - display: block; +} + +.main-title { + font-size: 20px; +} + +header.test-case-log-header { + border-bottom: 1px solid #000; + display: table; + width: 100%; + margin-top: 10px; +} + +.test-case-log-title { + display: table-cell; + width: 40%; +} + +.test-case-log-input-area { + display: table-cell; + width: 60%; +} + +.test-case-log-input-center { + display: flex; + width: 100%; + align-items: center; + justify-content: space-around; +} + +.test-case-log-input-center span { + /*vertical-align: top;*/ + /*float: left;*/ +} + +table.summary { + border: 0; + border-spacing: 0; +} +table.summary td { + padding: 5px; +} + +.summary-header { + background-color: #333333; +} + +.summary-content { + background-color: #111111; +} + +.summary-content-row-header-text { + font-weight: bold; } table.test-steps td { white-space: pre-wrap; + word-break: break-word; font-family: "SFMono-Regular",Consolas,"Liberation Mono",Menlo,Courier,monospace; margin: 0; } diff --git a/boofuzz/web/static/js/index.js b/boofuzz/web/static/js/index.js new file mode 100644 index 00000000..b04cc0fc --- /dev/null +++ b/boofuzz/web/static/js/index.js @@ -0,0 +1,238 @@ +nbsp = '\xa0'; +let failure_map = {}; +let test_case_log_snap = true; +let test_case_log_index = 0; + +const StringUtilities = { + repeat: function (str, times) { + return (new Array(times + 1)).join(str); + } +}; + +function update_current_run_info(response) { + document.getElementById('current_index').textContent = response.session_info.current_index; + document.getElementById('num_mutations').textContent = response.session_info.num_mutations; + document.getElementById('current_index_element').textContent = response.session_info.current_index_element; + document.getElementById('num_mutations_element').textContent = response.session_info.num_mutations_element; + document.getElementById('current_element').textContent = response.session_info.current_element; + + + let fraction_complete_total = response.session_info.current_index / response.session_info.num_mutations; + document.getElementById('progress_percentage_total').textContent = progress_percentage(fraction_complete_total); + document.getElementById('progress_bar_total').textContent = progress_bars(fraction_complete_total); + + let fraction_complete_element = response.session_info.current_index_element / response.session_info.num_mutations_element; + document.getElementById('progress_percentage_element').textContent = progress_percentage(fraction_complete_element); + document.getElementById('progress_bar_element').textContent = progress_bars(fraction_complete_element); + + if (response.session_info.is_paused){ + document.getElementById('is_paused_indicator').textContent = 'paused'; + document.getElementById('is_paused_indicator').className = 'paused'; + } + else { + document.getElementById('is_paused_indicator').textContent = 'running'; + document.getElementById('is_paused_indicator').className = 'running'; + + } + + if (response.session_info.crashes.length > 0) { + let failures_table = document.getElementById('crash-summary-table'); + + for (let i = 0; i < response.session_info.crashes.length; i++) { + let key = response.session_info.crashes[i].key; + if (!(key in failure_map)) + { + let reasons = response.session_info.crashes[i].reasons; + failure_map[key] = reasons; + let new_row = failures_table.insertRow(failures_table.rows.length); + + let id_cell = new_row.insertCell(0); + id_cell.className = 'fixed'; + + let failure_id_link = document.createElement('a'); + failure_id_link.textContent = key; + failure_id_link.classList.add('link'); + failure_id_link.addEventListener('click', function(){logNavGoTo(key)}, false); + id_cell.appendChild(failure_id_link); + + let reasons_cell = new_row.insertCell(); + reasons.forEach(function (reason) { + let reason_item = document.createElement('div'); + reason_item.textContent = reason; + reasons_cell.appendChild(reason_item); + }) + } + } + } +} + +function update_current_test_case_log(response) { + logUpdateIndex(response.index); + + // Create log table entries + let new_entries = document.createElement('tbody'); + response.log_data.forEach(function(log_entry) { + let new_span = document.createElement('span'); + new_span.setAttribute('class', log_entry.css_class); + new_span.textContent = log_entry.log_line; + let new_td = document.createElement('td'); + let new_tr = document.createElement('tr'); + new_td.appendChild(new_span); + new_tr.appendChild(new_td); + new_entries.appendChild(new_tr); + }); + + // Insert log table entries + let test_cases_table = document.getElementById('test-steps-table'); + while (test_cases_table.firstChild){ + test_cases_table.removeChild(test_cases_table.firstChild); + } + test_cases_table.appendChild(new_entries); +} + +function continually_update_current_run_info() +{ + function update_repeat(response) + { + update_current_run_info(response); + setTimeout(continually_update_current_run_info, 100); + } + function _repeat_only() + { + setTimeout(continually_update_current_run_info, 100); + } + fetch(new Request('/api/current-run'), {method: 'GET'}) + .then(function(response) { return response.json() }) + .then(update_repeat) + .catch(_repeat_only); +} + +function continually_update_current_test_case_log() +{ + function update_repeat(response) + { + if (test_case_log_snap) { + update_current_test_case_log(response); + } + setTimeout(continually_update_current_test_case_log, 100); + } + function _repeat_only() + { + setTimeout(continually_update_current_test_case_log, 100); + } + if (test_case_log_snap) { + fetch(new Request('/api/current-test-case'), {method: 'GET'}) + .then(function(response) { return response.json() }) + .then(update_repeat) + .catch(_repeat_only); + } + else { + setTimeout(continually_update_current_test_case_log, 100); + } +} + +function updateTestCaseLog(index){ + // function tryAgain() + // { + // setTimeout(function(){updateTestCaseLog(document.getElementById('test-case-log-index-input').textContent.trim())}, 100); + // } + fetch(new Request('/api/test-case/' + index), {method: 'GET'}) + .then(function(response) { return response.json() }) + .then(function(response) {update_current_test_case_log(response);}) + // .catch(tryAgain) + ; +} + +function progress_bars(fraction){ + return '[' + + StringUtilities.repeat('=', Math.round(fraction * 50)) + + StringUtilities.repeat(nbsp, 50 - Math.round(fraction * 50)) + ']'; +} + +function progress_percentage(fraction){ + return (fraction * 100).toFixed(3) + '%'; +} + +function start_live_update() { + initialize_state(); + continually_update_current_run_info(); + continually_update_current_test_case_log(); +} + +function read_failure_map_from_dom() { + let failures_table = document.getElementById('crash-summary-table'); + let failure_rows = Array.from(failures_table.rows).slice(1); + failure_rows.forEach(function (row) { + let key = row.cells[0].textContent.trim(); + failure_map[key] = row.cells[1].textContent.trim(); + }); +} + +function set_failure_link_event_handlers() { + let failures_table = document.getElementById('crash-summary-table'); + let failure_rows = Array.from(failures_table.rows).slice(1); + failure_rows.forEach(function (row) { + let key = row.cells[0].textContent.trim(); + row.cells[0].getElementsByClassName('link')[0].addEventListener('click', function(){logNavGoTo(Number(key))}, false); + }); +} + +function initialize_state(){ + read_failure_map_from_dom(); +} + +function logInputChangeHandler(event){ + logUpdateSnap(false); + let new_index = event.target.value; + updateTestCaseLog(new_index); +} + +function logSnapChangeHandler(event){ + test_case_log_snap = event.target.checked; + if (test_case_log_snap) { + document.getElementById('test-case-log-index-input').value = ''; + } +} + +function logNavMove(num){ + logNavGoTo(test_case_log_index + num); +} + +function logNavGoTo(num){ + logUpdateSnap(false); + logUpdateIndex(num); + logUpdateLogBody(num); +} + +function logUpdateIndex(num){ + test_case_log_index = num; + + let test_case_log_title_index = document.getElementById('test-case-log-title-index'); + test_case_log_title_index.textContent = num; + + let index_input = document.getElementById('test-case-log-index-input'); + if (document.activeElement !== index_input){ + index_input.value = num; + } +} + +function logUpdateLogBody(num){ + updateTestCaseLog(num); +} + +function logUpdateSnap(on){ + test_case_log_snap = on; + document.getElementById('test-case-log-snap').checked = on; +} + +function initPage(){ + test_case_log_snap = document.getElementById('test-case-log-snap').checked; + document.getElementById('test-case-log-index-input').addEventListener('change', logInputChangeHandler, false); + document.getElementById('test-case-log-snap').addEventListener('click', logSnapChangeHandler, false); + document.getElementById('test-case-log-left').addEventListener('click', function(){logNavMove(-1)}, false); + document.getElementById('test-case-log-right').addEventListener('click', function(){logNavMove(1)} , false); + set_failure_link_event_handlers(); + start_live_update(); +} + +document.addEventListener('DOMContentLoaded', initPage, false); diff --git a/boofuzz/web/templates/index.html b/boofuzz/web/templates/index.html index e7356bcb..ebc5b4d2 100644 --- a/boofuzz/web/templates/index.html +++ b/boofuzz/web/templates/index.html @@ -1,92 +1,72 @@ {% extends "base.html" %} {% block body %} +
- - - - -
- - - - - - - +
-
boofuzz Fuzz Control
-
- {% if state.session.is_paused %} -
PAUSED
- {% else %} -
RUNNING
- {% endif %} -
- - - - - - - - +
+
Total: {{ state.total_mutant_index }} of {{ state.total_num_mutations }} {{ state.progress_total_bar | safe }} {{ state.progress_total }}
+ + + + + - - - - +
boofuzz Fuzz Control
+ {% if state.session.is_paused %} +
paused
+ {% else %} +
running
+ {% endif %} +
+ + + + + + + - - - - - - - + + + + + + + -
Total: {{ state.total_mutant_index }} of {{ state.total_num_mutations }} {{ state.progress_total_bar | safe }} {{ state.progress_total }}
- - {{ state.current_name }}: - - - {{ state.current_mutant_index }} - - of - - {{ state.current_num_mutations }} - - {{ state.progress_current_bar | safe }} - - {{ state.progress_current }} -
{{ state.current_name }}: {{ state.current_mutant_index }} of {{ state.current_num_mutations }} {{ state.progress_current_bar | safe }} {{ state.progress_current }}
-
-
- -
-
+
+ +
+
- - - - - - - {% for crash in crashes %} - - - - +
Test Case #Crash SynopsisCaptured Bytes
- - {{crash.key}} - - - {{crash.value}} - - {{crash.status_bytes}} -
+ + + - {% endfor %} + {% for crash in crashes %} + + + + + + {% endfor %}
Test Case #Crash Synopsis
{{crash.key}} {% for reason in crash.reasons %}
{{reason}}
{% endfor %}
-
+
+

Test Case Log:

+
+
+ + + + +
+
+
+
+
+
+
{% endblock %} \ No newline at end of file diff --git a/boofuzz/web/templates/test-case-log-widget.html b/boofuzz/web/templates/test-case-log-widget.html new file mode 100644 index 00000000..80fa6d03 --- /dev/null +++ b/boofuzz/web/templates/test-case-log-widget.html @@ -0,0 +1,12 @@ +{% if test_case is not none %} +

Test Case Log

+ + + {% for step in test_case.steps %} + + {% endfor %} +
{{ test_case.html_log_line }}
{{ step.html_log_line }}
+{% else %} +

Test Case Not Found

+

No record found. This test case does not exist, was not executed, or was not executed yet.

+{% endif %} diff --git a/boofuzz/web/templates/test-case.html b/boofuzz/web/templates/test-case.html index bd040470..a4dbfb5e 100644 --- a/boofuzz/web/templates/test-case.html +++ b/boofuzz/web/templates/test-case.html @@ -12,27 +12,14 @@

Crash Info

{% if crashinfo is none %} None {% else %} -
{{ crashinfo.decode('utf-8', errors='replace') }}
+ {% for reason in crashinfo %} +
{{ reason.decode('utf-8', errors='replace') }}
+ {% endfor %} {% endif %} {% endif %} - {% if test_case is not none %} -

Test Case Log

- - - - - {% for step in test_case.steps %} - - - - {% endfor %} -
{{ test_case.html_log_line }}
{{ step.html_log_line }}
- {% else %} -

Test Case Not Found

-

No record found. This test case does not exist, was not executed, or was not executed yet.

- {% endif %} + {% include 'test-case-log-widget.html' %} {% endblock %} diff --git a/unit_tests/test_fuzz_logger_text.py b/unit_tests/test_fuzz_logger_text.py index f2294322..d66573a0 100644 --- a/unit_tests/test_fuzz_logger_text.py +++ b/unit_tests/test_fuzz_logger_text.py @@ -2,24 +2,11 @@ from __future__ import unicode_literals from builtins import bytes, chr import unittest -import re import StringIO import boofuzz.helpers from boofuzz import fuzz_logger_text -LOGGER_PREAMBLE = ".*" -TEST_CASE_FORMAT = fuzz_logger_text.FuzzLoggerText.TEST_CASE_FORMAT + '\n' -TEST_STEP_FORMAT = fuzz_logger_text.FuzzLoggerText.TEST_STEP_FORMAT + '\n' -LOG_CHECK_FORMAT = fuzz_logger_text.FuzzLoggerText.LOG_CHECK_FORMAT + '\n' -LOG_INFO_FORMAT = fuzz_logger_text.FuzzLoggerText.LOG_INFO_FORMAT + '\n' -LOG_PASS_FORMAT = fuzz_logger_text.FuzzLoggerText.LOG_PASS_FORMAT + '\n' -LOG_FAIL_FORMAT = fuzz_logger_text.FuzzLoggerText.LOG_FAIL_FORMAT + '\n' -LOG_RECV_FORMAT = fuzz_logger_text.FuzzLoggerText.LOG_RECV_FORMAT + '\n' -LOG_SEND_FORMAT = fuzz_logger_text.FuzzLoggerText.LOG_SEND_FORMAT + '\n' -LOG_ERROR_FORMAT = fuzz_logger_text.FuzzLoggerText.LOG_ERROR_FORMAT + '\n' -DEFAULT_TEST_CASE_ID = fuzz_logger_text.FuzzLoggerText.DEFAULT_TEST_CASE_ID - class TestFuzzLoggerTextFreeFunctions(unittest.TestCase): def test_get_time_stamp(self): @@ -367,7 +354,7 @@ def test_log_recv_empty(self): # Then self.virtual_file.seek(0) self.assertTrue(self.some_test_case_id in self.virtual_file.readline()) - self.assertTrue(fuzz_logger_text.DEFAULT_HEX_TO_STR(bytes('', 'ascii')) in self.virtual_file.readline()) + # we don't really care exactly what an empty receive log looks like def test_log_send_empty(self): """ diff --git a/unit_tests/test_session_failure_handling.py b/unit_tests/test_session_failure_handling.py index 0453cd7e..caab9346 100644 --- a/unit_tests/test_session_failure_handling.py +++ b/unit_tests/test_session_failure_handling.py @@ -145,7 +145,7 @@ def x(target): def test_no_response_causes_restart(self): """ Given: A listening server which will give no response - and: A Session ready to fuzz that server + and: A Session ready to fuzz that server, including two messages in sequence When: Calling fuzz_single_case() Then: The restart_target method is called. """ @@ -166,14 +166,19 @@ def test_no_response_causes_restart(self): ) session.restart_target = self._mock_restart_target() - s_initialize("test-msg") + s_initialize("test-msg-a") s_string("test-str-value") s_static("\r\n") - session.connect(s_get("test-msg")) + s_initialize("test-msg-b") + s_string("test-str-value") + s_static("\r\n") + + session.connect(s_get("test-msg-a")) + session.connect(s_get("test-msg-a"), s_get("test-msg-b")) # When - session.fuzz_single_case(1) + session.fuzz_single_case(s_get("test-msg-a").num_mutations() + 1) # Then t.join(THREAD_WAIT_TIMEOUT)