@@ -122,10 +122,30 @@ def list_injections(
122122 vadinfo .winnt_protections ,
123123 )
124124 write_exec = "EXECUTE" in protection_string and "WRITE" in protection_string
125+ dirty_page_check = False
125126
126- # the write/exec check applies to everything
127127 if not write_exec :
128- continue
128+ """
129+ # Inspect "PAGE_EXECUTE_READ" VAD pages to detect
130+ # non writable memory regions having been injected
131+ # using elevated WriteProcessMemory().
132+ """
133+ if "EXECUTE" in protection_string :
134+ for page in range (
135+ vad .get_start (), vad .get_end (), proc_layer .page_size
136+ ):
137+ try :
138+ # If we have a dirty page in a non writable "EXECUTE" region, it is suspicious.
139+ if proc_layer .is_dirty (page ):
140+ dirty_page_check = True
141+ break
142+ except exceptions .InvalidAddressException :
143+ # Abort as it is likely that other addresses in the same range will also fail.
144+ break
145+ if not dirty_page_check :
146+ continue
147+ else :
148+ continue
129149
130150 if (vad .get_private_memory () == 1 and vad .get_tag () == "VadS" ) or (
131151 vad .get_private_memory () == 0
@@ -134,6 +154,11 @@ def list_injections(
134154 if cls .is_vad_empty (proc_layer , vad ):
135155 continue
136156
157+ if dirty_page_check :
158+ # Useful information to investigate the page content with volshell afterwards.
159+ vollog .warning (
160+ f"[proc_id { proc_id } ] Found suspicious DIRTY + { protection_string } page at { hex (page )} " ,
161+ )
137162 data = proc_layer .read (vad .get_start (), 64 , pad = True )
138163 yield vad , data
139164
0 commit comments