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.

Advertisement

0 Responses to “Dangerous Detours, Part 3: Messing execution flow”



  1. Leave a Comment

Leave a Reply

Fill in your details below or click an icon to log in:

Gravatar
WordPress.com Logo

Please log in to WordPress.com to post a comment to your blog.

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Connecting to %s




Categories

Try Visual Assert, the unit testing add-in for Visual Studio (R)


NTrace: Function Boundary Tracing for Windows on IA-32

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 LinkedIn Profile
Xing Xing Profile
Twitter Follow me on Twitter (new)

Follow

Get every new post delivered to your Inbox.