11# This file is Copyright 2021 Volatility Foundation and licensed under the Volatility Software License 1.0
22# which is available at https://www.volatilityfoundation.org/license/vsl-v1.0
33#
4- from typing import Callable , Iterable , List , Any , Tuple
4+ from typing import Any , Callable , Iterable , List
55
6- from volatility3 .framework import renderers , interfaces
6+ from volatility3 .framework import interfaces , renderers
77from volatility3 .framework .configuration import requirements
88from volatility3 .framework .objects import utility
99from volatility3 .framework .renderers import format_hints
10+ from volatility3 .framework .symbols import intermed
11+ from volatility3 .framework .symbols .linux .extensions import elf
12+ from volatility3 .plugins .linux import elfs
1013
1114
1215class PsList (interfaces .plugins .PluginInterface ):
@@ -24,6 +27,9 @@ def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface]
2427 description = "Linux kernel" ,
2528 architectures = ["Intel32" , "Intel64" ],
2629 ),
30+ requirements .PluginRequirement (
31+ name = "elfs" , plugin = elfs .Elfs , version = (2 , 0 , 0 )
32+ ),
2733 requirements .ListRequirement (
2834 name = "pid" ,
2935 description = "Filter on specific process IDs" ,
@@ -42,6 +48,12 @@ def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface]
4248 optional = True ,
4349 default = False ,
4450 ),
51+ requirements .BooleanRequirement (
52+ name = "dump" ,
53+ description = "Extract listed processes" ,
54+ optional = True ,
55+ default = False ,
56+ ),
4557 ]
4658
4759 @classmethod
@@ -66,38 +78,12 @@ def filter_func(x):
6678 else :
6779 return lambda _ : False
6880
69- def _get_task_fields (
70- self , task : interfaces .objects .ObjectInterface , decorate_comm : bool = False
71- ) -> Tuple [int , int , int , str ]:
72- """Extract the fields needed for the final output
73-
74- Args:
75- task: A task object from where to get the fields.
76- decorate_comm: If True, it decorates the comm string of
77- - User threads: in curly brackets,
78- - Kernel threads: in square brackets
79- Defaults to False.
80- Returns:
81- A tuple with the fields to show in the plugin output.
82- """
83- pid = task .tgid
84- tid = task .pid
85- ppid = task .parent .tgid if task .parent else 0
86- name = utility .array_to_string (task .comm )
87- if decorate_comm :
88- if task .is_kernel_thread :
89- name = f"[{ name } ]"
90- elif task .is_user_thread :
91- name = f"{{{ name } }}"
92-
93- task_fields = (format_hints .Hex (task .vol .offset ), pid , tid , ppid , name )
94- return task_fields
95-
9681 def _generator (
9782 self ,
9883 pid_filter : Callable [[Any ], bool ],
9984 include_threads : bool = False ,
10085 decorate_comm : bool = False ,
86+ dump : bool = False ,
10187 ):
10288 """Generates the tasks list.
10389
@@ -110,14 +96,63 @@ def _generator(
11096 - User threads: in curly brackets,
11197 - Kernel threads: in square brackets
11298 Defaults to False.
99+ dump: If True, the main executable of the process is written to a file
100+ Defaults to False.
113101 Yields:
114102 Each rows
115103 """
116104 for task in self .list_tasks (
117105 self .context , self .config ["kernel" ], pid_filter , include_threads
118106 ):
119- row = self ._get_task_fields (task , decorate_comm )
120- yield (0 , row )
107+ elf_table_name = intermed .IntermediateSymbolTable .create (
108+ self .context ,
109+ self .config_path ,
110+ "linux" ,
111+ "elf" ,
112+ class_types = elf .class_types ,
113+ )
114+ file_output = "Disabled"
115+ if dump :
116+ proc_layer_name = task .add_process_layer ()
117+ if not proc_layer_name :
118+ continue
119+
120+ # Find the vma that belongs to the main ELF of the process
121+ file_output = "Error outputting file"
122+
123+ for v in task .mm .get_mmap_iter ():
124+ if v .vm_start == task .mm .start_code :
125+ file_handle = elfs .Elfs .elf_dump (
126+ self .context ,
127+ proc_layer_name ,
128+ elf_table_name ,
129+ v ,
130+ task ,
131+ self .open ,
132+ )
133+ if file_handle :
134+ file_output = str (file_handle .preferred_filename )
135+ file_handle .close ()
136+ break
137+
138+ pid = task .tgid
139+ tid = task .pid
140+ ppid = task .parent .tgid if task .parent else 0
141+ name = utility .array_to_string (task .comm )
142+ if decorate_comm :
143+ if task .is_kernel_thread :
144+ name = f"[{ name } ]"
145+ elif task .is_user_thread :
146+ name = f"{{{ name } }}"
147+
148+ yield 0 , (
149+ format_hints .Hex (task .vol .offset ),
150+ pid ,
151+ tid ,
152+ ppid ,
153+ name ,
154+ file_output ,
155+ )
121156
122157 @classmethod
123158 def list_tasks (
@@ -155,6 +190,7 @@ def run(self):
155190 pids = self .config .get ("pid" )
156191 include_threads = self .config .get ("threads" )
157192 decorate_comm = self .config .get ("decorate_comm" )
193+ dump = self .config .get ("dump" )
158194 filter_func = self .create_pid_filter (pids )
159195
160196 columns = [
@@ -163,7 +199,8 @@ def run(self):
163199 ("TID" , int ),
164200 ("PPID" , int ),
165201 ("COMM" , str ),
202+ ("File output" , str ),
166203 ]
167204 return renderers .TreeGrid (
168- columns , self ._generator (filter_func , include_threads , decorate_comm )
205+ columns , self ._generator (filter_func , include_threads , decorate_comm , dump )
169206 )
0 commit comments