SDT hooking

 

Understanding exports

f.c

#include <windows.h>

void abc()

{

}

void pqr()

{

}

void xyz()

{

}

 

f.def

LIBRARY f.dll

EXPORTS

            abc @2

            pqr @5

            xyz @12

 

Cl /c f.c

link /def:f.def /dll f.obj

 

We have created a dll f.dll that three exported methods abc, pqr and xyz with ordinal numbers of 2 5 and 10.

 

#include <windows.h>

#include <stdio.h>

void main()

{

int i;

HANDLE hfile,hfile1;

unsigned char *pmappedfile;

hfile = CreateFile("f.dll",GENERIC_WRITE | GENERIC_READ,0,0,OPEN_EXISTING,0,0);

hfile1 = CreateFileMapping (hfile, 0, PAGE_READWRITE, 0, 0, 0);

pmappedfile = (unsigned char *)MapViewOfFile(hfile1,FILE_MAP_ALL_ACCESS,0,0,0);

printf("pmappedfile=%p\n",pmappedfile);

IMAGE_DOS_HEADER *pimagedosheader  = (IMAGE_DOS_HEADER *)pmappedfile;

IMAGE_FILE_HEADER *pimagefileheader = (IMAGE_FILE_HEADER *)(pmappedfile + pimagedosheader->e_lfanew +4);

IMAGE_OPTIONAL_HEADER *pimageoptionalheader = (IMAGE_OPTIONAL_HEADER *)((unsigned char *)pimagefileheader+sizeof(IMAGE_FILE_HEADER));

DWORD beginexportsrva,startoffile;

beginexportsrva = pimageoptionalheader->DataDirectory[0].VirtualAddress;

printf("beginexportsrva=%p Size=%x \n",beginexportsrva,pimageoptionalheader->DataDirectory[0].Size);

PIMAGE_EXPORT_DIRECTORY pexportdirectory;

pexportdirectory = (PIMAGE_EXPORT_DIRECTORY)(pmappedfile + beginexportsrva);

printf("pexportdirectory=%p\n",pexportdirectory);

char *nameoffile;

nameoffile  = (PSTR)(pexportdirectory->Name + pmappedfile);

printf("FileName:%s %x\n", nameoffile,nameoffile);

printf("Ordinal Numbers Starting From:%08X\n", pexportdirectory->Base);

printf("Number of functions:%08X\n", pexportdirectory->NumberOfFunctions);

printf("Number of Names:%08X\n", pexportdirectory->NumberOfNames);

printf("Address of functions array rva=%08x\n",pexportdirectory->AddressOfFunctions);

printf("Address of names array rva=%08x\n",pexportdirectory->AddressOfNames);

printf("Address of ordinals array rva=%08x\n",pexportdirectory->AddressOfNameOrdinals);

long *pfunctionsactual;

short *pordinalsactual;

pfunctionsactual = (long *)((int)pexportdirectory->AddressOfFunctions  + pmappedfile);

pordinalsactual  = (short *)((int)pexportdirectory->AddressOfNameOrdinals + pmappedfile);

char **ppnamesactual;

ppnamesactual = (char  **)((int)pexportdirectory->AddressOfNames + pmappedfile);

printf("\ni  Entry Pt  Ordn  Name\n");

for ( i=0; i < pexportdirectory->NumberOfFunctions; i++ )

{

DWORD startoffunctionsrva = pfunctionsactual[i];

DWORD j;

if ( startoffunctionsrva == 0 ) 

continue;  

printf("%d  %08X  %4u", i , startoffunctionsrva, i + pexportdirectory->Base );

for ( j=0; j < pexportdirectory->NumberOfNames; j++ )

if ( pordinalsactual[j] == i )

printf("  %s", ppnamesactual[j] + (int)pmappedfile);

printf("\n");

}

printf("Array of Address of Functions\n");

for ( i = 0 ; i < pexportdirectory->NumberOfFunctions; i++)

printf("%d %08X\n",i,pfunctionsactual[i]);

printf("Array of NameOrdinals\n");

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

printf("%d %08X\n",i,pordinalsactual[i]);

printf("Names of functions\n");

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

printf("%d %08X %s\n",i,ppnamesactual[i] + (int)pmappedfile , ppnamesactual[i]  + (int)pmappedfile);

}

pmappedfile=00340000

beginexportsrva=00009A50 Size=78

pexportdirectory=00349A50

FileName:f.dll 349ab6

Ordinal Numbers Starting From:00000002

Number of functions:0000000B

Number of Names:00000003

Address of functions array rva=00009a78

Address of names array rva=00009aa4

Address of ordinals array rva=00009ab0

i  Entry Pt  Ordn  Name

0  00001000     2  abc

3  00001010     5  pqr

10  00001020    12  xyz

Array of Address of Functions

0 00001000

1 00000000

2 00000000

3 00001010

4 00000000

5 00000000

6 00000000

7 00000000

8 00000000

9 00000000

10 00001020

Array of NameOrdinals

0 00000000

1 00000003

2 0000000A

Names of functions

0 00349ABC abc

1 00349AC0 pqr

2 00349AC4 xyz

 

The CreateFile method gives us a file handle on f.dll. We then use the CreateFileMapping method to give us a file mapping handle. We also supply the permissions which are read and write. This mapping handle is used to load the file into memory using the MapViewOfFile method. In our case the file is loaded in memory at location 00340000.

 

The MapViewOfFile method actually is used by the windows loader to load the file into memory. Thus each section is loaded on a page boundary which is 4k. If we did not have such a method we would have to write code ourselves to align the file as the windows loader would have done so. We start a PE file with a IMAGE_DOS_HEADER followed by the signature of the file PE00. This is followed by three back to back structure, the IMAGE_FILE_HEADER the IMAGE_OPTIONAL_HEADER and the IMAGE_SECTION_HEADER.

 

The optional header ends with a series of DataDirectory structures that have two members Virtual Address and Size. The first data director tells us where the exports structures start. Thus the variable beginexportsrva will give us the RVA of where the export directory structure starts. Add to this the start of the file and we will get the actual address.

 

This export structure has lots of fields, we will display the ones that we want like the name of the dll f.dll. The base member tells us the first ordinal number which in our case is 2.  We have three array that make up the export directory. The array of ordinals and names will have the same number of members 3. This is because we have three exported functions. The array of function addresses will have as many members as the highest ordinal number minus the base. In our case this value is 12 – 2 or 10.

 

At the end of the program we are actually displaying the three arrays. The array of names contains the RVAs of the names of the functions. The array of ordinals contains some numbers and the array of function addresses contains in our case 10 members. We start by iterating the function addresses array. We loop back if we have a function address of 0.

 

When we find a  array entry with a non zero value we display this value as the entry point of the function. We then use the index or array offset as the ordinal number plus base. Thus for function with a entry point of 1020 the ordinal number is 10 + 2 or 12. We next find this number 10 in the name ordinals array. It is found as the third entry which has a index or array offset of 2. This number 2 is used as the index into names array to give us a value of xyz.

 

Coming to our code, we scan the arrays of function entry points and I is used as the index or ordinal number. If we find a entry point of 0 we continue. We then scan the ordinal array for a value of i. The minute we do we use the index j to give us the name of the function.

 

SDT Hooking

 

d.cpp

#include <stdio.h>

#include <windows.h>

#include <aclapi.h>

struct UNICODE_STRING

{

USHORT  Length,MaximumLength;

PWSTR  Buffer;

};

struct OBJECT_ATTRIBUTES

{

ULONG Length;

HANDLE RootDirectory;

UNICODE_STRING *ObjectName;

ULONG Attributes;

long a[2];

};

struct SYSTEM_MODULE_INFORMATION

{

ULONG a[2];

PVOID Base;

};

VOID (WINAPI *RtlInitUnicodeString)(UNICODE_STRING *DestinationString, const wchar_t *SourceString);

long (WINAPI *NtOpenSection)(PHANDLE SectionHandle, ACCESS_MASK DesiredAccess,OBJECT_ATTRIBUTES  *ObjectAttributes);

long (WINAPI *NtMapViewOfSection)(HANDLE SectionHandle,HANDLE  ProcessHandle,PVOID *BaseAddress,ULONG ZeroBits,ULONG CommitSize,LARGE_INTEGER *SectionOffset,PULONG  ViewSize, int  InheritDisposition, IN ULONG  AllocationType,ULONG  Protect);

long (WINAPI *NtQuerySystemInformation)(UINT, PVOID, ULONG, PULONG);

HMODULE hntdll;

OBJECT_ATTRIBUTES oAttr;

UNICODE_STRING uStr;

PACL dacl,newDacl;

PSECURITY_DESCRIPTOR sd;

EXPLICIT_ACCESS ea;

HANDLE hPhyMem,fp, hfile1;

LPVOID exePtr,pBuffer;

char *ptr,**nativeApiNames,**ppnamesactual,*pmappedfile1,*pmappedfile;

PIMAGE_SECTION_HEADER psectionheader;

IMAGE_FILE_HEADER *pimagefileheader;

IMAGE_DOS_HEADER *pimagedosheader;

IMAGE_OPTIONAL_HEADER *pimageoptionalheader;

PIMAGE_EXPORT_DIRECTORY pexportdirectory;

SYSTEM_MODULE_INFORMATION *smi;

long *pfunctionsactual,beginexportsrva,j,diff ,serviceNum, apiAddr;

short *pordinalsactual;

DWORD sdtAddr,pAddr,len,start,serviceTableAddr,sdtCount,wantedBytes,hookCount,i;

void main()

{

hntdll = GetModuleHandle("ntdll.dll");

*(FARPROC *)&RtlInitUnicodeString = GetProcAddress(hntdll, "RtlInitUnicodeString");

*(FARPROC *)&NtOpenSection = GetProcAddress(hntdll, "NtOpenSection");

*(FARPROC *)&NtMapViewOfSection = GetProcAddress(hntdll, "NtMapViewOfSection");

*(FARPROC *)&NtQuerySystemInformation = GetProcAddress(hntdll, "ZwQuerySystemInformation");

RtlInitUnicodeString(&uStr,L"\\device\\physicalmemory");

oAttr.Length = sizeof(OBJECT_ATTRIBUTES);

oAttr.Attributes = 0x00000040L;

oAttr.ObjectName = &uStr;

NtOpenSection(&hPhyMem,READ_CONTROL|WRITE_DAC,&oAttr);

GetSecurityInfo(hPhyMem,SE_KERNEL_OBJECT, DACL_SECURITY_INFORMATION,0,0,&dacl,0,&sd);

ea.grfAccessPermissions = SECTION_MAP_WRITE;

ea.grfAccessMode = GRANT_ACCESS;

ea.Trustee.ptstrName = "vijay";

SetEntriesInAcl(1,&ea,dacl,&newDacl);

SetSecurityInfo(hPhyMem,SE_KERNEL_OBJECT,DACL_SECURITY_INFORMATION,0,0,newDacl,0);

NtOpenSection(&hPhyMem,SECTION_MAP_READ|SECTION_MAP_WRITE,&oAttr);

fp = CreateFile("c:\\windows\\system32\\ntoskrnl.exe",GENERIC_READ|GENERIC_WRITE,0,0,OPEN_EXISTING,0,0);

hfile1 = CreateFileMapping (fp, 0, PAGE_READWRITE, 0, 0, 0);

pmappedfile = (char *)MapViewOfFile(hfile1,FILE_MAP_ALL_ACCESS,0,0,0);

pimagedosheader  = (IMAGE_DOS_HEADER *)pmappedfile;

pimagefileheader = (IMAGE_FILE_HEADER *)(pmappedfile + pimagedosheader->e_lfanew +4);

pimageoptionalheader = (IMAGE_OPTIONAL_HEADER *)((char *)pimagefileheader+sizeof(IMAGE_FILE_HEADER));

beginexportsrva = pimageoptionalheader->DataDirectory[0].VirtualAddress;

pexportdirectory = (PIMAGE_EXPORT_DIRECTORY)(pmappedfile + beginexportsrva);

pfunctionsactual = (long *)((int)pexportdirectory->AddressOfFunctions  + pmappedfile);

pordinalsactual  = (short *)((int)pexportdirectory->AddressOfNameOrdinals  + pmappedfile);

ppnamesactual = (char  **)((int)pexportdirectory->AddressOfNames  + pmappedfile);

for ( i=0; i < pexportdirectory->NumberOfFunctions; i++ )

{

for ( j=0; j < pexportdirectory->NumberOfNames; j++ )

if ( pordinalsactual[j] == i && strcmp(ppnamesactual[j]  + (long)pmappedfile , "KeServiceDescriptorTable") == 0)

sdtAddr = pfunctionsactual[i];

}

printf("sdtAddr=%x\n",sdtAddr);

pBuffer = malloc(50000);

NtQuerySystemInformation(11,pBuffer,50000,0);

smi = (SYSTEM_MODULE_INFORMATION *)((char *)pBuffer + 4);

pAddr = sdtAddr + (DWORD)smi->Base - 0x80000000;

printf("smi->Base=%x pAddr=%x\n",smi->Base, pAddr);

len = 0x2000;

LARGE_INTEGER viewBase;

viewBase.QuadPart = (ULONGLONG)pAddr;

NtMapViewOfSection(hPhyMem,(HANDLE)-1,(void **)&ptr,0,len,&viewBase,&len,1,0,PAGE_READWRITE);

pAddr = viewBase.LowPart;

printf("pAddr=%x ptr=%x len=%x\n",pAddr,ptr,len);

start = sdtAddr + (DWORD)smi->Base - 0x80000000 - pAddr;

serviceTableAddr = *((DWORD *)(&ptr[start]));

sdtCount = *(((DWORD *)(&ptr[start])) + 2);

printf("start=%x serviceTableAddr=%x sdtCount=%d\n",start,serviceTableAddr,sdtCount);

pAddr = serviceTableAddr - 0x80000000;

printf("pAddr=%x\n");

len = 0x2000;

viewBase.QuadPart = (ULONGLONG) pAddr;

NtMapViewOfSection(hPhyMem,(HANDLE)-1,(void **)&ptr,0,len,&viewBase,&len,1,0,PAGE_READWRITE);

pAddr = viewBase.LowPart;

start = serviceTableAddr - 0x80000000 - pAddr;

printf("pAddr=%x start=%x\n",pAddr,start);

nativeApiNames = (char **)malloc(sizeof(char *) * sdtCount);

fp = CreateFile("c:\\windows\\system32\\ntdll.dll", GENERIC_READ,FILE_SHARE_READ,0,OPEN_EXISTING,0,0);

hfile1 = CreateFileMapping(fp,0,PAGE_READONLY,0,0,0);

pmappedfile1 = (char *)MapViewOfFile(hfile1,FILE_MAP_READ,0,0,0);

pimagedosheader  = (IMAGE_DOS_HEADER *)pmappedfile1;

pimagefileheader = (IMAGE_FILE_HEADER *)(pmappedfile1 + pimagedosheader->e_lfanew +4);

pimageoptionalheader = (IMAGE_OPTIONAL_HEADER *)((unsigned char *)pimagefileheader+sizeof(IMAGE_FILE_HEADER));

psectionheader = (PIMAGE_SECTION_HEADER)((char *)pimageoptionalheader + sizeof(IMAGE_OPTIONAL_HEADER));

beginexportsrva = pimageoptionalheader->DataDirectory[0].VirtualAddress;

diff = (INT)(psectionheader->VirtualAddress - psectionheader->PointerToRawData);

pexportdirectory = (PIMAGE_EXPORT_DIRECTORY)(pmappedfile1 + beginexportsrva - diff);

pfunctionsactual = (long *)((int)pexportdirectory->AddressOfFunctions - diff + pmappedfile1);

pordinalsactual  = (short *)((int)pexportdirectory->AddressOfNameOrdinals - diff + pmappedfile1);

ppnamesactual = (char  **)((int)pexportdirectory->AddressOfNames - diff + pmappedfile1);

for ( i=0; i < pexportdirectory->NumberOfFunctions; i++ )

{

apiAddr = (DWORD)pfunctionsactual[i] - diff + (int)pmappedfile1;

serviceNum = *(DWORD *)((char *)apiAddr + 1);

for ( j=0; j < pexportdirectory->NumberOfNames; j++ )

if ( pordinalsactual[j] == i && serviceNum  <= sdtCount)

nativeApiNames[serviceNum] = ppnamesactual[j] - diff + (int)pmappedfile1;

}

DWORD *serviceTable = (DWORD *)(&ptr[start]);

DWORD *fileServiceTable = (DWORD *)((DWORD)pmappedfile + (DWORD)serviceTableAddr - (DWORD)smi->Base);

printf("%x:%x\n",serviceTable[0],fileServiceTable[0]);

printf("%x:%x\n",serviceTable[0] - (DWORD)smi->Base,fileServiceTable[0]);

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

{                                                                                 

if( (serviceTable[i] - (DWORD)smi->Base + 0x400000) != fileServiceTable[i])

{

printf("%-25s %3X at %X %x %x\n",nativeApiNames[i],i,serviceTable[i],serviceTable[i] - (DWORD)smi->Base + 0x400000,fileServiceTable[i]);

hookCount++;

}

}

printf("%u\n", hookCount);

}

 

sdtAddr=82480

smi->Base=804d7000 pAddr=559480

pAddr=559000 ptr=379000 len=2000

start=480 serviceTableAddr=804e26a8 sdtCount=284

pAddr=4056bf

pAddr=4e2000 start=6a8

805862de:4af2de

af2de:4af2de

ZwQueryDirectoryFile       91 at B21C3026 320ec026 49d4a5

ZwQuerySystemInformation   AD at B21C3000 320ec000 4a54aa

2

 

C:\sonal3>y -u

 

C:\sonal3>d

sdtAddr=82480

smi->Base=804d7000 pAddr=559480

pAddr=559000 ptr=379000 len=2000

start=480 serviceTableAddr=804e26a8 sdtCount=284

pAddr=4056bf

pAddr=4e2000 start=6a8

805862de:4af2de

af2de:4af2de

0

                                                                                 

DbgView output

DriverEntry1 b21c3026 b21c3000

KeServiceDescriptorTable=804e26a8 0 11c

Enteries 805862de 8056fded 8058945b

hooked functions NewZwQueryDirectoryFile=b21c3026 NewZwQuerySystemInformation=b21c3000

Unloading1

 

This program detects whether our SDT table has been hooked. The method GetModuleHandle assumes that a dll has been loaded in memory and  returns the starting address of that dll. In the case of Windows two dll’s are loaded in memory  nt.dll.dll and kernel32.dll. The starting address of nt.dll.dll in the case of Windows XP SP2 is 7c900000.

 

We then use the GetProcAddress  function to get the address of 4 functions which are present in nt.dll.dll. These are RtlInitUnicodeString,  NtOpenSection, NtMapViewOfSection and  ZwQuerySytemInformation. All these functions may reside in nt.dll.dll but there code is a stub to move to ring 0 where the actual code of the function actually is.

 

We now want to talk to our device driver called /device/physicalmemory whose job is to give us a virtual address in ring 3 so that we can write to physical memory in ring 0. Thus we will give this driver an RVA from 2GB and it will return to us a virtual address in ring 3. Each time we read or write to this virtual address we are actually reading or writing a address in ring 0.

 

All strings in Windows are now Unicode and hence we use the RtlInitUnicodeString method to give us a Unicode string in uStr which is \device\physicalmemory.

 

The method NtOpenSection is what we will use to open a handle to our device driver. The only problem is that ring 3 programs like ours cannot use this driver unless we say please. We first need permission to access this driver. Thus we call the method NtOpenSection with the address of our handle and then attributes we want on the open handle. Here we want to from read the driver as well as change the Discretionarily Access List to gives us permission denoted by the macro WRITE_DAC.

 

The third parameter is a structure of type OBJECT_ATTRIBUTES of which we always initialize the length member to the size of the structure. The attributes member is set to a value which means that the name is case insensitive and the ObjectName member to the Unicode structure physicalmemory. The hPhyMem handle now allows us to read and change the permissions on the driver.

 

The GetSecurityInfo method takes a device handle which represents a kernel object and gives us the security descriptor associated with that object as the last parameter. Now we want the user vijay to be granted access to write to the device. We first create a new ACL or access control list by using the function SetEntriesInAcl which merges the dacl and the new permissions in ea. Then we actually add this new acl using the SetSecurityInfo method to the device. This is how we can now open the handle to the device with write permissions.

 

A better way would for Microsoft to give write access for all devices to Administrator, would save a lot of our time.

 

The file ntoskrnl.exe which is loaded in ring 0 has an exported method called KeServiceDescriptorTable. This is pointer to a structure in ring 0. We scan the array of functions entry points and then scan the array of ordinal numbers. We use this index j to give us the name of the method. If it is KeServiceDescriptorTable we store the entry point in the variable stdAddr which is 82480. This means that this value is relative to the start of  ntoskrnl.exe. The next question is where does ntoskrnl.exe start in memory.

 

We first allocate an area of memory 50000 bytes using malloc and call ZwQuerySystemInformation with value of 11 which means list of modules loaded. The value returned in pBuffer will contain a series of back to back structures of type SYSTEM_MODULE_INFORMATION. These structures are preceded by a long that tells us number of structures present.

 

We take a shortcut and assume that the first structure is that of ntoskrnl and the base member tells us where this structure starts in memory. The other shortcut is that the area of memory should be arbitrary  fixed at 50000, we should call the function with a pBuffer of 0 which will then tells us whether the amount of memory specified as the size of the buffer as the third parameter is right or not. We do this in a loop until we get success and if not we increase this parameter by a certain value.

 

We are told that ntoskrnl.exe starts at 804d7000. Thus the KeServiceDescriptorTable start at 804d7000 + 82480. We will subtract 80000000 from this value to give us a RVA from 2 GB which is stored in  pAddr which gets a value of 559480. Thus we could have got the same value by adding 4d7000 and 82480.

 

Now starts the actual program. We call NtMapViewOfSection that takes a handle which in our case is to physicalmemory and then a pointer to a virtual address. This pointer ptr will contain a memory location which when we write to will actual write to a ring 0 address. We specify a LARGE_INTEGER to specify a physical address from the start of 2 GB.

 

The pAddr member contains a RVA of KeServiceDescriptorTable.  The len is passed twice once to specify the size of memory to be mapped and the second time an address to tell us how much memory has been mapped. The value of pAddr which was 559480 has been set to 559000 because even though we asked for a mapping of memory 559480 the system has returned a page boundary address which is 559000.

 

The first 12 bits of a page address is 0. Thus if we write to 379000 we are writing to 559480. This gives us a problem as we now have to account for some extra bytes. Ptr does not stand for KeServiceDescriptorTable but to the start of the page table that contains this value.

 

The sdtAddr tells us where KeServiceDescriptorTable starts 82480 which we cannot change at all and we add the difference from 2G where ntoskrnl.exe starts which gives us 4d7000. Thus this is a RVA which tells us where KeServiceDescriptorTable begins. We however have a problem as the value of pAddr has changed and hence we subtract this new value which gives us a start or a difference of 480. We have lost the old value of pAddr and hence we have to recalculate.

 

Now ptr is a pointer to the actual SSDT table provided we add the count offset of 480. Thus the array of function pointers begins at 804e26a8. The next member plus 1 contains  the actual number of functions which is stored in variable sdtCount  and has a value of 284.

 

 

The serviceTableAddr is the address of an array and we need to find a virtual memory pointer that we can use to reference this array from ring 3. We thus subtract 2 gb from this value to give us an RVA of 40567. We once again set len to the same value it has of 0x2000 and call the same MapView method. We are given a value of 4e2000 instead of the value we had asked for. We there fore subtract the original servicetable address from kernel base and also subtract the new page table address. This gives us a value of start of 6a8 which is the offset from the start of the page for the function pointer array.

 

Normally the VirtualAddress and PointerToRawData should be the same. If there is a difference than we have to subtract this difference all the time. Bytes 2,3 ,4 and 5 from the start of any function in nt.dll.dll contains the service numbers. This is value moved into eax in the prologue of the function. We need to create an array of function names using these service numbers and not the ordinal numbers. The service numbers have nothing to do with ordinal numbers. We iterate as per the number of functions, we then read the service numbers using the entry point of each function and we place the name of the function in the array using the service number. We need to make sure that the service number does not exceed the number pointed to by the KeServiceDescriptorTable.

 

Finally we come to the countdown. The serviceTable member is initialized to the start of the function pointer array using the ptr variable with start as the offset. The ntoskrnl.exe resides in ring 0 and serviceTableAddr - (DWORD)smi->Base will tell us where this array begins from the start of the file. Pmappedfile tells us where the file ntoskrnl starts from the beginning of memory. The problem is that KeServiceDescriptorTable contains the address of a structure and one of the members is the address of the array. This array is not exported and hence we need another way of getting at this address.

 

Thus serviceTable contains the actual pointer to the real sdt table whereas fileServiceTable is the pristine values from disk. The next problem is that the file values are numbers like 4af2de whereas the one in memory is 805862de. We thus subtract the kernel base from this to get a number af2de. We have to add 400000 to this value as all exe files under windows start at this memory location.

 

In DbgView we can see that the two functions that we have hooked in the driver have the same values in the output of our program b21c3026 b21c3000.

 

e.bat

del d.exe

del d.obj

cl d.cpp advapi32.lib

y -i

d

y -u

d

 

r.c

#include <ntddk.h>

#include <stdio.h>

#include <string.h>

typedef struct

{

            unsigned int *ServiceTableBase;

            unsigned int *ServiceCounterTableBase;

            unsigned int NumberOfServices;

            unsigned char *ParamTableBase;

} sdt;

__declspec(dllimport) sdt KeServiceDescriptorTable;

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

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;

}

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);

            return(rc);

}

long no,no1;

VOID DriverUnload(PDRIVER_OBJECT DriverObject)

{

            DbgPrint("Unloading1");

            _asm cli

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

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

            _asm sti

}

NTSTATUS DriverEntry(PDRIVER_OBJECT driverObject, PUNICODE_STRING RegistryPath)

{

            char *p;

DbgPrint("DriverEntry1 %x %x",NewZwQueryDirectoryFile,NewZwQuerySystemInformation);

            driverObject->DriverUnload = DriverUnload;

            p = (char *) ZwQueryDirectoryFile;

            p = p + 1;

            no = *(long *)p;

            p = (char *) ZwQuerySystemInformation;

            p = p + 1;

            no1 = *(long *)p;

            OldZwQuerySystemInformation= KeServiceDescriptorTable. ServiceTableBase [no1];

            DbgPrint("KeServiceDescriptorTable=%x %x %x",KeServiceDescriptorTable,KeServiceDescriptorTable.ServiceTableBase,&KeServiceDescriptorTable.ServiceTableBase);

            DbgPrint("Enteries %08x %08x %08x", KeServiceDescriptorTable. ServiceTableBase[0],KeServiceDescriptorTable.ServiceTableBase[1],KeServiceDescriptorTable. ServiceTableBase[2]);

            DbgPrint("hooked functions NewZwQueryDirectoryFile=%x NewZwQuerySystemInformation=%x",NewZwQueryDirectoryFile,NewZwQuerySystemInformation);

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

            _asm cli

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

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

            _asm sti

 

                        return(STATUS_SUCCESS);

}