22# which is available at https://www.volatilityfoundation.org/license/vsl-v1.0
33#
44
5+ from dataclasses import dataclass
56import logging
67import re
78from typing import Dict , Generator , List , Set , Tuple , Optional
1516vollog = 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+
1871class 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