diff --git a/htpclient/hashcat_cracker.py b/htpclient/hashcat_cracker.py index 06f900b..6075326 100644 --- a/htpclient/hashcat_cracker.py +++ b/htpclient/hashcat_cracker.py @@ -13,7 +13,7 @@ from htpclient.hashcat_status import HashcatStatus from htpclient.initialize import Initialize from htpclient.jsonRequest import JsonRequest, os -from htpclient.helpers import send_error, update_files, kill_hashcat, get_bit, print_speed, get_rules_and_hl, get_wordlist, escape_ansi +from htpclient.helpers import send_error, update_files, kill_hashcat, get_bit, print_speed, get_rules_and_hl, get_wordlist, escape_ansi, strip_increment from htpclient.dicts import * @@ -452,11 +452,11 @@ def measure_keyspace(self, task, chunk): if 'useBrain' in task and task['useBrain']: full_cmd = f"{full_cmd} -S" - output = b'' try: logging.debug(f"CALL: {full_cmd}") output = subprocess.check_output(full_cmd, shell=True, cwd=self.cracker_path, stderr=subprocess.STDOUT) except subprocess.CalledProcessError as e: + output = e.output if e.output else b'' logging.error("Error during keyspace measure: " + str(e) + " Output: " + output.decode(encoding='utf-8')) send_error("Keyspace measure failed!", self.config.get_value('token'), task['taskId'], None) sleep(5) @@ -637,7 +637,7 @@ def run_speed_benchmark(self, task): hashlist_out_path = Path(self.config.get_value('hashlists-path'), f"{str(task['hashlistId'])}.out") if 'usePrince' in task and task['usePrince']: - attackcmd = get_rules_and_hl(update_files(task['attackcmd'])) + attackcmd = get_rules_and_hl(update_files(task['attackcmd']), task['hashlistAlias']) # Replace #HL# with the real hashlist attackcmd = attackcmd.replace(task['hashlistAlias'], f'"{hashlist_path}"') @@ -663,13 +663,17 @@ def run_speed_benchmark(self, task): args.append(f'"{hashlist_out_path}"') full_cmd = ' '.join(args) + # hashcat rejects --progress-only combined with --increment, which would + # make the speed benchmark fail for every increment task. Drop the + # increment flags: the measured speed is independent of the mask length. + full_cmd = strip_increment(full_cmd) full_cmd = f"{self.callPath} {full_cmd}" - output = b'' try: logging.debug(f"CALL: {''.join(full_cmd)}") output = subprocess.check_output(full_cmd, shell=True, cwd=self.cracker_path, stderr=subprocess.STDOUT) except subprocess.CalledProcessError as e: + output = e.output if e.output else b'' logging.error("Error during speed benchmark, return code: " + str(e.returncode) + " Output: " + output.decode(encoding='utf-8')) send_error("Speed benchmark failed!", self.config.get_value('token'), task['taskId'], None) return 0 diff --git a/htpclient/helpers.py b/htpclient/helpers.py index 2698cbe..95948ce 100644 --- a/htpclient/helpers.py +++ b/htpclient/helpers.py @@ -105,14 +105,33 @@ def get_rules_and_hl(command, alias): return " ".join(rules) +# hashcat rejects --increment together with --progress-only, so the speed +# benchmark can't run it. Drop the increment flags; the measured cracking speed +# is independent of the mask length / increment range. Iterate the tokens +# directly and keep any empty ones - they rejoin as harmless spaces, so there is +# nothing to filter out. +def strip_increment(command): + ret = [] + skip_next = False + for part in command.split(" "): + if skip_next: + skip_next = False + continue + if part == '--increment' or part == '-i': + continue + if part == '--increment-min' or part == '--increment-max': + skip_next = True + continue + if part.startswith('--increment-min=') or part.startswith('--increment-max='): + continue + ret.append(part) + return " ".join(ret) + + def clean_list(element_list): - index = 0 - for part in element_list: - if not part: - del element_list[index] - index -= 1 - index += 1 - return element_list + # Drop empty entries. Must not delete while iterating the same list - + # that skips elements and leaves real tokens behind. + return [part for part in element_list if part] # the prince flag is deprecated diff --git a/tests/test_helpers.py b/tests/test_helpers.py new file mode 100644 index 0000000..c483648 --- /dev/null +++ b/tests/test_helpers.py @@ -0,0 +1,51 @@ +import unittest + +from htpclient.helpers import strip_increment, clean_list + + +class CleanList(unittest.TestCase): + def test_removes_all_empty_entries_including_consecutive_ones(self): + self.assertEqual(clean_list(['a', '', '', 'b', '', 'c']), ['a', 'b', 'c']) + + def test_leaves_full_list_untouched(self): + self.assertEqual(clean_list(['-a', '3', '?d?d']), ['-a', '3', '?d?d']) + + +class StripIncrement(unittest.TestCase): + def test_removes_increment_with_equals_values(self): + cmd = '#HL# -a 3 ?1 --increment --increment-min=1 --increment-max=6 -1 ?l?u?d?s' + expected = '#HL# -a 3 ?1 -1 ?l?u?d?s' + self.assertEqual(strip_increment(cmd), expected) + + def test_removes_increment_with_space_separated_values(self): + cmd = '#HL# -a 3 ?1 --increment --increment-min 1 --increment-max 6 -1 ?l?u?d?s' + expected = '#HL# -a 3 ?1 -1 ?l?u?d?s' + self.assertEqual(strip_increment(cmd), expected) + + def test_removes_short_increment_flag(self): + cmd = '#HL# -a 3 ?d?d?d?d -i' + expected = '#HL# -a 3 ?d?d?d?d' + self.assertEqual(strip_increment(cmd), expected) + + def test_leaves_command_without_increment_untouched(self): + cmd = '#HL# -a 0 example.dict -r best64.rule' + self.assertEqual(strip_increment(cmd), cmd) + + def test_does_not_touch_unrelated_flags(self): + cmd = '#HL# -a 3 ?a?a?a --increment -w 3' + expected = '#HL# -a 3 ?a?a?a -w 3' + self.assertEqual(strip_increment(cmd), expected) + + def test_preserves_mask_with_runs_of_spaces(self): + # The assembled benchmark command contains runs of spaces; the mask and + # every other real token must survive (regression: a mask like ?d?d?d?d?d + # was being dropped, leaving -a 3 with no mask). + cmd = '-a 3 "/hashlists/11" ?d?d?d?d?d --hash-type=500 -o "/out"' + result = strip_increment(cmd) + self.assertIn('?d?d?d?d?d', result.split()) + self.assertIn('--hash-type=500', result.split()) + self.assertIn('-o', result.split()) + + +if __name__ == '__main__': + unittest.main()