You are on page 1of 14

* Author:

Nishad P. Herath No 339, Samupakara Mawatha, Nawagamuwa South, Rannala, Sri Lanka. 94-1-74-443901 <YouCan_ReachMe@hotmail.com> <Joey__ / d3m_m1k / Andarae> on <EFNET / UnderNet>

* Version: 00.80 - 10.08.1998 - Applicable to Windows NT v4.0 SP3 ( Server / WorkStation ). 00.81 - 10.10.1998 - Minor typographic modifications and fixes. (c) 1998, Nishad P. Herath, All Rights Reserved. INFORMATION PROVIDED IN THIS DOCUMENT IS PROVIDED AS IS WITHOUT WARRANTY OF ANY KI ND, EITHER EXPRESS OR IMPLIED, FITNESS FOR A PARTICULAR PURPOSE, AND FREEDOM FROM INFRINGEM ENT. THE USER ASSUMES THE ENTIRE RISK AS TO THE ACCURACY AND THE USE OF THIS DOCUMENT.

ADDING NEW SERVICES TO THE WINDOWS NT KERNEL ( NATIVE API ) ON INTEL 80X86 PROCESSORS ----------------------------------------------------------Windows NT provides a largely undocumented set of base system services, called the Native API which is somewhat similar to the interrupt based system call interface prese nt in the UNIX operating systems. These kernel-mode base system services are used by the operat ing environment subsystems like Win32, Posix and OS2 ( Windows NT is a modified micro-kernel arc hitecture operating system ) for the implementation of their operating environments, on to p of the Windows NT micro-kernel. Under Win32 user-mode, access to this Native API is implemented via the exported functions of NTDLL.DLL ( as extensively used by KERNEL32.DLL, where som e of it's exported functions are nothing but direct forwards into NTDLL.DLL functions ). H owever the Native API, accessible both in user-mode and kernel-mode, in every operating env ironment subsystem is really implemented via an INT 2EH system trap, called the Native Ca ll Interface ( NCI ). Native API functions NTDLL.DLL provide are just a set wrappers to this NCI. The NCI Dispatcher on Windows NT is implemented in the micro-kernel itse lf ( NTOSKRNL.EXE ) in the form of an NCI Dispatcher ( _KiSystemService ) which is the INT 2EH handler. The NCI dispatcher locates the appropriate handler for a service using the System Service Descriptor Table ( SSDT ) and calls it with the parameters passed on via the NCI. ( It is intersting to note that IMHO the best kernel mode debugger for Windows NT, Nu

Mega Soft-ICE knows about the SSDT and can display it via the NTCALL command ). There is NO DO CUMENTED METHOD to extend this Native API with our own kernel-mode services that can be called v ia the NCI as far as I know. The objective of this document is to provide a workaround to this problem. But before that, let's take a very brief look into using the Native API services via the NCI, or in general using any NCI service. If you take a look at NTDLL.DLL, you will see exactly how the Native API is call ed via the NCI. Look at the following assembly language snippet.... mov eax, dwServiceID . ( In Windows NT vices implemented in the micro-kernel and their IDs are from 0 x00 to 0xd3. There are 519 additional services callable via NCI, implemented in the WIN32K.SYS which provide the kernel-m ode Graphical User Interface primitives, IDs ranging fro m 0x1000 to 0x1206. But these services are only accessible fr om user-mode! NCI allows this via separate SSDT structures for kernel-mode and user-mode, which I will discuss in de pth later. As for the somewhat odd, non-contiguos allocatio n of service IDs, it will mov edx, lpParameterStack we pass the calling. int 2eh r via INT 2EH system trap. mov dwRetVal, eax r. The status STATUS type and on success the status code is STATUS_SUC CESS. * lpParameterStack is a pointer to the first parameter ( First parameter defined as the leftmost parameter in a C-style function declaration ), on the stack, as param eters are pushed in the C ( __cdecl ) calling convention, right to left. If we look at t he stack.... [STACK] . ( increasing ESP ) . . be explained later in the document. ) <- Pointer to the parameter stack* through which parameters to the NCI service we are <- Pass the control to the NCI Dispatche <- The service returns the status in EAX registe codes are defined in the NT DDK as NT <- ID of the NCI service we want to call v4.0 SP3 there are 212 Native API ser

dwParam2 dwParam1 dwParam0 to the first parameter, . stack. ( decreasing ESP ) . .

<- lpParameterStack points here, which is pushed last onto the

The following is a sample C wrapper function to access an NCI service which take s 3 parameters.... typedef DWORD NTSTATUS; e NT DDK. __stdcall NTSTATUS CallNCIServiceWith3Parameters dwParam0, DWORD dwParam1, DWORD dwParam2 ) { void **lpParameterStack = &dwParam0; the parameter . _asm { mov eax, dwServiceID vice ID. mov edx, lpParameterStack parameter stack. int 2eh Dispatcher system trap. } } The following is a C template NCI service implementation ( within a kernel-mode driver using the NT DDK ) which takes 3 parameters... NTSTATUS NCIServiceWith3Parameters , dwParam1, DWORD dwParam2 ) { . . . ( the function body goes here ) . . . return STATUS_SUCCESS; e NT DDK. } ( DWORD dwParam0 DWORD // Call the NCI // EDX -> to the // EAX = NCI ser // Create the pointer to // stack // defined in th ( DWORD dwServiceID, DWORD

// Defined in th

Now let's get to the real subject matter of this document, adding our own kernel -mode services to the Windows NT Native API.... As I was learning about the Windows NT Native API and the NCI sometime back, I w as always driven by the curiosity as to whether or not the Native API or rather the NCI is extensible. Because if it was indeed extensible, then it can provide a very simple interface to access custom kernel-mode code from any mode under any operating environment subsystem. Besides the idea of actually being able to extend the Native API with custom services was a very attractive one. So I set about looking for a technique to achieve this very goal and I was wondering whether or not NT uses such a technique itself to add additional Native API serv ices into the kernel. Upon initial investigation it turned out that there are 212 NCI services ( I work with NT v4.0 SP3 ) which is the Native API, available upon the starting of the microkernel and they are implemented in the micro-kernel itself. But NT 4.0 brought the GUI primitive s from user-mode ( NT 3.51 ) to kernel-mode in order to increase performance and as such they hav e made them accessible to all user-mode operating environment subsystems ( to be used in an X-Windows server under Posix subsytem for example ). Now how did they acheive that ? How else, vi a the NCI! So the kernel-mode GUI primitives implemented in WIN32K.SYS must be extending the N CI and after seing all those INT 2EH instructions scattered around in USER32.DLL and GDI32.DL L I had no doubt... I was on my way! ( Turns out upon initialization, WIN32K.SYS adds 519 s ervices to the NCI, accessible only from user-mode ). The following is what I discovered resear ching along those lines for a couple of days. There are 2 System Service Descriptor Tables ( SSDTs ) which are fundamental in handling Native API calls. The first SSDT is the _KeServiceDescriptorTable, which is exported as KeServiceDescriptorTable by NTOSKRNL.EXE. This SSDT is the one used if the NCI s ervice request came from kernel-mode. The second SSDT is _KeServiceDescriptorTableShadow, which is used if the NCI service request came from user-mode. Each of these SSDTs comprise of 4 S ystem Service Descriptors ( SSDs ). An SSDT has the following structure; typedef struct SystemServiceDescriptorTable { SSD SystemServiceDescriptors[4]; rray of 4 SSDs. } SSDT, *LPSSDT;

// The a

An SSD records the attributes of a single System Service Table ( SST ). An SSD h as the following structure; typedef struct SystemServiceDescriptor { LPSSTAT lpSystemServiceTableAddressTable; e Address Table // ( SSTAT ) structure of the SST. BOOL bUnknown; Always set to FALSE. DWORD dwSystemServiceTableNumEntries; the SST. LPSSTPT lpSystemServiceTableParameterTable; ter Table // ( SSTPT ) structure of the SST. } SSD, *LPSSD; Where System Service Table Address Table ( SSTAT ) and System Service Table Para meter Table ( SSTPT ) have the following structures; typedef VOID *SSTAT[]; handler addresses typedef BYTE SSTPT[]; e of the parameter in the SST. And pointers LPSSTAT and LPSSTPT are of the following types; typedef SSTAT *LPSSTAT; typedef SSTPT *LPSSTPT; // LPSSTAT is a pointer to an SSTAT. // LPSSTPT is a pointer to an SSTPT. // SSTAT is an array of pointers to the service // of each service entry in the SST. // SSTPT is an array of bytes containing the siz // stack in bytes for each service entry

// Pointer to th

// ( ? ) // Number of entries in // Pointer to the Parame

In plain english, both SSDTs, _KeServiceDescriptorTable and _KeServiceDescriptor TableShadow contain 4 SSDs each. Each one of these SSDs in turn contain attributes which def ine a single SST , which are; * lpSystemServiceTableAddressTable, which is a pointer to an array of dwSystemServiceTableNumEntries number of pointers to functions that implement each service in the SST. * bUnknown, which is set to FALSE. So far I have only witnessed FALSE for this f ield. But there are couple places in the micro-kernel where it checks this field for TRUE. If someone knows anything more about this field, please let me know as I am too lazy to persue this further. * dwSystemServiceTableNumEntries, which is the number of services in the SST ( M aximum allowed is 0x00001000 as will become evident later ). * lpSystemServiceTableParameterTable, which is a pointer to an array of

dwSystemServiceTableNumEntries number of bytes which contain the size of the p arameter stack in bytes, for each service in the SST. A maximum value of 0xff here implies th at the maximum number of DWORD parameters that can be passed to a Native API service is 0x3f. The Native Call Interface ( NCI ) Dispatcher _KiSystemService uses both the SSDT structures to locate the service handler functions and the number of parameters the functions take, for a given service ID ( dwServiceID ). The kernel-mode SSDT _KeServiceDescriptorTable contains only one valid SSD ( 0 ) by default and it represents the SST which is for the Native API. The user-mode SSDT _KeServiceDescriptorTableShadow, by default contains 2 valid SSDs and SSD ( 0 ) represents the SST which is for the Native API while SSD ( 1 ) represents the SS T which is for the kernel-mode GUI primitives services. So this means by default NT allows acce ss to the Native API in both user-mode and kernel-mode while it only allows access to the kernel-mode GUI primitive services only in user-mode, via the NCI. Now lets take a closer look a t the NCI dispatcher.... in psuedo code; _KiSystemService { . . ( Intialization code ) . . if ( called from kernel-mode ) then lpSSDT = &_KeServiceDescriptorTable l-mode SSDT. else lpSSDT = &_KeServiceDescriptorTableShadow; mode SSDT. dwSystemServiceTable = ( EAX & 0x00003000 ) >> 0x0c; ndex. dwSystemServiceID = ( EAX & 0x00000fff ); he service ID index. // Get t // Use the kerne // Use the user// Get the SST i

lpSST = &( lpSSDT->SystemServiceDescriptors[dwSystemServiceTable] ); // Choose the appropriate SST. if ( dwSystemServiceID < lpSST->dwSystemServiceTableNumEntries ) { if ( dwSystemServiceTable == 1 ) // Kerne l-mode GUI primitive // service requested. {

( call function via _KeGdiFlushUserBatch pointer, which is setup by _PsEstablishWin32Callouts, exported as PsEstablishWin3 2Callouts by NTOSKRNL.EXE ) // Out of the scope of this document // I didn't really see into these // functions... But if anyone has // information regarding these // functions please let me know. } lpServiceAddress = lpSST->lpSystemServiceTableAddressTable[dwSys temServiceID]; // Retrieve the service handler // function address. dwParameterStackSize = lpSST->lpSystemServiceTableParameterTable [dwSystemServiceID]; // Retreive the parameter stack // size in bytes. memcpy ( lpServiceStack, EDX, dwParameterStackSize ); // Copy the parameters onto the // stack we are gonna use to call // the service handler function. ( call the function pointed to by lpServiceAddress with the stack area pointed to by lpServiceStack as the stack. ) } else goto _KiEndUnexpectedRange ! Invalid service ID. . . ( Clean-Up code ) . . } Above code solves the mistery of non-contiguos NCI service IDs. dwServiceID is f ormulated with the bits 12 & 13 forming the SSD index within the SSDT and the least significant 12 bits ( bits 0 - 11 ) forming the service index within the selected SST. Thats why we can have only 0x00001000 services per SST ( 12-bits don't allow for more! ). // Error

Turns out that there is a function in the micro-kernel called _KeAddSystemServic eTable which is exported as KeAddSystemServiceTable by NTOSKRNL.EXE. It is used by WIN32K.SYS to add the kernel-mode GUI primitive services to the NCI upon it's initialization, at SSD ( 1 ) within the user-mode SSDT. This function call was not very stable initially.. but now in SP 3 it has become stable. ( If you think otherwise please let me know. ) This function however doe s not let us alter SSDs which are already allocated, in either one of the SSDTs. But since by default only SSD ( 0 ) is allocated in the kernel-mode SSDT and and only SSDs ( 0 ) and ( 1 ) are allocated within the user-mode SSDT, we can add our own SSTs at SSDs ( 2 ) and ( 3 ). Note that this function adds SSTs at SSDs ( 0 ), ( 2 ) and ( 3 ) to both SSDTs due to which, th e NCI services we add via our SSTs will be available in both user-mode and kernel-mode. Further more due to this we can't have different SSTs for the same SSD in the two SSDTs, to implemen t different NCI services to kernel-mode and user-mode. The function returns TRUE on success, FAL SE on failure. in psuedo code; __stdcall BOOL _KeAddSystemServiceTable ( LPSSTAT er to the SSTAT // structure of the SST. BOOL // Unknown. Always set // to FALSE. If you have // any information // regarding this please // let me know. DWORD dwNumEntries, // Number of entries in // the SST. LPSSTPT terTable, // Pointer to the SSTPT // structure of the SST. DWORD dwTableID ) // Index of the SSD to // add the SST to. { if ( dwTableID <= 3 ) // We have only 4 SSDs per SSDT. { if ( ( _KeServiceDescriptorTable[dwTableID].lpSystemServiceTable AddressTable \ == 0 ) && lpParame bUnknown, lpAddressTable, // Point

( _KeServiceDescriptorTableShadow[dwTableID].lpSystemServiceT ableAddressTable \ == 0 ) ) sure the requested SSD in both // SSDTs are not allocated. { // // Setup the user-mode SSDT. // _KeServiceDescriptorTableShadow[dwTableID].lpSystemServi ceTableAddressTable = \ lpAddressTable; _KeServiceDescriptorTableShadow[dwTableID].lpSystemServi ceTableParameterTable = \ lpParameterTable; _KeServiceDescriptorTableShadow[dwTableID].bUnknown = \ bUnknown; _KeServiceDescriptorTableShadow[dwTableID].dwSystemServi ceTableNumEntries = \ dwNumEntries; // // Setup the kernel-mode SSDT if SSD index is 0, 2 or 3. // if ( dwTableID != 1 ) { _KeServiceDescriptorTable[dwTableID].lpSystemSer viceTableAddressTable = \ lpAddressTable; _KeServiceDescriptorTable[dwTableID].lpSystemSer viceTableParameterTable = \ lpParameterTable; _KeServiceDescriptorTable[dwTableID].bUnknown = \ bUnknown; _KeServiceDescriptorTable[dwTableID].dwSystemSer viceTableNumEntries = \ dwNumEntries; } return true; // Success. } } else return false; // Failure. } Let us assume that we have 3 services we need to add to the NCI, first one takin g no parameters, second one taking 4 DWORD parameters and the third taking 6 DWORD parameters. So we have in psuedo code; NTSTATUS NCIService0 ( void ) { . ( the function body goes here ) . return STATUS_SUCCESS; } NTSTATUS NCIService1 ( DWORD dwParam0, // Make

DWORD dwParam1, DWORD dwParam2, DWORD dwParam3 ) { . ( the function body goes here ) . return STATUS_SUCCESS; } NTSTATUS NCIService2 ( DWORD dwParam0, DWORD dwParam1, DWORD dwParam2, DWORD dwParam3, DWORD dwParam4, DWORD dwParam5 ) { . ( the function body goes here ) . return STATUS_SUCCESS; } Now let's create SSTAT and SSTPT structures for our internal use... VOID *mySSTAT[3]; BYTE mySSTPT[3]; mySSTAT[0] mySSTPT[0] mySSTAT[1] mySSTPT[1] mySSTAT[2] mySSTPT[2] = = = = = = ( void * ) &NCIService0; 0x00; ( void * ) &NCIService1; 0x10; ( void * ) &NCIService2; 0x18;

Now lets use _KeAddSystemServiceTable function to add our services to the NCI at SSD ( 2 ) in both SSDTs... Note that KeAddSystemServiceTable, ExFreePool and ExAllocatePoolWi thTag are exports from the micro-kernel, NTOSKRNL.EXE. LPSSTAT lpMySSTAT = NULL; LPSSTPT lpMySSTPT = NULL; // // Allocate a memory pool for the SSTAT structure. // if ( !( lpMySSTAT = ExAllocatePoolWithTag ( 1, 3 * 0x04,( DWORD )'Ddk ' ) ) ) { . ( Error occured... couldn't allocate a memory pool for the SSTAT structu re! ) . } // // Allocate a memory pool for the SSTPT structure. // if ( !( lpMySSTPT = ExAllocatePoolWithTag ( 1, 3,( DWORD )'Ddk ' ) ) ) { .

( Error occured... couldn't allocate a memory pool for the SSTPT structu re! ) . ExFreePool ( lpMySSTAT ); } // // Copy our internal SSTAT and SSTPT structures to the memory pools we allocated . // if ( !( memcpy ( lpMySSTAT, &mySSTAT, 3 * 0x04 ) ) || !( memcpy ( lpMySSTPT, &mySSTPT, 3 ) ) ) { . ( Error occured... couldn't copy internal structures to the allocated me mory pools! ) . ExFreePool ( lpMySSTPT ); ExFreePool ( lpMySSTAT ); } // // Add the services to the NCI at SSD ( 2 ) in both SSDTs. // if ( ! ( KeAddSystemServiceTable ( lpMySTTAT, false, 3, lpMySTTPT, 2 ) ) ) { . ( Error occured... couldn't add SST to both SSDTs at SSD ( 2 )! ) . } If we want to have different SSTs for user-mode and kernel-mode for the same SSD number or or want to add more services to an existing SST or want to have an SST either on ly for kernel-mode or only for user-mode, we need to look beyond the above function and alter the SSDT structures ourselves. But therein lies a problem. SSDTs are possibly not lo cated at the same memory address in all SP/Hotfix versions of the NTOSKRNL.EXE. Accessing the kernel-mode SSDT is not a problem because it is exported as KeServiceDescriptorTable by NTOS KRNL.EXE. But how do we access the user-mode SSDT _KeServiceDescriptorTableShadow ? Well I couldn't think of an answer which will work on all versions of NTOSKRNL.EXE ( If you can, please let me know ). But as far as NT v4.0 SP3 is concerned _KeServiceDescriptorTableShadow S SDT is located 0x00000230 bytes below the start of the _KeServiceDescriptorTable SSDT which we can locate. Now lets manually alter the SSDTs to add our services as true Native API extensi ons, SSD ( 0 ) services within both SSDTs, to the NCI. Let our services have IDs from 0x100 to 0x102, leaving ample room for the NT Native API. Note that KeServiceDescriptorTable, ExFreePool and ExAllocatePoolWithTag are exports from the micro-kernel, NTOSKRNL.EXE. LPSSDT lpKeServiceDescriptorTable = NULL; LPSSDT lpKeServiceDescriptorTableShadow = NULL; LPSSTAT lpMySSTAT = NULL;

LPSSTPT lpMySSTPT = NULL; LPSSTAT lpNtSSTAT = NULL; LPSSTPT lpNtSSTPT = NULL; lpKeServiceDescriptorTable = &KeServiceDescriptorTable; // -> Kernel-mode SSDT. lpKeServiceDescriptorTableShadow = &KeServiceDescriptorTable - 0x230; // -> Us er-mode SSDT. lpNtSSTAT = \ lpKeServiceDescriptorTable->SystemServiceDescriptors[0].lpSystemServiceTableAdd ressTable; // Get -> NT kernel-mode // native API SSTAT. lpNtSSTPT = \ lpKeServiceDescriptorTable->SystemServiceDescriptors[0].lpSystemServiceTablePar ameterTable; // Get -> NT kernel-mode // native API SSTPT. // native API SSTPT. // // Allocate a memory pool for the SSTAT structure. // if ( !( lpMySSTAT = ExAllocatePoolWithTag ( 1, ( 3 + 0x100 ) * 0x04,( DWORD )'Dd k ' ) ) ) { . ( Error occured... couldn't allocate a memory pool for the SSTAT structu re! ) . } // // Allocate a memory pool for the SSTPT structure. // if ( !( lpMySSTPT = ExAllocatePoolWithTag ( 1, ( 3 + 0x100 ),( DWORD )'Ddk ' ) ) ) { . ( Error occured... couldn't allocate a memory pool for the SSTPT structu re! ) . ExFreePool ( lpMySSTAT ); } // // Copy NT Native API SSTAT and SSTPT structures to the memory pools we allocate d. // if ( !( memcpy ( lpMySSTAT, lpNtSSTAT, 0x100 * 0x04 ) ) || !( memcpy ( lpMySSTPT, lpNtSSTPT, 0x100 ) ) ) { . ( Error occured... couldn't copy NT Native API structures to the allocat ed memory pools! ) . ExFreePool ( lpMySSTPT ); ExFreePool ( lpMySSTAT );

} // // Copy our internal SSTAT and SSTPT structures to the memory pools we allocated . // if ( !( memcpy ( lpMySSTAT + ( 0x100 * 0x04 ), &mySSTAT, 3 * 0x04 ) ) || !( memcpy ( lpMySSTPT + 0x100, &mySSTPT, 3 ) ) ) { . ( Error occured... couldn't copy internal structures to the allocated me mory pools! ) . ExFreePool ( lpMySSTPT ); ExFreePool ( lpMySSTAT ); } // // Add the new SST into the both SSDTs at SSD ( 0 ). // lpKeServiceDescriptorTable->SystemServiceDescriptors[0].lpSystemServiceTablePara meterTable = \ lpMySSTPT; lpKeServiceDescriptorTableShadow->SystemServiceDescriptors[0].lpSystemServiceTab leParameterTable = \ lpMySSTPT; lpKeServiceDescriptorTable->SystemServiceDescriptors[0].dwSystemServiceTableNumE ntries = 0x103; lpKeServiceDescriptorTableShadow->SystemServiceDescriptors[0].dwSystemServiceTab leNumEntries = \ 0x103; lpKeServiceDescriptorTable->SystemServiceDescriptors[0].bUnknown = false; lpKeServiceDescriptorTableShadow->SystemServiceDescriptors[0].bUnknown = false; lpKeServiceDescriptorTable->SystemServiceDescriptors[0].lpSystemServiceTableAddr essTable = \ lpMySSTAT; lpKeServiceDescriptorTableShadow->SystemServiceDescriptors[0].lpSystemServiceTab leAddressTable = \ lpMySSTAT; OKAY..... thats it. After 72 hours of being awake I cant think straight anymore. .. I could have provided some working source code with this document but believe it or not I don 't even have the Windows NT DDK. THERE MAY BE MANY NUMBER OF ERRORS IN THIS DOCUMENT, LET IT BE IN THE TEXTUAL CO NTENT, PSUEDO CODE OR THE INFORMATIONAL CONTENT. PLEASE DON'T HESITATE TO FIX THEM AND SEND ME THE REVISED VERSION OF THE DOCUMENT. I WILL REVIEW IT AND RE-PUBLISH IT WITH THE DUE CREDITS FOR REVISIONS. IF YOU ARE INTERESTED IN SHARING / DISCUSSING NT HARDCORE TECHNICAL INFORMATION, ADVANCED WIN32 PLATFORM DEVELOPMENT ISSUES OR COMPUTER NETWORK / SOFTWARE SECURITY ISSUES WITH ME, PLEASE DON'T HESITATE TO CONTACT ME. IF YOU ARE INTERESTED IN RE-PUBLISHING / USING THE INFORMATION I PRESENT IN THIS DOCUMENT, YOU CAN BE A NICE GUY AND LET ME KNOW ABOUT IT. I HOPE YOU WONT PLAGIARISE THE INFORMATION I PRESENT HERE. I AM A 20 YEAR OLD HIGH SCHOOL DROP-O UT FROM A THIRD

WORLD COUNTRY CALLED SRI LANKA, WITH NO FORMAL EDUCATION IN THE IT FIELD. SO BAR E WITH ME IF I DON'T SOUND / CODE / WRITE LIKE A BIG SHOT IT PROFESSIONAL.