NtQuerySystemInformation: a simple way to bypass rootkits which hide processes by hooking

You’ve probably seen code like this:

NTSTATUS MyRootkitNtQuerySystemInformation(
    ULONG SystemInformationClass,
    PVOID SystemInformation,
    ULONG SystemInformationLength,
    PULONG ReturnLength
)
    if (SystemInformationClass == 5) // SystemProcessInformation
    {
        // do some pointer manipulation to hide our rootkit process
        ...
    }
    else
    {
        return OriginalNtQuerySystemInformation(...);
    }
}

For example, this is what Hacker Defender does to hide itself. What most people don’t know is that there are two more system information classes to enumerate processes!

SystemSessionProcessInformation (53)
SystemSessionProcessInformation is exactly the same as SystemProcessInformation except that you specify a session ID and the kernel returns processes in that session. You do this by passing a SYSTEM_SESSION_PROCESS_INFORMATION structure to NtQuerySystemInformation:

#define SystemSessionProcessInformation 53

typedef struct _SYSTEM_SESSION_PROCESS_INFORMATION
{
    ULONG SessionId;
    ULONG BufferLength;
    PVOID Buffer;
} SYSTEM_SESSION_PROCESS_INFORMATION, PSYSTEM_SESSION_PROCESS_INFORMATION;

...
SYSTEM_SESSION_PROCESS_INFORMATION info;
PVOID buffer = malloc(whatever_you_want);
ULONG returnLength;

info.SessionId = session_id_to_get_processes_for;
info.BufferLength = size_of_the_buffer;
info.Buffer = buffer;

NtQuerySystemInformation(SystemSessionProcessInformation, &info, sizeof(SYSTEM_SESSION_PROCESS_INFORMATION, &returnLength);
...
/* buffer will be filled with SYSTEM_PROCESS_INFORMATION structures, just like with SystemProcessInformation */
...

SystemExtendedProcessInformation (57)
This is similar to SystemProcessInformation, except that you get more information (obviously). Ever wondered why PageDirectoryBase was always 0 when you used SystemProcessInformation? With SystemExtendedProcessInformation, it’s actually filled in. Here are the revised structs:

#define SystemExtendedProcessInformation 57

typedef struct _SYSTEM_THREAD_INFORMATION
{
    LARGE_INTEGER KernelTime;
    LARGE_INTEGER UserTime;
    LARGE_INTEGER CreateTime;
    ULONG WaitTime;
    PVOID StartAddress;
    CLIENT_ID ClientId;
    LONG Priority;
    LONG BasePriority;
    ULONG ContextSwitches;
    ULONG ThreadState;
    ULONG WaitReason;
} SYSTEM_THREAD_INFORMATION, *PSYSTEM_THREAD_INFORMATION;

typedef struct _SYSTEM_EXTENDED_THREAD_INFORMATION
{
    SYSTEM_THREAD_INFORMATION ThreadInfo;
    PVOID StackBase;
    PVOID StackLimit;
    PVOID Win32StartAddress;
    PVOID TebAddress; /* This is only filled in on Vista and above */
    ULONG Reserved1;
    ULONG Reserved2;
    ULONG Reserved3;
} SYSTEM_EXTENDED_THREAD_INFORMATION, *PSYSTEM_EXTENDED_THREAD_INFORMATION;

typedef struct _SYSTEM_PROCESS_INFORMATION
{
    ULONG NextEntryOffset;
    ULONG NumberOfThreads;
    LARGE_INTEGER SpareLi1;
    LARGE_INTEGER SpareLi2;
    LARGE_INTEGER SpareLi3;
    LARGE_INTEGER CreateTime;
    LARGE_INTEGER UserTime;
    LARGE_INTEGER KernelTime;
    UNICODE_STRING ImageName;
    KPRIORITY BasePriority;
    ULONG UniqueProcessId;
    ULONG InheritedFromUniqueProcessId;
    ULONG HandleCount;
    ULONG SessionId;
    PVOID PageDirectoryBase;
    VM_COUNTERS VirtualMemoryCounters;
    SIZE_T PrivatePageCount;
    IO_COUNTERS IoCounters;
    SYSTEM_EXTENDED_THREAD_INFORMATION Threads[1];
} SYSTEM_PROCESS_INFORMATION, *PSYSTEM_PROCESS_INFORMATION;

...
PVOID buffer = malloc(whatever_size);
ULONG returnLength;

NtQuerySystemInformation(SystemExtendedProcessInformation, buffer, size_of_the_buffer, &returnLength);
...