22# which is available at https://www.volatilityfoundation.org/license/vsl-v1.0
33#
44import logging
5- import contextlib
5+
6+ from typing import Optional , Tuple , Generator , Dict
67
78from volatility3 .framework import interfaces , exceptions
89from volatility3 .framework import renderers
910from volatility3 .framework .configuration import requirements
1011from volatility3 .framework .objects import utility
1112from volatility3 .framework .renderers import format_hints
12- from volatility3 .plugins .windows import pslist
13+ from volatility3 .plugins .windows import pslist , vadinfo
1314
1415vollog = logging .getLogger (__name__ )
1516
1617
1718class ProcessGhosting (interfaces .plugins .PluginInterface ):
18- """Lists processes whose DeletePending bit is set or whose FILE_OBJECT is set to 0"""
19+ """Lists processes whose DeletePending bit is set or whose FILE_OBJECT is set to 0 or Vads that are DeleteOnClose """
1920
21+ _version = (1 , 0 , 0 )
2022 _required_framework_version = (2 , 4 , 0 )
2123
2224 @classmethod
@@ -33,52 +35,163 @@ def get_requirements(cls):
3335 ),
3436 ]
3537
36- def _generator (self , procs ):
37- kernel = self .context .modules [self .config ["kernel" ]]
38+ @classmethod
39+ def _process_checks (
40+ cls ,
41+ proc : interfaces .objects .ObjectInterface ,
42+ mapped_files : Dict [int , Tuple [str , interfaces .objects .ObjectInterface ]],
43+ ) -> Generator [
44+ Tuple [int , Optional [int ], Optional [int ], int , Optional [str ]], None , None
45+ ]:
46+ """
47+ Checks the EPROCESS for signs of ghosting
48+ """
49+ if not proc .has_member ("ImageFilePointer" ):
50+ return
3851
39- if not kernel .get_type ("_EPROCESS" ).has_member ("ImageFilePointer" ):
40- vollog .warning (
41- "This plugin only supports Windows 10 builds when the ImageFilePointer member of _EPROCESS is present"
52+ delete_pending = None
53+
54+ # if it is 0 then its a side effect of process ghosting
55+ if proc .ImageFilePointer .vol .offset != 0 :
56+ try :
57+ file_object = proc .ImageFilePointer
58+ delete_pending = file_object .DeletePending
59+ file_object = file_object .dereference ().vol .offset
60+ except exceptions .InvalidAddressException :
61+ file_object = 0
62+
63+ # ImageFilePointer equal to 0 means process ghosting or similar techniques were used
64+ else :
65+ file_object = 0
66+
67+ # delete_pending besides 0 or 1 = smear
68+ if isinstance (delete_pending , int ) and delete_pending not in [0 , 1 ]:
69+ vollog .debug (
70+ f"Invalid delete_pending value { delete_pending } found for process { proc .UniqueProcessId } "
4271 )
72+ delete_pending = None
73+
74+ if file_object == 0 or delete_pending == 1 :
75+ yield file_object , delete_pending , None , proc .SectionBaseAddress
76+
77+ @classmethod
78+ def _vad_checks (
79+ cls , control_area : interfaces .objects .ObjectInterface , vad_path : str
80+ ) -> Generator [Tuple [int , Optional [int ], Optional [int ]], None , None ]:
81+ """
82+ Checks the control area for delete on close or delete pending being set
83+ """
84+ try :
85+ file_object = control_area .FilePointer .dereference ().cast ("_FILE_OBJECT" )
86+ except exceptions .InvalidAddressException :
4387 return
4488
45- for proc in procs :
46- delete_pending = renderers .UnreadableValue ()
47- process_name = utility .array_to_string (proc .ImageFileName )
89+ try :
90+ delete_on_close = control_area .u .Flags .DeleteOnClose
91+ except exceptions .InvalidAddressException :
92+ delete_on_close = None
4893
49- # if it is 0 then its a side effect of process ghosting
50- if proc .ImageFilePointer .vol .offset != 0 :
51- try :
52- file_object = proc .ImageFilePointer
53- delete_pending = file_object .DeletePending
54- except exceptions .InvalidAddressException :
55- file_object = 0
94+ if delete_on_close and vad_path .lower ().endswith ((".exe" , ".dll" )):
95+ yield file_object .vol .offset , None , delete_on_close
5696
57- # ImageFilePointer equal to 0 means process ghosting or similar techniques were used
58- else :
59- file_object = 0
97+ try :
98+ delete_pending = file_object .DeletePending
99+ except exceptions .InvalidAddressException :
100+ delete_pending = None
60101
61- if isinstance (delete_pending , int ) and delete_pending not in [0 , 1 ]:
102+ if delete_pending == 1 :
103+ yield file_object .vol .offset , delete_pending , None
104+
105+ @classmethod
106+ def check_for_ghosting (
107+ cls ,
108+ proc : interfaces .objects .ObjectInterface ,
109+ mapped_files : Dict [int , Tuple [str , interfaces .objects .ObjectInterface ]],
110+ ) -> Generator [
111+ Tuple [int , Optional [int ], Optional [int ], int , Optional [str ]], None , None
112+ ]:
113+ """
114+ Returns process or vad info for ghosting files
115+
116+ Args:
117+ proc:
118+ mapped_files: A dictionary mapping vad base addreses to the path and vad instance for the process
119+
120+ Return:
121+ A Generator of tuples of the file object address, the delete pending state, delete on close state, base address of the VAD, and the path
122+ """
123+ # check the direct file object of the process
124+ yield from cls ._process_checks (proc , mapped_files )
125+
126+ # walk each vad, check if it is pending delete or has its delete on close bit set
127+ for vad_base , (path , vad ) in mapped_files .items ():
128+ # these checks have no meaning for private memory areas
129+ if vad .get_private_memory () == 1 :
130+ continue
131+
132+ try :
133+ if vad .has_member ("ControlArea" ):
134+ control_area = vad .ControlArea
135+ elif vad .has_member ("Subsection" ):
136+ control_area = vad .Subsection .ControlArea
137+ # We got here from a short vad, likely smear
138+ else :
139+ continue
140+ except exceptions .InvalidAddressException :
62141 vollog .debug (
63- f"Invalid delete_pending value { delete_pending } found for { process_name } { proc .UniqueProcessId } "
142+ f"Unable to get control area for vad at base { vad_base :#x } for process with pid { proc .UniqueProcessId } "
64143 )
144+ continue
145+
146+ for file_object_address , delete_pending , delete_on_close in cls ._vad_checks (
147+ control_area , path
148+ ):
149+ yield format_hints .Hex (
150+ file_object_address
151+ ), delete_pending , delete_on_close , vad_base
152+
153+ def _generator (self , procs ):
154+ kernel = self .context .modules [self .config ["kernel" ]]
65155
66- # delete_pending besides 0 or 1 = smear
67- if file_object == 0 or delete_pending == 1 :
68- path = renderers .UnreadableValue ()
69- if file_object :
70- with contextlib .suppress (exceptions .InvalidAddressException ):
71- path = file_object .FileName .String
72-
73- yield (
74- 0 ,
75- (
76- proc .UniqueProcessId ,
77- process_name ,
78- format_hints .Hex (file_object ),
79- delete_pending ,
80- path ,
81- ),
156+ has_imagefilepointer = kernel .get_type ("_EPROCESS" ).has_member (
157+ "ImageFilePointer"
158+ )
159+ if not has_imagefilepointer :
160+ vollog .warning (
161+ "ImageFilePointer checks are only supported on Windows 10+ builds when the ImageFilePointer member of _EPROCESS is present"
162+ )
163+
164+ for proc in procs :
165+ process_name = utility .array_to_string (proc .ImageFileName )
166+ pid = proc .UniqueProcessId
167+
168+ # base address -> (file path, VAD instance)
169+ mapped_files : Dict [int , Tuple [str , interfaces .objects .ObjectInterface ]] = {}
170+ for vad in vadinfo .VadInfo .list_vads (proc ):
171+ path = vad .get_file_name ()
172+ if isinstance (path , str ):
173+ mapped_files [vad .get_start ()] = (path , vad )
174+
175+ for (
176+ file_object_address ,
177+ delete_pending ,
178+ delete_on_close ,
179+ base_address ,
180+ ) in self .check_for_ghosting (proc , mapped_files ):
181+ vad_info = mapped_files .get (base_address )
182+ if vad_info :
183+ path = vad_info [0 ]
184+ else :
185+ path = renderers .NotAvailableValue ()
186+
187+ yield 0 , (
188+ pid ,
189+ process_name ,
190+ format_hints .Hex (base_address ),
191+ format_hints .Hex (file_object_address ),
192+ delete_pending or renderers .NotApplicableValue (),
193+ delete_on_close or renderers .NotApplicableValue (),
194+ path ,
82195 )
83196
84197 def run (self ):
@@ -88,8 +201,10 @@ def run(self):
88201 [
89202 ("PID" , int ),
90203 ("Process" , str ),
204+ ("Base" , format_hints .Hex ),
91205 ("FILE_OBJECT" , format_hints .Hex ),
92- ("DeletePending" , str ),
206+ ("DeletePending" , int ),
207+ ("DeleteOnClose" , int ),
93208 ("Path" , str ),
94209 ],
95210 self ._generator (
0 commit comments