88from volatility3 .framework .objects import utility
99from volatility3 .framework .renderers import format_hints
1010from volatility3 .plugins .windows import pslist , pe_symbols
11+ from volatility3 .plugins .windows import inlinehooks
1112
1213vollog = logging .getLogger (__name__ )
1314
1415
1516# 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."""
2522
2623 _version = (1 , 0 , 0 )
2724 _required_framework_version = (2 , 26 , 0 )
2825
29- etw_functions = {
26+ av_functions = {
3027 "ntdll.dll" : {
3128 pe_symbols .wanted_names_identifier : [
3229 "EtwEventWrite" ,
@@ -41,6 +38,14 @@ class EtwPatch(interfaces.plugins.PluginInterface):
4138 "advapi32.dll" : {
4239 pe_symbols .wanted_names_identifier : ["EventWrite" , "TraceEvent" ],
4340 },
41+ "amsi.dll" : {
42+ pe_symbols .wanted_names_identifier : [
43+ "AmsiScanBuffer" ,
44+ "AmsiScanString" ,
45+ "AmsiInitialize" ,
46+ "AmsiOpenSession" ,
47+ ],
48+ },
4449 }
4550
4651 @classmethod
@@ -57,6 +62,9 @@ def get_requirements(cls):
5762 requirements .VersionRequirement (
5863 name = "pe_symbols" , component = pe_symbols .PESymbols , version = (3 , 0 , 0 )
5964 ),
65+ requirements .VersionRequirement (
66+ name = "inlinehooks" , component = inlinehooks .InlineHooks , version = (1 , 0 , 0 )
67+ ),
6068 requirements .ListRequirement (
6169 name = "pid" ,
6270 description = "Filter on specific process IDs" ,
@@ -66,16 +74,18 @@ def get_requirements(cls):
6674 ]
6775
6876 def _generator (self ):
69- # Get all ETW function addresses before looping through processes
77+ # Get all ETW & AMSI function addresses before looping through processes
7078 found_symbols = pe_symbols .PESymbols .addresses_for_process_symbols (
7179 context = self .context ,
7280 config_path = self .config_path ,
7381 kernel_module_name = self .config ["kernel" ],
74- symbols = self .etw_functions ,
82+ symbols = self .av_functions ,
7583 )
7684
7785 filter_func = pslist .PsList .create_pid_filter (self .config .get ("pid" , None ))
7886
87+ inlineHooks = inlinehooks .InlineHooks (self .context , self .config_path )
88+
7989 for proc in pslist .PsList .list_processes (
8090 context = self .context ,
8191 kernel_module_name = self .config ["kernel" ],
@@ -89,20 +99,19 @@ def _generator(self):
8999 vollog .debug (f"Unable to create process layer for PID { proc_id } " )
90100 continue
91101
92- # Map of opcodes to their instruction names
93- opcode_map = {
94- 0xC3 : "RET" ,
95- 0xE9 : "JMP" ,
96- }
97-
98102 for dll_name , functions in found_symbols .items ():
99103 for func_name , func_addr in functions :
100104 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+ )
106115 yield (
107116 0 ,
108117 (
@@ -111,7 +120,13 @@ def _generator(self):
111120 dll_name ,
112121 func_name ,
113122 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 ,
115130 ),
116131 )
117132 except exceptions .InvalidAddressException :
@@ -126,8 +141,10 @@ def run(self):
126141 ("Process" , str ),
127142 ("DLL" , str ),
128143 ("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 ),
131148 ],
132149 self ._generator (),
133150 )
0 commit comments