diff --git a/src/slinger.py b/src/slinger.py index 71916bc..8cc03fd 100755 --- a/src/slinger.py +++ b/src/slinger.py @@ -141,8 +141,11 @@ def main(): except SystemExit: continue except Exception as e: - print_warning(f"Uncaught Error: {e}") - print_debug(str(e), sys.exc_info()) + if "Invalid command entered" in str(e): + pass + else: + print_warning(f"Uncaught Error: {e}") + print_debug(str(e), sys.exc_info()) continue except argparse.ArgumentError as e: diff --git a/src/slingerpkg/__init__.py b/src/slingerpkg/__init__.py index 91f77e0..20d8e42 100755 --- a/src/slingerpkg/__init__.py +++ b/src/slingerpkg/__init__.py @@ -1,2 +1,2 @@ -__version__ = '0.6.0' +__version__ = '0.7.0' __package__ = 'slinger' \ No newline at end of file diff --git a/src/slingerpkg/lib/dcetransport.py b/src/slingerpkg/lib/dcetransport.py index b717552..10c26db 100755 --- a/src/slingerpkg/lib/dcetransport.py +++ b/src/slingerpkg/lib/dcetransport.py @@ -738,12 +738,40 @@ def _reg_create_key(self, keyName): print_debug('Error 0x%08x while creating key %s' % (ans2['ErrorCode'], keyName)) return False - + def _GetTitleDatabase(self, arch=64): + + result = {} + if not self.is_connected: + raise Exception("Not connected to remote host") + self.bind_override = True + try: + self._bind(rrp.MSRPC_UUID_RRP) + except: + pass + + # Open Performance Data + openhkpd_result = rrp.hOpenPerformanceData(self.dce) + + queryvalue_result = rrp.hBaseRegQueryValue(self.dce, openhkpd_result['phKey'], lpValueName="Counter 009") + + pos = 0 + result = {} + + status, pos, result['title_database'] = parse_perf_title_database(queryvalue_result[1], pos) + # print the title database using tabulate + if status: + return result['title_database'] + else: + return None + def _hQueryPerformaceData(self, object_num, arch=64): + print_warning("Performance Data querying is experimental and is still under development") if arch == 64: - bitwise = True + print_debug("Setting 64-bit architecture in hQueryPerformaceData") + bitwise = True else: + print_debug("Setting 32-bit architecture in hQueryPerformaceData") bitwise = False result = {} @@ -776,15 +804,20 @@ def _hQueryPerformaceData(self, object_num, arch=64): pos = 0 status, pos, data_block = parse_perf_data_block(queryvalue_result[1], pos) # works up to here - - #for key, value in data_block.items(): - # print(key + ": " + str(value)) + # store values in a list + dbg_perf_block_list = [] + for key, value in data_block.items(): + dbg_perf_block_list.append(f"{key} : {str(value)}") + + # print list with \n as separator + s = "\n".join(dbg_perf_block_list) + print_debug(f"Data Block: \n{s}") if not status: print("Error parsing data block") return False, pos - + print_debug(f"Found #{data_block['NumObjectTypes']} object types") for i in range(data_block['NumObjectTypes']): object_start = pos @@ -795,12 +828,20 @@ def _hQueryPerformaceData(self, object_num, arch=64): # Get the type of the object status, pos, object_type = parse_perf_object_type(queryvalue_result[1], pos, is_64bit=bitwise) # correct up to here - if not status: - return False, pos - #print_info("Object Type: " + str(object_type)) object_name = result['title_database'][object_type['ObjectNameTitleIndex']] + print_debug(f"Object #{i} - Object Name: " + str(object_name), sys.exc_info()) + print_debug(f"Object #{i} - Object Type: " + str(object_type), sys.exc_info()) + + if not status: + return False, pos + + if object_type['ObjectNameTitleIndex'] == 0: + print_debug("Skipping object type with index 0") + pos = object_start + object_type['TotalByteLength'] + continue + result[object_name] = {} # correct up to here # Bring the position to the beginning of the counter definitions @@ -815,7 +856,7 @@ def _hQueryPerformaceData(self, object_num, arch=64): print_debug("Error parsing counter definitions", sys.exc_info()) return False, pos - #print_info("Counter Definitions: " + str(counter_definitions)) # correct up to here + print_debug("Counter Definitions: \n" + str(counter_definitions)) # correct up to here # Bring the position to the beginning of the instances (or counters) pos = object_start + object_type['DefinitionLength'] @@ -823,6 +864,7 @@ def _hQueryPerformaceData(self, object_num, arch=64): # Check if we have any instances #print_info("NumInstances: " + str(object_type['NumInstances'])) if object_type['NumInstances'] > 0: + print_debug(f"Found {str(object_type['NumInstances'])} instances") # Parse the object instances and counters for j in range(object_type['NumInstances']): instance_start = pos @@ -853,34 +895,77 @@ def _hQueryPerformaceData(self, object_num, arch=64): print_debug("Error parsing counter block", sys.exc_info()) return False, pos #print_info("NumCounters: " + str(object_type['NumCounters'])) + print_debug("NumCounters: " + str(object_type['NumCounters']), sys.exc_info()) for k in range(object_type['NumCounters']): + # Each individual counter - status, pos, counter_result = parse_perf_counter(queryvalue_result[1], pos, counter_definitions[k]) + status, pos, counter_result = parse_perf_counter_data(queryvalue_result[1], pos, counter_definitions[k]) if not status: print_debug("Error parsing counter", sys.exc_info()) return False, pos counter_name = result['title_database'][counter_definitions[k]['CounterNameTitleIndex']] + print_debug(f"#{k} Counter Name: " + str(counter_name), sys.exc_info()) result[object_name][instance_name][counter_name] = counter_result # Bring the pos to the end of the next section pos = instance_start + object_instances[j]['ByteLength'] + counter_block['ByteLength'] - else: - print_debug("Did not find instances") - print_debug("NumCounters: " + str(object_type['NumCounters']), sys.exc_info()) - for k in range(object_type['NumCounters']+1): - # Each individual counter - status, pos, counter_result = parse_perf_counter(queryvalue_result[1], pos, counter_definitions[k]) - print("Counter Result: " + str(counter_result)) + else: # if NumInstances == 0 + #https://learn.microsoft.com/en-us/windows/win32/perfctrs/performance-data-format + # start at the end of the PERF_COUNTER_DEFINITIONS and PERF_OBJECT_TYPE + print_debug(f"Found {str(object_type['NumInstances'])} instances") + + total_counter_definitions_length = sum(cd['ByteLength'] for cd in counter_definitions.values()) + counter_block_start = object_start + object_type['HeaderLength'] + total_counter_definitions_length + + print_debug("Counter Block Start: " + str(counter_block_start)) + print_debug("Original Position: " + str(pos)) + + # Parse the PERF_COUNTER_BLOCK + status, pos, counter_block = parse_perf_counter_block_test(queryvalue_result[1], pos) + print_debug("Counter Block: " + str(counter_block)) + if not status: + # Handle error + print_debug("Error parsing counter block", sys.exc_info()) + return False, pos + + print_debug("New Position: " + str(pos)) + + # Start parsing the counter data + print_debug("NumCounters: " + str(object_type['NumCounters'])) + for k in range(object_type['NumCounters']): + counter_def = counter_definitions[k] + counter_name = result['title_database'][counter_definitions[k]['CounterNameTitleIndex']] + + print_debug(f"#{k} Counter Name: " + str(counter_name)) + + print_debug(f"Counter Block Start: {counter_block_start + counter_def['CounterOffset']} = {counter_block_start} + {counter_def['CounterOffset']}") + counter_block_start = counter_block_start + counter_def['CounterOffset'] + + status, pos, counter_result = parse_perf_counter_data(queryvalue_result[1], counter_block_start, counter_def) + if not status: + # Handle error print_debug("Error parsing counter", sys.exc_info()) - return False, pos + result[object_name][counter_name] = "" + continue + #return False, pos - counter_name = result['title_database'][counter_definitions[k]['CounterNameTitleIndex']] - print_debug("Counter Name: " + str(counter_name), sys.exc_info()) + print_debug("Counter Result: " + str(counter_result)) + + # Store counter result result[object_name][counter_name] = counter_result + + + # Update pos after processing all counters + pos = counter_block_start + counter_block['ByteLength'] + + + print_debug("Exiting Counter Definitions Loop") + + return True, pos, result \ No newline at end of file diff --git a/src/slingerpkg/lib/msrpcperformance.py b/src/slingerpkg/lib/msrpcperformance.py index 3477cdb..4280dd5 100644 --- a/src/slingerpkg/lib/msrpcperformance.py +++ b/src/slingerpkg/lib/msrpcperformance.py @@ -9,7 +9,51 @@ #https://learn.microsoft.com/en-us/windows/win32/api/winperf/ #https://svn.nmap.org/nmap/nselib/msrpcperformance.lua -def parse_perf_counter(data, pos, counter_definition): +""" +PERF_DATA_BLOCK +| +|--> PERF_OBJECT_TYPE #1 + | + |--> PERF_COUNTER_DEFINITION #1 + |--> PERF_COUNTER_DEFINITION #2 + | ... + |--> PERF_COUNTER_DEFINITION #N + | + |--> (If no instances) + | | + | |--> PERF_COUNTER_BLOCK + | | + | |--> Raw Counter Data for Counter #1 + | |--> Raw Counter Data for Counter #2 + | | ... + | |--> Raw Counter Data for Counter #N + | + |--> (If instances exist) + | + |--> PERF_INSTANCE_DEFINITION #1 + | | + | |--> Instance Name #1 + | |--> PERF_COUNTER_BLOCK #1 + | | + | |--> Raw Counter Data for Counter #1 + | |--> Raw Counter Data for Counter #2 + | | ... + | |--> Raw Counter Data for Counter #N + | + |--> PERF_INSTANCE_DEFINITION #2 + | ... + |--> PERF_INSTANCE_DEFINITION #M + | + |--> Instance Name #M + |--> PERF_COUNTER_BLOCK #M + | + |--> Raw Counter Data for Counter #1 + |--> Raw Counter Data for Counter #2 + | ... + |--> Raw Counter Data for Counter #N +""" + +def parse_perf_counter_data(data, pos, counter_definition): try: # Define format strings int32_fmt = ')' have duplicate names but are unique processes") - \ No newline at end of file + def show_avail_counters(self, args): + self.setup_dce_transport() + print_info("Retrieving Title Database") + if self.titledb_list: + for elem in self.titledb_list: + for k,v in elem.items(): + if args.filter: + if args.filter.lower() in v.lower(): + print(f"{k} - {v}") + else: + print(f"{k} - {v}") + return + self.dce_transport._connect('winreg') + result = self.dce_transport._GetTitleDatabase() + # sort result by key + result = dict(sorted(result.items())) + #enter_interactive_mode(local=locals()) + # remove all non-ascii characters + for k,v in result.items(): + desc = re.sub(r'[^\x00-\x7f]',r'', v) + self.titledb_list.append({k: desc}) + + + for elem in self.titledb_list: + for k,v in elem.items(): + if args.filter: + if args.filter.lower() in v.lower(): + print(f"{k} - {v}") + else: + print(f"{k} - {v}") + + def show_perf_counter(self, args): + + # get current debug value + original_debug_value = get_config_value("debug") + set_config_value("debug", "True") + + + if not args.counter: + print_warning("Invalid arguments. Usage: debug-counter ") + return + self.setup_dce_transport() + self.dce_transport._connect('winreg') + result = self.dce_transport._GetTitleDatabase() + self.dce_transport._connect('winreg') + print_info("Retrieving Performance Counter: " + result[args.counter]) + if args.arch == "unk": + arch = self.get_processor_architecture() + elif args.arch == "x86": + arch = "32" + elif args.arch == "x64": + arch = "64" + self.dce_transport._connect('winreg') + result = self.dce_transport._hQueryPerformaceData(str(args.counter), int(arch)) + # remove the title database entry + title_db = result[2].pop("title_database") + perfData = result[2] + print_good("'result'\tAccess the entire Performance Counter dictionary") + print_good("'perfData'\tAccess the Performance Counter Data only") + combined_scope = globals().copy() + combined_scope.update(locals()) + enter_interactive_debug_mode(local=locals()) + set_config_value("debug", original_debug_value) + \ No newline at end of file diff --git a/src/slingerpkg/utils/cli.py b/src/slingerpkg/utils/cli.py index e919861..08cf34f 100755 --- a/src/slingerpkg/utils/cli.py +++ b/src/slingerpkg/utils/cli.py @@ -133,7 +133,7 @@ def setup_cli_parser(slingerClient): parser_pwd = subparsers.add_parser('pwd', help='Print working directory', description='Print the current working directory on the remote server', epilog='Example Usage: pwd') parser_pwd.set_defaults(func=slingerClient.print_current_path) # Subparser for 'exit' command - parser_exit = subparsers.add_parser('exit', help='Exit the program', description='Exit the application', epilog='Example Usage: exit') + parser_exit = subparsers.add_parser('exit', help='Exit the program', description='Exit the application', epilog='Example Usage: exit', aliases=['quit', 'logout', 'logoff']) # Subparser for 'help' command parser_help = subparsers.add_parser('help', help='Show help message', description='Display help information for the application', epilog='Example Usage: help') @@ -384,6 +384,16 @@ def setup_cli_parser(slingerClient): parser_env = subparsers.add_parser('env', help='Display environment variables', description='Display environment variables on the remote server', epilog='Example Usage: env') parser_env.set_defaults(func=slingerClient.show_env_handler) + parser_availCounters = subparsers.add_parser('debug-availcounters', help='Display available performance counters. This is for debug use only, it doesn\'t really give you anything.', description='Display available performance counters on the remote server. This is for debug use only, it doesn\'t really give you anything.', epilog='Example Usage: availcounters') + parser_availCounters.add_argument('-f', '--filter', help='Simple filter for case insenstive counters containing a given string', default=None, type=str) + parser_availCounters.set_defaults(func=slingerClient.show_avail_counters) + + parser_getCounter = subparsers.add_parser('debug-counter', help='Display a performance counter. This is for debug use only, it doesn\'t really give you anything.', description='Display a performance counter on the remote server. This is for debug use only, it doesn\'t really give you anything.', epilog='Example Usage: counter -c 123 [-a x86]') + parser_getCounter.add_argument('-c','--counter', help='Specify the counter to display', default=None, type=int) + parser_getCounter.add_argument('-a', '--arch', help='Specify the architecture of the remote server', choices=['x86','x64', 'unk'], default='unk') + parser_getCounter.set_defaults(func=slingerClient.show_perf_counter) + + return parser # def validate_args(parser, arg_list): diff --git a/src/slingerpkg/utils/common.py b/src/slingerpkg/utils/common.py index e7cafd1..67483be 100755 --- a/src/slingerpkg/utils/common.py +++ b/src/slingerpkg/utils/common.py @@ -109,8 +109,10 @@ def validate_xml(xml_string): print_log(e) return False -def enter_interactive_mode(local=locals()): +def enter_interactive_debug_mode(local=locals()): import code + combined_scope = globals().copy() + combined_scope.update(locals()) print_info("Entering interactive mode") print_warning("Ctrl-D to end interactive mode") code.interact(local=local) diff --git a/src/slingerpkg/utils/logger.py b/src/slingerpkg/utils/logger.py index ab7b3f1..016abc4 100644 --- a/src/slingerpkg/utils/logger.py +++ b/src/slingerpkg/utils/logger.py @@ -34,4 +34,6 @@ def error_logging(e): exc_type, exc_value, exc_traceback = e tb_lines = format_exception(exc_type, exc_value, exc_traceback) error_message = ''.join(tb_lines) + if "NoneType" in error_message: + error_message = "" return error_message \ No newline at end of file