Skip to content

Commit 48a770a

Browse files
authored
Merge pull request #1250 from volatilityfoundation/pe_symbols_new
Add new pe_symbols API, debug registers plugin, unhooked system calls…
2 parents b5b9372 + b788733 commit 48a770a

File tree

4 files changed

+1460
-4
lines changed

4 files changed

+1460
-4
lines changed
Lines changed: 211 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,211 @@
1+
# This file is Copyright 2024 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+
# Full details on the techniques used in these plugins to detect EDR-evading malware
5+
# can be found in our 20 page whitepaper submitted to DEFCON along with the presentation
6+
# https://www.volexity.com/wp-content/uploads/2024/08/Defcon24_EDR_Evasion_Detection_White-Paper_Andrew-Case.pdf
7+
8+
import logging
9+
10+
from typing import Tuple, Optional, Generator, List, Dict
11+
12+
from functools import partial
13+
14+
from volatility3.framework import renderers, interfaces, exceptions
15+
from volatility3.framework.configuration import requirements
16+
from volatility3.framework.renderers import format_hints
17+
import volatility3.plugins.windows.pslist as pslist
18+
import volatility3.plugins.windows.threads as threads
19+
import volatility3.plugins.windows.pe_symbols as pe_symbols
20+
21+
vollog = logging.getLogger(__name__)
22+
23+
24+
class DebugRegisters(interfaces.plugins.PluginInterface):
25+
# version 2.6.0 adds support for scanning for 'Ethread' structures by pool tags
26+
_required_framework_version = (2, 6, 0)
27+
_version = (1, 0, 0)
28+
29+
@classmethod
30+
def get_requirements(cls) -> List:
31+
return [
32+
requirements.ModuleRequirement(
33+
name="kernel",
34+
description="Windows kernel",
35+
architectures=["Intel32", "Intel64"],
36+
),
37+
requirements.VersionRequirement(
38+
name="pslist", component=pslist.PsList, version=(2, 0, 0)
39+
),
40+
requirements.VersionRequirement(
41+
name="pe_symbols", component=pe_symbols.PESymbols, version=(1, 0, 0)
42+
),
43+
]
44+
45+
@staticmethod
46+
def _get_debug_info(
47+
ethread: interfaces.objects.ObjectInterface,
48+
) -> Optional[Tuple[interfaces.objects.ObjectInterface, int, int, int, int, int]]:
49+
"""
50+
Gathers information related to the debug registers for the given thread
51+
Args:
52+
ethread: the thread (_ETHREAD) to examine
53+
Returns:
54+
Tuple[interfaces.objects.ObjectInterface, int, int, int, int, int]: The owner process of the thread and the values for dr7, dr0, dr1, dr2, dr3
55+
"""
56+
try:
57+
dr7 = ethread.Tcb.TrapFrame.Dr7
58+
state = ethread.Tcb.State
59+
except exceptions.InvalidAddressException:
60+
return None
61+
62+
# 0 = debug registers not active
63+
# 4 = terminated
64+
if dr7 == 0 or state == 4:
65+
return None
66+
67+
try:
68+
owner_proc = ethread.owning_process()
69+
except (AttributeError, exceptions.InvalidAddressException):
70+
return None
71+
72+
dr0 = ethread.Tcb.TrapFrame.Dr0
73+
dr1 = ethread.Tcb.TrapFrame.Dr1
74+
dr2 = ethread.Tcb.TrapFrame.Dr2
75+
dr3 = ethread.Tcb.TrapFrame.Dr3
76+
77+
# bail if all are 0
78+
if not (dr0 or dr1 or dr2 or dr3):
79+
return None
80+
81+
return owner_proc, dr7, dr0, dr1, dr2, dr3
82+
83+
def _generator(
84+
self,
85+
) -> Generator[
86+
Tuple[
87+
int,
88+
Tuple[
89+
str,
90+
int,
91+
int,
92+
int,
93+
int,
94+
format_hints.Hex,
95+
str,
96+
str,
97+
format_hints.Hex,
98+
str,
99+
str,
100+
format_hints.Hex,
101+
str,
102+
str,
103+
format_hints.Hex,
104+
str,
105+
str,
106+
],
107+
],
108+
None,
109+
None,
110+
]:
111+
kernel = self.context.modules[self.config["kernel"]]
112+
113+
vads_cache: Dict[int, pe_symbols.ranges_type] = {}
114+
115+
proc_modules = None
116+
117+
procs = pslist.PsList.list_processes(
118+
context=self.context,
119+
layer_name=kernel.layer_name,
120+
symbol_table=kernel.symbol_table_name,
121+
)
122+
123+
for proc in procs:
124+
for thread in threads.Threads.list_threads(kernel, proc):
125+
debug_info = self._get_debug_info(thread)
126+
if not debug_info:
127+
continue
128+
129+
owner_proc, dr7, dr0, dr1, dr2, dr3 = debug_info
130+
131+
vads = pe_symbols.PESymbols.get_vads_for_process_cache(
132+
vads_cache, owner_proc
133+
)
134+
if not vads:
135+
continue
136+
137+
# this lookup takes a while, so only perform if we need to
138+
if not proc_modules:
139+
proc_modules = pe_symbols.PESymbols.get_process_modules(
140+
self.context, kernel.layer_name, kernel.symbol_table_name, None
141+
)
142+
path_and_symbol = partial(
143+
pe_symbols.PESymbols.path_and_symbol_for_address,
144+
self.context,
145+
self.config_path,
146+
proc_modules,
147+
)
148+
149+
file0, sym0 = path_and_symbol(vads, dr0)
150+
file1, sym1 = path_and_symbol(vads, dr1)
151+
file2, sym2 = path_and_symbol(vads, dr2)
152+
file3, sym3 = path_and_symbol(vads, dr3)
153+
154+
# if none map to an actual file VAD then bail
155+
if not (file0 or file1 or file2 or file3):
156+
continue
157+
158+
process_name = owner_proc.ImageFileName.cast(
159+
"string",
160+
max_length=owner_proc.ImageFileName.vol.count,
161+
errors="replace",
162+
)
163+
164+
thread_tid = thread.Cid.UniqueThread
165+
166+
yield (
167+
0,
168+
(
169+
process_name,
170+
owner_proc.UniqueProcessId,
171+
thread_tid,
172+
thread.Tcb.State,
173+
dr7,
174+
format_hints.Hex(dr0),
175+
file0 or renderers.NotApplicableValue(),
176+
sym0 or renderers.NotApplicableValue(),
177+
format_hints.Hex(dr1),
178+
file1 or renderers.NotApplicableValue(),
179+
sym1 or renderers.NotApplicableValue(),
180+
format_hints.Hex(dr2),
181+
file2 or renderers.NotApplicableValue(),
182+
sym2 or renderers.NotApplicableValue(),
183+
format_hints.Hex(dr3),
184+
file3 or renderers.NotApplicableValue(),
185+
sym3 or renderers.NotApplicableValue(),
186+
),
187+
)
188+
189+
def run(self) -> renderers.TreeGrid:
190+
return renderers.TreeGrid(
191+
[
192+
("Process", str),
193+
("PID", int),
194+
("TID", int),
195+
("State", int),
196+
("Dr7", int),
197+
("Dr0", format_hints.Hex),
198+
("Range0", str),
199+
("Symbol0", str),
200+
("Dr1", format_hints.Hex),
201+
("Range1", str),
202+
("Symbol1", str),
203+
("Dr2", format_hints.Hex),
204+
("Range2", str),
205+
("Symbol2", str),
206+
("Dr3", format_hints.Hex),
207+
("Range3", str),
208+
("Symbol3", str),
209+
],
210+
self._generator(),
211+
)

0 commit comments

Comments
 (0)