44"""A module containing a collection of plugins that produce data typically
55found in Linux's /proc file system."""
66
7- from typing import List
7+ import logging
8+ from typing import List , Optional , Type
89
9- from volatility3 .framework import renderers , interfaces
10+ from volatility3 .framework import constants , interfaces , renderers
1011from volatility3 .framework .configuration import requirements
1112from volatility3 .framework .interfaces import plugins
1213from volatility3 .framework .objects import utility
1314from volatility3 .framework .renderers import format_hints
15+ from volatility3 .framework .symbols import intermed
16+ from volatility3 .framework .symbols .linux .extensions import elf
1417from volatility3 .plugins .linux import pslist
1518
19+ vollog = logging .getLogger (__name__ )
20+
1621
1722class Elfs (plugins .PluginInterface ):
1823 """Lists all memory mapped ELF files for all processes."""
1924
2025 _required_framework_version = (2 , 0 , 0 )
26+ _version = (2 , 0 , 0 )
2127
2228 @classmethod
2329 def get_requirements (cls ) -> List [interfaces .configuration .RequirementInterface ]:
@@ -36,9 +42,93 @@ def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface]
3642 element_type = int ,
3743 optional = True ,
3844 ),
45+ requirements .BooleanRequirement (
46+ name = "dump" ,
47+ description = "Extract listed processes" ,
48+ default = False ,
49+ optional = True ,
50+ ),
3951 ]
4052
53+ @classmethod
54+ def elf_dump (
55+ cls ,
56+ context : interfaces .context .ContextInterface ,
57+ layer_name : str ,
58+ elf_table_name : str ,
59+ vma : interfaces .objects .ObjectInterface ,
60+ task : interfaces .objects .ObjectInterface ,
61+ open_method : Type [interfaces .plugins .FileHandlerInterface ],
62+ ) -> Optional [interfaces .plugins .FileHandlerInterface ]:
63+ """Extracts an ELF as a FileHandlerInterface
64+ Args:
65+ context: the context to operate upon
66+ layer_name: The name of the layer on which to operate
67+ elf_table_name: the name for the symbol table containing the symbols for ELF-files
68+ vma: virtual memory allocation of ELF
69+ task: the task object whose memory should be output
70+ open_method: class to provide context manager for opening the file
71+ Returns:
72+ An open FileHandlerInterface object containing the complete data for the task or None in the case of failure
73+ """
74+
75+ proc_layer = context .layers [layer_name ]
76+ file_handle = None
77+
78+ elf_object = context .object (
79+ elf_table_name + constants .BANG + "Elf" ,
80+ offset = vma .vm_start ,
81+ layer_name = layer_name ,
82+ )
83+
84+ if not elf_object .is_valid ():
85+ return None
86+
87+ sections = {}
88+ # TODO: Apply more effort to reconstruct ELF, e.g.: https://github.com/enbarberis/core2ELF64 ?
89+ for phdr in elf_object .get_program_headers ():
90+ if phdr .p_type != 1 : # PT_LOAD = 1
91+ continue
92+
93+ start = phdr .p_vaddr
94+ size = phdr .p_memsz
95+ end = start + size
96+
97+ # Use complete memory pages for dumping
98+ # If start isn't a multiple of 4096, stick to the highest multiple < start
99+ # If end isn't a multiple of 4096, stick to the lowest multiple > end
100+ if start % 4096 :
101+ start = start & ~ 0xFFF
102+
103+ if end % 4096 :
104+ end = (end & ~ 0xFFF ) + 4096
105+
106+ real_size = end - start
107+
108+ # Check if ELF has a legitimate size
109+ if real_size < 0 or real_size > constants .linux .ELF_MAX_EXTRACTION_SIZE :
110+ raise ValueError (f"The claimed size of the ELF is invalid: { real_size } " )
111+
112+ sections [start ] = real_size
113+
114+ elf_data = b""
115+ for section_start in sorted (sections .keys ()):
116+ read_size = sections [section_start ]
117+
118+ buf = proc_layer .read (vma .vm_start + section_start , read_size , pad = True )
119+ elf_data = elf_data + buf
120+
121+ file_handle = open_method (
122+ f"pid.{ task .pid } .{ utility .array_to_string (task .comm )} .{ vma .vm_start :#x} .dmp"
123+ )
124+ file_handle .write (elf_data )
125+
126+ return file_handle
127+
41128 def _generator (self , tasks ):
129+ elf_table_name = intermed .IntermediateSymbolTable .create (
130+ self .context , self .config_path , "linux" , "elf" , class_types = elf .class_types
131+ )
42132 for task in tasks :
43133 proc_layer_name = task .add_process_layer ()
44134 if not proc_layer_name :
@@ -60,6 +150,21 @@ def _generator(self, tasks):
60150
61151 path = vma .get_name (self .context , task )
62152
153+ file_output = "Disabled"
154+ if self .config ["dump" ]:
155+ file_handle = self .elf_dump (
156+ self .context ,
157+ proc_layer_name ,
158+ elf_table_name ,
159+ vma ,
160+ task ,
161+ self .open ,
162+ )
163+ file_output = "Error outputting file"
164+ if file_handle :
165+ file_handle .close ()
166+ file_output = str (file_handle .preferred_filename )
167+
63168 yield (
64169 0 ,
65170 (
@@ -68,6 +173,7 @@ def _generator(self, tasks):
68173 format_hints .Hex (vma .vm_start ),
69174 format_hints .Hex (vma .vm_end ),
70175 path ,
176+ file_output ,
71177 ),
72178 )
73179
@@ -81,6 +187,7 @@ def run(self):
81187 ("Start" , format_hints .Hex ),
82188 ("End" , format_hints .Hex ),
83189 ("File Path" , str ),
190+ ("File Output" , str ),
84191 ],
85192 self ._generator (
86193 pslist .PsList .list_tasks (
0 commit comments