Search code examples
pythonwindowsprocesspathpsutil

Find the path of every running process in Python


I want to find the path of every running process in windows. I tried to use the psutil module but it doesn't show me all the paths. It can't find many processes' paths because of the error: "psutil.AccessDenied"

c = wmi.WMI()
for process in c.Win32_Process():
        p = psutil.Process(int(process.ProcessId))
        try:
            path = p.exe()
        except:
            path = "-"

Is there another way to get a path of a process?


Solution

  • As an administrator you might be able to get PROCESS_QUERY_LIMITED_INFORMATION (0x1000) access for a given process if you can't get PROCESS_QUERY_INFORMATION (0x400). QueryFullProcessImageNameW only requires limited access. However, not even this will work in all cases. For example, the security descriptor on csrss.exe only grants access to the SYSTEM account, not administrators. Another example is services.exe, which runs at System (S-1-16-16384) integrity level, while an administrator token is only at High (S-1-16-12288) integrity level.

    You normally can't open a handle to such processes. But as an administrator you have the almost omnipotent SeDebugPrivilege. If you enable this privilege, Windows AccessCheck will suddenly become your best friend (but even best friends have their limits).

    Below is some ctypes code to enable and disable a privilege in the current process access token. The privilege has to be present in the token to begin with, so be sure to run this using the Administrator account or as an elevated administrator if using UAC.

    from ctypes import *
    from ctypes.wintypes import *
    
    kernel32 = WinDLL('kernel32', use_last_error=True)
    advapi32 = WinDLL('advapi32', use_last_error=True)
    
    SE_PRIVILEGE_ENABLED = 0x00000002
    TOKEN_ALL_ACCESS = 0x000F0000 | 0x01FF
    
    class LUID(Structure):
        _fields_ = (('LowPart',  DWORD),
                    ('HighPart', LONG))
    
    class LUID_AND_ATTRIBUTES(Structure):
        _fields_ = (('Luid',       LUID),
                    ('Attributes', DWORD))
    
    class TOKEN_PRIVILEGES(Structure):
        _fields_ = (('PrivilegeCount', DWORD),
                    ('Privileges', LUID_AND_ATTRIBUTES * 1))
        def __init__(self, PrivilegeCount=1, *args):
            super(TOKEN_PRIVILEGES, self).__init__(PrivilegeCount, *args)
    
    PDWORD = POINTER(DWORD)
    PHANDLE = POINTER(HANDLE)
    PLUID = POINTER(LUID)
    PTOKEN_PRIVILEGES = POINTER(TOKEN_PRIVILEGES)
    
    def errcheck_bool(result, func, args):
        if not result:
            raise WinError(get_last_error())
        return args
    
    kernel32.CloseHandle.argtypes = (HANDLE,)
    
    kernel32.GetCurrentProcess.errcheck = errcheck_bool
    kernel32.GetCurrentProcess.restype = HANDLE
    
    # https://msdn.microsoft.com/en-us/library/aa379295
    advapi32.OpenProcessToken.errcheck = errcheck_bool
    advapi32.OpenProcessToken.argtypes = (
        HANDLE,  # _In_  ProcessHandle
        DWORD,   # _In_  DesiredAccess
        PHANDLE) # _Out_ TokenHandle
    
    # https://msdn.microsoft.com/en-us/library/aa379180
    advapi32.LookupPrivilegeValueW.errcheck = errcheck_bool
    advapi32.LookupPrivilegeValueW.argtypes = (
        LPCWSTR, # _In_opt_ lpSystemName
        LPCWSTR, # _In_     lpName
        PLUID)   # _Out_    lpLuid
    
    # https://msdn.microsoft.com/en-us/library/aa375202
    advapi32.AdjustTokenPrivileges.errcheck = errcheck_bool
    advapi32.AdjustTokenPrivileges.argtypes = (
        HANDLE,            # _In_      TokenHandle
        BOOL,              # _In_      DisableAllPrivileges
        PTOKEN_PRIVILEGES, # _In_opt_  NewState
        DWORD,             # _In_      BufferLength
        PTOKEN_PRIVILEGES, # _Out_opt_ PreviousState
        PDWORD)            # _Out_opt_ ReturnLength
    
    def enable_privilege(privilege):
        hToken = HANDLE()
        luid = LUID()
        advapi32.LookupPrivilegeValueW(None, privilege, byref(luid))
        try:
            advapi32.OpenProcessToken(kernel32.GetCurrentProcess(),
                                      TOKEN_ALL_ACCESS,
                                      byref(hToken))
            tp = TOKEN_PRIVILEGES()
            tp.Privileges[0].Luid = luid
            tp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED
            advapi32.AdjustTokenPrivileges(hToken, False,
                                           byref(tp),
                                           sizeof(tp),
                                           None, None)
        finally:
            if hToken:
                kernel32.CloseHandle(hToken)
    
    def disable_privilege(privilege):
        hToken = HANDLE()
        luid = LUID()
        advapi32.LookupPrivilegeValueW(None, privilege, byref(luid))
        try:
            advapi32.OpenProcessToken(kernel32.GetCurrentProcess(),
                                      TOKEN_ALL_ACCESS,
                                      byref(hToken))
            tp = TOKEN_PRIVILEGES()
            tp.Privileges[0].Luid = luid
            tp.Privileges[0].Attributes = 0
            advapi32.AdjustTokenPrivileges(hToken, False,
                                           byref(tp),
                                           sizeof(tp),
                                           None, None)
        finally:
            if hToken:
                kernel32.CloseHandle(hToken)
    

    Test:

    if __name__ == '__main__':
        import psutil
        system_process_names = {'smss.exe',
                                'csrss.exe',
                                'wininit.exe',
                                'winlogon.exe',
                                'services.exe',
                                'lsass.exe',
                                'lsm.exe'}
        system_processes = []
    
        print('SeDebugPrivilege Enabled')
        enable_privilege('SeDebugPrivilege')    
        for proc in psutil.process_iter():
            try:
                name = proc.name().lower()
                path = proc.exe()
            except psutil.AccessDenied:
                print('{:04d} ACCESS_DENIED'.format(proc.pid))
                continue
            if name in system_process_names:
                system_process_names.remove(name)
                system_processes.append(proc)
                print('{:04d} {}'.format(proc.pid, path))
        assert not system_process_names
    
        print('\nSeDebugPrivilege Disabled')
        disable_privilege('SeDebugPrivilege')
        for proc in system_processes:
            try:
                path = psutil.Process(proc.pid).exe() # avoid cache
            except psutil.AccessDenied:
                path = 'ACCESS DENIED'
            print('{:04d} {}'.format(proc.pid, path))
    

    Output

    SeDebugPrivilege Enabled
    0000 ACCESS_DENIED
    0004 ACCESS_DENIED
    0256 C:\Windows\System32\smss.exe
    0404 C:\Windows\System32\csrss.exe
    0492 C:\Windows\System32\wininit.exe
    0540 C:\Windows\System32\winlogon.exe
    0588 C:\Windows\System32\services.exe
    0596 C:\Windows\System32\lsass.exe
    0604 C:\Windows\System32\lsm.exe
    4704 ACCESS_DENIED
    
    SeDebugPrivilege Disabled
    0256 ACCESS DENIED
    0404 ACCESS DENIED
    0492 ACCESS DENIED
    0540 ACCESS DENIED
    0588 ACCESS DENIED
    0596 ACCESS DENIED
    0604 ACCESS DENIED
    

    It's understandable to be denied access to the Idle (0) and System (4) processes. However, it's interesting that access was denied to PID 4704, even to a debugger. This is audiodg.exe, which is a protected process, as described in the "Protected Processes" white paper available for download at the Windows Hardware Dev Center Archive. Protected processes allow querying limited information, such as the image path. Let's quickly verify that this is the case:

    >>> kernel32.OpenProcess(0x1000, 0, 4704)
    304
    >>> path = (c_wchar * 260)()
    >>> size = c_uint(260)
    >>> kernel32.QueryFullProcessImageNameW(304, 0, path, byref(size))
    1
    >>> path.value
    'C:\\Windows\\System32\\audiodg.exe'