Skip to content

Commit

Permalink
added experiminetal feature to query performance data from msrpc
Browse files Browse the repository at this point in the history
  • Loading branch information
ghost-ng committed Jan 23, 2024
1 parent 31d9091 commit d1c7342
Show file tree
Hide file tree
Showing 8 changed files with 249 additions and 31 deletions.
7 changes: 5 additions & 2 deletions src/slinger.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
2 changes: 1 addition & 1 deletion src/slingerpkg/__init__.py
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
__version__ = '0.6.0'
__version__ = '0.7.0'
__package__ = 'slinger'
127 changes: 106 additions & 21 deletions src/slingerpkg/lib/dcetransport.py
Original file line number Diff line number Diff line change
Expand Up @@ -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 = {}
Expand Down Expand Up @@ -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
Expand All @@ -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
Expand All @@ -815,14 +856,15 @@ 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']

# 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
Expand Down Expand Up @@ -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] = "<null>"
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


58 changes: 55 additions & 3 deletions src/slingerpkg/lib/msrpcperformance.py
Original file line number Diff line number Diff line change
Expand Up @@ -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 = '<I' # 32-bit unsigned integer
Expand All @@ -31,7 +75,8 @@ def parse_perf_counter(data, pos, counter_definition):
pos = end_pos

return True, pos, result
except struct.error:
except struct.error as e:
print_debug("MSRPC: ERROR: Error unpacking data: {}".format(e), sys.exc_info())
return False, "Error unpacking data", None


Expand Down Expand Up @@ -66,6 +111,7 @@ def parse_perf_instance_definition(data, pos=0): # no need for 64 bit han
try:
result['InstanceName'] = data[name_start:name_end].decode('utf-16le', errors='ignore').rstrip('\x00')
except UnicodeDecodeError:
print_debug("MSRPC: ERROR: Error decoding instance name")
result['InstanceName'] = "Instance name contains invalid characters"
return False, pos, result

Expand Down Expand Up @@ -125,6 +171,7 @@ def parse_perf_counter_definition(data, pos=0, is_64bit=False): # nee

return True, pos, counter_def
except struct.error as e:
print_debug("MSRPC: ERROR: Error unpacking data: {}".format(e), sys.exc_info())
return False, "Error unpacking data: " + str(e), None


Expand Down Expand Up @@ -172,6 +219,7 @@ def parse_perf_object_type(data, pos=0, is_64bit=False):

return True, pos, object_type
except struct.error as e:
print_debug("MSRPC: ERROR: Error unpacking data: {}".format(e), sys.exc_info())
return False, "Error unpacking data: " + str(e), None


Expand All @@ -186,9 +234,13 @@ def parse_perf_counter_block(data, pos=0): # no need for 64 bit handling

return True, pos, {'ByteLength': byte_length}
except struct.error as e:
print_debug("MSRPC: ERROR: Error unpacking data: {}".format(e), sys.exc_info())
return False, "Error unpacking data: {}".format(e), None


def parse_perf_counter_block_test(data, pos=0): # no need for 64 bit handling
result = {}
pos, result['ByteLength'] = unmarshall_int32(data, pos)
return True, pos, result

def remove_null_terminator(s):
# Remove common null terminator patterns from the end of the string
Expand Down
Loading

0 comments on commit d1c7342

Please sign in to comment.