C++ implementation of inline hook principle and application examples

  • 2020-04-02 02:33:48
  • OfStack

This paper briefly describes the principle and application of inline hook in C++, which is helpful for us to better understand the principle and application of inline hook. The specific contents are as follows:

I. Inline Hook

1.INLINE HOOK principle:

Inline hooks by hard-coded to the memory space of the kernel apis (usually start a byte, and the general before the first call, do this is to prevent stack chaos) write jump statements, in this way, the API, as long as the called program will jump to our function, in writing your own functions we need to complete three tasks:

1) readjust the current stack. At the beginning of the jump, the kernel API was not finished executing, and our function needed to filter the information according to its results, so we needed to ensure that the kernel API could return to our function after the smooth execution, which required an adjustment to the current stack.

2) execute the lost command. When we give the kernel API address space some jump instructions (JMP XXXXXXXX), it is bound to overwrite some Of the original assembly instructions, so we must ensure that these overwritten instructions can be executed smoothly (otherwise, your and will be BSOD, ha ha, Blue Screen Of Death). As for the execution of this part of the instruction, we generally put it in our function, let our function "help" the kernel API execute the overwritten instruction, and then jump back to the overwritten address in the kernel API to continue the rest of the execution. When you jump back, make sure you figure out what address you're jumping back to, which byte is the starting address of the kernel API.

3) information filtering. This need not say more, the kernel API smoothly executed and returned to our function, we will naturally according to its results to do some information filtering, this part of the content is different according to the hook API and hook purpose.

2. Workflow of Inline hook:

1) verify the version of the kernel API (feature code matching).

2) write your own function to accomplish the above three tasks.

3) get the address of their function, overwrite the kernel API memory, for jump.

In short, the principle of inlinehook is to modify the function to jump to the place we specified.

Common have change function entry, also have change function end, function in the middle
For example, the usual assembly code at the beginning of a function is: mov edi,edi; Push the esp. Mov ebp,esp, and we can HOOK by modifying here.

2. Sample code (the sample is from see snow)


#include <ntifs.h>
#include <windef.h>
ULONG g_KiInsertQueueApc;
ULONG g_uCr0;
BYTE g_HookCode[5] = { 0xe9, 0, 0, 0, 0 }; //JMP NEAR
BYTE g_OrigCode[5] = { 0 }; //The first byte of the original function
BYTE jmp_orig_code[7] = { 0xEA, 0, 0, 0, 0, 0x08, 0x00 }; //JMP FAR
BOOL g_bHooked = FALSE;
VOID
fake_KiInsertQueueApc (
            PKAPC Apc,
            KPRIORITY Increment
            );
VOID
Proxy_KiInsertQueueApc (
            PKAPC Apc,
            KPRIORITY Increment
            );
void WPOFF()
{
  ULONG uAttr;
  _asm
  {
    push eax;
    mov eax, cr0;
    mov uAttr, eax;
    and eax, 0FFFEFFFFh; // CR0 16 BIT = 0
    mov cr0, eax;
    pop eax;
    cli
  };
  g_uCr0 = uAttr; //Save the original CRO contents
}
VOID WPON()
{
  _asm
  {
    sti
      push eax;
    mov eax, g_uCr0; //Restore the original CR0
    mov cr0, eax;
    pop eax;
  };
}
//
//Stop the inline hooks
//
VOID UnHookKiInsertQueueApc ()
{
  KIRQL oldIrql;
  WPOFF();
  oldIrql = KeRaiseIrqlToDpcLevel();
  RtlCopyMemory ( (BYTE*)g_KiInsertQueueApc, g_OrigCode, 5 );
  KeLowerIrql(oldIrql);
  WPON();
  g_bHooked = FALSE;
}
//
//Start inline hook -- KiInsertQueueApc
//
VOID HookKiInsertQueueApc ()
{
  KIRQL oldIrql;
  if (g_KiInsertQueueApc == 0) {
    DbgPrint("KiInsertQueueApc == NULLn");
    return;
  }
  //DbgPrint(" start inline hook -- KiInsertQueueApcn");
  DbgPrint( "KiInsertQueueApc The address of the t0x%08xn", (ULONG)g_KiInsertQueueApc );
  DbgPrint( "fake_KiInsertQueueApc The address of the t0x%08xn", (ULONG)fake_KiInsertQueueApc );
  
  //Saves the first byte of the original function
  RtlCopyMemory (g_OrigCode, (BYTE*)g_KiInsertQueueApc, 5);
  //JMP instruction, short hop here, calculate relative offset, at the same time, JMP XXXXXX this instruction occupies 5 bytes
  *( (ULONG*)(g_HookCode + 1) ) = (ULONG)fake_KiInsertQueueApc - (ULONG)g_KiInsertQueueApc - 5;
  //Disable system write protection and promote IRQL to DPC
  WPOFF();
  oldIrql = KeRaiseIrqlToDpcLevel();
  RtlCopyMemory ( (BYTE*)g_KiInsertQueueApc, g_HookCode, 5 );
  *( (ULONG*)(jmp_orig_code + 1) ) = (ULONG) ( (BYTE*)g_KiInsertQueueApc + 5 );
  RtlCopyMemory ( (BYTE*)Proxy_KiInsertQueueApc, g_OrigCode, 5);
  RtlCopyMemory ( (BYTE*)Proxy_KiInsertQueueApc + 5, jmp_orig_code, 7);
  //Restore write protection and reduce IRQL
  KeLowerIrql(oldIrql);
  WPON();
  g_bHooked = TRUE;
}
//
//Jump to our function for preprocessing, bare function, with caller for stack balancing
//
__declspec (naked)
VOID
fake_KiInsertQueueApc (
            PKAPC Apc,
            KPRIORITY Increment
            )
{
  //Remove DbgPrint, otherwise this hook will generate recursion
  //DbgPrint (" inline hook - KiInsertQueueApc successful  n ");
  __asm
  {
    jmp Proxy_KiInsertQueueApc
  }
}
//
//The proxy function is responsible for jumping to the original function to continue execution
//
__declspec (naked)
VOID
Proxy_KiInsertQueueApc (
            PKAPC Apc,
            KPRIORITY Increment
            )
{
  __asm { //A total of bytes
    _emit 0x90
      _emit 0x90
      _emit 0x90
      _emit 0x90
      _emit 0x90 //The preceding byte implements the function's header function
      _emit 0x90 //This fills the JMP
      _emit 0x90
      _emit 0x90
      _emit 0x90
      _emit 0x90 //This byte holds the address at the function +5
      _emit 0x90 
      _emit 0x90 //Since it is a long transition, it must be 0x0080
  }
}
ULONG GetFunctionAddr( IN PCWSTR FunctionName)
{
  UNICODE_STRING UniCodeFunctionName;
  RtlInitUnicodeString( &UniCodeFunctionName, FunctionName );
  return (ULONG)MmGetSystemRoutineAddress( &UniCodeFunctionName ); 
}
//KiInsertQueueApc is searched from KeInsertQueueApc search based on the eigenvalues
ULONG FindKiInsertQueueApcAddress()
{
  char * Addr_KeInsertQueueApc = 0;
  int i = 0;
  char Findcode[] = { 0xE8, 0xcc, 0x29, 0x00, 0x00 };
  ULONG Addr_KiInsertQueueApc = 0;
  Addr_KeInsertQueueApc = (char *) GetFunctionAddr(L"KeInsertQueueApc");
  for(i = 0; i < 100; i ++)
  {
    if( Addr_KeInsertQueueApc[i] == Findcode[0] &&
      Addr_KeInsertQueueApc[i + 1] == Findcode[1] &&
      Addr_KeInsertQueueApc[i + 2] == Findcode[2] &&
      Addr_KeInsertQueueApc[i + 3] == Findcode[3] &&
      Addr_KeInsertQueueApc[i + 4] == Findcode[4]
    )
    {
      Addr_KiInsertQueueApc = (ULONG)&Addr_KeInsertQueueApc[i] + 0x29cc + 5;
      break;
    }
  }
  return Addr_KiInsertQueueApc;
}
VOID OnUnload( IN PDRIVER_OBJECT DriverObject )
{
  DbgPrint("My Driver Unloaded!");
  UnHookKiInsertQueueApc();
}
NTSTATUS DriverEntry( IN PDRIVER_OBJECT theDriverObject, IN PUNICODE_STRING theRegistryPath )
{
  DbgPrint("My Driver Loaded!");
  theDriverObject->DriverUnload = OnUnload;
  g_KiInsertQueueApc = FindKiInsertQueueApcAddress();
  HookKiInsertQueueApc();
  return STATUS_SUCCESS;
}

Related articles: