1010from volatility .framework .configuration import requirements
1111from volatility .framework .renderers import format_hints
1212from volatility .framework .objects import utility
13- from typing import List , Tuple
13+ from typing import List , Tuple , Type
1414vollog = logging .getLogger (__name__ )
1515
1616FILE_DEVICE_DISK = 0x7
2424class DumpFiles (interfaces .plugins .PluginInterface ):
2525 """Dumps cached file contents from Windows memory samples."""
2626
27+ _required_framework_version = (2 , 0 , 0 )
2728 _version = (1 , 0 , 0 )
2829
2930 @classmethod
@@ -49,6 +50,7 @@ def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface]
4950
5051 def dump_file_producer (self , file_object : interfaces .objects .ObjectInterface ,
5152 memory_object : interfaces .objects .ObjectInterface ,
53+ open_method : Type [interfaces .plugins .FileHandlerInterface ],
5254 layer : interfaces .layers .DataLayerInterface ,
5355 desired_file_name : str ) -> str :
5456 """Produce a file from the memory object's get_available_pages() interface.
@@ -59,23 +61,24 @@ def dump_file_producer(self, file_object: interfaces.objects.ObjectInterface,
5961 :param desired_file_name: name of the output file
6062 :return: result status
6163 """
62- filedata = interfaces . plugins . FileInterface (desired_file_name )
64+ filedata = open_method (desired_file_name )
6365 try :
6466 # Description of these variables:
6567 # memoffset: offset in the specified layer where the page begins
6668 # fileoffset: write to this offset in the destination file
6769 # datasize: size of the page
70+
71+ # track number of bytes written so we don't write empty files to disk
72+ bytes_written = 0
6873 for memoffset , fileoffset , datasize in memory_object .get_available_pages ():
6974 data = layer .read (memoffset , datasize , pad = True )
70- filedata .data .seek (fileoffset )
71- filedata .data .write (data )
75+ bytes_written += len (data )
76+ filedata .seek (fileoffset )
77+ filedata .write (data )
7278
73- # Avoid writing files to disk if they are going to be empty or all zeros.
74- cached_length = len (filedata .data .getvalue ())
75- if cached_length == 0 or filedata .data .getvalue ().count (0 ) == cached_length :
79+ if not bytes_written :
7680 result_text = "No data is cached for the file at {0:#x}" .format (file_object .vol .offset )
7781 else :
78- self .produce_file (filedata )
7982 result_text = "Stored {}" .format (filedata .preferred_filename )
8083 except exceptions .InvalidAddressException :
8184 result_text = "Unable to dump file at {0:#x}" .format (file_object .vol .offset )
@@ -141,7 +144,7 @@ def process_file_object(self, file_obj: interfaces.objects.ObjectInterface) -> T
141144 ntpath .basename (obj_name ),
142145 extension )
143146
144- result_text = self .dump_file_producer (file_obj , memory_object , layer , desired_file_name )
147+ result_text = self .dump_file_producer (file_obj , memory_object , self . open , layer , desired_file_name )
145148
146149 yield (cache_name , format_hints .Hex (file_obj .vol .offset ),
147150 ntpath .basename (obj_name ), # temporary, so its easier to visualize output
0 commit comments