Introducing WJ’s Backup

Recently I became frustrated with Cobian Backup. It was the only free software I could find that:

  1. supported incremental backups,
  2. supported Volume Shadow Copy,
  3. and didn’t install a bunch of extra, useless startup entries and services.

However, two things sucked:

  1. Incremental backups only seemed to work properly when it used the “archive” attribute. This meant that it couldn’t recover from interrupted backups properly.
  2. It didn’t provide any way of restoring files, so you had to go through the different .7z files and find the file you want to restore.

So, I decided to create my own backup program: “WJ’s Backup”. The possessive in the title is not to indicate some obsessive sense of ownership, but to emphasize the fact that the program was not developed with any other user’s perspective in mind.

Unlike most other backup programs I could find, WJ’s Backup maintains a database with file/directory metadata for each revision. It keeps file versions in a SVN-like way, but without diffs for file content (each new version is stored in full). 7-Zip compression is mandatory.

Project page at SourceForge

PAE patch updated for Windows 7 SP1

Note: An updated version for Windows 8 is available.

This patch allows you to use more than 3/4GB of RAM on an x86 Windows system. Works on Vista and 7, has been tested on Windows Vista SP2, Windows 7 SP0 and Windows 7 SP1. Instructions and source code included.

Download: PatchPae.zip (4926347 downloads)

Note: I do not offer any support for this. If this did not work for you, either:

  • You cannot follow instructions correctly, or
  • You cannot use more than 4GB of physical memory on 32-bit Windows due to hardware/software conflicts.

To remove the patch:

  1. Run msconfig, click Boot, highlight the entry named “Windows 7 (PAE Patched)”, and click Delete.
  2. Delete the files ntkrnlpx.exe and winloadp.exe from C:\Windows\system32.

The NT "reserve object"

Windows 7 introduced two new object types: UserApcReserve and IoCompletionReserve. What do these object types have in common? They’re both created using NtAllocateReserveObject. If we look inside this system call we can see that the third argument is an index into two arrays, PspMemoryReserveObjectSizes and PspMemoryReserveObjectTypes. Notice that PspInitPhase0 creates a set number (currently two) of object types, drawing the names from another array: PspMemoryReserveObjectNames. Here’s my reversed definition of NtAllocateReserveObject:

#define USER_APC_RESERVE_TYPE 0
#define IO_COMPLETION_RESERVE_TYPE 1

NTSYSCALLAPI
NTSTATUS
NTAPI
NtAllocateReserveObject(
    __out PHANDLE MemoryReserveHandle,
    __in_opt POBJECT_ATTRIBUTES ObjectAttributes,
    __in ULONG Type
    );

Two new system calls take advantage of the UserApcReserve object and IoCompletionReserve object: NtQueueApcThreadEx and NtSetIoCompletionEx, respectively. In NtQueueApcThreadEx we can see that if a user APC reserve handle/object is supplied, the function uses the space allocated for that object to store the APC, instead of allocating from the pool. Similarly NtSetIoCompletionEx uses the I/O completion reserve object’s already allocated space to store the I/O completion mini-packet, instead of allocating from the pool. It is now clear what the purpose of these reserve objects are: to allow processes to reserve memory before performing certain system calls in order to avoid out-of-memory problems occurring at bad spots (or critical code). Here’s my reversed definitions for the two system calls:

NTSYSCALLAPI
NTSTATUS
NTAPI
NtQueueApcThreadEx(
    __in HANDLE ThreadHandle,
    __in_opt HANDLE UserApcReserveHandle,
    __in PPS_APC_ROUTINE ApcRoutine,
    __in_opt PVOID ApcArgument1,
    __in_opt PVOID ApcArgument2,
    __in_opt PVOID ApcArgument3
    );

NTSYSCALLAPI
NTSTATUS
NTAPI
NtSetIoCompletionEx(
    __in HANDLE IoCompletionHandle,
    __in HANDLE IoCompletionReserveHandle,
    __in PVOID KeyContext,
    __in_opt PVOID ApcContext,
    __in NTSTATUS IoStatus,
    __in ULONG_PTR IoStatusInformation
    );

Note that NtQueueApcThread now calls NtQueueApcThreadEx, which is why the reserve object is optional.

HOWTO: Use I_QueryTagInformation

Process Explorer 12 includes a new feature whereby you can view service names associated with threads. To find out how this works, read this article by Alex Ionescu. You won’t be completely satisfied, though. You still don’t know how to use I_QueryTagInformation.

First step: Getting the service tag for a thread
This is simple; use NtQueryInformationThread to get the thread’s TEB address, then read the SubProcessTag from the TEB:

THREAD_BASIC_INFORMATION basicInfo;
PVOID subProcessTag;

NtQueryInformationThread(your_thread_handle, ThreadBasicInformation, &basicInfo, sizeof(basicInfo), NULL);
NtReadVirtualMemory(handle_to_the_process_containing_the_thread, (PVOID)((ULONG_PTR)basicInfo.TebBaseAddress + FIELD_OFFSET(TEB, SubProcessTag)), &subProcessTag, sizeof(PVOID), NULL);

// If you don't have the full TEB definition, here are the offsets (replace the FIELD_OFFSET invocation with these):
// x86: 0xf60
// x64: 0x1720

// You now have the service tag in subProcessTag.

Second step: Using I_QueryTagInformation
I_QueryTagInformation is located in advapi32.dll. Here’s what I’ve reverse-engineered from looking at advapi32.dll and services.exe:

typedef enum _SC_SERVICE_TAG_QUERY_TYPE
{
    ServiceNameFromTagInformation = 1,
    ServiceNamesReferencingModuleInformation,
    ServiceNameTagMappingInformation
} SC_SERVICE_TAG_QUERY_TYPE, *PSC_SERVICE_TAG_QUERY_TYPE;

typedef struct _SC_SERVICE_TAG_QUERY
{
    ULONG ProcessId;
    ULONG ServiceTag;
    ULONG Unknown;
    PVOID Buffer;
} SC_SERVICE_TAG_QUERY, *PSC_SERVICE_TAG_QUERY;

typedef ULONG (NTAPI *_I_QueryTagInformation)(
    __in PVOID Unknown,
    __in SC_SERVICE_TAG_QUERY_TYPE QueryType,
    __inout PSC_SERVICE_TAG_QUERY Query
    );

The usage is fairly obvious: you want the service name for the service tag (ServiceNameFromTagInformation), you fill in the SC_SERVICE_TAG_QUERY structure, and you call I_QueryTagInformation:

_I_QueryTagInformation I_QueryTagInformation;
SC_SERVICE_TAG_QUERY query;

I_QueryTagInformation = GetProcAddress(GetModuleHandle(L"advapi32.dll"), "I_QueryTagInformation");

if (I_QueryTagInformation)
{
    query.ProcessId = (ULONG)your_process_ID;
    query.ServiceTag = (ULONG)subProcessTag;
    query.Unknown = 0;
    query.Buffer = NULL;
    
    I_QueryTagInformation(NULL, ServiceNameFromTagInformation, &query);
    
    wprintf(L"Service name: %s\n", query.Buffer);
    LocalFree(query.Buffer);
}

Get the image file name of any process from any user on Vista and above

On Vista and above there is an information class for NtQuerySystemInformation which I call SystemProcessImageNameInformation (88). (Note that I reverse-engineered this, so it is probably not the correct name for the information class.) The structure definition is below:

typedef struct _SYSTEM_PROCESS_IMAGE_NAME_INFORMATION
{
    HANDLE ProcessId;
    UNICODE_STRING ImageName;
} SYSTEM_PROCESS_IMAGE_NAME_INFORMATION, *PSYSTEM_PROCESS_IMAGE_NAME_INFORMATION;

This information class allows you to get the image file name of any process, regardless of your user, privileges, or access to the process. Usage:

NTSTATUS status;
PVOID buffer;
SYSTEM_PROCESS_IMAGE_NAME_INFORMATION info;

buffer = malloc(0x100);
info.ProcessId = WhateverTheProcessIdIs;
info.ImageName.Length = 0;
info.ImageName.MaximumLength = (USHORT)0x100;
info.ImageName.Buffer = buffer;

status = NtQuerySystemInformation(88, &info, sizeof(info), NULL);

if (status == STATUS_INFO_LENGTH_MISMATCH)
{
    // Our buffer was too small. The required buffer length is stored in MaximumLength.
    free(buffer);
    buffer = malloc(info.ImageName.MaximumLength);
    info.ImageName.Buffer = buffer;
    status = NtQuerySystemInformation(88, &info, sizeof(info), NULL);
}

if (NT_SUCCESS(status))
{
    wprintf(L"File name: %.*s\n", info.ImageName.Length / 2, info.ImageName.Buffer);
}

free(buffer);

Fast reader-writer lock in C

I ported my fast reader-writer lock from C# to C for Process Hacker 2. Here it is.

#include <windows.h>
#include <intrin.h>

// Put this in a header file.
typedef struct _PH_FAST_LOCK
{
    ULONG Value;
    HANDLE ExclusiveWakeEvent;
    HANDLE SharedWakeEvent;
} PH_FAST_LOCK, *PPH_FAST_LOCK;

#define PH_LOCK_OWNED 0x1
#define PH_LOCK_EXCLUSIVE_WAKING 0x2

#define PH_LOCK_SHARED_OWNERS_SHIFT 2
#define PH_LOCK_SHARED_OWNERS_MASK 0x3ff
#define PH_LOCK_SHARED_OWNERS_INC 0x4

#define PH_LOCK_SHARED_WAITERS_SHIFT 12
#define PH_LOCK_SHARED_WAITERS_MASK 0x3ff
#define PH_LOCK_SHARED_WAITERS_INC 0x1000

#define PH_LOCK_EXCLUSIVE_WAITERS_SHIFT 22
#define PH_LOCK_EXCLUSIVE_WAITERS_MASK 0x3ff
#define PH_LOCK_EXCLUSIVE_WAITERS_INC 0x400000

#define PH_LOCK_EXCLUSIVE_MASK \
    (PH_LOCK_EXCLUSIVE_WAKING | \
    (PH_LOCK_EXCLUSIVE_WAITERS_MASK << PH_LOCK_EXCLUSIVE_WAITERS_SHIFT))

static ULONG PhLockSpinCount;

// Call this method BEFORE using any of the other functions.
VOID PhFastLockInitialization()
{
    SYSTEM_INFO systemInfo;

    GetSystemInfo(&systemInfo);

    if (systemInfo.dwNumberOfProcessors > 1)
        PhLockSpinCount = 4000;
    else
        PhLockSpinCount = 0;
}

VOID PhInitializeFastLock(
    __out PPH_FAST_LOCK FastLock
    )
{
    FastLock->Value = 0;
    FastLock->ExclusiveWakeEvent = NULL;
    FastLock->SharedWakeEvent = NULL;
}

VOID PhDeleteFastLock(
    __inout PPH_FAST_LOCK FastLock
    )
{
    if (FastLock->ExclusiveWakeEvent)
    {
        CloseHandle(FastLock->ExclusiveWakeEvent);
        FastLock->ExclusiveWakeEvent = NULL;
    }

    if (FastLock->SharedWakeEvent)
    {
        CloseHandle(FastLock->SharedWakeEvent);
        FastLock->SharedWakeEvent = NULL;
    }
}

#ifdef _M_IX86

FORCEINLINE PVOID _InterlockedCompareExchangePointer(
    __inout PVOID volatile *Destination,
    __in PVOID Exchange,
    __in PVOID Comparand
    )
{
    return (PVOID)_InterlockedCompareExchange(
        (PLONG_PTR)Destination,
        (LONG_PTR)Exchange,
        (LONG_PTR)Comparand
        );
}

FORCEINLINE PVOID _InterlockedExchangePointer(
    __inout PVOID volatile *Destination,
    __in PVOID Exchange
    )
{
    return (PVOID)_InterlockedExchange(
        (PLONG_PTR)Destination,
        (LONG_PTR)Exchange
        );
}

#endif

FORCEINLINE VOID PhpEnsureEventCreated(
    __inout PHANDLE Handle
    )
{
    HANDLE handle;

    if (*Handle != NULL)
        return;

    handle = CreateSemaphore(NULL, 0, MAXLONG, NULL);

    if (_InterlockedCompareExchangePointer(
        Handle,
        handle,
        NULL
        ) != NULL)
    {
        CloseHandle(handle);
    }
}

VOID PhAcquireFastLockExclusive(
    __inout PPH_FAST_LOCK FastLock
    )
{
    ULONG value;
    ULONG i = 0;

    while (TRUE)
    {
        value = FastLock->Value;

        if (!(value & (PH_LOCK_OWNED | PH_LOCK_EXCLUSIVE_WAKING)))
        {
            if (_InterlockedCompareExchange(
                &FastLock->Value,
                value + PH_LOCK_OWNED,
                value
                ) == value)
                break;
        }
        else if (i >= PhLockSpinCount)
        {
            PhpEnsureEventCreated(&FastLock->ExclusiveWakeEvent);

            if (_InterlockedCompareExchange(
                &FastLock->Value,
                value + PH_LOCK_EXCLUSIVE_WAITERS_INC,
                value
                ) == value)
            {
                if (WaitForSingleObject(
                    FastLock->ExclusiveWakeEvent,
                    INFINITE
                    ) != WAIT_OBJECT_0)
                {
                    // You might want to raise an exception here.
                }

                do
                {
                    value = FastLock->Value;
                } while (_InterlockedCompareExchange(
                    &FastLock->Value,
                    value + PH_LOCK_OWNED - PH_LOCK_EXCLUSIVE_WAKING,
                    value
                    ) != value);

                break;
            }
        }

        i++;
        YieldProcessor();
    }
}

VOID PhAcquireFastLockShared(
    __inout PPH_FAST_LOCK FastLock
    )
{
    ULONG value;
    ULONG i = 0;

    while (TRUE)
    {
        value = FastLock->Value;

        if (!(value & (
            PH_LOCK_OWNED |
            (PH_LOCK_SHARED_OWNERS_MASK << PH_LOCK_SHARED_OWNERS_SHIFT) |
            PH_LOCK_EXCLUSIVE_MASK
            )))
        {
            if (_InterlockedCompareExchange(
                &FastLock->Value,
                value + PH_LOCK_OWNED + PH_LOCK_SHARED_OWNERS_INC,
                value
                ) == value)
                break;
        }
        else if (
            (value & PH_LOCK_OWNED) &&
            ((value >> PH_LOCK_SHARED_OWNERS_SHIFT) & PH_LOCK_SHARED_OWNERS_MASK) > 0 &&
            !(value & PH_LOCK_EXCLUSIVE_MASK)
            )
        {
            if (_InterlockedCompareExchange(
                &FastLock->Value,
                value + PH_LOCK_SHARED_OWNERS_INC,
                value
                ) == value)
                break;
        }
        else if (i >= PhLockSpinCount)
        {
            PhpEnsureEventCreated(&FastLock->SharedWakeEvent);

            if (_InterlockedCompareExchange(
                &FastLock->Value,
                value + PH_LOCK_SHARED_WAITERS_INC,
                value
                ) == value)
            {
                if (WaitForSingleObject(
                    FastLock->SharedWakeEvent,
                    INFINITE
                    ) != WAIT_OBJECT_0)
                {
                    // You might want to raise an exception here.
                }

                continue;
            }
        }

        i++;
        YieldProcessor();
    }
}

VOID PhReleaseFastLockExclusive(
    __inout PPH_FAST_LOCK FastLock
    )
{
    ULONG value;

    while (TRUE)
    {
        value = FastLock->Value;

        if ((value >> PH_LOCK_EXCLUSIVE_WAITERS_SHIFT) & PH_LOCK_EXCLUSIVE_WAITERS_MASK)
        {
            if (_InterlockedCompareExchange(
                &FastLock->Value,
                value - PH_LOCK_OWNED + PH_LOCK_EXCLUSIVE_WAKING - PH_LOCK_EXCLUSIVE_WAITERS_INC,
                value
                ) == value)
            {
                ReleaseSemaphore(FastLock->ExclusiveWakeEvent, 1, NULL);

                break;
            }
        }
        else
        {
            ULONG sharedWaiters;

            sharedWaiters = (value >> PH_LOCK_SHARED_WAITERS_SHIFT) & PH_LOCK_SHARED_WAITERS_MASK;

            if (_InterlockedCompareExchange(
                &FastLock->Value,
                value & ~(PH_LOCK_OWNED | (PH_LOCK_SHARED_WAITERS_MASK << PH_LOCK_SHARED_WAITERS_SHIFT)),
                value
                ) == value)
            {
                if (sharedWaiters)
                    ReleaseSemaphore(FastLock->SharedWakeEvent, sharedWaiters, 0);

                break;
            }
        }

        YieldProcessor();
    }
}

VOID PhReleaseFastLockShared(
    __inout PPH_FAST_LOCK FastLock
    )
{
    ULONG value;

    while (TRUE)
    {
        value = FastLock->Value;

        if (((value >> PH_LOCK_SHARED_OWNERS_SHIFT) & PH_LOCK_SHARED_OWNERS_MASK) > 1)
        {
            if (_InterlockedCompareExchange(
                &FastLock->Value,
                value - PH_LOCK_SHARED_OWNERS_INC,
                value
                ) == value)
                break;
        }
        else if ((value >> PH_LOCK_EXCLUSIVE_WAITERS_SHIFT) & PH_LOCK_EXCLUSIVE_WAITERS_MASK)
        {
            if (_InterlockedCompareExchange(
                &FastLock->Value,
                value - PH_LOCK_OWNED + PH_LOCK_EXCLUSIVE_WAKING -
                PH_LOCK_SHARED_OWNERS_INC - PH_LOCK_EXCLUSIVE_WAITERS_INC,
                value
                ) == value)
            {
                ReleaseSemaphore(FastLock->ExclusiveWakeEvent, 1, NULL);

                break;
            }
        }
        else
        {
            if (_InterlockedCompareExchange(
                &FastLock->Value,
                value - PH_LOCK_OWNED - PH_LOCK_SHARED_OWNERS_INC,
                value
                ) == value)
                break;
        }

        YieldProcessor();
    }
}

BOOLEAN PhTryAcquireFastLockExclusive(
    __inout PPH_FAST_LOCK FastLock
    )
{
    ULONG value;

    value = FastLock->Value;

    if (value & (PH_LOCK_OWNED | PH_LOCK_EXCLUSIVE_WAKING))
        return FALSE;

    return _InterlockedCompareExchange(
        &FastLock->Value,
        value + PH_LOCK_OWNED,
        value
        ) == value;
}

BOOLEAN PhTryAcquireFastLockShared(
    __inout PPH_FAST_LOCK FastLock
    )
{
    ULONG value;

    value = FastLock->Value;

    if (value & PH_LOCK_EXCLUSIVE_MASK)
        return FALSE;

    if (!(value & PH_LOCK_OWNED))
    {
        return _InterlockedCompareExchange(
            &FastLock->Value,
            value + PH_LOCK_OWNED + PH_LOCK_SHARED_OWNERS_INC,
            value
            ) == value;
    }
    else if ((value >> PH_LOCK_SHARED_OWNERS_SHIFT) & PH_LOCK_SHARED_OWNERS_MASK)
    {
        return _InterlockedCompareExchange(
            &FastLock->Value,
            value + PH_LOCK_SHARED_OWNERS_INC,
            value
            ) == value;
    }
    else
    {
        return FALSE;
    }
}

12 ways to terminate a process

TerminateProcess or NtTerminateProcess

Everyone knows about TerminateProcess. You simply open a handle to the target process and call TerminateProcess. In case TerminateProcess is hooked, you can call the equivalent Native API function NtTerminateProcess.

CreateRemoteThread, ExitProcess

For this method you will have to find the address of ExitProcess within the target process. It is usually the same as ExitProcess for your process, so you can use GetModuleHandle and GetProcAddress. You can then create a thread inside the target process which executes ExitProcess, killing the target process.

NtQuerySystemInformation or toolhelp32, TerminateThread or NtTerminateThread

Simply loop through the threads of the target process and terminate each one using TerminateThread. If it’s hooked, call NtTerminateThread.

NtQuerySystemInformation or toolhelp32, SetThreadContext

Loop through the threads of the target process and set their contexts using SetThreadContext; modify their contexts so that eip points to ExitProcess.

DuplicateHandle

Loop from 0 to 4096 and call DuplicateHandle with Options = 1, leaving TargetProcess and TargetProcessHandle null. This will close most, if not all handles opened by the target process. This method works best for complex applications like security software – it doesn’t crash Notepad, for example.

CreateJobObject, AssignProcessToJobObject, TerminateJobObject (and their Native API equivalents)

Create a job object using CreateJobObject, assign the target process to it using AssignProcessToJobObject, and terminate it using TerminateJobObject. This only works if the process is not already associated with a job object. This technique works well if NtAssignProcessToJobObject and NtTerminateJobObject are not hooked because NtTerminateJobObject calls PsTerminateProcess directly.

NtCreateDebugObject, NtDebugActiveProcess, CloseHandle

People usually implement this technique by using DebugActiveProcess and then exiting the current process. They do this because they don’t know how DebugActiveProcess works. Behind the scenes kernel32 is calling ntdll which calls NtDebugActiveProcess with an already-created debug object. You don’t have to exit the current process for the debuggee to get killed; you just need to close the debug object. When it is closed, the kernel will kill the debuggee using PsTerminateProcess.

To implement this technique, you can create a debug object using NtCreateDebugObject (specifying the kill-on-close flag), debug the process using NtDebugActiveProcess (the process handle needs PROCESS_SUSPEND_RESUME access), and close the handle to the debug object using CloseHandle. Here are the definitions:

#define DEBUG_OBJECT_READEVENT 0x1
#define DEBUG_OBJECT_PROCESSASSIGN 0x2
#define DEBUG_OBJECT_SETINFORMATION 0x4
#define DEBUG_OBJECT_QUERYINFORMATION 0x8

#define DEBUG_OBJECT_KILLONCLOSE 0x1

NTSTATUS NTAPI NtCreateDebugObject(
    PHANDLE DebugObjectHandle,
    ACCESS_MASK DesiredAccess,
    POBJECT_ATTRIBUTES ObjectAttributes,
    ULONG Flags
    );

NTSTATUS NTAPI NtDebugActiveProcess(
    HANDLE ProcessHandle,
    HANDLE DebugObjectHandle
    );

VirtualQueryEx, VirtualProtectEx

Loop through the memory regions of the target process using VirtualQueryEx and set their protections to PAGE_NOACCESS. The program will crash as soon as it context-switches into user-mode code because it will be unable to read any code from memory.

VirtualQueryEx, WriteProcessMemory

Loop through the memory regions of the target process and write random data to it using WriteProcessMemory.

VirtualAllocEx

Call VirtualAllocEx in a loop until you can’t reserve any more memory in the target process. It will crash when it is unable to allocate any more memory.

PsTerminateProcess

PsTerminateProcess is an internal kernel-mode function which is not exported by ntoskrnl. You will need to locate it by scanning kernel-mode memory for a specific signature, which I will not post here. WARNING: On XP you should locate PspTerminateProcess instead, which is stdcall. On Vista PsTerminateProcess is thiscall (first argument goes in ecx), so you will need some hand-coded assembly.

typedef NTSTATUS (*_PsTerminateProcess)(
    PEPROCESS Process,
    NTSTATUS ExitStatus
    );

PspTerminateThreadByPointer

This function is not exported either. WARNING: On XP, there are two arguments. On Vista, there are three.

/* XP */
typedef NTSTATUS (NTAPI *_PspTerminateThreadByPointer51)(
    PETHREAD Thread,
    NTSTATUS ExitStatus
    );

/* Vista */
typedef NTSTATUS (NTAPI *_PspTerminateThreadByPointer60)(
    PETHREAD Thread,
    NTSTATUS ExitStatus,
    BOOLEAN DirectTerminate
    );

A note about process handles

Before you get started on these methods, you will need to know the ways of getting a handle to the victim process. Surely, the only way to do this is by calling OpenProcess/NtOpenProcess, right? Wrong. If you can’t get a handle with the right access because of a hooked function (such as with security software), you can open the target process with SYNCHRONIZE access (or whatever access you think will be granted) and call DuplicateHandle to get new access rights. This won’t always work though because security software vendors are starting to hook ZwDuplicateObject to prevent this.

On Windows Vista there are two Native API functions named NtGetNextProcess and NtGetNextThread. Almost no one knows about this and almost no one hooks these two functions. Here are their definitions:

NTSTATUS NTAPI NtGetNextProcess(
    HANDLE ProcessHandle,
    ACCESS_MASK DesiredAccess,
    ULONG HandleAttributes,
    ULONG Flags,
    PHANDLE NewProcessHandle
    );

NTSTATUS NTAPI NtGetNextThread(
    HANDLE ProcessHandle,
    HANDLE ThreadHandle,
    ACCESS_MASK DesiredAccess,
    ULONG HandleAttributes,
    ULONG Flags,
    PHANDLE NewThreadHandle
    );

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);
...