1

On Windows, it is possible to enumerate all running processes on the system, e.g. via the CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS...) API.

However, this will directly only report the executable name and the process ID without further info.

To identify the binary of the process, you need to OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION...) and QueryFullProcessImageName.

And OpenProcess, even with PROCESS_QUERY_LIMITED_INFORMATION, requires the SeDebugPrivilege when you need to query processes run under an elevated context - which means you need to be admin / elevated.

Question: Is it possible to get the full path to the binary of ANY given process (ID), including Windows services running as SYSTEM and other admin processes, without the SeDebugPrivilege or even better without requiring any additional rights from a non-elevated context?

OS: Windows 10 from v1809 upwards.

Here is the C++ example code to read out the info: https://gist.github.com/bilbothebaggins/f5852dbf22177620975207fe60f2f1f1

On my machine, given that I run with UAC, non-elevated, I see:

  • I can query my own processes (non-elevated)
  • I can also query a process started by another local, non-privileged user.
  • Query on all Windows services fails. (These running as SYSTEM)

X Y problem disclaimer: As a pre-build step, I need to determine for a given local directory if any (different user / admin / Windows service) background processes are running who's executables/binaries reside in the local directory. The build runs as non-admin. I can think of various other workarounds for this (all more complicated in our case), so can we stick to the question as asked above for this here? Thanks.

Martin Ba
  • 37,187
  • 33
  • 183
  • 337
  • 1
    From a non-elevated PowerShell process I can get paths of non-elevated processes using `QueryFullProcessImageName`, without `SeDebugPrivilege`. See [PowerShell example](https://stackoverflow.com/a/74030307/7571258). – zett42 Jan 11 '23 at 11:47
  • 1
    Are you looking for *system* processes? I believe you should be able to get the full path to non-system processes using only `PROCESS_QUERY_LIMITED_INFORMATION` with `QueryFullProcessImageName`, but not for system processes; for those, you'll need `SeDebugPrivilege`. I assume you have code that you have written implementing this to test it out? Can you share that? Would probably make it easier for others to debug. – Cody Gray - on strike Jan 11 '23 at 12:47
  • @CodyGray - I have now added a link to the code gist. While doing this I noticed that I messed up while writing the question, because "of course" :-) I need to (also) access SYSTEM processes. – Martin Ba Jan 11 '23 at 13:46
  • @zett42 - yeah, I failed to clearly mention that I also want to read SYSTEM processes. My bad. Added code and fixed that. – Martin Ba Jan 11 '23 at 13:46

1 Answers1

0

you can do it by using NtQuerySystemInformation with SystemProcessIdInformation (0x58) - with this way - we not need open process handle.

typedef struct SYSTEM_PROCESS_ID_INFORMATION
{
    HANDLE ProcessId;
    UNICODE_STRING ImageName;
} *PSYSTEM_PROCESS_ID_INFORMATION;

NTSTATUS GetProcessPathById(HANDLE ProcessId)
{
    SYSTEM_PROCESS_ID_INFORMATION spii = { ProcessId, { 0, 0x80 } };
    NTSTATUS status;

    do 
    {
        if (spii.ImageName.Buffer)
        {
            LocalFree(spii.ImageName.Buffer);
        }

        if (!(spii.ImageName.Buffer = (PWSTR)LocalAlloc(LMEM_FIXED, spii.ImageName.MaximumLength)))
        {
            status = STATUS_NO_MEMORY;
            break;
        }

    } while (STATUS_INFO_LENGTH_MISMATCH == (status = 
        NtQuerySystemInformation(SystemProcessIdInformation, &spii, sizeof(spii), 0)));

    if (0 > status)
    {
        DbgPrint("%p - %x\n", ProcessId, status); 
    }
    else
    {
        DbgPrint("%p - %wZ\n", ProcessId, &spii.ImageName);
    }

    if (spii.ImageName.Buffer)
    {
        LocalFree(spii.ImageName.Buffer);
    }

    return status;
}
RbMm
  • 31,280
  • 3
  • 35
  • 56
  • 1
    The `NtQuerySystemInformation` function is documented as one that application developers should not use, and the `SystemProcessIdInformation` information class is *not even documented*. This is not a reasonable solution to the problem. – Cody Gray - on strike Jan 12 '23 at 05:14
  • @CodyGray - i have another opinion. – RbMm Jan 12 '23 at 06:04
  • 1
    If a solution is based off of unsupported functionality, that should be **explicitly** called out in an answer. It is crucial information for engineers that need to answer questions as mundane as: *"What OS versions do we need to test?"* or *"Will this run on a yet-to-be-released version of Windows?"*. – IInspectable Jan 12 '23 at 11:31
  • @IInspectable this is on all versions begin from vista – RbMm Jan 12 '23 at 11:35
  • That is understood. But how does that help answer the second question: *"Will this run on a yet-to-be-released version of Windows?"*. – IInspectable Jan 12 '23 at 11:49
  • @IInspectable you know this answer – RbMm Jan 12 '23 at 11:54
  • I will acknowledge your confidence in me, but no, I don't know the answer. I'm not clairvoyant. Unless *"maybe"* counts as an answer. – IInspectable Jan 12 '23 at 12:17
  • @IInspectable how many native api removed or changed ? How many infoclass for system info removed or changed? I dont know any changed for 20+ years. But maybe you know. Api related to event pair and ldt is gone ( more exactly return now error ) but probably you know much more and can enum – RbMm Jan 12 '23 at 12:58