Skip to content

Commit 1f9d58d

Browse files
committed
Plugins: Windows.string speed enhancements by eve
1 parent 7536e9d commit 1f9d58d

File tree

1 file changed

+173
-75
lines changed

1 file changed

+173
-75
lines changed

volatility3/framework/plugins/windows/strings.py

Lines changed: 173 additions & 75 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
# which is available at https://www.volatilityfoundation.org/license/vsl-v1.0
33
#
44

5+
from dataclasses import dataclass
56
import logging
67
import re
78
from typing import Dict, Generator, List, Set, Tuple, Optional
@@ -15,13 +16,66 @@
1516
vollog = logging.getLogger(__name__)
1617

1718

19+
@dataclass
20+
class MappingNode:
21+
def __init__(
22+
self,
23+
physical_addr_start,
24+
physical_addr_end,
25+
virtual_addr_start,
26+
virtual_addr_end,
27+
process_id,
28+
region,
29+
) -> None:
30+
self.physical_addr_start = physical_addr_start
31+
self.physical_addr_end = physical_addr_end
32+
self.virtual_addr_start = virtual_addr_start
33+
self.virtual_addr_end = virtual_addr_end
34+
self.process_id = process_id
35+
self.region = region
36+
37+
38+
class MappingTree:
39+
def __init__(self, root=None) -> None:
40+
self.root = root
41+
self.left = None
42+
self.right = None
43+
44+
def add(self, node):
45+
if isinstance(node, MappingNode):
46+
if self.root == None:
47+
self.root = node
48+
elif node.physical_addr_start < self.root.physical_addr_start:
49+
if self.left == None:
50+
self.left = MappingTree(node)
51+
else:
52+
self.left.add(node)
53+
else:
54+
if self.right == None:
55+
self.right = MappingTree(node)
56+
else:
57+
self.right.add(node)
58+
else:
59+
raise TypeError()
60+
61+
def at(self, point):
62+
if self.root:
63+
if self.root.physical_addr_start <= point <= self.root.physical_addr_end:
64+
yield self.root
65+
if point < self.root.physical_addr_start and self.left:
66+
yield from self.left.at(point)
67+
elif self.right:
68+
yield from self.right.at(point)
69+
70+
1871
class Strings(interfaces.plugins.PluginInterface):
1972
"""Reads output from the strings command and indicates which process(es) each string belongs to."""
2073

2174
_required_framework_version = (2, 0, 0)
2275

2376
# 2.0.0 - change signature of `generate_mapping`
24-
_version = (2, 0, 0)
77+
# 3.0.0 - Interval mapping
78+
_version = (3, 0, 0)
2579

2680
strings_pattern = re.compile(rb"^(?:\W*)([0-9]+)(?:\W*)(\w[\w\W]+)\n?")
2781

@@ -46,11 +100,16 @@ def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface]
46100
name="strings_file", description="Strings file"
47101
),
48102
]
49-
# TODO: Make URLRequirement that can accept a file address which the framework can open
50103

51104
def run(self):
52105
return renderers.TreeGrid(
53-
[("String", str), ("Physical Address", format_hints.Hex), ("Result", str)],
106+
[
107+
("String", str),
108+
("Region", str),
109+
("PID", int),
110+
("Physical Address", format_hints.Hex),
111+
("Virtual Address", format_hints.Hex),
112+
],
54113
self._generator(),
55114
)
56115

@@ -71,37 +130,52 @@ def _generator(self) -> Generator[Tuple, None, None]:
71130
except ValueError:
72131
vollog.error(f"Line in unrecognized format: line {count}")
73132
line = strings_fp.readline()
133+
kernel = self.context.modules[self.config["kernel"]]
74134

75-
revmap = self.generate_mapping(
76-
context=self.context,
77-
kernel_module_name=self.config["kernel"],
135+
revmap_tree = self.generate_mapping(
136+
self.context,
137+
kernel.layer_name,
138+
kernel.symbol_table_name,
78139
progress_callback=self._progress_callback,
79140
pid_list=self.config["pid"],
80141
)
81142

82143
last_prog: float = 0
83144
line_count: float = 0
84145
num_strings = len(string_list)
85-
for offset, string in string_list:
146+
147+
for phys_offset, string in string_list:
86148
line_count += 1
87-
try:
88-
revmap_list = [
89-
name + ":" + hex(offset) for (name, offset) in revmap[offset >> 12]
90-
]
91-
except (IndexError, KeyError):
92-
revmap_list = ["FREE MEMORY"]
93-
yield (
94-
0,
95-
(
96-
str(string, "latin-1"),
97-
format_hints.Hex(offset),
98-
", ".join(revmap_list),
99-
),
100-
)
101-
prog = line_count / num_strings * 100
102-
if round(prog, 1) > last_prog:
103-
last_prog = round(prog, 1)
104-
self._progress_callback(prog, "Matching strings in memory")
149+
150+
matched_region = False
151+
for node in revmap_tree.at(phys_offset):
152+
matched_region = True
153+
154+
region_offset = phys_offset - node.physical_addr_start
155+
offset = node.virtual_addr_start + region_offset
156+
yield (
157+
0,
158+
(
159+
str(string.strip(), "latin-1"),
160+
node.region,
161+
node.process_id,
162+
format_hints.Hex(phys_offset),
163+
format_hints.Hex(offset),
164+
),
165+
)
166+
167+
if not matched_region:
168+
# no maps found for this offset
169+
yield (
170+
0,
171+
(
172+
str(string.strip(), "latin-1"),
173+
"Unallocated",
174+
-1,
175+
format_hints.Hex(phys_offset),
176+
format_hints.Hex(0x00),
177+
),
178+
)
105179

106180
def _parse_line(self, line: bytes) -> Tuple[int, bytes]:
107181
"""Parses a single line from a strings file.
@@ -123,7 +197,8 @@ def _parse_line(self, line: bytes) -> Tuple[int, bytes]:
123197
def generate_mapping(
124198
cls,
125199
context: interfaces.context.ContextInterface,
126-
kernel_module_name: str,
200+
layer_name: str,
201+
symbol_table: str,
127202
progress_callback: constants.ProgressCallback = None,
128203
pid_list: Optional[List[int]] = None,
129204
) -> Dict[int, Set[Tuple[str, int]]]:
@@ -132,68 +207,91 @@ def generate_mapping(
132207
133208
Args:
134209
context: the context for the method to run against
135-
kernel_module_name: the name of the module forthe kernel
210+
layer_name: the name of the windows intel layer to be scanned
211+
symbol_table: the name of the kernel symbol table
136212
progress_callback: an optional callable to display progress
137213
pid_list: a lit of process IDs to consider when generating the reverse map
138214
139215
Returns:
140216
A mapping of virtual offsets to strings and physical offsets
141217
"""
142218
filter = pslist.PsList.create_pid_filter(pid_list)
219+
revmap_tree = MappingTree()
143220

144-
kernel = context.modules[kernel_module_name]
145-
146-
layer = context.layers[kernel.layer_name]
147-
reverse_map: Dict[int, Set[Tuple[str, int]]] = dict()
221+
# start with kernel mappings
222+
layer = context.layers[layer_name]
223+
min_kernel_addr = 2 ** (layer._maxvirtaddr - 1)
148224
if isinstance(layer, intel.Intel):
149225
# We don't care about errors, we just wanted chunks that map correctly
150-
for mapval in layer.mapping(0x0, layer.maximum_address, ignore_errors=True):
151-
offset, _, mapped_offset, mapped_size, maplayer = mapval
152-
for val in range(mapped_offset, mapped_offset + mapped_size, 0x1000):
153-
cur_set = reverse_map.get(val >> 12, set())
154-
cur_set.add(("kernel", offset))
155-
reverse_map[val >> 12] = cur_set
226+
for mapval in layer.mapping(
227+
min_kernel_addr, layer.maximum_address, ignore_errors=True
228+
):
229+
(
230+
virt_offset,
231+
virt_size,
232+
phy_offset,
233+
phy_mapping_size,
234+
_phy_layer_name,
235+
) = mapval
236+
237+
node = MappingNode(
238+
phy_offset,
239+
phy_offset + phy_mapping_size,
240+
virt_offset,
241+
virt_offset + virt_size,
242+
-1,
243+
"Kernel",
244+
)
245+
revmap_tree.add(node)
246+
156247
if progress_callback:
157248
progress_callback(
158-
(offset * 100) / layer.maximum_address,
159-
"Creating reverse kernel map",
249+
(virt_offset * 100) / layer.maximum_address,
250+
f"Creating custom tree mapping for kernel",
160251
)
161252

162-
# TODO: Include kernel modules
253+
# now process normal processes, ignoring kernel addrs
254+
for process in pslist.PsList.list_processes(context, layer_name, symbol_table):
255+
if not filter(process):
256+
proc_id = "Unknown"
257+
try:
258+
proc_id = process.UniqueProcessId
259+
proc_layer_name = process.add_process_layer()
260+
except exceptions.InvalidAddressException as excp:
261+
vollog.debug(
262+
"Process {}: invalid address {} in layer {}".format(
263+
proc_id, excp.invalid_address, excp.layer_name
264+
)
265+
)
266+
continue
163267

164-
for process in pslist.PsList.list_processes(
165-
context=context, kernel_module_name=kernel_module_name
166-
):
167-
if not filter(process):
168-
proc_id = "Unknown"
169-
try:
170-
proc_id = process.UniqueProcessId
171-
proc_layer_name = process.add_process_layer()
172-
except exceptions.InvalidAddressException as excp:
173-
vollog.debug(
174-
f"Process {proc_id}: invalid address {excp.invalid_address} in layer {excp.layer_name}"
268+
proc_layer = context.layers[proc_layer_name]
269+
max_proc_addr = (2 ** (proc_layer._maxvirtaddr - 1)) - 1
270+
if isinstance(proc_layer, linear.LinearlyMappedLayer):
271+
for mapval in proc_layer.mapping(
272+
0, max_proc_addr, ignore_errors=True
273+
):
274+
(
275+
virt_offset,
276+
virt_size,
277+
phy_offset,
278+
phy_mapping_size,
279+
_phy_layer_name,
280+
) = mapval
281+
282+
node = MappingNode(
283+
phy_offset,
284+
phy_offset + phy_mapping_size,
285+
virt_offset,
286+
virt_offset + virt_size,
287+
proc_id,
288+
"Process",
175289
)
176-
continue
177-
178-
proc_layer = context.layers[proc_layer_name]
179-
if isinstance(proc_layer, linear.LinearlyMappedLayer):
180-
for mapval in proc_layer.mapping(
181-
0x0, proc_layer.maximum_address, ignore_errors=True
182-
):
183-
mapped_offset, _, offset, mapped_size, _maplayer = mapval
184-
for val in range(
185-
mapped_offset, mapped_offset + mapped_size, 0x1000
186-
):
187-
cur_set = reverse_map.get(mapped_offset >> 12, set())
188-
cur_set.add(
189-
(f"Process {process.UniqueProcessId}", offset)
190-
)
191-
reverse_map[mapped_offset >> 12] = cur_set
192-
# FIXME: make the progress for all processes, rather than per-process
193-
if progress_callback:
194-
progress_callback(
195-
(offset * 100) / layer.maximum_address,
196-
f"Creating mapping for task {process.UniqueProcessId}",
197-
)
198-
199-
return reverse_map
290+
revmap_tree.add(node)
291+
292+
if progress_callback:
293+
progress_callback(
294+
(virt_offset * 100) / max_proc_addr,
295+
f"Creating custom tree mapping for task {proc_id}",
296+
)
297+
return revmap_tree

0 commit comments

Comments
 (0)