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
21- _version = (1 , 2 , 0 )
74+ _version = (2 , 0 , 0 )
2275 _required_framework_version = (2 , 0 , 0 )
2376 strings_pattern = re .compile (rb"^(?:\W*)([0-9]+)(?:\W*)(\w[\w\W]+)\n?" )
2477
@@ -43,11 +96,16 @@ def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface]
4396 name = "strings_file" , description = "Strings file"
4497 ),
4598 ]
46- # TODO: Make URLRequirement that can accept a file address which the framework can open
4799
48100 def run (self ):
49101 return renderers .TreeGrid (
50- [("String" , str ), ("Physical Address" , format_hints .Hex ), ("Result" , str )],
102+ [
103+ ("String" , str ),
104+ ("Region" , str ),
105+ ("PID" , int ),
106+ ("Physical Address" , format_hints .Hex ),
107+ ("Virtual Address" , format_hints .Hex ),
108+ ],
51109 self ._generator (),
52110 )
53111
@@ -70,7 +128,7 @@ def _generator(self) -> Generator[Tuple, None, None]:
70128 line = strings_fp .readline ()
71129 kernel = self .context .modules [self .config ["kernel" ]]
72130
73- revmap = self .generate_mapping (
131+ revmap_tree = self .generate_mapping (
74132 self .context ,
75133 kernel .layer_name ,
76134 kernel .symbol_table_name ,
@@ -81,26 +139,39 @@ def _generator(self) -> Generator[Tuple, None, None]:
81139 last_prog : float = 0
82140 line_count : float = 0
83141 num_strings = len (string_list )
84- for offset , string in string_list :
142+
143+ for phys_offset , string in string_list :
85144 line_count += 1
86- try :
87- revmap_list = [
88- name + ":" + hex (offset ) for (name , offset ) in revmap [offset >> 12 ]
89- ]
90- except (IndexError , KeyError ):
91- revmap_list = ["FREE MEMORY" ]
92- yield (
93- 0 ,
94- (
95- str (string , "latin-1" ),
96- format_hints .Hex (offset ),
97- ", " .join (revmap_list ),
98- ),
99- )
100- prog = line_count / num_strings * 100
101- if round (prog , 1 ) > last_prog :
102- last_prog = round (prog , 1 )
103- self ._progress_callback (prog , "Matching strings in memory" )
145+
146+ matched_region = False
147+ for node in revmap_tree .at (phys_offset ):
148+ matched_region = True
149+
150+ region_offset = phys_offset - node .physical_addr_start
151+ offset = node .virtual_addr_start + region_offset
152+ yield (
153+ 0 ,
154+ (
155+ str (string .strip (), "latin-1" ),
156+ node .region ,
157+ node .process_id ,
158+ format_hints .Hex (phys_offset ),
159+ format_hints .Hex (offset ),
160+ ),
161+ )
162+
163+ if not matched_region :
164+ # no maps found for this offset
165+ yield (
166+ 0 ,
167+ (
168+ str (string .strip (), "latin-1" ),
169+ "Unallocated" ,
170+ - 1 ,
171+ format_hints .Hex (phys_offset ),
172+ format_hints .Hex (0x00 ),
173+ ),
174+ )
104175
105176 def _parse_line (self , line : bytes ) -> Tuple [int , bytes ]:
106177 """Parses a single line from a strings file.
@@ -118,83 +189,91 @@ def _parse_line(self, line: bytes) -> Tuple[int, bytes]:
118189 offset , string = match .group (1 , 2 )
119190 return int (offset ), string
120191
121- @classmethod
122192 def generate_mapping (
123193 cls ,
124194 context : interfaces .context .ContextInterface ,
125195 layer_name : str ,
126196 symbol_table : str ,
127197 progress_callback : constants .ProgressCallback = None ,
128198 pid_list : Optional [List [int ]] = None ,
129- ) -> Dict [int , Set [Tuple [str , int ]]]:
130- """Creates a reverse mapping between virtual addresses and physical
131- addresses.
132-
133- Args:
134- context: the context for the method to run against
135- layer_name: the layer to map against the string lines
136- symbol_table: the name of the symbol table for the provided layer
137- progress_callback: an optional callable to display progress
138- pid_list: a lit of process IDs to consider when generating the reverse map
139-
140- Returns:
141- A mapping of virtual offsets to strings and physical offsets
142- """
199+ ):
143200 filter = pslist .PsList .create_pid_filter (pid_list )
201+ revmap_tree = MappingTree ()
144202
203+ # start with kernel mappings
145204 layer = context .layers [layer_name ]
146- reverse_map : Dict [ int , Set [ Tuple [ str , int ]]] = dict ( )
205+ min_kernel_addr = 2 ** ( layer . _maxvirtaddr - 1 )
147206 if isinstance (layer , intel .Intel ):
148207 # We don't care about errors, we just wanted chunks that map correctly
149- for mapval in layer .mapping (0x0 , layer .maximum_address , ignore_errors = True ):
150- offset , _ , mapped_offset , mapped_size , maplayer = mapval
151- for val in range (mapped_offset , mapped_offset + mapped_size , 0x1000 ):
152- cur_set = reverse_map .get (val >> 12 , set ())
153- cur_set .add (("kernel" , offset ))
154- reverse_map [val >> 12 ] = cur_set
208+ for mapval in layer .mapping (
209+ min_kernel_addr , layer .maximum_address , ignore_errors = True
210+ ):
211+ (
212+ virt_offset ,
213+ virt_size ,
214+ phy_offset ,
215+ phy_mapping_size ,
216+ _phy_layer_name ,
217+ ) = mapval
218+
219+ node = MappingNode (
220+ phy_offset ,
221+ phy_offset + phy_mapping_size ,
222+ virt_offset ,
223+ virt_offset + virt_size ,
224+ - 1 ,
225+ "Kernel" ,
226+ )
227+ revmap_tree .add (node )
228+
155229 if progress_callback :
156230 progress_callback (
157- (offset * 100 ) / layer .maximum_address ,
158- "Creating reverse kernel map " ,
231+ (virt_offset * 100 ) / layer .maximum_address ,
232+ f "Creating custom tree mapping for kernel " ,
159233 )
160234
161- # TODO: Include kernel modules
235+ # now process normal processes, ignoring kernel addrs
236+ for process in pslist .PsList .list_processes (context , layer_name , symbol_table ):
237+ if not filter (process ):
238+ proc_id = "Unknown"
239+ try :
240+ proc_id = process .UniqueProcessId
241+ proc_layer_name = process .add_process_layer ()
242+ except exceptions .InvalidAddressException as excp :
243+ vollog .debug (
244+ "Process {}: invalid address {} in layer {}" .format (
245+ proc_id , excp .invalid_address , excp .layer_name
246+ )
247+ )
248+ continue
162249
163- for process in pslist .PsList .list_processes (
164- context , layer_name , symbol_table
165- ):
166- if not filter (process ):
167- proc_id = "Unknown"
168- try :
169- proc_id = process .UniqueProcessId
170- proc_layer_name = process .add_process_layer ()
171- except exceptions .InvalidAddressException as excp :
172- vollog .debug (
173- "Process {}: invalid address {} in layer {}" .format (
174- proc_id , excp .invalid_address , excp .layer_name
175- )
250+ proc_layer = context .layers [proc_layer_name ]
251+ max_proc_addr = (2 ** (proc_layer ._maxvirtaddr - 1 )) - 1
252+ if isinstance (proc_layer , linear .LinearlyMappedLayer ):
253+ for mapval in proc_layer .mapping (
254+ 0 , max_proc_addr , ignore_errors = True
255+ ):
256+ (
257+ virt_offset ,
258+ virt_size ,
259+ phy_offset ,
260+ phy_mapping_size ,
261+ _phy_layer_name ,
262+ ) = mapval
263+
264+ node = MappingNode (
265+ phy_offset ,
266+ phy_offset + phy_mapping_size ,
267+ virt_offset ,
268+ virt_offset + virt_size ,
269+ proc_id ,
270+ "Process" ,
176271 )
177- continue
178-
179- proc_layer = context .layers [proc_layer_name ]
180- if isinstance (proc_layer , linear .LinearlyMappedLayer ):
181- for mapval in proc_layer .mapping (
182- 0x0 , proc_layer .maximum_address , ignore_errors = True
183- ):
184- mapped_offset , _ , offset , mapped_size , maplayer = mapval
185- for val in range (
186- mapped_offset , mapped_offset + mapped_size , 0x1000
187- ):
188- cur_set = reverse_map .get (mapped_offset >> 12 , set ())
189- cur_set .add (
190- (f"Process { process .UniqueProcessId } " , offset )
191- )
192- reverse_map [mapped_offset >> 12 ] = cur_set
193- # FIXME: make the progress for all processes, rather than per-process
194- if progress_callback :
195- progress_callback (
196- (offset * 100 ) / layer .maximum_address ,
197- f"Creating mapping for task { process .UniqueProcessId } " ,
198- )
199-
200- return reverse_map
272+ revmap_tree .add (node )
273+
274+ if progress_callback :
275+ progress_callback (
276+ (virt_offset * 100 ) / max_proc_addr ,
277+ f"Creating custom tree mapping for task { proc_id } " ,
278+ )
279+ return revmap_tree
0 commit comments