Dangerous Detours, Part 3: Messing execution flow

Last time I showed that as a result of its implementation strategy, Detours is unable to properly instrument certain kinds of functions. Unlike last time, where this merely led to unexpected debugging output, we will see how this can easily lead to a crash.

Consider this code (error checking omitted):

#include <stdio.h>
#include <tchar.h>
#include <windows.h>
#include <detours.h>

static PDETOUR_TRAMPOLINE Trampoline;

__declspec(noinline)
static void DoSomething()
{
  wprintf( L"DoSomething\n" );
  Sleep( 100 );
}

//
// This is the function we want to instrument.
//
__declspec(noinline)
static void OriginalFunction( PCWSTR Arg1, LONG Arg2 )
{
  _asm xor eax, eax;
  for (;;)
  {
    DoSomething();
  }  
}

//
// The generic hook we will install using Detours.
//
__declspec(naked)
static void GenericHook()
{
  //
  // Do something -- not important what...
  //
  __asm pushad;
  wprintf( L"GenericHook\n" );
  __asm popad;
  
  //
  // Done with instrumentation code, continue with
  // the real function. The trampoline (created 
  // by Detours) contains the replaced instructions
  // and a jump to the body of the real function.
  //
  __asm jmp [Trampoline];
}

int wmain()
{
  ... Install hook -- same as last time ...
  
  //
  // Call the (now instrumented) function.
  //
  OriginalFunction( L"Hello", 42 );

  return 0;
}

The only difference to the code last time is the

_asm xor eax, eax;

inserted at the beginning of OriginalFunction. Note that any other instruction occupying less than 5 bytes should work just as well.

Try running this code and you will notice that the process immediately crashes with an Illegal Instruction exception. If you replace

_asm xor eax, eax;

by

_asm nop;

you get a Privileged instruction exception, which is no better. In any case, there is something going completely wrong here, so let us have a look at the assembly code again.

Unlike last time, where OriginalFunction consisted of a single basic block, we now have two blocks:

Flow Chart

The important thing to notice is that the first block is shorter than 5 bytes. This forces Detours to copy this block as well as the beginning of the following block to the trampoline in order to make place for the jump to the hook. After the hooking, the code looks like this:

OriginalFunction2:
00401031  jmp         GenericHook (40101Dh) 
00401036  int         3    		; Padding bytes inserted 
00401037  int         3    		; by Detours
00401038  jmp         OriginalFunction2+2 (401033h) 

Like last time, Detours fails to notice the short jump pointing back into the first 5 bytes. However, this time the consequences are worse — whereas last time the jump pointed to a jump instruction (the jump to the hook), this time the jump points to the third byte of the jump instruction! So after following the jump, the processor begins decoding at the wrong position in code and suddenly sees this:

OriginalFunction2:
00401031  db          e9h  
00401032  db          e7h  
00401033  db          ffh  ; <== EIP after following the jump
00401034  db          ffh  
00401035  dec         esp  
00401037  int         3    
00401038  jmp         OriginalFunction2+2 (401033h) 

Or, when using nop rather that xor:

OriginalFunction2:
00401031  db          e9h  
00401032  out         0FFh,eax ; <== EIP after following the jump
00401034  db          ffh  
00401035  dec         esp  
00401037  jmp         OriginalFunction2+1 (401032h) 

…which explains the exceptions.

This concludes the discussion of detouring functions with unusual prologs. However, there is another interesting catch regarding function un-hooking, which I will describe next time.

Advertisements

Categories




About me

Johannes Passing, M.Sc., living in Berlin, Germany.

Besides his consulting work, Johannes mainly focusses on Win32, COM, and NT kernel mode development, along with Java and .Net. He also is the author of cfix, a C/C++ unit testing framework for Win32 and NT kernel mode, Visual Assert, a Visual Studio Unit Testing-AddIn, and NTrace, a dynamic function boundary tracing toolkit for Windows NT/x86 kernel/user mode code.

Contact Johannes: jpassing (at) acm org

Johannes' GPG fingerprint is BBB1 1769 B82D CD07 D90A 57E8 9FE1 D441 F7A0 1BB1.

LinkedIn Profile
Xing Profile
Github Profile

%d bloggers like this: