Skip to content

Commit 32d9ed5

Browse files
committed
Detect the DLL proxying technique with Modex
1 parent 960e9bf commit 32d9ed5

File tree

1 file changed

+77
-14
lines changed

1 file changed

+77
-14
lines changed

modex.py

Lines changed: 77 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,9 @@ def get_basic_information(self):
112112
'process_id': self.process_id, 'filename': self.filename,
113113
'number_of_retrieved_pages': len(self.pages)}
114114

115+
def get_information_for_metadata_file(self):
116+
return {'path': self.path, 'base_address': self.base_address, 'size': self.size, 'process_id': self.process_id}
117+
115118

116119
def delete_dmp_files(modules: List[Module]) -> None:
117120
for module in modules:
@@ -288,6 +291,46 @@ def dump_page(page: Page, page_offset: int, file_path: str) -> None:
288291
dumped_page.write(page_contents)
289292

290293

294+
def detect_dll_proxying(modules: List[Module], output_directory: str, detection_info_filename: str, logger) -> List[
295+
str]:
296+
mapped_modules_info: List[Dict[str, Any]] = []
297+
for module in modules:
298+
mapped_modules_info.append(module.get_information_for_metadata_file())
299+
detection_info: Dict[str, Any] = {'mapped_modules': mapped_modules_info}
300+
most_common_path: str = get_most_common_element([module.path.casefold() for module in modules])
301+
most_common_size: int = get_most_common_element([module.size for module in modules])
302+
303+
# Look at the cases where the path or the size do not match with the most common ones and mark them as suspicious
304+
suspicious_modules: List[Module] = []
305+
for module in modules:
306+
if module.path.casefold() != most_common_path or module.size != most_common_size:
307+
suspicious_modules.append(module)
308+
309+
detection_result_key: str = 'dll_proxying_detection_result'
310+
if suspicious_modules:
311+
detection_info[detection_result_key] = True
312+
else:
313+
detection_info[detection_result_key] = False
314+
315+
detection_details: Dict[str, Any] = {}
316+
suspicious_processes: List[int] = []
317+
for suspicious_module in suspicious_modules:
318+
suspicious_processes.append(suspicious_module.process_id)
319+
320+
detection_details['suspicious_processes'] = suspicious_processes
321+
detection_info['detection_details'] = detection_details
322+
323+
files_generated: List[str] = []
324+
detection_info_path: str = os.path.join(output_directory, detection_info_filename)
325+
with open(detection_info_path, 'w') as detection_details_file:
326+
json.dump(detection_info, detection_details_file, ensure_ascii=False, indent=4)
327+
328+
files_generated.append(detection_info_path)
329+
logger.info(
330+
f'\nThe --detect option was supplied to detect the presence of the DLL proxying technique. For more details check the {detection_info_filename} file.')
331+
return files_generated
332+
333+
291334
def mix_modules(modules: List[Module], output_directory: str, mixed_module_filename: str,
292335
mixed_module_metadata_filename: str, dump_anomalies: bool, logger, is_modex_calling: bool,
293336
start_time) -> List[str]:
@@ -428,14 +471,18 @@ def mix_modules(modules: List[Module], output_directory: str, mixed_module_filen
428471
logger.info(
429472
f'\t\t{private_bytes_retrieved / bytes_retrieved:.2%} were private ({private_bytes_retrieved} private bytes in total)')
430473

474+
mixed_modules_info: List[Dict[str, Any]] = []
475+
for module in modules:
476+
mixed_modules_info.append(module.get_information_for_metadata_file())
477+
431478
# Join all the metadata about the mixed module
432479
mixed_module_metadata: Dict[str, Any] = {'module_path': module_path.casefold(),
433480
'module_base_address': hex(module_base_address),
434481
'module_size': module_size,
435482
'general_statistics': {'bytes_retrieved': bytes_retrieved,
436483
'shared_bytes_retrieved': shared_bytes_retrieved,
437484
'private_bytes_retrieved': private_bytes_retrieved},
438-
'pages': mixed_module_pages_metadata}
485+
'pages': mixed_module_pages_metadata, 'mixed_modules': mixed_modules_info}
439486

440487
# Statistics regarding a Modex extraction
441488
if is_modex_calling:
@@ -504,6 +551,10 @@ def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface]
504551
requirements.BooleanRequirement(name='dump_anomalies',
505552
description="When there are different shared pages at the same offset, dump those pages",
506553
default=False,
554+
optional=True),
555+
requirements.BooleanRequirement(name='detect',
556+
description="Detect the presence of the DLL proxying technique",
557+
default=False,
507558
optional=True)
508559
]
509560

@@ -517,6 +568,7 @@ def run(self):
517568

518569
module_supplied: str = self.config['module'].casefold()
519570
dump_anomalies: bool = self.config['dump_anomalies']
571+
is_detect_option_supplied: bool = self.config['detect']
520572
modules_to_mix: List[Module] = []
521573
files_finally_generated: List[str] = [log_file_path]
522574

@@ -550,26 +602,31 @@ def run(self):
550602
module_size = None
551603

552604
if module_base_address is not None and module_size is not None:
553-
file_handle = dlllist.DllList.dump_pe(self.context,
554-
pe_table_name,
555-
entry,
556-
self.open,
557-
process_layer_name,
558-
prefix=f'pid.{process_id}.')
559-
if file_handle:
560-
file_handle.close()
561-
dumped_module_filename = file_handle.preferred_filename
605+
if is_detect_option_supplied: # In this case, there is no need to dump the modules
562606
modules_to_mix.append(
563-
Module(module_name, module_path, module_base_address, module_size,
564-
process_id, dumped_module_filename, []))
607+
Module(module_name, module_path, module_base_address, module_size, process_id, '',
608+
[]))
609+
else:
610+
file_handle = dlllist.DllList.dump_pe(self.context,
611+
pe_table_name,
612+
entry,
613+
self.open,
614+
process_layer_name,
615+
prefix=f'pid.{process_id}.')
616+
if file_handle:
617+
file_handle.close()
618+
dumped_module_filename = file_handle.preferred_filename
619+
modules_to_mix.append(
620+
Module(module_name, module_path, module_base_address, module_size,
621+
process_id, dumped_module_filename, []))
565622
except exceptions.InvalidAddressException:
566623
pass
567624

568625
if not modules_to_mix:
569626
logger.info('The module supplied is not mapped in any process')
570627
return renderers.TreeGrid([("Filename", str)], self._generator(files_finally_generated))
571628

572-
logger.info(f'Modules to mix (before validation) ({len(modules_to_mix)}):')
629+
logger.info(f'Mapped modules (before validation) ({len(modules_to_mix)}):')
573630
for module_to_mix in modules_to_mix:
574631
logger.info(f'\t{module_to_mix.get_basic_information()}')
575632

@@ -578,7 +635,13 @@ def run(self):
578635

579636
if not modules_to_mix:
580637
logger.info(
581-
'\nAll the identified modules are under the C:\\Windows\\SysWOW64 directory, as a result, they cannot be mixed')
638+
'\nAll the identified modules are under the C:\\Windows\\SysWOW64 directory, as a result, the execution cannot proceed')
639+
return renderers.TreeGrid([("Filename", str)], self._generator(files_finally_generated))
640+
641+
if is_detect_option_supplied:
642+
detection_info_filename: str = 'detection.json'
643+
files_finally_generated += detect_dll_proxying(modules_to_mix, output_directory, detection_info_filename,
644+
logger)
582645
return renderers.TreeGrid([("Filename", str)], self._generator(files_finally_generated))
583646

584647
# Make sure that the modules can be mixed

0 commit comments

Comments
 (0)