Skip to content

Commit e22e82d

Browse files
authored
Merge pull request #1787 from JakePeralta7/develop
Detect ETW Patching
2 parents 0c08b94 + 19b094a commit e22e82d

File tree

1 file changed

+128
-0
lines changed

1 file changed

+128
-0
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
# This file is Copyright 2025 Volatility Foundation and licensed under the Volatility Software License 1.0
2+
# which is available at https://www.volatilityfoundation.org/license/vsl-v1.0
3+
#
4+
import logging
5+
6+
from volatility3.framework import exceptions, interfaces, renderers
7+
from volatility3.framework.configuration import requirements
8+
from volatility3.framework.objects import utility
9+
from volatility3.framework.renderers import format_hints
10+
from volatility3.plugins.windows import pslist, pe_symbols
11+
12+
vollog = logging.getLogger(__name__)
13+
14+
15+
class EtwPatch(interfaces.plugins.PluginInterface):
16+
"""Identifies ETW (Event Tracing for Windows) patching techniques used by malware to evade detection.
17+
18+
This plugin examines the first opcode of key ETW functions in ntdll.dll and advapi32.dll
19+
to detect common ETW bypass techniques such as return pointer manipulation (RET) or function
20+
redirection (JMP). Attackers often patch these functions to prevent security tools from
21+
receiving telemetry about process execution, API calls, and other system events.
22+
"""
23+
24+
_version = (1, 0, 0)
25+
_required_framework_version = (2, 26, 0)
26+
27+
etw_functions = {
28+
"ntdll.dll": {
29+
pe_symbols.wanted_names_identifier: [
30+
"EtwEventWrite",
31+
"EtwEventWriteFull",
32+
"NtTraceEvent",
33+
],
34+
},
35+
"advapi32.dll": {
36+
pe_symbols.wanted_names_identifier: ["EventWrite"],
37+
},
38+
}
39+
40+
@classmethod
41+
def get_requirements(cls):
42+
return [
43+
requirements.ModuleRequirement(
44+
name="kernel",
45+
description="Windows kernel",
46+
architectures=["Intel32", "Intel64"],
47+
),
48+
requirements.VersionRequirement(
49+
name="pslist", component=pslist.PsList, version=(3, 0, 0)
50+
),
51+
requirements.VersionRequirement(
52+
name="pe_symbols", component=pe_symbols.PESymbols, version=(3, 0, 0)
53+
),
54+
requirements.ListRequirement(
55+
name="pid",
56+
description="Filter on specific process IDs",
57+
element_type=int,
58+
optional=True,
59+
),
60+
]
61+
62+
def _generator(self):
63+
# Get all ETW function addresses before looping through processes
64+
found_symbols = pe_symbols.PESymbols.addresses_for_process_symbols(
65+
context=self.context,
66+
config_path=self.config_path,
67+
kernel_module_name=self.config["kernel"],
68+
symbols=self.etw_functions,
69+
)
70+
71+
filter_func = pslist.PsList.create_pid_filter(self.config.get("pid", None))
72+
73+
for proc in pslist.PsList.list_processes(
74+
context=self.context,
75+
kernel_module_name=self.config["kernel"],
76+
filter_func=filter_func,
77+
):
78+
79+
try:
80+
proc_id = proc.UniqueProcessId
81+
proc_name = utility.array_to_string(proc.ImageFileName)
82+
proc_layer_name = proc.add_process_layer()
83+
except exceptions.InvalidAddressException:
84+
vollog.debug(f"Unable to create process layer for PID {proc_id}")
85+
continue
86+
87+
# Map of opcodes to their instruction names
88+
opcode_map = {
89+
0xC3: "RET",
90+
0xE9: "JMP",
91+
}
92+
93+
for dll_name, functions in found_symbols.items():
94+
for func_name, func_addr in functions:
95+
try:
96+
opcode = self.context.layers[proc_layer_name].read(
97+
func_addr, 1
98+
)[0]
99+
if opcode in opcode_map:
100+
instruction = opcode_map[opcode]
101+
yield (
102+
0,
103+
(
104+
proc_id,
105+
proc_name,
106+
dll_name,
107+
func_name,
108+
format_hints.Hex(func_addr),
109+
f"{opcode:02x} ({instruction})",
110+
),
111+
)
112+
except exceptions.InvalidAddressException:
113+
vollog.debug(
114+
f"Invalid address when reading function {func_name} at {func_addr:#x} in process {proc_id}"
115+
)
116+
117+
def run(self):
118+
return renderers.TreeGrid(
119+
[
120+
("PID", int),
121+
("Process", str),
122+
("DLL", str),
123+
("Function", str),
124+
("Offset", format_hints.Hex),
125+
("Opcode", str),
126+
],
127+
self._generator(),
128+
)

0 commit comments

Comments
 (0)