44"""A module containing a collection of plugins that produce data typically
55found in Linux's /proc file system."""
66
7- from volatility3 .framework import renderers
7+ import logging
8+ from typing import Callable , List , Generator , Iterable , Type , Optional
9+
10+ from volatility3 .framework import renderers , interfaces , exceptions
811from volatility3 .framework .configuration import requirements
912from volatility3 .framework .interfaces import plugins
1013from volatility3 .framework .objects import utility
1114from volatility3 .framework .renderers import format_hints
1215from volatility3 .plugins .linux import pslist
1316
17+ vollog = logging .getLogger (__name__ )
1418
1519class Maps (plugins .PluginInterface ):
1620 """Lists all memory maps for all processes."""
1721
1822 _required_framework_version = (2 , 0 , 0 )
23+ MAXSIZE_DEFAULT = 1024 * 1024 * 1024 # 1 Gb
1924
2025 @classmethod
2126 def get_requirements (cls ):
@@ -35,16 +40,138 @@ def get_requirements(cls):
3540 element_type = int ,
3641 optional = True ,
3742 ),
43+ requirements .BooleanRequirement (
44+ name = "dump" ,
45+ description = "Extract listed memory segments" ,
46+ default = False ,
47+ optional = True ,
48+ ),
49+ requirements .ListRequirement (
50+ name = "address" ,
51+ description = "Process virtual memory address to include "
52+ "(all other address ranges are excluded). This must be "
53+ "a base address, not an address within the desired range." ,
54+ element_type = int ,
55+ optional = True ,
56+ ),
57+ requirements .IntRequirement (
58+ name = "maxsize" ,
59+ description = "Maximum size for dumped VMA sections "
60+ "(all the bigger sections will be ignored)" ,
61+ default = cls .MAXSIZE_DEFAULT ,
62+ optional = True ,
63+ ),
3864 ]
3965
66+ @classmethod
67+ def list_vmas (
68+ cls ,
69+ task : interfaces .objects .ObjectInterface ,
70+ filter_func : Callable [
71+ [interfaces .objects .ObjectInterface ], bool
72+ ] = lambda _ : False ,
73+ ) -> Generator [interfaces .objects .ObjectInterface , None , None ]:
74+ """Lists the Virtual Memory Areas of a specific process.
75+
76+ Args:
77+ task: task object from which to list the vma
78+ filter_func: Function to take a vma and return True if it should be filtered out
79+
80+ Returns:
81+ A list of vmas based on the task and filtered based on the filter function
82+ """
83+ if task .mm :
84+ for vma in task .mm .get_mmap_iter ():
85+ if not filter_func (vma ):
86+ yield vma
87+
88+ @classmethod
89+ def vma_dump (
90+ cls ,
91+ context : interfaces .context .ContextInterface ,
92+ task : interfaces .objects .ObjectInterface ,
93+ vma : interfaces .objects .ObjectInterface ,
94+ open_method : Type [interfaces .plugins .FileHandlerInterface ],
95+ maxsize : int = MAXSIZE_DEFAULT ,
96+ ) -> Optional [interfaces .plugins .FileHandlerInterface ]:
97+ """Extracts the complete data for VMA as a FileInterface.
98+
99+ Args:
100+ context: The context to retrieve required elements (layers, symbol tables) from
101+ task: an task_struct instance
102+ vma: The suspected VMA to extract (ObjectInterface)
103+ open_method: class to provide context manager for opening the file
104+ maxsize: Max size of VMA section (default MAXSIZE_DEFAULT)
105+
106+ Returns:
107+ An open FileInterface object containing the complete data for the task or None in the case of failure
108+ """
109+ try :
110+ vm_start = vma .vm_start
111+ vm_end = vma .vm_end
112+ except AttributeError :
113+ vollog .debug ("Unable to find the vm_start and vm_end" )
114+ return None
115+
116+ vm_size = vm_end - vm_start
117+ if 0 < maxsize < vm_size :
118+ vollog .debug (
119+ f"Skip virtual memory dump { vm_start :#x} -{ vm_end :#x} due to maxsize limit"
120+ )
121+ return None
122+
123+ pid = "Unknown"
124+ try :
125+ pid = task .tgid
126+ proc_layer_name = task .add_process_layer ()
127+ except exceptions .InvalidAddressException as excp :
128+ vollog .debug (
129+ "Process {}: invalid address {} in layer {}" .format (
130+ pid , excp .invalid_address , excp .layer_name
131+ )
132+ )
133+ return None
134+
135+ proc_layer = context .layers [proc_layer_name ]
136+ file_name = f"pid.{ pid } .vma.{ vm_start :#x} -{ vm_end :#x} .dmp"
137+ try :
138+ file_handle = open_method (file_name )
139+ chunk_size = 1024 * 1024 * 10
140+ offset = vm_start
141+ while offset < vm_start + vm_size :
142+ to_read = min (chunk_size , vm_start + vm_size - offset )
143+ data = proc_layer .read (offset , to_read , pad = True )
144+ if not data :
145+ break
146+ file_handle .write (data )
147+ offset += to_read
148+
149+ except Exception as excp :
150+ vollog .debug (f"Unable to dump virtual memory { file_name } : { excp } " )
151+ return None
152+
153+ return file_handle
154+
40155 def _generator (self , tasks ):
156+ # build filter for addresses if required
157+ address_list = self .config .get ("address" , [])
158+ if address_list == []:
159+ # do not filter as no address_list was supplied
160+ filter_func = lambda _ : False
161+ else :
162+ # filter for any vm_start that matches the supplied address config
163+ def filter_function (x : interfaces .objects .ObjectInterface ) -> bool :
164+ return x .vm_start not in address_list
165+
166+ filter_func = filter_function
167+
41168 for task in tasks :
42169 if not task .mm :
43170 continue
44171
45172 name = utility .array_to_string (task .comm )
46173
47- for vma in task . mm . get_mmap_iter ( ):
174+ for vma in self . list_vmas ( task , filter_func = filter_func ):
48175 flags = vma .get_protection ()
49176 page_offset = vma .get_page_offset ()
50177 major = 0
@@ -61,6 +188,16 @@ def _generator(self, tasks):
61188
62189 path = vma .get_name (self .context , task )
63190
191+ file_output = "Disabled"
192+ if self .config ["dump" ]:
193+ file_handle = self .vma_dump (
194+ self .context , task , vma , self .open , self .config ["maxsize" ]
195+ )
196+ file_output = "Error outputting file"
197+ if file_handle :
198+ file_handle .close ()
199+ file_output = file_handle .preferred_filename
200+
64201 yield (
65202 0 ,
66203 (
@@ -74,6 +211,7 @@ def _generator(self, tasks):
74211 minor ,
75212 inode ,
76213 path ,
214+ file_output ,
77215 ),
78216 )
79217
@@ -92,6 +230,7 @@ def run(self):
92230 ("Minor" , int ),
93231 ("Inode" , int ),
94232 ("File Path" , str ),
233+ ("File output" , str ),
95234 ],
96235 self ._generator (
97236 pslist .PsList .list_tasks (
0 commit comments