Monday, September 24, 2012

Base Relocation Demystified

///////////////////////////////////////////////////////////////////////////////

Base Relocation Demystified

by cawan - cawan[at]ieee.org (2005)

///////////////////////////////////////////////////////////////////////////////

Info:

The paper will discuss the mechanism of base relocation in detail. The reason
that I write this paper is to explain the method in finding the KiServiceTable
by checking requests to KeServiceDescriptorTable through relocation
information. The idea was firstly introduced by 90210 and the assembly
implementation was presented by Piotr Bania. Besides, Tan Chew Keong has
adopted this technique in his SDTrestore (Version 0.2) in finding the
KiServiceTable. I would like to express my special thanks to 90210,
Piotr Bania, and Tan Chew Keong for their excellent codes in order to inspire
my desire to look into the details of base relocation.

///////////////////////////////////////////////////////////////////////////////

Table of Contents:

1. EXE vs DLL
2. IMAGE_DATA_DIRECTORY and Data Blocks of Base Relocation Table
3. IMAGE_FIXUP_ENTRY of Base Relocation Table
4. Dumping SDT From ntoskrnl.exe

///////////////////////////////////////////////////////////////////////////////

1. EXE vs DLL

I am not attempt to explain what is PE format since there are quite a number of
good literatures are available by utilizing Google [1,2]. The attribute of base
relocation is initialized from the Characteristics field (16 bits) in
IMAGE_FILE_HEADER to determine,

1. Either the relocation information is stripped or not (Bit 0)
2. Either the file is dll or not (bit 13)

For the first case, if the bit is set, there is no relocation information in
the file. Hence, base relocation is not available in this type of file. For the
second case, if the bit is set, the PE loader will treat the file as DLL.
Otherwise, it is treated as EXE. Now, the question is what\'s the difference
between the file is treated as DLL or EXE? Despite all the others discussion
which is not related to the base relocation, the answer is the immediate
address call in EXE is relative to the module handle, instead of relative
to the ImageBase in DLL. A sample format for immediate address call instruction
is shown below:-

call dword ptr [xxxxxxxx]

Let us have an examples with ntoskrnl.exe to show the appropriate definition.
By going through the paper of \"Exploiting Windows Device Drivers\" by Piotr
Bania, we intend to get the address of KeUserModeCallback_Routine where is
called by an instruction in KeUserModeCallback. The KeUserModeCallback is
exported in the PE\'s export table. So, it can be located by using
GetProcAddress. In order to start our experiments about base relocation, the
ntoskrnl.exe is duplicated from c:\\winnt\\system32\\ into c:\\ for two identical
copies and rename as ntoskrnl1.exe and ntoskrnl2.exe, respectively.
Then, both the files are loaded into memory by using LoadLibraryExA in
sequence to observe the action of base relocation. Both the module handles
must not be same since they are loaded in different portion of memory space.
According to Piotr Bania, ntoskrnl.exe should has an immediate address call
instruction in KeUserModeCallback. Thus, each of the loaded module should
comprises the instruction where the calling address is our interest in this
section. Let us look into the following codes.

#include
#include

int main(void)
{
HANDLE libhandlea,prochandlea,calladdra;
HANDLE libhandleb,prochandleb,calladdrb;

libhandlea=LoadLibraryExA(\"c:\\\\ntoskrnl1.exe\",0,1);
if(libhandlea==0)
return 0;

prochandlea=GetProcAddress((HINSTANCE)libhandlea,\"KeUserModeCallback\");
if(prochandlea==0)
return 0;

__asm
{
push edi
push eax
mov edi,prochandlea
scan_calla:
inc edi
cmp word ptr [edi],015ffh
jne scan_calla

mov eax,[edi+2]
mov calladdra,eax
pop eax
pop edi
}

printf(\"\\nlibhandle(ntoskrnl1.exe): %.8x\",libhandlea);
printf(\"\\nprochandle(KeUserModeCallback): %.8x\", prochandlea);
printf(\"\\ncalladdrA: %.8x\", calladdra);
printf(\"\\n\");

libhandleb=LoadLibraryExA(\"c:\\\\ntoskrnl2.exe\",0,1);
if(libhandleb==0)
return 0;

prochandleb=GetProcAddress((HINSTANCE)libhandleb,\"KeUserModeCallback\");
if(prochandleb==0)
return 0;

__asm
{
push edi
push eax
mov edi,prochandleb
scan_callb:
inc edi
cmp word ptr [edi],015ffh
jne scan_callb

mov eax,[edi+2]
mov calladdrb,eax
pop eax
pop edi
}

printf(\"\\nlibhandle(ntoskrnl2.exe): %.8x\",libhandleb);
printf(\"\\nprochandle(KeUserModeCallback): %.8x\", prochandleb);
printf(\"\\ncalladdrB: %.8x\", calladdrb);
printf(\"\\n\");

FreeLibrary((HINSTANCE)libhandlea);
FreeLibrary((HINSTANCE)libhandleb);

return 0;
}

The codes will search from KeUserModeCallback for appropriate instruction and
locate the calling addrees. Before running the codes, it is important to
identify the Characteristics field in IMAGE_FILE_HEADER first. By default,
both of the files are having 0x030E where the base relocation is NOT stripped
and it will be treated as EXE. The output is shown below.

libhandle(ntoskrnl1.exe): 00440000
prochandle(KeUserModeCallback): 00470e62
calladdrA: 00485500

libhandle(ntoskrnl2.exe): 005f0000
prochandle(KeUserModeCallback): 00620e62
calladdrB: 00485500

From the output, we know that the first image (ntoskrnl1.exe) is loaded at
0x00440000 and the second image is loaded at 0x005f0000. The reason why the
first image is not loaded at the ImageBase of PE (00400000) is because it
has been taken by the main process. Besides, each of the KeUserModeCallback
handle is relative to the respective module handle. However, both of the
calling addresses (calladdrA and calladdrB) are unchanged although their
module and KeUserModeCallback handles are located at different location.
The reason is an EXE will not expected to be loaded within a process. Instead,
it is normally initializing a process and locates itself at ImageBase. While
the EXEs are loaded within a process, both the calladdrA and calladdrB are
in fact relative to ImageBase. So, their RVA should be

0x00485500 - 0x00400000 = 0x00085500
---------- ---------- ----------
calladdr ImageBase RVA

To start our first experiment, let us modify the Characteristics field of
each file in order to \"cheat\" the loader to treat the EXE as DLL and observe
their calling addresses again. So, we change the their Characteristics field
from 0x030E to 0x210E and run the code again. The output is shown below.

libhandle(ntoskrnl1.exe): 00440000
prochandle(KeUserModeCallback): 00470e62
calladdrA: 004c5500

libhandle(ntoskrnl2.exe): 005f0000
prochandle(KeUserModeCallback): 00620e62
calladdrB: 00675500

From the output, we know that the module and KiUserModeCallback handles of each
file are not affected either they are loaded as EXE or DLL, as expected.
However, their respective calling address has been changed! The calladdrA and
calladdrB are changed into 0x004C5500 and 0x00675500, respectively. Let us
calculate their RVAs of calling address.

0x004c5500 - 0x00440000 = 0x00085500 //RVA of calladdrA
0x00675500 - 0x005f0000 = 0x00085500 //RVA of calladdrB
---------- ---------- ----------
calladdr ModuleHandle RVA

Obviously, while the files are treated as DLL, the calling address is relative
to the module handle, but not the ImageBase. Base relocation table will keep
a record to track each of such calling address in order to perform appropriate
modification while an executable (EXE or DLL) is getting loaded. We will
discuss the detail of base relocation table in the following sections. As an
additional annotation, the filename extention will never affect the loader in
order to treat the file as EXE or DLL. In other words, if we rename the
ntoskrnl.exe into ntoskrnl.dll, the loader will still identify the file as
ntoskrnl.exe, but not ntoskrnl.dll, as expected. Let us look into base
relocation table now.

///////////////////////////////////////////////////////////////////////////////

2. IMAGE_DATA_DIRECTORY and Data Blocks of Base Relocation Table

Base relocation table is pointed by BaseRelocate in IMAGE_DATA_DIRECTORY, which
is at 0xA0 from IMAGE_FILE_HEADER. The BaseRelocate is expressed in RVA, so it
has to add with module handle in order to point at base relocation table. The
following codes show how the base relocation table can be located.

#include
#include

int main(void)
{
HANDLE syshandle;
UINT imagebase,basereloc_rva,basereloc_va;
syshandle=LoadLibraryExA(\"c:\\\\ntoskrnl1.exe\",0,1);
if (syshandle==0)
return 0;

__asm
{
push eax
push ebx

mov eax,syshandle
mov ebx,[eax+0x3c]
mov ebx,[eax+ebx+0x34]
mov imagebase,ebx
mov ebx,[eax+0x3c]
mov ebx,[eax+ebx+0xa0]
mov basereloc_rva,ebx

pop ebx
pop eax
}

basereloc_va=(UINT)syshandle+basereloc_rva;

printf(\"\\nsyshandle: %.8x\",syshandle);
printf(\"\\nimagebase: %.8x\",imagebase);
printf(\"\\nbasereloc_rva: %.8x\",basereloc_rva);
printf(\"\\nbasereloc_va: %.8x\",basereloc_va);
printf(\"\\n\");

FreeLibrary((HMODULE)syshandle);

return 0;

}

The output is shown below.

syshandle: 00440000
imagebase: 00400000
basereloc_rva: 00193e80
basereloc_va: 005d3e80

From the output, we know that the BaseRelocate is 0x00193e80 in RVA. Since the
module handle is 0x00440000, then the address of base relocation table should be

0x00440000 + 0x00193e80 = 0x005d3e80
---------- ---------- ----------
ModuleHandle BaseRelocate BaseRelocationTable

Now, let us look into the memory space where the base relocation table is
resided.

005D3E80 00 00 00 00 64 01 00 00 ....d...
005D3E88 E4 36 E8 36 F0 36 F4 36 ?¤6?¨6?°6?′6
005D3E90 FC 36 00 37 08 37 0C 37 ??6.7.7.7
005D3E98 14 37 18 37 20 37 24 37 .7.7 7$7
005D3EA0 2C 37 30 37 40 37 50 37 ,707@7P7
005D3EA8 5C 37 60 37 6C 37 70 37 \\7`7l7p7

The first 8 bytes of the table are known as IMAGE_BASE_RELOCATION block and is
defined as follow.

typedef struct _IMAGE_BASE_RELOCATION {
DWORD VirtualAddress;
DWORD SizeOfBlock;
} IMAGE_BASE_RELOCATION;

The first 4 bytes is defined as VirtualAddress or also known as allocated
memory block in RVA. The following 4 bytes is SizeOfBlock where indicate the
size of the allocated memory block in unit of byte. The size of
IMAGE_BASE_RELOCATION block is included in the SizeOfBlock. After the
8 bytes of IMAGE_BASE_RELOCATION block, this is the start of the entry of
base relocation table. Records of locations getting modified by loader are
located here. Each record takes 2 bytes and it is defined as IMAGE_FIXUP_ENTRY.
The definition of IMAGE_FIXUP_ENTRY is shown below.

typedef struct {
WORD offset:12;
WORD type:4;
} IMAGE_FIXUP_ENTRY;

By referring to the memory space of base relocation table above, the first
IMAGE_FIXUP_ENTRY is started from 0x005D3E88 until the end of the allocated
memory block. In general, the base relocation table comprises a number of
allocated memory block. In order to locate the starting address of the second
memory block in base relocation table, the address of the first
IMAGE_FIXUP_ENTRY plus the size of the memory block should be the exact
location. However, the SizeOfBlock given in IMAGE_BASE_RELOCATION (0x00000164)
has included the size of the IMAGE_BASE_RELOCATION block. Hence, the
SizeOfBlock should be deducted by the size of IMAGE_BASE_RELOCATION block
before adding to the address of the first IMAGE_FIXUP_ENTRY in order to obtain
the starting address of the second memory block. The starting address of the
second memory block should be

(Address of first IMAGE_FIXUP_ENTRY)+(SizeOfBlock-sizeof(IMAGE_BASE_RELOCATION))
=0x005D3E88+(0x00000164-8)
=0x005D3FE4

Let us have a look to the memory space of 0x005D3FE4.

005D3FCC 6C 3F 70 3F 78 3F 7C 3F l?p?x?|?
005D3FD4 84 3F 88 3F 90 3F 94 3F a€?????.?a€??
005D3FDC 9C 3F A0 3F AC 3F B0 3F ?“????????°?
005D3FE4 00 10 00 00 70 02 00 00 ....p...
005D3FEC 4C 30 50 30 58 30 5C 30 L0P0X0\\0
005D3FF4 64 30 68 30 74 30 78 30 d0h0t0x0

From the given memory view, we know that the first memory block is ended at
0x005D3FE3 where the last IMAGE_FIXUP_ENTRY is 0x3FB0. The second memory block
is started at 0x005D3FE4, exactly. The VirtualAddress and SizeOfBlock of the
second memory block are 0x00001000 and 0x00000270, respectively. Similarly, the
address of the first IMAGE_FIXUP_ENTRY in the second memory block is at
0x005D3FEC. With the same calculation, the starting address of the third memory
block of base relocation table is

0x005D3FEC+(0x00000270-8)=0x005D4254

Again, the respective memory space is shown below.

005D4234 D0 3D D8 3D DC 3D E4 3D ??=??=??=?¤=
005D423C E8 3D F4 3D F8 3D 04 3E ?¨=?′=??=.>
005D4244 08 3E 14 3E 18 3E 24 3E .>.>.>$>
005D424C 28 3E 34 3E 38 3E 00 00 (>4>8>..
005D4254 00 20 00 00 1C 00 00 00 . ......
005D425C 74 31 78 31 80 31 84 31 t1x1a??1a€?1

Again, with the same calculation, the starting address of the fourth memory
block is

0x005D425C+(0x0000001C-8)= 0x005D4270

The memory view of 0x005D4270 is shown below.

005D4258 1C 00 00 00 74 31 78 31 ....t1x1
005D4260 80 31 84 31 90 31 9C 31 a??1a€?1.1?“1
005D4268 A0 31 A8 31 AC 31 00 00 ??1?¨1??1..
005D4270 00 50 00 00 F8 02 00 00 .P..??...
005D4278 CC 34 D0 34 DC 34 E0 34 ??4??4??4??4
005D4280 EC 34 F0 34 F8 34 FC 34 ??4?°4??4??4

As a summary of observation to the four allocated memory block of base
relocation table, there is nothing special in IMAGE_BASE_RELOCATION block.
However, it is interesting to identify that starting from the second memory
block of base relocation table, each of the memory block is ended with
0x0000 (2 bytes), excluding the first memory block. In next section, the
IMAGE_FIXUP_ENTRY will be discussed in detail.

///////////////////////////////////////////////////////////////////////////////

3. IMAGE_FIXUP_ENTRY of Base Relocation Table

As mentioned in the previous section, IMAGE_FIXUP_ENTRY is defined as follow.

typedef struct {
WORD offset:12;
WORD type:4;
} IMAGE_FIXUP_ENTRY;

Each of the IMAGE_FIXUP_ENTRY takes 16 bits (2 bytes) where the first 12 bits
represent the offset to the location which is getting modified by the loader.
On the other hand, the remaining 4 bits represent the base relocation type.
For ntoskrnl.exe, the base relocation type should be 3(IMAGE_REL_BASED_HIGHLOW)
which means the location getting modified is located at an address where the
offset is summated with ImageBase and allocated memory block. Since we use
LoadLibraryExA to load the ntoskrnl.exe from the main program, it is impossible
for the ntoskrnl.exe to be loaded at its Imagebase (0x00400000) because it is
already taken by the main program. So, the offset should be summated with
module handle, instead of ImageBase, associated with allocated memory block
in order to identify the exact location getting modified. As reminder, the
allocated memory block is represented by the VirtualAddress in
IMAGE_BASE_RELOCATION block. In order to have better visualization about
IMAGE_FIXUP_ENTRY, let us have an example. We run the codes in the previous
section and look into the memory at 0x005D3E80 (starting address of the base
relocation table) again.

005D3E80 00 00 00 00 64 01 00 00 ....d...
005D3E88 E4 36 E8 36 F0 36 F4 36 ?¤6?¨6?°6?′6
005D3E90 FC 36 00 37 08 37 0C 37 ??6.7.7.7
005D3E98 14 37 18 37 20 37 24 37 .7.7 7$7
005D3EA0 2C 37 30 37 40 37 50 37 ,707@7P7
005D3EA8 5C 37 60 37 6C 37 70 37 \\7`7l7p7
005D3EB0 80 37 90 37 98 37 9C 37 a??7.7??7?“7
005D3EB8 A8 37 B8 37 C8 37 D8 37 ?¨7??7??7??7

The first IMAGE_FIXUP_ENTRY located at 0x005D3E88 is 0x36E4. The following
IMAGE_FIXUP_ENTRY are 0x36E8, 0x36F0, 0x36F4, and so on. By referring to the
definition of IMAGE_FIXUP_ENTRY, those the 0x6E4, 0x6E8, 0x6F0, 0x6F4 are
offsets of the summation of module handle and allocated memory block, which
represent the exact locations getting modified. Hence, those the locations
can be calculated as below.

0x6E4+0x00440000+0x00000000=0x004406E4
0x6E8+0x00440000+0x00000000=0x004406E8
0x6F0+0x00440000+0x00000000=0x004406F0
0x6F4+0x00440000+0x00000000=0x004406F4
----- ---------- ---------- ----------
A B C D

where,

A = offset of IMAGE_FIXUP_ENTRY
B = Module Handle
C = VirtualAddress of IMAGE_BASE_RELOCATION
D = Location to be Modified by Loader

In order to have more understanding about the base relocation table, let us
have an interesting example with ntoskrnl.exe again. In section 1, we already
know that there is an instruction of immediate address call in
KeUserModeCallback. So, we will first identify the calling address and the
caller location of the instruction by getting into KeUserModeCallback with
GetProcAddress and search for the instruction of immediate address call.
The call instruction that is being used is 0xff15. The following codes show
the implementation process.

#include
#include

int main(void)
{

HANDLE syshandle,prochandle;
UINT calladdr,loc_calladdr;

syshandle=LoadLibraryExA(\"c:\\\\ntoskrnl1.exe\",0,1);
if (syshandle==0)
return 0;

prochandle=GetProcAddress((HINSTANCE)syshandle,\"KeUserModeCallback\");
if (prochandle==0)
return 0;

__asm
{
push esi
push eax

mov esi,prochandle
scan_call:
inc esi
cmp word ptr [esi],015ffh
jne scan_call
mov eax,esi
add eax,2
mov loc_calladdr,eax
mov eax,[esi+2]
mov calladdr,eax

pop eax
pop esi

}

printf(\"\\nsyshandle: %.8x\",syshandle);
printf(\"\\nprochandle: %.8x\",prochandle);
printf(\"\\ncalladdr: %.8x\",calladdr);
printf(\"\\nloc_calladdr: %.8x\",loc_calladdr);
printf(\"\\n\");

FreeLibrary((HMODULE)syshandle);

return 0;
}

The output is shown below.

syshandle: 00440000
prochandle: 00470e62
calladdr: 00485500
loc_calladdr: 00470f22

From the output, we know that at address 0x00470f22, there is an address that
will be called by instruction 0xff15, and the address is 0x00485500. Of course,
one of the records in base relocation table must point to 0x00470f22, and this
address must contain 0x00485500. So, let us try to find this record in base
relocation table. The following codes show the implementation process.

#include
#include


struct fixupblock
{
unsigned long pagerva;
unsigned long blocksize;
};

int main(void)
{
HANDLE syshandle,prochandle;
UINT calladdr;
UINT imagebase,basereloc_rva,basereloc_va;

syshandle=LoadLibraryExA(\"c:\\\\ntoskrnl1.exe\",0,1);
if (syshandle==0)
return 0;

prochandle=GetProcAddress((HMODULE)syshandle,\"KeUserModeCallback\");
if (prochandle==0)
return 0;

__asm
{
push eax
push ebx

mov eax,syshandle
mov ebx,[eax+0x3c]
mov ebx,[eax+ebx+0x34]
mov imagebase,ebx
mov ebx,[eax+0x3c]
mov ebx,[eax+ebx+0xa0]
mov basereloc_rva,ebx

mov eax,prochandle
scan_call:
inc eax
cmp word ptr [eax],015ffh
jne scan_call
mov ebx,[eax+2]
mov calladdr,ebx

pop ebx
pop eax
}

basereloc_va=(UINT)syshandle+basereloc_rva;

fixupblock *fixblk=(fixupblock *)basereloc_va;
while(fixblk->blocksize)
{
int numentry=(fixblk->blocksize-sizeof(fixupblock))>>1;
unsigned short *offsetptr=(unsigned short *)(fixblk+1);
for(int i=0;i {
int reloctype=(*offsetptr&0xf000)>>12;
if(reloctype==3)
{
UINT *codeloc=(UINT *)((UINT)syshandle+fixblk->pagerva+(*offsetptr&0x0fff));
if(*codeloc==calladdr && *(WORD *)((UINT)codeloc-2)==0x15ff)
{
printf(\"\\ngot it !!\");

printf(\"\\ncalladdr from baserelocate: %.8x\",*(UINT *)codeloc);
printf(\"\\nloc_calladdr from baserelocate: %.8x\",(UINT)codeloc);

printf(\"\\n\");
FreeLibrary((HMODULE)syshandle);
return 0;
}
}
offsetptr++;
}
fixblk=(fixupblock *)offsetptr;
}

printf(\"\\nError...\");
printf(\"\\n\");

FreeLibrary((HMODULE)syshandle);

return 0;
}

The output is shown below.

got it !!
calladdr from baserelocate: 00485500
loc_calladdr from baserelocate: 00470f22

Well, both of the calling address and caller location are identical regardless
the methods being used to locate at the instruction in KeUserModeCallback. So,
I wish the interpretation so far is sufficient to guide all the readers to have
a clear picture to the mechanism of base relocation. In next section, we will
have a complete example regarding how to dump KiServiceTable or SDT from
ntoskrnl.exe by locating the entry through the base relocation table

///////////////////////////////////////////////////////////////////////////////

4. Dumping SDT From ntoskrnl.exe

The following code is implemented to dump KiServiceTable (SDT) from
ntodkrnl.exe file.

#include
#include

typedef struct
{
ULONG Reserved[2];
PVOID Base;
ULONG Size;
ULONG Flag;
USHORT Index;
USHORT Unknown;
USHORT LoadCount;
USHORT ModuleNameOffset;
CHAR ImageName[256];
}SYSTEM_MODULE_INFORMATION,*PSYSTEM_MODULE_INFORMATION;

typedef (WINAPI *ntquery) (UINT,PVOID,ULONG,PULONG);

ntquery NtQuerySystemInformation;

struct fixupblock
{
unsigned long pagerva;
unsigned long blocksize;
};

int main(void)
{
HANDLE syshandle,prochandle;
UINT imagebase,basereloc_rva,basereloc_va;

syshandle=LoadLibraryExA(\"c:\\\\ntoskrnl1.exe\",0,1);
if (syshandle==0)
return 0;

prochandle=GetProcAddress((HMODULE)syshandle,\"KeServiceDescriptorTable\");
if (prochandle==0)
return 0;

UINT offset=(UINT)prochandle-(UINT)syshandle;

PSYSTEM_MODULE_INFORMATION pSysModuleInfo;
LONG status;
ULONG retsize;
PVOID pBuffer;
pBuffer=LocalAlloc(LPTR,4);
if(pBuffer==0)
{
printf(\"\\nLocalAlloc error...\");
return 0;
}

NtQuerySystemInformation=(ntquery)GetProcAddress(GetModuleHandle(\"ntdll\"),
\"NtQuerySystemInformation\");

if(!NtQuerySystemInformation)
return 0;

status=NtQuerySystemInformation(11,pBuffer,4,&retsize);
if(status==0xC0000004)
{
LocalFree(pBuffer);
pBuffer=LocalAlloc(LPTR,retsize);
if(pBuffer==0)
{
printf(\"\\nLocalAlloc error...\");
return 0;
}

status=NtQuerySystemInformation(11,pBuffer,retsize,&retsize);
if(status!=NO_ERROR)
{
printf(\"\\nNtQuerySystemInformation error...\");
return 0;
}
}

pSysModuleInfo=(PSYSTEM_MODULE_INFORMATION)((ULONG)pBuffer+4);

UINT kernelbase=(UINT)pSysModuleInfo->Base;

__asm
{
push eax
push ebx

mov eax,syshandle
mov ebx,[eax+0x3c]
mov ebx,[eax+ebx+0x34]
mov imagebase,ebx
mov ebx,[eax+0x3c]
mov ebx,[eax+ebx+0xa0]
mov basereloc_rva,ebx

pop ebx
pop eax
}

basereloc_va=(UINT)syshandle+basereloc_rva;

fixupblock *fixblk=(fixupblock *)basereloc_va;
while(fixblk->blocksize)
{
int numentry=(fixblk->blocksize-sizeof(fixupblock))>>1;
unsigned short *offsetptr=(unsigned short *)(fixblk+1);
for(int i=0;i {
int reloctype=(*offsetptr&0xf000)>>12;
if(reloctype==3)
{
UINT *codeloc=(UINT *)((UINT)syshandle+fixblk->pagerva+(*offsetptr&0x0fff));
if(*codeloc==offset+imagebase && *(WORD *)((UINT)codeloc-2)==0x05c7)
{
printf(\"\\ngot it !!\");
printf(\"\\ncode: %.4x\",*(WORD *)((UINT)codeloc-2));
printf(\"\\ndesaddr: %.8x\",*(UINT *)codeloc);
printf(\"\\nsourceaddr: %.8x\",*(UINT *)((UINT)codeloc+4));
UINT sdtoffset=*(UINT *)((UINT)codeloc+4)-imagebase;
UINT sdtaddr=sdtoffset+(UINT)syshandle;
UINT sdtentry;

printf(\"\\nDumping SDT...\\n\");

for (int j=0;j<16 br="br" j="j">{
sdtentry=*(UINT *)((UINT *)sdtaddr+j);
printf(\"\\n%x\",sdtentry-imagebase+kernelbase);
}

printf(\"\\n\");
FreeLibrary((HMODULE)syshandle);
return 0;
}
}
offsetptr++;
}
fixblk=(fixupblock *)offsetptr;
}

printf(\"\\nError...\");
printf(\"\\n\");
printf(\"\\n\");

FreeLibrary((HMODULE)syshandle);

return 0;
}

The output is shown below.

got it !!
code: 05c7
desaddr: 00484e00
sourceaddr: 00476730
Dumping SDT...

804ca37a
804fecea
80501b5e
804fed14
80501b94
8045eba6
80501bd4
80501c14
80499fd8
804fabe0
804fa7c0
804e9e5c
804e9e12
8049a480
8044d670
80499c6e

The output shows 16 entries of SDT getting dumped out from ntoskrnl.exe,
exactly. To verify the entries are accurate, let us check the SDT with windbg.

kd> dd keservicedescriptortable
80484e00 80476730 00000000 000000f8 80476b14
80484e10 00000000 00000000 00000000 00000000
80484e20 00000000 00000000 00000000 00000000
80484e30 00000000 00000000 00000000 00000000
80484e40 00000000 00000000 00000000 00000000
80484e50 00000000 00000000 00000000 00000000
80484e60 ffdff120 85e9b120 00000000 00000000
80484e70 00000000 00000000 00000000 00000000

kd> dd 80476730
80476730 804ca37a 804fecea 80501b5e 804fed14
80476740 80501b94 8045eba6 80501bd4 80501c14
80476750 80499fd8 804fabe0 804fa7c0 804e9e5c
80476760 804e9e12 8049a480 8044d670 80499c6e
80476770 804ce49a 804d4ef4 804ea220 804337f8
80476780 804a55b4 80417f90 804c92d6 80494084
80476790 804517f6 805020f0 804cabda beb223bd
804767a0 8046b834 804de1dc 804940c8 8049b9ce

Since the entries shown in windbg are identical with the dumped output from
ntoskrnl.exe, so the result is confirmed.

///////////////////////////////////////////////////////////////////////////////

References

[1] LUEVELSMEYER, \"The PE File Format\"

[2] Microsoft, \"winnt.h\"

[3] Piotr Bania, \"Exploiting Windows Device Drivers\"

[4] 90210, \"A More Stable Way To Locate Real KiService Table\"

[5] Piotr Bania, \"Finding KiServiceTable In More Stable Way\"

[6] Tan Chew Keong, \"Defeating Kernel Native API Hookers By Direct Service
Dispatch Table Restoration\"

[7] Matt Pietrek, \"An In-Depth Look Into The Win32 Portable Executable File
Format\"

[8] Matt Pietrek, \"An In-Depth Look Into The Win32 Portable Executable File
Format, Part 2\"

///////////////////////////////////////////////////////////////////////////////