Tutorial Process Management (Kernel-Mode -> Suspension (PsSuspendProcess)) #2

D

Deleted member 65228

Guest
#1
Hello everyone.


Introduction

Today we will look into how we can suspend and resume a process in kernel-mode. As you may already know, in user-mode there are Win32 API functions like SuspendThread/ResumeThread, and there are Native API routines which can be invoked from user-mode thanks to system calls performed by NTDLL called NtSuspendProcess, NtResumeProcess, NtSuspendThread and NtResumeThread. What about kernel-mode? Functions like NtSuspendProcess, NtResumeProcess, NtSuspendThread or NtResumeThread are not exported by NTOSKRNL (ntoskrnl.exe) and therefore you cannot just call the APIs - you'd have to find the address by scanning the System Service Dispatch Table (SSDT), which is essentially an array in kernel-mode which holds pointer addresses to various kernel-mode routines (only the ones which can be invoked from user-mode via NTDLL system calls).

Unfortunately, KeServiceDescriptorTable is not exported by NTOSKRNL on 64-bit versions of Windows; this is also linked to Kernel Patch Protection (KPP), a feature present within PatchGuard (PG - designed to prevent unsigned device drivers being loaded through Driver Signature Enforcement (DSE) and patching of the Windows NT Kernel through Kernel Patch Protection (KPP)).

Thankfully, there does happen to be two interesting functions exported by NTOSKRNL which can save the day - we won't have to find the address of the SSDT for 64-bit support (or at all) - called PsSuspendProcess and PsResumeProcess.

Process suspension is about suspending the threads of a process, because the threads are responsible for executing the code belonging to the program. The process itself represents everything put together (e.g. the threads, data about the running program which can be queried by the OS itself and other active third-party software and so forth). Therefore, you may have already guessed that PsSuspendProcess and PsResumeProcess will do - they will walk (enumerate) through the threads of the target process and suspend/resume appropriately depending on which routine was called. In-case you're wondering, "How does it suspend or resume the threads then?", the answer to that is through a call to two other routines (one for the PsSuspendProcess, another for PsResumeProcess) which do not happen to be exported by NTOSKRNL, called KeSuspendThread and KeResumeThread.

If you check the exports of ntoskrnl.exe, as long as you're running Windows Vista and above, you should find PsSuspendProcess and PsResumeProcess listed - it may also be on Windows XP but I am not sure. If it isn't on Windows XP, NtSuspendProcess/NtResumeProcess still are, and in that case they will probably handle the enumeration of the threads for suspension/resume tasks themselves. Even on newer versions of Windows, of course NtSuspendProcess and NtResumeProcess still exist... If you're reading this, you probably use those two APIs all the time from user-mode! NtSuspendProcess will call PsSuspendProcess and NtResumeProcess will call PsResumeProcess on modern versions of Windows where these exported ntoskrnl.exe routines exist.





The function prototype for PsSuspendProcess is the following.
Code:
typedef NTSTATUS(NTAPI *pPsSuspendProcess)(
    PEPROCESS Process
    );
The function prototype for PsResumeProcess is the following.
Code:
typedef NTSTATUS(NTAPI *pPsResumeProcess)(
    PEPROCESS Process
    );
As we can see, both of the Ps* functions we will be using take in only one parameter, which is a pointer-structure for EPROCESS representing the targeted process. In the first thread for 'Process Management' in kernel-mode (click here to be redirected) we used EPROCESS* (PEPROCESS) for KeAttachProcess and KeDetachProcess, and we acquired it via PsLookupProcessByProcessId. We'll be using PsLookupProcessByProcessId to acquire the PEPROCESS for the targeted process again, however there's no need to attack to the process this time - if we did ths





Mash up details from the above paragraphs:
- There's no need to find the address of NtSuspendProcess or NtResumeProcess in kernel-mode thanks to NTOSKRNL exporting two other functions called PsSuspendProcess and PsResumeProcess, as long as you're running at least Windows Vista. These Ps* functions are called by routines like NtSuspendProcess and NtResumeProcess, and these Ps* functions handle the thread enumeration and thread suspension/resume tasks.
- PsSuspendProcess will enumerate through the threads of the targeted process and call KeSuspendThread appropriately.
- PsResumeProcess will enumerate through the threads of the targeted process and call KeResumeThread appropriately.
- We can pass in the PEPROCESS returned by PsLookupProcessByProcessId for PsSuspendProcess or PsResumeProcess.


Source code example

I've written an source code example (demonstration purposes) on how you can:
1. Find the Process Identifier (aka. Process ID/PID for short) from the name of the process via ZwQuerySystemInformation (from the previous thread).
2. Use the Process ID with PsLookupProcessByProcessId to retrieve the EPROCESS* structure (PEPROCESS) for the targeted process (also from the previous thread).
3. Pass in the retrieved EPROCESS* (PEPROCESS) structure variable for PsSuspendProcess/NtResumeProcess.

The function SuspendResumeProcess takes in two parameters: the Process ID (ULONG) and the Suspend status (TRUE represents that you wish to suspend the process whereas FALSE represents you wish to resume it if it is already suspended). The SuspendResumeProcess function will then call PsLookupProcessByProcessId using the wrapper function to retrieve the PEPROCESS and then it will decide whether a call to PsSuspendProcess or PsResumeProcess will be made depending on the BOOLEAN value for the second parameter (for the function call). If you're bothered, just check if Suspend is TRUE and if it is then call fPsSuspendProcess (return the function call result in the if statement without assigning NtStatus and then returning that) and if it isn't then just return fPsResumeProcess(...).

The addresses for ZwQuerySystemInformation, PsSuspendProcess and PsResumeProcess are retrieved via MmGetSystemRoutineAddress.








driver.h
Code:
#pragma once
#include <ntifs.h>

#include "ntdef.h"
#include "ntexport.h"
#include "processmm.h"

NTSTATUS DriverEntry(
    PDRIVER_OBJECT DriverObject,
    PUNICODE_STRING RegistryPath
);

VOID SuspendTest();
ntdef.h
Code:
#pragma once
#include "driver.h"

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_PROCESS_INFORMATION {
    ULONG NextEntryOffset;
    ULONG NumberOfThreads;
    LARGE_INTEGER WorkingSetPrivateSize;
    ULONG HardFaultCount;
    ULONG NumberOfThreadsHighWatermark;
    ULONGLONG CycleTime;
    LARGE_INTEGER CreateTime;
    LARGE_INTEGER UserTime;
    LARGE_INTEGER KernelTime;
    UNICODE_STRING ImageName;
    LONG BasePriority;
    PVOID UniqueProcessId;
    PVOID InheritedFromUniqueProcessId;
    ULONG HandleCount;
    ULONG SessionId;
    ULONG_PTR UniqueProcessKey;
    ULONG_PTR PeakVirtualSize;
    ULONG_PTR VirtualSize;
    ULONG PageFaultCount;
    ULONG_PTR PeakWorkingSetSize;
    ULONG_PTR WorkingSetSize;
    ULONG_PTR QuotaPeakPagedPoolUsage;
    ULONG_PTR QuotaPagedPoolUsage;
    ULONG_PTR QuotaPeakNonPagedPoolUsage;
    ULONG_PTR QuotaNonPagedPoolUsage;
    ULONG_PTR PagefileUsage;
    ULONG_PTR PeakPagefileUsage;
    ULONG_PTR PrivatePageCount;
    LARGE_INTEGER ReadOperationCount;
    LARGE_INTEGER WriteOperationCount;
    LARGE_INTEGER OtherOperationCount;
    LARGE_INTEGER ReadTransferCount;
    LARGE_INTEGER WriteTransferCount;
    LARGE_INTEGER OtherTransferCount;
    SYSTEM_THREAD_INFORMATION ProcessThreads[1];
}SYSTEM_PROCESS_INFORMATION, *PSYSTEM_PROCESS_INFORMATION;

typedef enum _SYSTEM_INFORMATION_CLASS {
    SystemBasicInformation = 0,
    SystemProcessorInformation = 1,
    SystemPerformanceInformation = 2,
    SystemTimeOfDayInformation = 3,
    SystemPathInformation = 4,
    SystemProcessInformation = 5,
    SystemCallCountInformation = 6,
    SystemDeviceInformation = 7,
    SystemProcessorPerformanceInformation = 8,
    SystemFlagsInformation = 9,
    SystemCallTimeInformation = 10,
    SystemModuleInformation = 11,
    SystemLocksInformation = 12,
    SystemStackTraceInformation = 13,
    SystemPagedPoolInformation = 14,
    SystemNonPagedPoolInformation = 15,
    SystemHandleInformation = 16,
    SystemObjectInformation = 17,
    SystemPageFileInformation = 18,
    SystemVdmInstemulInformation = 19,
    SystemVdmBopInformation = 20,
    SystemFileCacheInformation = 21,
    SystemPoolTagInformation = 22,
    SystemInterruptInformation = 23,
    SystemDpcBehaviorInformation = 24,
    SystemFullMemoryInformation = 25,
    SystemLoadGdiDriverInformation = 26,
    SystemUnloadGdiDriverInformation = 27,
    SystemTimeAdjustmentInformation = 28,
    SystemSummaryMemoryInformation = 29,
    SystemMirrorMemoryInformation = 30,
    SystemPerformanceTraceInformation = 31,
    SystemObsolete0 = 32,
    SystemExceptionInformation = 33,
    SystemCrashDumpStateInformation = 34,
    SystemKernelDebuggerInformation = 35,
    SystemContextSwitchInformation = 36,
    SystemRegistryQuotaInformation = 37,
    SystemExtendedServiceTableInformation = 38,
    SystemPrioritySeparation = 39,
    SystemVerifierAddDriverInformation = 40,
    SystemVerifierRemoveDriverInformation = 41,
    SystemProcessorIdleInformation = 42,
    SystemLegacyDriverInformation = 43,
    SystemCurrentTimeZoneInformation = 44,
    SystemLookasideInformation = 45,
    SystemTimeSlipNotification = 46,
    SystemSessionCreate = 47,
    SystemSessionDetach = 48,
    SystemSessionInformation = 49,
    SystemRangeStartInformation = 50,
    SystemVerifierInformation = 51,
    SystemVerifierThunkExtend = 52,
    SystemSessionProcessInformation = 53,
    SystemLoadGdiDriverInSystemSpace = 54,
    SystemNumaProcessorMap = 55,
    SystemPrefetcherInformation = 56,
    SystemExtendedProcessInformation = 57,
    SystemRecommendedSharedDataAlignment = 58,
    SystemComPlusPackage = 59,
    SystemNumaAvailableMemory = 60,
    SystemProcessorPowerInformation = 61,
    SystemEmulationBasicInformation = 62,
    SystemEmulationProcessorInformation = 63,
    SystemExtendedHandleInformation = 64,
    SystemLostDelayedWriteInformation = 65,
    SystemBigPoolInformation = 66,
    SystemSessionPoolTagInformation = 67,
    SystemSessionMappedViewInformation = 68,
    SystemHotpatchInformation = 69,
    SystemObjectSecurityMode = 70,
    SystemWatchdogTimerHandler = 71,
    SystemWatchdogTimerInformation = 72,
    SystemLogicalProcessorInformation = 73,
    SystemWow64SharedInformationObsolete = 74,
    SystemRegisterFirmwareTableInformationHandler = 75,
    SystemFirmwareTableInformation = 76,
    SystemModuleInformationEx = 77,
    SystemVerifierTriageInformation = 78,
    SystemSuperfetchInformation = 79,
    SystemMemoryListInformation = 80,
    SystemFileCacheInformationEx = 81,
    SystemThreadPriorityClientIdInformation = 82,
    SystemProcessorIdleCycleTimeInformation = 83,
    SystemVerifierCancellationInformation = 84,
    SystemProcessorPowerInformationEx = 85,
    SystemRefTraceInformation = 86,
    SystemSpecialPoolInformation = 87,
    SystemProcessIdInformation = 88,
    SystemErrorPortInformation = 89,
    SystemBootEnvironmentInformation = 90,
    SystemHypervisorInformation = 91,
    SystemVerifierInformationEx = 92,
    SystemTimeZoneInformation = 93,
    SystemImageFileExecutionOptionsInformation = 94,
    SystemCoverageInformation = 95,
    SystemPrefetchPatchInformation = 96,
    SystemVerifierFaultsInformation = 97,
    SystemSystemPartitionInformation = 98,
    SystemSystemDiskInformation = 99,
    SystemProcessorPerformanceDistribution = 100,
    SystemNumaProximityNodeInformation = 101,
    SystemDynamicTimeZoneInformation = 102,
    SystemCodeIntegrityInformation = 103,
    SystemProcessorMicrocodeUpdateInformation = 104,
    SystemProcessorBrandString = 105,
    SystemVirtualAddressInformation = 106,
    SystemLogicalProcessorAndGroupInformation = 107,
    SystemProcessorCycleTimeInformation = 108,
    SystemStoreInformation = 109,
    SystemRegistryAppendString = 110,
    SystemAitSamplingValue = 111,
    SystemVhdBootInformation = 112,
    SystemCpuQuotaInformation = 113,
    SystemNativeBasicInformation = 114,
    SystemErrorPortTimeouts = 115,
    SystemLowPriorityIoInformation = 116,
    SystemBootEntropyInformation = 117,
    SystemVerifierCountersInformation = 118,
    SystemPagedPoolInformationEx = 119,
    SystemSystemPtesInformationEx = 120,
    SystemNodeDistanceInformation = 121,
    SystemAcpiAuditInformation = 122,
    SystemBasicPerformanceInformation = 123,
    SystemQueryPerformanceCounterInformation = 124,
    SystemSessionBigPoolInformation = 125,
    SystemBootGraphicsInformation = 126,
    SystemScrubPhysicalMemoryInformation = 127,
    SystemBadPageInformation = 128,
    SystemProcessorProfileControlArea = 129,
    SystemCombinePhysicalMemoryInformation = 130,
    SystemEntropyInterruptTimingInformation = 131,
    SystemConsoleInformation = 132,
    SystemPlatformBinaryInformation = 133,
    SystemThrottleNotificationInformation = 134,
    SystemPolicyInformation = 134,
    SystemHypervisorProcessorCountInformation = 135,
    SystemDeviceDataInformation = 136,
    SystemDeviceDataEnumerationInformation = 137,
    SystemMemoryTopologyInformation = 138,
    SystemMemoryChannelInformation = 139,
    SystemBootLogoInformation = 140,
    SystemProcessorPerformanceInformationEx = 141,
    SystemSpare0 = 142,
    SystemSecureBootPolicyInformation = 143,
    SystemPageFileInformationEx = 144,
    SystemSecureBootInformation = 145,
    SystemEntropyInterruptTimingRawInformation = 146,
    SystemPortableWorkspaceEfiLauncherInformation = 147,
    SystemFullProcessInformation = 148,
    SystemKernelDebuggerInformationEx = 149,
    SystemBootMetadataInformation = 150,
    SystemSoftRebootInformation = 151,
    SystemElamCertificateInformation = 152,
    SystemOfflineDumpConfigInformation = 153,
    SystemProcessorFeaturesInformation = 154,
    SystemRegistryReconciliationInformation = 155,
    SystemEdidInformation = 156,
    SystemManufacturingInformation = 157,
    SystemEnergyEstimationConfigInformation = 158,
    SystemHypervisorDetailInformation = 159,
    SystemProcessorCycleStatsInformation = 160,
    SystemVmGenerationCountInformation = 161,
    SystemTrustedPlatformModuleInformation = 162,
    SystemKernelDebuggerFlags = 163,
    SystemCodeIntegrityPolicyInformation = 164,
    SystemIsolatedUserModeInformation = 165,
    SystemHardwareSecurityTestInterfaceResultsInformation = 166,
    SystemSingleModuleInformation = 167,
    SystemAllowedCpuSetsInformation = 168,
    SystemDmaProtectionInformation = 169,
    SystemInterruptCpuSetsInformation = 170,
    SystemSecureBootPolicyFullInformation = 171,
    SystemCodeIntegrityPolicyFullInformation = 172,
    SystemAffinitizedInterruptProcessorInformation = 173,
    SystemRootSiloInformation = 174,
    SystemCpuSetInformation = 175,
    SystemCpuSetTagInformation = 176,
    MaxSystemInfoClass = 177
} SYSTEM_INFORMATION_CLASS;
ntexport.h
Code:
#pragma once
#include "driver.h"

typedef NTSTATUS(NTAPI *pZwQuerySystemInformation)(
    SYSTEM_INFORMATION_CLASS SystemInformationClass,
    PVOID SystemInformation,
    ULONG SystemInformationLength,
    PULONG ReturnLength OPTIONAL
    );

typedef NTSTATUS(NTAPI *pPsSuspendProcess)(
    PEPROCESS Process
    );

typedef NTSTATUS(NTAPI *pPsResumeProcess)(
    PEPROCESS Process
    );

extern pZwQuerySystemInformation fZwQuerySystemInformation;
extern pPsSuspendProcess fPsSuspendProcess;
extern pPsResumeProcess fPsResumeProcess;

NTSTATUS SetupExports();
processmm.h
Code:
#pragma once
#include "driver.h"

NTSTATUS ReturnProcessStruct(
    ULONG ProcessId,
    PEPROCESS *Process
);

ULONG ReturnProcessId(
    WCHAR *ProcessName
);

NTSTATUS SuspendResumeProcess(
    ULONG ProcessId,
    BOOLEAN Suspend
);
driver.c
Code:
#include "driver.h"

DEVICE_OBJECT DeviceObject = { 0 };

UNICODE_STRING usDriverInfo[2] = {
    RTL_CONSTANT_STRING(L"\\Device\\KmdfExample"),
    RTL_CONSTANT_STRING(L"\\DosDevices\\KmdfExample")
};

VOID SuspendTest()
{
    NTSTATUS NtStatus = STATUS_SUCCESS;
    ULONG ProcessId = 0;

    NtStatus = SetupExports();

    if (!NT_SUCCESS(NtStatus))
    {
        return;
    }

    ProcessId = ReturnProcessId(L"notepad.exe");

    if (!ProcessId)
    {
        DbgPrint("The process could not be found\n");

        return;
    }

    NtStatus = SuspendResumeProcess(ProcessId,
        FALSE);

    if (NT_SUCCESS(NtStatus))
    {
        DbgPrint("The process was suspended from kernel-mode\n");
    }
}

VOID DriverUnload(
    PDRIVER_OBJECT DriverObject
)
{
    IoDeleteSymbolicLink(&usDriverInfo[1]);
    IoDeleteDevice(DriverObject->DeviceObject);
}

NTSTATUS DriverEntry(
    PDRIVER_OBJECT DriverObject,
    PUNICODE_STRING RegistryPath
)
{
    UNREFERENCED_PARAMETER(RegistryPath);

    NTSTATUS NtStatus = STATUS_SUCCESS;

    NtStatus = IoCreateDevice(DriverObject,
        NULL,
        &usDriverInfo[0],
        FILE_DEVICE_UNKNOWN,
        FILE_DEVICE_SECURE_OPEN,
        FALSE,
        &DeviceObject);

    if (!NT_SUCCESS(NtStatus))
    {
        return NtStatus;
    }

    NtStatus = IoCreateSymbolicLink(&usDriverInfo[1],
        &usDriverInfo[0]);

    if (!NT_SUCCESS(NtStatus))
    {
        return NtStatus;
    }

    DriverObject->DriverUnload = DriverUnload;

    SuspendTest();

    return NtStatus;
}
ntexport.c
Code:
#include "driver.h"

pZwQuerySystemInformation fZwQuerySystemInformation;
pPsSuspendProcess fPsSuspendProcess;
pPsResumeProcess fPsResumeProcess;

NTSTATUS SetupExports()
{
    UNICODE_STRING usSystemRoutineName[3] = {
        RTL_CONSTANT_STRING(L"ZwQuerySystemInformation"),
        RTL_CONSTANT_STRING(L"PsSuspendProcess"),
        RTL_CONSTANT_STRING(L"PsResumeProcess")
    };

    PVOID FunctionAddresses[3] = {
        MmGetSystemRoutineAddress(&usSystemRoutineName[0]),
        MmGetSystemRoutineAddress(&usSystemRoutineName[1]),
        MmGetSystemRoutineAddress(&usSystemRoutineName[2])
    };

    if (!FunctionAddresses[0] ||
        !FunctionAddresses[1] ||
        !FunctionAddresses[2])
    {
        return STATUS_UNSUCCESSFUL;
    }

    fZwQuerySystemInformation = (pZwQuerySystemInformation)FunctionAddresses[0];
    fPsSuspendProcess = (pPsSuspendProcess)FunctionAddresses[1];
    fPsResumeProcess = (pPsResumeProcess)FunctionAddresses[2];

    if (!fZwQuerySystemInformation ||
        !fPsSuspendProcess ||
        !fPsResumeProcess)
    {
        return STATUS_UNSUCCESSFUL;
    }

    return STATUS_SUCCESS;
}
processmm.c
Code:
#include "driver.h"

NTSTATUS ReturnProcessStruct(
    ULONG ProcessId,
    PEPROCESS *Process
)
{
    NTSTATUS NtStatus = STATUS_SUCCESS;

    NtStatus = PsLookupProcessByProcessId((HANDLE)ProcessId,
        Process);

    return NtStatus;
}

ULONG ReturnProcessId(
    WCHAR *ProcessName
)
{
    NTSTATUS NtStatus = STATUS_SUCCESS;
    PSYSTEM_PROCESS_INFORMATION SystemProcessInfo = { 0 };
    PVOID BufferMemory = 0;
    SIZE_T RegionSize = 0;
    ULONG ReturnLength = 0, ProcessId = 0;

    NtStatus = fZwQuerySystemInformation((SYSTEM_INFORMATION_CLASS)SystemProcessInformation,
        NULL,
        NULL,
        &ReturnLength);

    if (!ReturnLength)
    {
        return ProcessId;
    }

    RegionSize = (SIZE_T)ReturnLength;

    BufferMemory = ExAllocatePool(NonPagedPool,
        RegionSize);

    if (!BufferMemory)
    {
        return ProcessId;
    }

    NtStatus = fZwQuerySystemInformation((SYSTEM_INFORMATION_CLASS)SystemProcessInformation,
        BufferMemory,
        ReturnLength,
        NULL);

    if (!NT_SUCCESS(NtStatus))
    {
        return ProcessId;
    }

    SystemProcessInfo = (PSYSTEM_PROCESS_INFORMATION)BufferMemory;

    while (SystemProcessInfo->NextEntryOffset)
    {
        SystemProcessInfo = (SYSTEM_PROCESS_INFORMATION*)((UCHAR*)SystemProcessInfo + SystemProcessInfo->NextEntryOffset);

        if (!wcscmp(SystemProcessInfo->ImageName.Buffer, ProcessName))
        {
            ProcessId = (ULONG)SystemProcessInfo->UniqueProcessId;

            break;
        }
    }

    if (BufferMemory)
    {
        ExFreePool(BufferMemory);
    }

    return ProcessId;
}

NTSTATUS SuspendResumeProcess(
    ULONG ProcessId,
    BOOLEAN Suspend
)
{
    NTSTATUS NtStatus = STATUS_SUCCESS;
    PEPROCESS Process = { 0 };

    NtStatus = ReturnProcessStruct(ProcessId,
        &Process);

    if (!NT_SUCCESS(NtStatus))
    {
        return NtStatus;
    }

    if (Suspend)
    {
        NtStatus = fPsSuspendProcess(Process);
    }
    else
    {
        NtStatus = fPsResumeProcess(Process);
    }

    return NtStatus;
}


The demonstration code will attempt to find the Process ID of notepad.exe by default, and if found will attempt to suspend it. The resume support isn't utilised at all in the demonstration code however it is still there, you have to pass FALSE as the second parameter to the SuspendResumeProcess (NTSTATUS) function instead of TRUE.


Thank you for reading.
 

Attachments

Last edited by a moderator:
D

Deleted member 65228

Guest
#3
How can we find function addresses which are not exported from SSDT?
Reverse engineer ntoskrnl.exe to find the function you are looking for and develop a byte-pattern for the function which is reliable.

1. ZwQuerySystemInformation -> passing SystemModuleInformation as the class.
2. Enumerate through the results from the ZwQuerySystemInformation call -> the first one should be ntoskrnl.exe (now you can have the base address of ntoskrnl.exe).
3. Scan through the memory of ntoskrnl.exe starting at the base address of course -> all executable memory regions.
4. Apply filtering for your byte-pattern signature.
5. When you find your byte-pattern, depending on how you developed it, you can subtract back until you hit the start of the function prologue -> now you have the start address.
6. Now you have the address of the non-exported kernel-mode routine.

All you will need after that is a correct type definition for the function. You can use the address just like any dynamic import. Example...

Code:
typedef NTSTATUS(NTAPI *pTypeName)(
              PARAMS HERE IF ANY
);

... now you have the address? so you do

pTypeName YourDynamicRoutine = (pTypeName)Address;
You can apply scanning of ntoskrnl.exe memory and byte-pattern filtering to find KeServiceDescriptorTable on x64 systems where it is not exported like on 32-bit systems, too. The System Service Dispatch Table (KeServiceDescriptorTable) will only have pointer addresses for the routines callable from user-mode I believe, because the whole point of it was to allow transition from user-mode to kernel-mode.

NTDLL will move the function ID into the EAX register. The system call is then performed depending on circumstances (e.g. old OS on 32-bit pass through KiFastSystemCall, and WOW64 nowadays still will pass through functions like Wow64Transition -> eventually move into 64-bit world through use of 0x33 segment selector -> system call is performed -> 0x23 segment selector is used again). When the system call actually happens, a kernel-mode routine is invoked which gets the correct pointer address from the SSDT. Before the Nt* function is called, other things happen such as the Previous Mode being changed to Kernel-Mode (and then back to UserMode before transition is replied back to UM).

Therefore, non-exported functions like NtCreateNamedPipeFile can still find the address using SSDT. But since you are in kernel-mode, why bother anyway? It is better and more advanced to just make a wrapper for it to do what the function would have done for you. That also allows you to bypass SSDT hooks entirely. Why care about a hook on SSDT for NtOpenProcess when you can call ObOpenObjectByPointer. Why care about a SSDT hook on NtCreateNamedPipeFile when you can call IoCreateFile? etc. :)

So to cut it short, you scan memory of ntoskrnl.exe after finding the base address for executable regions (e.g. PAGE_EXECUTE, PAGE_EXECUTE_READWRITE) -> byte-signature scanning. You can make the byte signature during disassembly (e.g. debugging or what-not you find the function -> now you see the opcodes (operation codes) for the function to make a byte signature). :)
 
D

Deleted member 65228

Guest
#4
1. ZwQuerySystemInformation -> passing SystemModuleInformation as the class.
2. Enumerate through the results from the ZwQuerySystemInformation call -> the first one should be ntoskrnl.exe (now you can have the base address of ntoskrnl.exe).
This is what EB/DP did to find the srv.sys driver. -> it didn't scan ntoskrnl.exe memory though, but it used SystemModuleInformation class to find the driver.

Basically, kernel-mode device drivers will be modules of ntoskrnl.exe. The same way a user-mode program imports a DLL and now it is under the imports of that process. The kernel-mode device drivers are like children to ntoskrnl.exe... SystemModuleInformation is an undocumented class for Zw/NtQuerySystemInformation and will list all the modules of ntoskrnl.exe, allowing you to find the base address of each loaded kernel-mode device driver. However, ntoskrnl.exe is a kernel-mode image and the results are the Imports of ntoskrnl.exe therefore the first entry in the results will be ntoskrnl.exe itself. Which is the Windows Kernel. Therefore all the kernel-mode routines like PspOpenProcess, they are all internal to ntoskrnl.exe. Not all of them are exported of course. Routines like NtOpenProcess will simply use other functions -> use more other functions. Eventually the functions being used are internal completely.

Zw* routines are only present to work differently. In user-mode the addresses to Nt/Zw are identical. Therefore, ZwTerminateProcess points to the exact same address as NtTerminateProcess in NTDLL. However, in kernel-mode it is a bit different.

Zw* will still lead to the kernel-mode routine invoked when user-mode programs perform system calls. Why? Because other things need to be done like changing the Previous Mode. This is why if you use Nt* in kernel-mode by default it will not work simply because you have to handle that stuff manually in that case. This is why patching of the SSDT can still affect kernel-mode device drivers, but patching of the SSDT was never actually reliable or safe to do. And you cannot really do much in the scope of kernel-mode against other kernel-mode code unless you have a lot more power like through virtualisation, I guess inline hooking of the actual ntoskrnl.exe routines are more secure but still unsafe.

Anyway once you find the base address of ntoskrnl.exe you can scan memory and find the address to any non-exported function within it that you like. But you will certainly have to reverse engineer a lot to disassemble the function to make the byte signature pattern and know the correct parameters, return types, etc. It is extremely unrecommended and you'll need to check OS versions and put a lock on your product working on other OS versions (e.g. new ones) which have not been checked yet because anything can change at any given moment and the last thing you want to do is cause a load of crashes, and this sort of thing can cause that to happen very easily.

undocumented but I admit still interesting to do and to learn :)
 
D

Deleted member 65228

Guest
#6
such as the Previous Mode being changed to Kernel-Mode (and then back to UserMode before transition is replied back to UM).
Just an update for elaboration...

The Previous Mode is recorded under the ETHREAD structure (kernel-mode) for the calling thread. Within the ETHREAD structure, the very first entry will have a KTHREAD data-type. Under the KTHREAD structure there will be an entry with a data-type of
KPROCESSOR_MODE called Previous Mode.

struct KTHREAD -> for Windows Vista. use WinDbg to check latest OS versions. Nirsoft says CHAR but you can use KPROCESSOR_MODE

You're not supposed to change the values manually but you can do it in kernel-mode of course since you'd have access to the memory and would be able to write. You can just + for the offset to reach that entry and then re-assign the data, and then revert it after the Nt* call so you don't have to use Zw*. However it is still unrecommended, unless you have a genuine reason to do so, don't do it.
 
Last edited by a moderator:
D

Deleted member 65228

Guest
#7
How can we find function addresses which are not exported from SSDT?
I came back to this thread to reply again to your question because last time I did not explain it so well for 64-bit support. My initial response had 64-bit environments in mind, where the System Service Descriptor Table is not exported by ntoskrnl.exe. On 32-bit environments you aren't required to do anything tricky to find the base address of the System Service Descriptor Table, but on 64-bit environments it is not exported and thus you must locate the base address yourself.

There is a routine in ntoskrnl.exe (not exported) named KiSystemCall64. This routine is the first key for you to find the address of the System Service Descriptor Table, without having to do a much-lengthier process of byte pattern scanning... The first step will be to the find the address of this routine. There are different ways you can go about doing this: the first being to apply byte pattern scanning which is the most obvious technique; and the second would be to use the __readmsr macro. The second is the best method in my opinion, because you'll be required to perform less instructions to get your desired result, without degrading effectiveness.

To use the __readmsr macro to find the address of KiSystemCall64, you must understand why we would use this macro in the first place. There is an MSR called IA32_LSTAR (C0000082). When a system-call is performed, the RIP register points to this MSR which points to the address of KiSystemCall64. Therefore, by using __readmsr and passing 0xC0000082 as the parameter, we will be able to locate the base address of KiSystemCall64 at ease without being required to apply more time-consuming (and less-reliable) operations to locate the address of the routine. This routine is not exported by ntoskrnl.exe of course and thus this is why we are trying to manually find the base address in an automated way.

The reason we are locating the base address of KiSystemCall64 is simple. The routine will move us closer to where the address of KeServiceDescriptorTable (another name for the System Service Dispatch Table however that one is more technical) becomes exposed.

For the time being I will put an end to the heavy theory and explain further with some practical demonstrations. You'll require an analysis environment to replicate the following (e.g. either an additional physical machine or a Virtual Machine which is correctly setup to be used as a target machine for remote kernel debugging - I use WinDbg either standalone or within Visual Studio).

1. We will locate the address of KiSystemCall64 by using the MSR we noted down earlier. With WinDbg we can use the rdmsr function.



As you can see from the above, WinDbg returned the address pointed to by the MSR IA32_LSTAR. We know that the MSR we targeted was IA32_LSTAR because we used C0000082 (0xC0000082) which is IA32_LSTAR. The address returned to us is the address to KiSystemCall64 (NTOSKRNL) for my environment since the environment I am remotely debugging for the kernel is 64-bit (Windows 10).

2. We will perform disassembly using the address of KiSystemCall64 by using the uf function with the address returned by the rdmsr function we previously used.



Now we have the disassembly of KiSystemCall64 and many other Ki* routines which are neither exported by ntoskrnl.exe. We can simply ignore the KiSystemCall64 routine now, we have bigger fish to fry! We need to locate the address of KiSystemServiceRepeat (non-exported routine) in our disassembly.

3. Scroll down until we find KiSystemServiceRepeat in the disassembly output.



As we can see from my kernel debugging and disassembly output, the KiSystemServiceRepeat routine exposes the address of the KeServiceDescriptorTable as well as the shadow variant. If we look at the first red highlight, we can see the HEX equivalent of the bytes which represent the instructions for the disassembly.

The key bytes which are of interest to find the base address of KeServiceDescriptorTable is 0x4C 0x8D 0x15. That is the target byte pattern signature which we will need to use.

Now remember that we have the base address of KiSystemCall64. KiSystemServiceRepeat is not that far away from KiSystemCall64, and very simplistic for us to apply byte pattern scanning. The signature is 0x4C 0x8D 0x15 therefore all you are required to do is go through the operation codes (opcodes - just like my profile name!) until you match the pattern. You aren't going to run into conflicting situations with a pattern match for another routine because there are no matches from the range of KiSystemCall64 to KiSystemServiceRepeat... Therefore the first match you trigger is correct.

Now you are so much closer... To finding the address. Retrieve the address referenced (the normal or the shadow table one) and then perform another calculation (offset calculation) and bobs your uncle! ;)

Once you have the base address of the KeServiceDescriptorTable you can use it to find the address of all the functions present in the System Service Descriptor Table, the same way you can on 32-bit systems. However as a precaution, remember that the variable types for the System Service Descriptor Table structure on 64-bit environments is slightly different compared to the 32-bit version.

As an example, you could use this to locate the address of NtCreateNamedPipeFile so you don't have to make a custom wrapper implementation via IoCreateFile, locate the address of NtQueueApcThread so you don't have to make a custom wrapper via KeInitializeApc and KeInsertQueueApc, locate the address of NtWriteVirtualMemory so you don't have to attach and rely on memcpy and then detach or MmCopyVirtualMemory, or locate the address of a routine like NtProtectVirtualMemory (which should end up at MiProtectPrivateMemory eventually).

This should help you on your way further than with my previous response.
 
Last edited by a moderator:

Similar Threads

Similar Threads