8
8
from typing import Dict , Any , List
9
9
10
10
from modex import Module , Page , get_current_utc_timestamp , create_logger , check_if_all_elements_are_equal , \
11
- check_if_modules_can_be_mixed , mix_modules
11
+ check_if_modules_can_be_mixed , mix_modules , get_detection_information_filename , log_detection_process_common_parts , \
12
+ get_most_common_element
13
+
14
+
15
+ class ModexDetectionAttempt :
16
+ def __init__ (self , memory_dump_location : str , mapped_modules : List [Module ], result : bool ,
17
+ suspicious_processes : List [int ]):
18
+ self .memory_dump_location : str = memory_dump_location
19
+ self .mapped_modules : List [Module ] = mapped_modules
20
+ self .result : bool = result
21
+ self .suspicious_processes : List [int ] = suspicious_processes
12
22
13
23
14
24
class ModexExtraction :
@@ -82,16 +92,19 @@ def convert_modex_extraction_to_module(modex_extraction: ModexExtraction) -> Mod
82
92
return Module ('' , module_path , module_base_address , module_size , 0 , modex_extraction .module_path , pages )
83
93
84
94
85
- def check_if_modex_run_successfully (modex_output_directory : str ) -> bool :
95
+ def check_if_modex_ran_successfully (modex_output_directory : str , was_detect_option_supplied : bool ) -> bool :
86
96
"""Check if an output from the Modex plugin contains the files it should contain for a successful execution."""
87
- # An output from the Modex plugin, without taking directories into account, should contain 3 files if the execution was successful:
97
+ # An output from the Modex plugin, without taking directories into account and without supplying the --detect option , should contain 3 files if the execution was successful:
88
98
# - A .dmp file
89
99
# - A .json file
90
100
# - A .txt file
101
+ # Note: If the --detect option was supplied to Modex, then a .dmp file will not be created
102
+
103
+ number_of_generated_files : int = 2 if was_detect_option_supplied else 3
104
+ extensions : List [str ] = ['.json' , '.txt' ] if was_detect_option_supplied else ['.dmp' , '.json' , '.txt' ]
91
105
not_hidden_files_inside_modex_output : List [str ] = get_not_hidden_files_inside_directory (modex_output_directory )
92
- if len (not_hidden_files_inside_modex_output ) == 3 :
93
- extensions : List [str ] = ['.dmp' , '.json' , '.txt' ]
94
- presence_of_extensions : List [bool ] = [False , False , False ]
106
+ if len (not_hidden_files_inside_modex_output ) == number_of_generated_files :
107
+ presence_of_extensions : List [bool ] = [False , False ] if was_detect_option_supplied else [False , False , False ]
95
108
for file_inside_modex_output in not_hidden_files_inside_modex_output :
96
109
file_has_required_extension : bool = False
97
110
i : int = 0
@@ -108,8 +121,7 @@ def check_if_modex_run_successfully(modex_output_directory: str) -> bool:
108
121
return False
109
122
110
123
111
- def perform_mixture (modex_outputs_directory : str , perform_derelocation : bool , sum_path : str , dump_anomalies : bool ,
112
- output_directory : str , logger ) -> None :
124
+ def get_succesful_modex_outputs (modex_outputs_directory : str , was_detect_option_supplied : bool , logger ) -> List [str ]:
113
125
modex_outputs : List [str ] = os .listdir (modex_outputs_directory )
114
126
for i in range (0 , len (modex_outputs )):
115
127
modex_outputs [i ] = os .path .join (modex_outputs_directory , modex_outputs [i ])
@@ -123,12 +135,78 @@ def perform_mixture(modex_outputs_directory: str, perform_derelocation: bool, su
123
135
successful_modex_outputs : List [str ] = []
124
136
for modex_output in modex_outputs :
125
137
if os .path .isdir (modex_output ):
126
- if check_if_modex_run_successfully (modex_output ):
138
+ if check_if_modex_ran_successfully (modex_output , was_detect_option_supplied ):
127
139
successful_modex_outputs .append (modex_output )
128
140
logger .info (f'\t { modex_output } meets the requirements for a successful Modex execution' )
129
141
else :
130
142
logger .info (
131
143
f'\t { modex_output } does not meet the requirements for a successful Modex execution. As a result, it will not be considered for the mixture.' )
144
+ return successful_modex_outputs
145
+
146
+
147
+ def convert_mapped_modules_from_json_to_objects (mapped_modules : List [Dict [str , Any ]]) -> List [Module ]:
148
+ mapped_modules_converted : List [Module ] = []
149
+ for mapped_module in mapped_modules :
150
+ mapped_modules_converted .append (
151
+ Module ('' , mapped_module ['path' ], mapped_module ['base_address' ], mapped_module ['size' ],
152
+ mapped_module ['process_id' ], '' , []))
153
+ return mapped_modules_converted
154
+
155
+
156
+ def detect_dll_proxying_across_several_memory_dumps (modex_detection_attempts : List [ModexDetectionAttempt ],
157
+ output_directory : str , logger ) -> None :
158
+ detection_info : Dict [str , Any ] = {}
159
+ mapped_modules : List [Module ] = []
160
+ for modex_detection_attempt in modex_detection_attempts :
161
+ mapped_modules += modex_detection_attempt .mapped_modules
162
+
163
+ most_common_path : str = get_most_common_element ([module .path .casefold () for module in mapped_modules ])
164
+ most_common_size : int = get_most_common_element ([module .size for module in mapped_modules ])
165
+
166
+ suspicious_processes : Dict [str , Any ] = {} # The keys are memory dumps
167
+ for modex_detection_attempt in modex_detection_attempts :
168
+ for module in modex_detection_attempt .mapped_modules :
169
+ if module .path .casefold () != most_common_path or module .size != most_common_size :
170
+ if modex_detection_attempt .memory_dump_location not in suspicious_processes .keys ():
171
+ suspicious_processes [modex_detection_attempt .memory_dump_location ] = [module .process_id ]
172
+ else :
173
+ suspicious_processes [modex_detection_attempt .memory_dump_location ].append (module .process_id )
174
+
175
+ detection_info ['dll_proxying_detection_result' ] = True if suspicious_processes else False
176
+ detection_info ['suspicious_processes' ] = suspicious_processes
177
+
178
+ detection_info_path : str = os .path .join (output_directory , get_detection_information_filename ())
179
+ with open (detection_info_path , 'w' ) as detection_info_file :
180
+ json .dump (detection_info , detection_info_file , ensure_ascii = False , indent = 4 )
181
+
182
+ log_detection_process_common_parts (logger )
183
+
184
+
185
+ def perform_detection (modex_outputs_directory : str , output_directory : str , logger ) -> None :
186
+ successful_modex_outputs : List [str ] = get_succesful_modex_outputs (modex_outputs_directory , True , logger )
187
+ modex_detection_attempts : List [ModexDetectionAttempt ] = []
188
+
189
+ # Get the .json files from each successful Modex output and perform the detection
190
+ for successful_modex_output in successful_modex_outputs :
191
+ json_file : str = get_file_from_modex_output (successful_modex_output , '.json' )
192
+ if json_file is not None :
193
+ with open (json_file ) as detection_info_file :
194
+ detection_info : Dict [str , Any ] = json .load (detection_info_file )
195
+ mapped_modules : List [Module ] = convert_mapped_modules_from_json_to_objects (detection_info ['mapped_modules' ])
196
+ modex_detection_attempts .append (
197
+ ModexDetectionAttempt (detection_info ['memory_dump_location' ], mapped_modules ,
198
+ detection_info ['dll_proxying_detection_result' ],
199
+ detection_info ['suspicious_processes' ]))
200
+ else :
201
+ logger .info (
202
+ f'\t The Modex output { successful_modex_output } was considered successful, but the .json file does not exist, and it must exist for a Modex output to be successful. As a result, this output will not be considered in the detection process.' )
203
+
204
+ detect_dll_proxying_across_several_memory_dumps (modex_detection_attempts , output_directory , logger )
205
+
206
+
207
+ def perform_mixture (modex_outputs_directory : str , perform_derelocation : bool , sum_path : str , dump_anomalies : bool ,
208
+ output_directory : str , logger ) -> None :
209
+ successful_modex_outputs : List [str ] = get_succesful_modex_outputs (modex_outputs_directory , False , logger )
132
210
133
211
# Take the .dmp and .json files from each successful Modex output and perform the mixture
134
212
modex_extractions : List [ModexExtraction ] = []
@@ -168,9 +246,10 @@ def perform_mixture(modex_outputs_directory: str, perform_derelocation: bool, su
168
246
logger .info (f'\n The derelocation process was not successful (exit code { derelocator_exit_code } )' )
169
247
170
248
171
- def perform_extraction (module : str , memory_dumps_directory : str , remove_modex_outputs : bool , perform_derelocation : bool ,
172
- sum_path : str , volatility_path : str , dump_anomalies : bool , output_directory : str ,
173
- logger ) -> None :
249
+ def perform_operation_after_getting_modex_outputs (module : str , memory_dumps_directory : str , remove_modex_outputs : bool ,
250
+ perform_derelocation : bool , sum_path : str , volatility_path : str ,
251
+ dump_anomalies : bool , detect : bool , output_directory : str ,
252
+ logger ) -> None :
174
253
memory_dumps : List [str ] = get_not_hidden_files_inside_directory (memory_dumps_directory )
175
254
logger .info ('Memory dumps provided:' )
176
255
for memory_dump in memory_dumps :
@@ -184,8 +263,12 @@ def perform_extraction(module: str, memory_dumps_directory: str, remove_modex_ou
184
263
# Invoke the Modex plugin for each memory dump inside the memory dumps directory
185
264
logger .info ('\n Modex plugin execution:' )
186
265
for memory_dump in memory_dumps :
187
- volatility_command = ['python3' , volatility_path , '-f' , memory_dump , 'windows.modex' , '--module' , module ,
188
- '--dump-anomalies' ]
266
+ if detect :
267
+ volatility_command = ['python3' , volatility_path , '-f' , memory_dump , 'windows.modex' , '--module' , module ,
268
+ '--detect' ]
269
+ else :
270
+ volatility_command = ['python3' , volatility_path , '-f' , memory_dump , 'windows.modex' , '--module' , module ,
271
+ '--dump-anomalies' ]
189
272
with subprocess .Popen (volatility_command , stdout = subprocess .DEVNULL , stderr = subprocess .DEVNULL ) as modex_plugin :
190
273
print (f'Running the Modex plugin for the following memory dump: { memory_dump } ' )
191
274
modex_plugin_exit_code = modex_plugin .wait ()
@@ -198,9 +281,12 @@ def perform_extraction(module: str, memory_dumps_directory: str, remove_modex_ou
198
281
os .chdir (current_working_directory ) # Restore the working directory
199
282
logger .debug (f'Working directory after restoring it: { os .getcwd ()} ' )
200
283
201
- # Mix the modules previously extracted
202
- perform_mixture (modex_outputs_directory_name , perform_derelocation , sum_path , dump_anomalies , output_directory ,
203
- logger )
284
+ if detect :
285
+ perform_detection (modex_outputs_directory_name , output_directory , logger )
286
+ else :
287
+ # Mix the modules previously extracted
288
+ perform_mixture (modex_outputs_directory_name , perform_derelocation , sum_path , dump_anomalies , output_directory ,
289
+ logger )
204
290
205
291
if remove_modex_outputs :
206
292
shutil .rmtree (modex_outputs_directory_name )
@@ -218,6 +304,9 @@ def validate_arguments() -> Dict[str, Any]:
218
304
arg_parser .add_argument ('-d' ,
219
305
'--memory-dumps-directory' ,
220
306
help = 'directory where the memory dumps are (the Modex plugin will be called)' )
307
+ arg_parser .add_argument ('--detect' ,
308
+ action = 'store_true' ,
309
+ help = 'detect the presence of the DLL proxying technique' )
221
310
arg_parser .add_argument ('-l' ,
222
311
'--log-level' ,
223
312
choices = ['DEBUG' , 'INFO' , 'WARNING' , 'ERROR' , 'CRITICAL' ],
@@ -258,6 +347,7 @@ def validate_arguments() -> Dict[str, Any]:
258
347
volatility_path = args .volatility_path
259
348
dump_anomalies = args .dump_anomalies
260
349
sum_path = args .sum_path
350
+ detect = args .detect
261
351
262
352
if memory_dumps_directory is not None and modex_outputs_directory is not None :
263
353
raise ValueError (
@@ -306,6 +396,10 @@ def validate_arguments() -> Dict[str, Any]:
306
396
if module is not None and len (module ) > 255 :
307
397
raise ValueError ('The module name is too long' )
308
398
399
+ if (perform_derelocation or dump_anomalies ) and detect :
400
+ raise ValueError (
401
+ 'You cannot supply the --detect option alongside the --perform-derelocation or --dump-anomalies options' )
402
+
309
403
if memory_dumps_directory is not None :
310
404
memory_dumps_directory = os .path .abspath (memory_dumps_directory )
311
405
@@ -334,7 +428,7 @@ def validate_arguments() -> Dict[str, Any]:
334
428
'remove_modex_outputs' : remove_modex_outputs ,
335
429
'perform_derelocation' : perform_derelocation , 'volatility_path' : volatility_path ,
336
430
'log_level_supplied' : log_level_supplied , 'dump_anomalies' : dump_anomalies ,
337
- 'sum_path' : sum_path }
431
+ 'sum_path' : sum_path , 'detect' : detect }
338
432
return arguments
339
433
340
434
@@ -349,16 +443,25 @@ def execute() -> None:
349
443
if modex_outputs_directory is not None :
350
444
create_output_directory (output_directory , False )
351
445
logger = get_logger (output_directory , validated_arguments ['log_level_supplied' ])
352
- perform_mixture (modex_outputs_directory , validated_arguments ['perform_derelocation' ],
353
- validated_arguments ['sum_path' ], validated_arguments ['dump_anomalies' ], output_directory ,
354
- logger )
446
+ if validated_arguments ['detect' ]:
447
+ perform_detection (modex_outputs_directory , output_directory , logger )
448
+ else :
449
+ perform_mixture (modex_outputs_directory , validated_arguments ['perform_derelocation' ],
450
+ validated_arguments ['sum_path' ], validated_arguments ['dump_anomalies' ],
451
+ output_directory ,
452
+ logger )
355
453
else :
356
454
create_output_directory (output_directory , True )
357
455
logger = get_logger (output_directory , validated_arguments ['log_level_supplied' ])
358
- perform_extraction (validated_arguments ['module' ], validated_arguments ['memory_dumps_directory' ],
359
- validated_arguments ['remove_modex_outputs' ], validated_arguments ['perform_derelocation' ],
360
- validated_arguments ['sum_path' ], validated_arguments ['volatility_path' ],
361
- validated_arguments ['dump_anomalies' ], output_directory , logger )
456
+ perform_operation_after_getting_modex_outputs (validated_arguments ['module' ],
457
+ validated_arguments ['memory_dumps_directory' ],
458
+ validated_arguments ['remove_modex_outputs' ],
459
+ validated_arguments ['perform_derelocation' ],
460
+ validated_arguments ['sum_path' ],
461
+ validated_arguments ['volatility_path' ],
462
+ validated_arguments ['dump_anomalies' ],
463
+ validated_arguments ['detect' ], output_directory ,
464
+ logger )
362
465
363
466
except Exception as exception :
364
467
print (f'An error occurred ({ exception } ). Here are more details about the error:\n ' )
0 commit comments