8
8
from volatility3 .framework .objects import utility
9
9
from volatility3 .framework .renderers import format_hints
10
10
from volatility3 .plugins .windows import pslist , pe_symbols
11
+ from volatility3 .plugins .windows import inlinehooks
11
12
12
13
vollog = logging .getLogger (__name__ )
13
14
14
15
15
16
# EtwpEventWriteFull -> https://github.com/SolitudePy/Stealthy-ETW-Patch
16
- # CAPA rule -> https://github.com/mandiant/capa-rules/blob/master/anti-analysis/anti-av/patch-event-tracing-for-windows-function.yml
17
- class EtwPatch (interfaces .plugins .PluginInterface ):
18
- """Identifies ETW (Event Tracing for Windows) patching techniques used by malware to evade detection.
19
-
20
- This plugin examines the first opcode of key ETW functions in ntdll.dll and advapi32.dll
21
- to detect common ETW bypass techniques such as return pointer manipulation (RET) or function
22
- redirection (JMP). Attackers often patch these functions to prevent security tools from
23
- receiving telemetry about process execution, API calls, and other system events.
24
- """
17
+ # ETW CAPA rule -> https://github.com/mandiant/capa-rules/blob/master/anti-analysis/anti-av/patch-event-tracing-for-windows-function.yml
18
+ # AMSI CAPA rule -> https://github.com/mandiant/capa-rules/blob/master/anti-analysis/anti-av/patch-antimalware-scan-interface-function.yml
19
+ # AMSI patch -> https://github.com/okankurtuluss/AMSIBypassPatch
20
+ class AvPatch (interfaces .plugins .PluginInterface ):
21
+ """Detects ETW & AMSI in-memory patching used by malware for defense evasion."""
25
22
26
23
_version = (1 , 0 , 0 )
27
24
_required_framework_version = (2 , 26 , 0 )
28
25
29
- etw_functions = {
26
+ av_functions = {
30
27
"ntdll.dll" : {
31
28
pe_symbols .wanted_names_identifier : [
32
29
"EtwEventWrite" ,
@@ -41,6 +38,14 @@ class EtwPatch(interfaces.plugins.PluginInterface):
41
38
"advapi32.dll" : {
42
39
pe_symbols .wanted_names_identifier : ["EventWrite" , "TraceEvent" ],
43
40
},
41
+ "amsi.dll" : {
42
+ pe_symbols .wanted_names_identifier : [
43
+ "AmsiScanBuffer" ,
44
+ "AmsiScanString" ,
45
+ "AmsiInitialize" ,
46
+ "AmsiOpenSession" ,
47
+ ],
48
+ },
44
49
}
45
50
46
51
@classmethod
@@ -57,6 +62,9 @@ def get_requirements(cls):
57
62
requirements .VersionRequirement (
58
63
name = "pe_symbols" , component = pe_symbols .PESymbols , version = (3 , 0 , 0 )
59
64
),
65
+ requirements .VersionRequirement (
66
+ name = "inlinehooks" , component = inlinehooks .InlineHooks , version = (1 , 0 , 0 )
67
+ ),
60
68
requirements .ListRequirement (
61
69
name = "pid" ,
62
70
description = "Filter on specific process IDs" ,
@@ -66,16 +74,18 @@ def get_requirements(cls):
66
74
]
67
75
68
76
def _generator (self ):
69
- # Get all ETW function addresses before looping through processes
77
+ # Get all ETW & AMSI function addresses before looping through processes
70
78
found_symbols = pe_symbols .PESymbols .addresses_for_process_symbols (
71
79
context = self .context ,
72
80
config_path = self .config_path ,
73
81
kernel_module_name = self .config ["kernel" ],
74
- symbols = self .etw_functions ,
82
+ symbols = self .av_functions ,
75
83
)
76
84
77
85
filter_func = pslist .PsList .create_pid_filter (self .config .get ("pid" , None ))
78
86
87
+ inlineHooks = inlinehooks .InlineHooks (self .context , self .config_path )
88
+
79
89
for proc in pslist .PsList .list_processes (
80
90
context = self .context ,
81
91
kernel_module_name = self .config ["kernel" ],
@@ -89,20 +99,19 @@ def _generator(self):
89
99
vollog .debug (f"Unable to create process layer for PID { proc_id } " )
90
100
continue
91
101
92
- # Map of opcodes to their instruction names
93
- opcode_map = {
94
- 0xC3 : "RET" ,
95
- 0xE9 : "JMP" ,
96
- }
97
-
98
102
for dll_name , functions in found_symbols .items ():
99
103
for func_name , func_addr in functions :
100
104
try :
101
- opcode = self .context .layers [proc_layer_name ].read (
102
- func_addr , 1
103
- )[0 ]
104
- if opcode in opcode_map :
105
- instruction = opcode_map [opcode ]
105
+ data = self .context .layers [proc_layer_name ].read (func_addr , 24 )
106
+ disasm = renderers .Disassembly (data , func_addr )
107
+ inline_hook_check = inlineHooks .check_inline_hook (
108
+ data = data , addr = func_addr
109
+ )
110
+
111
+ if inline_hook_check :
112
+ vollog .debug (
113
+ f"Inline hook detected at { func_addr :#x} in process { proc_id } ({ proc_name } ) for function { func_name } "
114
+ )
106
115
yield (
107
116
0 ,
108
117
(
@@ -111,7 +120,13 @@ def _generator(self):
111
120
dll_name ,
112
121
func_name ,
113
122
format_hints .Hex (func_addr ),
114
- f"{ opcode :02x} ({ instruction } )" ,
123
+ inline_hook_check [1 ],
124
+ (
125
+ format_hints .HexBytes (inline_hook_check [0 ])
126
+ if inline_hook_check [0 ]
127
+ else format_hints .HexBytes (b"" )
128
+ ),
129
+ disasm ,
115
130
),
116
131
)
117
132
except exceptions .InvalidAddressException :
@@ -126,8 +141,10 @@ def run(self):
126
141
("Process" , str ),
127
142
("DLL" , str ),
128
143
("Function" , str ),
129
- ("Offset" , format_hints .Hex ),
130
- ("Opcode" , str ),
144
+ ("Hook Address" , format_hints .Hex ),
145
+ ("Hook Info" , str ),
146
+ ("Hook Hexdump" , format_hints .HexBytes ),
147
+ ("Disasm" , renderers .Disassembly ),
131
148
],
132
149
self ._generator (),
133
150
)
0 commit comments