@@ -22,7 +22,6 @@ class ProcessSpoofing(plugins.PluginInterface):
2222
2323 _required_framework_version = (2 , 0 , 0 )
2424 _version = (1 , 1 , 0 )
25- deleted = " (deleted)"
2625
2726 @classmethod
2827 def get_requirements (cls ):
@@ -51,7 +50,7 @@ def get_executable_path(
5150 cls ,
5251 context : interfaces .context .ContextInterface ,
5352 task : interfaces .objects .ObjectInterface ,
54- ) -> Optional [str ]:
53+ ) -> Tuple [ Optional [str ], bool ]:
5554 """
5655 Extract the executable path from task_struct.mm.exe_file
5756
@@ -60,33 +59,54 @@ def get_executable_path(
6059 task: task_struct object of the process
6160
6261 Returns:
63- Executable path or None if not available
62+ Tuple of (basename, is_deleted) or ( None, False) if not available
6463 """
64+ is_deleted = False
65+
6566 try :
6667 mm = task .mm
67- if not mm or not mm . is_readable () :
68- # Kernel threads don't have mm struct
69- return None
68+ except ( exceptions . InvalidAddressException , AttributeError ) as e :
69+ vollog . debug ( f"Unable to access mm for task at { task . vol . offset :#x } : { e } " )
70+ return None , is_deleted
7071
72+ if not mm or not mm .is_readable ():
73+ # Kernel threads don't have mm struct
74+ return None , is_deleted
75+
76+ try :
7177 exe_file = mm .exe_file
78+ except (exceptions .InvalidAddressException , AttributeError ) as e :
79+ vollog .debug (
80+ f"Unable to access exe_file for task at { task .vol .offset :#x} : { e } "
81+ )
82+ return None , is_deleted
7283
73- if not exe_file or not exe_file .is_readable ():
74- return None
84+ if not exe_file or not exe_file .is_readable ():
85+ return None , is_deleted
7586
87+ try :
7688 exe_inode = exe_file .f_path .dentry .d_inode
7789 exe_path = linux .LinuxUtilities .path_for_file (context , task , exe_file )
90+ except (exceptions .InvalidAddressException , AttributeError ) as e :
91+ vollog .debug (
92+ f"Unable to read exe_file path for task at { task .vol .offset :#x} : { e } "
93+ )
94+ return None , is_deleted
7895
79- # If the inode link count is 0, the process image has been deleted
80- if exe_inode .i_nlink == 0 :
81- exe_path += cls .deleted
82-
83- return exe_path if exe_path else None
96+ if not exe_path :
97+ return None , is_deleted
8498
99+ try :
100+ # Check if the inode link count is 0 (process image has been deleted)
101+ is_deleted = exe_inode .i_nlink == 0
85102 except (exceptions .InvalidAddressException , AttributeError ) as e :
86103 vollog .debug (
87- f"Unable to read executable path for task at { task .vol .offset :#x} : { e } "
104+ f"Unable to check inode link count for task at { task .vol .offset :#x} : { e } "
88105 )
89- return None
106+ # Continue without deletion info - we still have the path
107+
108+ basename = PurePosixPath (exe_path ).name
109+ return basename , is_deleted
90110
91111 @classmethod
92112 def get_cmdline_basename (
@@ -155,29 +175,28 @@ def get_comm(cls, task: interfaces.objects.ObjectInterface) -> Optional[str]:
155175
156176 def _extract_process_names (
157177 self , task : interfaces .objects .ObjectInterface
158- ) -> Tuple [Optional [str ], Optional [str ], Optional [str ]]:
178+ ) -> Tuple [Optional [str ], Optional [str ], Optional [str ], bool ]:
159179 """
160180 Extract all three process name sources for comparison
161181
162182 Args:
163183 task: task_struct object of the process
164184
165185 Returns:
166- Tuple of (exe_path_basename , cmdline_basename, comm)
186+ Tuple of (exe_basename , cmdline_basename, comm, is_deleted )
167187 """
168- exe_path = self .get_executable_path (self .context , task )
169- exe_basename = PurePosixPath (exe_path ).name if exe_path else None
188+ exe_basename , is_deleted = self .get_executable_path (self .context , task )
170189 cmdline_basename = self .get_cmdline_basename (self .context , task )
171190 comm = self .get_comm (task )
172191
173- return exe_basename , cmdline_basename , comm
192+ return exe_basename , cmdline_basename , comm , is_deleted
174193
175194 def _detect_spoofing (
176195 self ,
177196 exe_basename : Optional [str ],
178197 cmdline_basename : Optional [str ],
179198 comm : Optional [str ],
180- ) -> Tuple [bool , bool , bool ]:
199+ ) -> Tuple [bool , bool ]:
181200 """
182201 Analyze the three name sources to detect potential spoofing
183202
@@ -187,34 +206,26 @@ def _detect_spoofing(
187206 comm: Name from comm field
188207
189208 Returns:
190- Tuple of (is_deleted, cmdline_spoofed, comm_spoofed) boolean flags
209+ Tuple of (cmdline_spoofed, comm_spoofed) boolean flags
191210 """
192- # Check if process image has been deleted
193- is_deleted = exe_basename and exe_basename .endswith (self .deleted )
194-
195- # Get clean basename for comparison (without " (deleted)" suffix)
196- clean_exe_basename = exe_basename
197- if is_deleted :
198- clean_exe_basename = exe_basename [: len (self .deleted ) * - 1 ]
199-
200211 # Skip kernel threads - need at least 2 sources for comparison
201212 available_sources = sum (
202- 1 for name in [clean_exe_basename , cmdline_basename , comm ] if name
213+ 1 for name in [exe_basename , cmdline_basename , comm ] if name
203214 )
204215 if available_sources < 2 :
205- return False , False , False
216+ return False , False
206217
207218 # Check for cmdline spoofing
208219 cmdline_spoofed = False
209- if clean_exe_basename and cmdline_basename :
210- cmdline_spoofed = clean_exe_basename != cmdline_basename
220+ if exe_basename and cmdline_basename :
221+ cmdline_spoofed = exe_basename != cmdline_basename
211222
212223 # Check for comm spoofing (comm is truncated to 15 characters)
213224 comm_spoofed = False
214- if clean_exe_basename and comm :
215- comm_spoofed = clean_exe_basename [:15 ] != comm
225+ if exe_basename and comm :
226+ comm_spoofed = exe_basename [:15 ] != comm
216227
217- return is_deleted , cmdline_spoofed , comm_spoofed
228+ return cmdline_spoofed , comm_spoofed
218229
219230 def _generator (self , tasks ) -> Iterator [Tuple [int , Tuple ]]:
220231 """
@@ -231,13 +242,19 @@ def _generator(self, tasks) -> Iterator[Tuple[int, Tuple]]:
231242 pid = task .pid
232243 ppid = task .get_parent_pid ()
233244
234- exe_basename , cmdline_basename , comm = self ._extract_process_names (task )
245+ exe_basename , cmdline_basename , comm , is_deleted = (
246+ self ._extract_process_names (task )
247+ )
235248
236- is_deleted , cmdline_spoofed , comm_spoofed = self ._detect_spoofing (
249+ cmdline_spoofed , comm_spoofed = self ._detect_spoofing (
237250 exe_basename , cmdline_basename , comm
238251 )
239252
253+ # Prepare display values
240254 exe_render = exe_basename if exe_basename else "N/A"
255+ if is_deleted and exe_basename :
256+ exe_render += " (deleted)"
257+
241258 cmdline_render = cmdline_basename if cmdline_basename else "N/A"
242259 comm_render = comm if comm else "N/A"
243260
@@ -273,7 +290,7 @@ def run(self):
273290 ("Comm" , str ),
274291 ("Cmdline_Spoofed" , bool ),
275292 ("Comm_Spoofed" , bool ),
276- ("Deleted " , bool ),
293+ ("Exe_Deleted " , bool ),
277294 ],
278295 self ._generator (
279296 pslist .PsList .list_tasks (
0 commit comments