22# which is available at https://www.volatilityfoundation.org/license/vsl-v1.0
33#
44import logging
5- from typing import NamedTuple
5+ from typing import NamedTuple , Dict
66
77from volatility3 .framework import interfaces , exceptions , constants
88from volatility3 .framework import renderers
1212
1313vollog = logging .getLogger (__name__ )
1414
15- VadInfo = NamedTuple (
16- "VadInfo " ,
15+ VadData = NamedTuple (
16+ "VadData " ,
1717 [
1818 ("protection" , str ),
1919 ("path" , str ),
2020 ],
2121)
2222
23- DLLInfo = NamedTuple (
24- "DLLInfo " ,
23+ DLLData = NamedTuple (
24+ "DLLData " ,
2525 [
2626 ("path" , str ),
2727 ],
2828)
2929
30+ ### Useful references on process hollowing
31+ # https://cysinfo.com/detecting-deceptive-hollowing-techniques/
32+ # https://github.com/m0n0ph1/Process-Hollowing
33+
3034
3135class HollowProcesses (interfaces .plugins .PluginInterface ):
3236 """Lists hollowed processes"""
@@ -56,7 +60,16 @@ def get_requirements(cls):
5660 ),
5761 ]
5862
59- def _get_vads_map (self , proc ):
63+ def _get_vads_data (
64+ self , proc : interfaces .objects .ObjectInterface
65+ ) -> Dict [int , VadData ]:
66+ """
67+ Returns a dictionary of:
68+ base address -> (protection string, file name)
69+ For each mapped VAD in the process. This is used
70+ for quick lookups of data and matching the DLL
71+ at the same base address as the VAD
72+ """
6073 vads = {}
6174
6275 kernel = self .context .modules [self .config ["kernel" ]]
@@ -73,11 +86,23 @@ def _get_vads_map(self, proc):
7386 if not fn or not isinstance (fn , str ):
7487 fn = "<Non-File Backed Region>"
7588
76- vads [vad .get_start ()] = VadInfo (protection_string , fn )
89+ vads [vad .get_start ()] = VadData (protection_string , fn )
7790
7891 return vads
7992
80- def _get_dlls_map (self , proc ):
93+ def _get_dlls_map (
94+ self , proc : interfaces .objects .ObjectInterface
95+ ) -> Dict [int , DLLData ]:
96+ """
97+ Returns a dictionary of:
98+ base address -> path
99+ for each DLL loaded in the process
100+
101+ This is used to cross compare with
102+ the corresponding VAD and to have a
103+ backup path source in case of smear
104+ in the VAD
105+ """
81106 dlls = {}
82107
83108 for entry in proc .load_order_modules ():
@@ -91,11 +116,14 @@ def _get_dlls_map(self, proc):
91116 except exceptions .InvalidAddressException :
92117 FullDllName = renderers .UnreadableValue ()
93118
94- dlls [base ] = DLLInfo (FullDllName )
119+ dlls [base ] = DLLData (FullDllName )
95120
96121 return dlls
97122
98- def _get_image_base (self , proc ):
123+ def _get_image_base (self , proc : interfaces .objects .ObjectInterface ) -> int :
124+ """
125+ Uses the PEB to get the image base of the process
126+ """
99127 kernel = self .context .modules [self .config ["kernel" ]]
100128
101129 try :
@@ -110,13 +138,29 @@ def _get_image_base(self, proc):
110138 return None
111139
112140 def _check_load_address (self , proc , _ , __ ):
141+ """
142+ Detects when the image base in the PEB, which is writable by process malware,
143+ does not match the section base address - whose value lives in kernel memory.
144+ Many malware samples will manipulate their image base to fool AVs/EDRs and
145+ as a necessary part of certain hollowing techniques
146+ """
113147 image_base = self ._get_image_base (proc )
114148 if image_base is not None and image_base != proc .SectionBaseAddress :
115149 yield "The ImageBaseAddress reported from the PEB ({:#x}) does not match the process SectionBaseAddress ({:#x})" .format (
116150 image_base , proc .SectionBaseAddress
117151 )
118152
119153 def _check_exe_protection (self , proc , vads , __ ):
154+ """
155+ Legitimately mapped application executables and DLLs
156+ will have a VAD present and its initial protection will be
157+ PAGE_EXECUTE_WRITECOPY.
158+ Many process hollowing and code injection techniques will
159+ unmap the real executable and/or map in executables with
160+ incorrect permissions.
161+ This check verifies the VAD for the application exe.
162+ `_check_dlls_protection` checks for DLLs mapped in the process.
163+ """
120164 base = proc .SectionBaseAddress
121165
122166 if base not in vads :
@@ -134,6 +178,7 @@ def _check_dlls_protection(self, _, vads, dlls):
134178 if dll_base not in vads :
135179 continue
136180
181+ # PAGE_EXECUTE_WRITECOPY is the only valid permission for mapped DLLs and .exe files
137182 if vads [dll_base ].protection != "PAGE_EXECUTE_WRITECOPY" :
138183 yield "Unexpected protection ({}) for DLL in the PEB's load order list ({:#x}) with path {}" .format (
139184 vads [dll_base ].protection , dll_base , dlls [dll_base ].path
@@ -155,7 +200,7 @@ def _generator(self, procs):
155200 if len (dlls ) < 3 :
156201 continue
157202
158- vads = self ._get_vads_map (proc )
203+ vads = self ._get_vads_data (proc )
159204 if len (vads ) < 5 :
160205 continue
161206
0 commit comments