Single Byte Hooks II

#include <ntddk.h>

extern PULONG *KeServiceDescriptorTable;

typedef NTSTATUS (NTAPI *NT_TERMINATE_PROCESS)(HANDLE ProcessHandle,NTSTATUS ExitStatus);

NTSTATUS NTAPI NtTerminateProcess(HANDLE ProcessHandle,NTSTATUS    ExitStatus);

typedef NTSTATUS (NTAPI *NT_CREATE_FILE)(PHANDLE             FileHandle,    IN  ACCESS_MASK         DesiredAccess,    IN  POBJECT_ATTRIBUTES  ObjectAttributes,    OUT PIO_STATUS_BLOCK    IoStatusBlock,    IN  PLARGE_INTEGER      AllocationSize, OPTIONAL    IN  ULONG               FileAttributes,    IN  ULONG               ShareAccess,    IN  ULONG               CreateDisposition,    IN  ULONG               CreateOptions,   IN  PVOID               EaBuffer, OPTIONAL    IN  ULONG               EaLength);

NTSTATUS NTAPI NtCreateFile(    OUT PHANDLE             FileHandle,    IN  ACCESS_MASK         DesiredAccess,    IN  POBJECT_ATTRIBUTES  ObjectAttributes,    OUT PIO_STATUS_BLOCK    IoStatusBlock,    IN  PLARGE_INTEGER      AllocationSize, OPTIONAL    IN  ULONG               FileAttributes,    IN  ULONG               ShareAccess,    IN  ULONG               CreateDisposition,    IN  ULONG               CreateOptions,    IN  PVOID               EaBuffer, OPTIONAL    IN  ULONG               EaLength);

typedef NTSTATUS (NTAPI *NT_OPEN_FILE)(    PHANDLE             FileHandle,    ACCESS_MASK         DesiredAccess,    POBJECT_ATTRIBUTES  ObjectAttributes,    PIO_STATUS_BLOCK    IoStatusBlock,    ULONG               ShareAccess,    ULONG               OpenOptions);

NTSTATUS NTAPI NtOpenFile(    PHANDLE             FileHandle,    ACCESS_MASK         DesiredAccess,    POBJECT_ATTRIBUTES  ObjectAttributes,    PIO_STATUS_BLOCK    IoStatusBlock,    ULONG               ShareAccess,    ULONG               OpenOptions);

typedef NTSTATUS (NTAPI *NT_CREATE_PROCESS)(    OUT PHANDLE         ProcessHandle,     IN  ACCESS_MASK     DesiredAccess,     IN  POBJECT_ATTRIBUTES ObjectAttributes, OPTIONAL    IN  HANDLE          ParentProcess,     IN  BOOLEAN         InheritObjectTable,     IN  HANDLE          SectionHandle, OPTIONAL    IN  HANDLE          DebugPort, OPTIONAL    IN  HANDLE          ExceptionPort OPTIONAL);

#define MAKELONG(a, b) ((unsigned long) (((unsigned short) (a)) | ((unsigned long) ((unsigned short) (b))) << 16))

NTSTATUS NTAPI NtTerminateProcessHook(PHANDLE ProcessHandle,NTSTATUS ExitStatus);

NTSTATUS NTAPI NtCreateFileHook(PHANDLE FileHandle,ACCESS_MASK DesiredAccess,POBJECT_ATTRIBUTES ObjectAttributes,

PIO_STATUS_BLOCK IoStatusBlock,PLARGE_INTEGER AllocationSize,ULONG FileAttributes,ULONG ShareAccess,

  ULONG CreateDisposition, ULONG CreateOptions, PVOID EaBuffer, ULONG EaLength);

NT_TERMINATE_PROCESS pNtTerminateProcess;

NT_CREATE_FILE pNtCreateFile;

PVOID pSavedIntHandler;

__declspec(naked) PVOID ByteHookIntHandler()

{

 __asm

{

    pop     eax

    add     esp,8

    sti

    cmp     eax,[pNtCreateFile]

    jnz     NotNtCreateFile

    jmp     [NtCreateFileHook]

NotNtCreateFile:

    cmp     eax,[pNtTerminateProcess]

    jnz     NotNtTerminateProcess

    jmp     [NtTerminateProcessHook]

NotNtTerminateProcess:

    jmp     [pSavedIntHandler]

}

}

#pragma pack(1)

struct IDTENTRY

{

unsigned short LowOffset , selector;

unsigned char unused_lo, segment_type:4,system_segment_flag:1,DPL:2,P:1;

unsigned short HiOffset;

} ;

struct IDTINFO

{

unsigned short IDTLimit;

unsigned short LowIDTbase;

unsigned short HiIDTbase;

};

#pragma pack()

NTSTATUS NTAPI NtTerminateProcessHook(PHANDLE ProcessHandle,NTSTATUS ExitStatus)

{

DbgPrint("NtTerminateProcessHook: %p killing handle %p\n", PsGetCurrentProcess(), ProcessHandle );

return pNtTerminateProcess( ProcessHandle,ExitStatus);

}

NTSTATUS NTAPI NtCreateFileHook(PHANDLE FileHandle, ACCESS_MASK DesiredAccess,POBJECT_ATTRIBUTES ObjectAttributes,

 PIO_STATUS_BLOCK IoStatusBlock,PLARGE_INTEGER AllocationSize,LONG FileAttributes,ULONG ShareAccess,

 ULONG CreateDisposition, ULONG CreateOptions, PVOID EaBuffer,ULONG EaLength)

{

DbgPrint( "NtCreateFileHook: %wZ\n", ObjectAttributes ? ObjectAttributes->ObjectName : NULL  );

return pNtCreateFile(FileHandle,DesiredAccess,ObjectAttributes,IoStatusBlock,AllocationSize,FileAttributes,ShareAccess,

                            CreateDisposition,CreateOptions,EaBuffer,EaLength );

}

void RtlCopyBytesProtected(PVOID DestBuffer,PVOID SrcBuffer,ULONG Length)

{

PMDL pMdl;

PUCHAR pBuffer;

pMdl = IoAllocateMdl( DestBuffer, Length, FALSE, FALSE, NULL );

MmBuildMdlForNonPagedPool( pMdl );

pBuffer = MmMapLockedPages( pMdl, KernelMode );

RtlCopyBytes( pBuffer, SrcBuffer, Length );

MmUnmapLockedPages( pBuffer, pMdl );

}

VOID DriverUnload(PDRIVER_OBJECT pDrvObj)

{

struct IDTINFO idt_info;

struct IDTENTRY *idt_entries;

PVOID pAddr = *(NT_CREATE_PROCESS*)(*KeServiceDescriptorTable+0x2f);

DbgPrint("Unload2");  

*((PUCHAR)pNtTerminateProcess-2) = 0x8b;

*((PUCHAR)pNtCreateFile-2) = 0x8b;

RtlCopyBytesProtected( (PUCHAR)pAddr-5, "\x90\x90\x90\x90\x90", 5 );

__asm    sidt idt_info

idt_entries = (struct IDTENTRY*) MAKELONG(idt_info.LowIDTbase,idt_info.HiIDTbase);

__asm cli

idt_entries[255].LowOffset = (unsigned short) pSavedIntHandler;

idt_entries[255].HiOffset = (unsigned short)((unsigned long)pSavedIntHandler >> 16);

__asm sti

}

NTSTATUS DriverEntry(PDRIVER_OBJECT pDrvObj,PUNICODE_STRING  pRegistry)

{

struct IDTINFO idt_info;

struct IDTENTRY *idt_entries,*i;

ULONG addr;

PVOID  pAddr = *(NT_CREATE_PROCESS*)(*KeServiceDescriptorTable+0x2f );

ULONG_PTR offset;

pDrvObj->DriverUnload = DriverUnload;

pNtTerminateProcess = *(NT_TERMINATE_PROCESS*)(*KeServiceDescriptorTable + 0x101);

pNtCreateFile = *(NT_CREATE_FILE*)(*KeServiceDescriptorTable + 0x25 );

DbgPrint("DriverEntry2 pAddr=%x pNtTerminateProcess=%x pNtCreateFile=%x",pAddr,pNtTerminateProcess,pNtCreateFile);

offset = (ULONG_PTR)ByteHookIntHandler - (ULONG_PTR)pAddr;

RtlCopyBytesProtected( (PUCHAR)pAddr-5,"\xe9",1);

RtlCopyBytesProtected((PUCHAR)pAddr-4,&offset,4);

__asm sidt idt_info

idt_entries = (struct IDTENTRY*) MAKELONG(idt_info.LowIDTbase,idt_info.HiIDTbase);

i = idt_entries + 0xff;

pSavedIntHandler = MAKELONG(i->LowOffset, i->HiOffset);

__asm cli

i->LowOffset = (unsigned short)((PUCHAR)pAddr-5) ;

i->HiOffset = (unsigned short)((unsigned long)((PUCHAR)pAddr-5)  >> 16);

__asm sti

(PUCHAR)pNtTerminateProcess = (PUCHAR)pNtTerminateProcess + 2;

(PUCHAR)pNtCreateFile = (PUCHAR)pNtCreateFile + 2;

*((PUCHAR)pNtTerminateProcess-2)= 0xCD;

*((PUCHAR)pNtCreateFile-2) = 0xCD;

return STATUS_SUCCESS;

}

 

The method NtCreateProcess has its entry at offset 2f in the SSDT array. Its begins at location 805ad314 in memory. Using the u command in WinDbg gives us the confirmation we need.

 

lkd> u nt!NtCreateProcess

nt!NtCreateProcess:

805ad314 8bff             mov     edi,edi

805ad316 55               push    ebp

805ad317 8bec             mov     ebp,esp

 

Thus pAddr gives us the address of NtCreateProcess and pNtTerminateProcess the address of NtTerminateProcess which is 80582c2b and pNtCreateFile the address of NtCreateFile which is

80570d48. Check it out yourselves. Thus we have the addresses of three functions in kernel memory.

 

Fortunately for us the 5 bytes before the code of NtCreateProcess starts has 5 NOP’s in them. This means that we can safely override these bytes as no one is using them. WinDbg confirms this.

 

lkd> db 805ad314-5

805ad30f  90 90 90 90 90 8b ff 55-8b ec 33

 

Thus we have found an area of memory that has some empty space and we can now use this to our advantage. In part 1 we showed how we calculated the offset of our function that we wanted to be called which in our case is ByteHookIntHandler. We use the same variable offset to tell us the difference between the start of ByteHookHandler and the starting address of NtCreateProcess. We write the jmp instruction on these 5 bytes using the same Rtl functions that are optional.

 

The key method is set_entry_idt which is passed the interrupt to be hooked 0xff and the starting point of this newly written jump instruction pAddr-5.

The method set_entry_idt is written in assembler but could have been written in C. We simply want to change the address that gets called on interrupt 2f. This is the only part of the code we rewrote in C. We first found the address of the IDT which is a series of structures and then placed the  address pAddr-5 at the location 255 from the start. We save the original value and when we unload the driver we place the original value back.

 

We then increase the two pointers to the method NtTerminateProcess and NtCreateFile by 2 and place 0xCd as the first byte. The opcode cd means call interrupt which becomes call interrupt FF as the second byte of every function is FF. Thus interrupt FF will get called each time we call these two functions. This in turn will call the jmp instruction at pAddr-5 which will call our hook method ByteHookIntHandler.

 

Now lets understand the handler method which calls one of the two functions. The calling convention is naked which means the compiler will generate no prologue or epilogue. This method is also called with no parameters whatsoever. When the user calls either of the two functions, int ff gets called which in turn calls the function ByteHookIntHandler. On the stack is the address of the function that is being called. This value we place in the eax register.

 

We then add 8 to esp because we have a 8 byte ret value on the stack. We compare this value in eax with the value of pNtCreateFile, if it ss equal we call the function NtCreateFileHook which in turn calls the original plus 2. If not we check whether eax is equal to pNtTerminateProcess, if true we jmp to the method NtTerminateProcessHook. If none is true we finally call the original handler.

 

This is how we use a one byte hook to beat the SSDT table check and yet get our code to be called. We change the first byte of our functions to CD and then get the same code to be called each time the interrupt occurs. We then figure which of the two functions were called and then jump to them.