Skip to content

Commit 2369950

Browse files
committed
reimplemented as avpatch (check_inline_hook + amsi)
1 parent ad85d2e commit 2369950

File tree

1 file changed

+43
-26
lines changed

1 file changed

+43
-26
lines changed

volatility3/framework/plugins/windows/etwpatch.py renamed to volatility3/framework/plugins/windows/avpatch.py

Lines changed: 43 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -8,25 +8,22 @@
88
from volatility3.framework.objects import utility
99
from volatility3.framework.renderers import format_hints
1010
from volatility3.plugins.windows import pslist, pe_symbols
11+
from volatility3.plugins.windows import inlinehooks
1112

1213
vollog = 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

Comments
 (0)