Download GenericDX.7z

00:06 Posted by Unknown
Download GenericDX.7z

Surprisingly I haven't found any existing information about this type of hooking and decided to implement it myself. The whole idea I believe belongs to Indy(Clerk), but there wasn't any code back then.

The point of this approach is to destroy some pointer value then catch generated exception and trace code up to target function. Full tracing can add significant performance hit, so trace engine skips function calls by invalidating return address upon entering each new function and waiting for exception on function return.

Pros:
- No code or page protection modifications. Even pointers in the heap can be used
- Harder to detect than other methods
- Hooking point may belong to virtually any function involved in target function execution path

Cons:
- Hard to implement
- Each hook must be carefully analyzed beforehand
- Can add noticeable performance hit

Also my implementation has multithreading issues and I still haven't found stable way to distinguish different hook tracing paths in exception handler.



Example:

I took Text3D.exe sample from directx SDK to demonstrate how it all works. Target functions are BeginScene and EndScene.

Application execution flow:
DXUTRender3DEnvironment9->OnFrameRender->BeginScene...EndScene.

Although it is possible to hook functions directly from OnFrameRender, I've chosen to destroy pointer to OnFrameMove for the sake of demonstration.

DXUTRender3DEnvironment9 fragment:
PHP Code:
 
v15 = GetDXUTState();
  
v40 = DXUTState::GetFrameMoveFunc(v15);
  if ( !
v40
    
|| (v16 = GetDXUTState(),
        
v17 = DXUTState::GetFrameMoveFuncUserContext(v16),
        ((
void (__stdcall *)(_DWORD, _DWORD, _DWORD, int))v40)(LODWORD(v37), HIDWORD(v37), LODWORD(v39), v17),  // OnFrameMove
        
(v1 = DXUTGetD3D9Device()) != 0) )
  {
    
v18 = GetDXUTState();
    if ( !(
unsigned __int8)DXUTState::GetRenderingPaused(v18) )
    {
      
v19 = GetDXUTState();
      
v40 = DXUTState::GetD3D9FrameRenderFunc(v19);
      if ( 
v40 )
      {
        
v20 = GetDXUTState();
        
v21 = DXUTState::GetD3D9FrameRenderFuncUserContext(v20);
        ((
void (__stdcall *)(int, _DWORD, _DWORD, _DWORD, int))v40)(v1, LODWORD(v37), HIDWORD(v37), LODWORD(v39), v21);  // OnFrameRender   
 
 
 After pointer to OnFrameMove is destroyed exception will occur upon execution of invalid address. Tracing path will be the following:

1. Return from OnFrameMove
2. Single-step and skip function calls until OnFrameRender is invoked.
3. Single-step and skip function calls until BeginScene and EndScene are invoked.

Now to the code.

Hook installation (dllmain.cpp):
PHP Code:
 
#include "HookMgr.h"
using namespace hook;
#ifdef _M_AMD64
#define STORAGE_OFST 0x70EF8
#define HOOK_OFST    0x420//0x468
#define FUNC_PTR     0x47B58
#else
#define STORAGE_OFST 0x67F8C
#define HOOK_OFST    0x370//0x394
#define FUNC_PTR     0x45F73
#endif
typedef HRESULT( __stdcall* fnEndScene )(IDirect3DDevice9*); fnEndScene pEndScene = nullptr; fnEndScene pBeginScene = nullptr;
HRESULT __stdcall hkEndScene( IDirect3DDevice9* arg )
{
    
OutputDebugStringA( "hkEndScene called\n" );
    return 
pEndScene( arg );
}
HRESULT __stdcall hkBeginScene( IDirect3DDevice9* arg )
{
    
OutputDebugStringA( "hkBeginScene called\n" );
    return 
pBeginScene( arg );
}
void Hook()
{
    
uintptr_t hMain = (uintptr_t)GetModuleHandleW( NULL );
    
uint8_t* pDxStorage = *(uint8_t**)(hMain + STORAGE_OFST);
    
uintptr_t* pDxDevice = *(uintptr_t**)(pDxStorage + sizeof(void*));

    
pEndScene = *(fnEndScene*)(*pDxDevice + 0x2A * sizeof(void*));
    
pBeginScene = *(fnEndScene*)(*pDxDevice + 0x29 * sizeof(void*));

    
HookContext::vecState path{ TS_StepOut, { TS_StepInto, hMain + FUNC_PTR }, TS_Step };

    
HookMgr::Instance().ApplyHook( pEndScene, &hkEndScene, pDxStorage + HOOK_OFST, path );
    
HookMgr::Instance().ApplyHook( pBeginScene, &hkBeginScene, pDxStorage + HOOK_OFST );
}
void Unhook()
{
    
HookMgr::Instance().RemoveHook( pEndScene );
    
HookMgr::Instance().RemoveHook( pBeginScene );

    
Sleep( 100 );
}
BOOL APIENTRY DllMain( HMODULE /*hModule*/, DWORD ul_reason_for_call, LPVOID /*lpReserved*/ )
{
    switch (
ul_reason_for_call)
    {
    case 
DLL_PROCESS_ATTACH:
        
Hook();
        break;

    case 
DLL_PROCESS_DETACH:
        
Unhook();
        break;
    }

    return 
TRUE;
}   
 
 Trace engine sources:
Hookmgr.h:
PHP Code:
 
#pragma once

#include "WinHeaders.h"

#include <stdint.h>
#include <vector>
#include <map>
#include <unordered_map>
namespace hook { enum TraceState {
    
TS_Start,       // Initial state. Internal use only
    
TS_Step,        // Do single-step
    
TS_StepOut,     // Break on function return
    
TS_StepInto,    // Step into specific function
    
TS_WaitReturn,  // Wait for break-on-return };
struct PathNode {
    
TraceState action;
    
uintptr_t arg;

    
PathNode( TraceState  _action, uintptr_t _arg = 0 )
        : 
action( _action )
        , 
arg( _arg ) { }
};

/// <summary>
/// Hook-related data
/// </summary>
struct HookContext {
    
typedef std::unordered_map<uintptr_t, std::pair<uintptr_t, bool>> mapHooks;
    
typedef std::vector<PathNode> vecState;

    
uintptr_t lastIP = 0;       // Previous EIP/RIP value
    
uintptr_t lastSP = 0;       // Previous ESP/RSP value
    
uintptr_t targetPtr = 0;    // Address causing exception
    
uintptr_t origPtrVal = 0;   // Original pointer value
    
uintptr_t checkIP = 0;      // Address of instruction that checks target pointer
    
uintptr_t breakValue = 0;   // Value used to generate exception
    
uintptr_t stateIdx = 0;     // Current state index in state vector

    
TraceState state = TS_Start;    // Current tracing state
    
vecState tracePath;             // Function trace path
    
mapHooks hooks;                 // List of hooks associated with current pointer


    /// <summary>
    /// Reset tracing state
    /// </summary>
    
void reset()
    {
        
state = TS_Start;
        
lastIP = lastSP = 0;
        
stateIdx = 0;

        
// Mark hooks as non-called
        
for (auto& item : hooks)
            
item.second.second = false;
    }
};

class 
HookMgr {

public:
    
typedef std::map<uintptr_t, HookContext> mapContext;
    
typedef std::vector <std::pair<uintptr_t, uintptr_t>> vecStackFrames;

public:
    ~
HookMgr();
    static 
HookMgr& Instance();

    
/// <summary>
    /// Setup hook
    /// </summary>
    /// <param name="targetFunc">Target function to be hooked</param>
    /// <param name="hookFunc">New function</param>
    /// <param name="ptrAddress">Address of pointer to destroy</param>
    /// <param name="tracePath">Function tracing path</param>
    /// <param name="checkIP">Optional. Address of instruction that checks target pointer</param>
    /// <returns>true on success, false if already hooked</returns>
    
bool ApplyHook( void* targetFunc, 
                    
void* hookFunc, 
                    
void* ptrAddress, 
                    const 
HookContext::vecState& tracePath = HookContext::vecState(),
                    
void* checkIP = 0 );

    
/// <summary>
    /// Remove existing hook
    /// </summary>
    /// <param name="targetFunc">Target function ptr</param>
    /// <returns>true on success, false if not found</returns>
    
bool RemoveHook( void* targetFunc );

private:
    
// 
    // Singleton
    //
    
HookMgr();
    
HookMgr( const HookMgr& ) = delete;
    
HookMgr& operator =( const HookMgr& ) = delete;

    
//
    // Exception handlers
    //
    
static LONG __stdcall VecHandler( PEXCEPTION_POINTERS ExceptionInfo );
    
LONG VecHandlerP( PEXCEPTION_POINTERS ExceptionInfo );

    
/// <summary>
    /// Capture stack frames
    /// </summary>
    /// <param name="ip">Current instruction pointer</param>
    /// <param name="sp">Current stack pointer</param>
    /// <param name="results">Found frames.</param>
    /// <param name="depth">Frame depth limit</param>
    /// <returns>Number of found frames</returns>
    
size_t StackBacktrace( uintptr_t ip, uintptr_t sp, vecStackFrames& results, uintptr_t depth = 10 );

    
/// <summary>
    /// Setup exception upon function return
    /// </summary>
    /// <param name="ExceptionInfo">The exception information</param>
    
inline void BreakOnReturn( uintptr_t sp );

    
/// <summary>
    /// Check if last instruction caused branching
    /// </summary>
    /// <param name="ctx">Current hook info</param>
    /// <param name="ip">Instruction pointer</param>
    /// <param name="sp">Stack pointer</param>
    /// <returns>True if branching has occurred</returns>
    
bool CheckBranching( const HookContext& ctx, uintptr_t ip, uintptr_t sp );

    
/// <summary>
    /// Handle branching
    /// </summary>
    /// <param name="ctx">Current hook context</param>
    /// <param name="exptContex">Thread context</param>
    
void HandleBranch( HookContext& ctx, PCONTEXT exptContex );

    
/// <summary>
    /// Restore original pointer value
    /// </summary>
    /// <param name="ctx">The CTX.</param>
    /// <param name="ExceptionInfo">The exception information</param>
    /// <returns>true on success, false if no invalid register was found</returns>
    
bool RestorePtr( const HookContext& ctx, PEXCEPTION_POINTERS ExceptionInfo );

private:
    
PVOID       _pExptHandler = nullptr;        // Exception handler
    
mapContext  _contexts;                      // Hook contexts
    
uintptr_t   _breakPtr = 0x2000;             // Exception pointer generator
    
uintptr_t   _lastCtx = 0;
};

}   
 
 HookMgr.cpp:
PHP Code:
 
#define BEA_USE_STDCALL
#include "BeaEngine/headers/BeaEngine.h"

#include <algorithm>

#define HIGHEST_BIT_SET     (1LL << (sizeof(void*) * 8 - 1))
#define HIGHEST_BIT_UNSET  ~HIGHEST_BIT_SET
#define SingleStep          0x100

// Architecture-specific
#ifdef _M_AMD64
#define Nip Rip
#define Nsp Rsp
#define Ndi Rdi
#define Nax Rax

#define ADDR_MASK 0xFFFFFFFFFFFFF000

#else
#define Nip Eip
#define Nsp Esp
#define Ndi Edi
#define Nax Eax

#define ADDR_MASK 0xFFFFF000
#endif
namespace hook {
HookMgr::HookMgr()
{
}
HookMgr::~HookMgr()
{
    if (
_pExptHandler != nullptr)
        
RemoveVectoredExceptionHandler( _pExptHandler );
}
HookMgr& HookMgr::Instance()
{
    static 
HookMgr instance;  
    return 
instance;
}
/// <summary>
/// Setup hook
/// </summary>
/// <param name="targetFunc">Target function to be hooked</param>
/// <param name="hookFunc">New function</param>
/// <param name="ptrAddress">Address of pointer to destroy</param>
/// <param name="tracePath">Function tracing path</param>
/// <param name="checkIP">Optional. Address of instruction that checks target pointer</param>
/// <returns>true on success, false if already hooked</returns>
bool HookMgr::ApplyHook( void* targetFunc, 
                         
void* hookFunc, 
                         
void* ptrAddress, 
                         const 
HookContext::vecState& tracePath /*= HookContext::vecState()*/,
                         
void* chekcIP /*= 0*/)
{
    
// Pointer already present
    
if (_contexts.count( (uintptr_t)ptrAddress ))
    {
        
HookContext& ctx = _contexts[(uintptr_t)ptrAddress];

        
// Already hooked
        
if (ctx.hooks.count( (uintptr_t)targetFunc ))
            return 
false;
        else
            
ctx.hooks.emplace( std::make_pair( (uintptr_t)targetFunc, std::make_pair( (uintptr_t)hookFunc, false ) ) );
    }
    
// Create new context
    
else
    {
        
HookContext ctx;

        
// Setup context
        
ctx.targetPtr = (uintptr_t)ptrAddress;
        
ctx.checkIP = (uintptr_t)chekcIP;
        
ctx.origPtrVal = *(uintptr_t*)ptrAddress;
        
ctx.breakValue = _breakPtr;
        
ctx.tracePath = tracePath;

        
ctx.hooks.emplace( std::make_pair( (uintptr_t)targetFunc, std::make_pair( (uintptr_t)hookFunc, false ) ) );
        
_contexts.emplace( std::make_pair( (uintptr_t)ptrAddress, std::move( ctx ) ) );

        if (
_pExptHandler == nullptr)
            
_pExptHandler = AddVectoredExceptionHandler( 0, &HookMgr::VecHandler );

        
// Setup exception
        
*(uintptr_t*)ptrAddress = ctx.breakValue;
        
_breakPtr += 0x10;
    }

    return 
true;
}
/// <summary>
/// Remove existing hook
/// </summary>
/// <param name="targetFunc">Target function ptr</param>
/// <returns>true on success, false if not found</returns>
bool HookMgr::RemoveHook( void* targetFunc )
{
    
auto findfn = [targetFunc]( const mapContext::value_type& val ) { 
        return 
val.second.hooks.count( (uintptr_t)targetFunc );
    };

    
// Get context containing target function
    
auto iter = std::find_if( _contexts.begin(), _contexts.end(), findfn );
                              
    if (
iter != _contexts.end())
    {
        
auto& ctx = iter->second;

        
// Remove function from list
        
ctx.hooks.erase( (uintptr_t)targetFunc );

        if (
ctx.hooks.empty())
        {
            
// Remove hook itself
            
*(uintptr_t*)ctx.targetPtr = ctx.origPtrVal;
            
_contexts.erase( iter );
        }

        
// Before handler is removed we need to make sure no thread will 
        // generate exception again
        
Sleep( 10 );

        
// Remove exception handler
        
if (_contexts.empty() && _pExptHandler != nullptr)
        {
            
RemoveVectoredExceptionHandler( _pExptHandler );
            
_pExptHandler = nullptr;
        }

        return 
true;
    }

    return 
false;
}

/// <summary>
/// Exception handler
/// </summary>
/// <param name="ExceptionInfo">The exception information</param>
/// <returns>Handling status</returns>
LONG __stdcall HookMgr::VecHandler( PEXCEPTION_POINTERS ExceptionInfo )
{
    return 
Instance().VecHandlerP( ExceptionInfo );
}
/// <summary>
/// Exception handler
/// </summary>
/// <param name="ExceptionInfo">The exception information</param>
/// <returns>Handling status</returns>
LONG HookMgr::VecHandlerP( PEXCEPTION_POINTERS ExceptionInfo )
{
    
auto exptContex = ExceptionInfo->ContextRecord;
    
auto exptRecord = ExceptionInfo->ExceptionRecord;
    
auto exptCode   = exptRecord->ExceptionCode;

    
// TODO: Somehow determine current hook context
    
HookContext* ctx = &_contexts.begin()->second;

    
// Check if exception should be handled
    
if (exptCode != EXCEPTION_SINGLE_STEP && exptCode != EXCEPTION_ACCESS_VIOLATION)
    {
        return 
EXCEPTION_CONTINUE_SEARCH;
    }
    else if (
exptCode == EXCEPTION_ACCESS_VIOLATION && (ctx->state == TS_Step || ctx->state == TS_StepInto))
    {
        if ((
exptRecord->ExceptionInformation[1] & ADDR_MASK) != (ctx->breakValue & ADDR_MASK))
            return 
EXCEPTION_CONTINUE_SEARCH;

        
// Pointer accessed by non-target address 
        
if (ctx->checkIP != 0 && exptContex->Nip != ctx->checkIP)
        {
            
exptContex->EFlags |= SingleStep;

            
RestorePtr( *ctx, ExceptionInfo );
            return 
EXCEPTION_CONTINUE_EXECUTION;
        }
    }
    
    switch (
ctx->state)
    {
        
// Start of tracing
        
case TS_Start:
            {
                
ctx->state = ctx->tracePath[ctx->stateIdx].action;

                
RestorePtr( *ctx, ExceptionInfo );
                return 
VecHandlerP( ExceptionInfo );
            }
            break;

        
// Single step
        
case TS_Step:
            {
                
// Function call occurred. Queue break on return.
                
if (CheckBranching( *ctx, exptContex->Nip, exptContex->Nsp ))
                {
                    
// Target function reached
                    
if (ctx->hooks.count( exptContex->Nip ))
                    {
                        
HandleBranch( *ctx, exptContex );
                        return 
EXCEPTION_CONTINUE_EXECUTION;
                    }
                    else
                    {
                        
ctx->state = TS_WaitReturn;
                        
BreakOnReturn( exptContex->Nsp );
                    }
                }
                
// Step further
                
else
                    
exptContex->EFlags |= SingleStep;
            }
            break;

        
// Step out from function
        
case TS_StepOut:
            {
                
// Get current stack frame
                
vecStackFrames frames;
                
StackBacktrace( exptContex->Nip, exptContex->Nsp, frames, 1 );

                if (
frames.size() > 1)
                {
                    
ctx->stateIdx++;
                    
ctx->state = TS_WaitReturn;
                    
BreakOnReturn( frames.back().first );
                }
            }
            break;

        
// Step into specific function
        
case TS_StepInto:
            {
                
// Check if step into path function has occurred
                
if (CheckBranching( *ctx, exptContex->Nip, exptContex->Nsp ))
                {
                    if (
exptContex->Nip == ctx->tracePath[ctx->stateIdx].arg)
                    {
                        
ctx->stateIdx++;
                        
ctx->state = ctx->tracePath[ctx->stateIdx].action;
                    }
                }

                
exptContex->EFlags |= SingleStep;
            }
            break;

        
// Break on 'ret' instruction
        
case TS_WaitReturn:
            {
                
exptContex->Nip &= HIGHEST_BIT_UNSET;

                
// Restore stack for x64
            #ifdef _M_AMD64
                
*(uintptr_t*)exptContex->Nsp &= HIGHEST_BIT_UNSET;
            
#endif // _M_AMD64

                // Continue stepping
                
exptContex->EFlags |= SingleStep;
                
ctx->state = ctx->tracePath[ctx->stateIdx].action;
            }
            break;

        default:
            break;
    }

    
ctx->lastIP = exptContex->Nip;
    
ctx->lastSP = exptContex->Nsp;
    
    return 
EXCEPTION_CONTINUE_EXECUTION;
}

/// <summary>
/// Check if last instruction caused branching
/// </summary>
/// <param name="ctx">Current hook info</param>
/// <param name="ip">Instruction pointer</param>
/// <param name="sp">Stack pointer</param>
/// <returns>True if branching has occurred</returns>
bool HookMgr::CheckBranching( const HookContext& ctx, uintptr_t ip, uintptr_t sp )
{
    
// Not yet initialized
    
if (ctx.lastIP == 0 || ctx.lastSP == 0)
        return 
false;

    
// Difference in instruction pointer more than possible 'call' length
    // Stack pointer changed
    
if (ip - ctx.lastIP >= 8 && sp != ctx.lastSP)
    {
        
DISASM info = { 0 };
        
info.EIP = ctx.lastIP;

    
#ifdef _M_AMD64
        
info.Archi = 64;
    
#endif  

        // Double-check call instruction using disasm
        
if (Disasm( &info ) > 0 && info.Instruction.BranchType == CallType)
            return 
true;
    }

    return 
false;
}
/// <summary>
/// Handle branching
/// </summary>
/// <param name="ctx">Current hook context</param>
/// <param name="exptContex">Thread context</param>
void HookMgr::HandleBranch( HookContext& ctx, PCONTEXT exptContex )
{
    
// Mark this hook as called
    
ctx.hooks[exptContex->Nip].second = true;

    
// Reset tracing state if all hooks were called
    
auto iter = std::find_if( ctx.hooks.begin(), ctx.hooks.end(),
                              []( const 
decltype(ctx.hooks)::value_type& val ){ return (val.second.second == false); } );

    
// Break after hook execution
    
if (iter != ctx.hooks.end())
    {
        
ctx.state = TS_WaitReturn;
        
BreakOnReturn( exptContex->Nsp );
    }
    
// Reset state
    
else
        
ctx.reset();

    
// Reroute to hook function
    
exptContex->Nip = ctx.hooks[exptContex->Nip].first;
}

/// <summary>
/// Setup exception upon function return
/// </summary>
/// <param name="ExceptionInfo">The exception information</param>
inline void HookMgr::BreakOnReturn( uintptr_t sp )
{
    *(
DWORD_PTR*)sp |= HIGHEST_BIT_SET;
}
/// <summary>
/// Restore original pointer value
/// </summary>
/// <param name="ctx">The CTX.</param>
/// <param name="ExceptionInfo">The exception information</param>
/// <returns>true on success, false if no invalid register was found</returns>
bool HookMgr::RestorePtr( const HookContext& ctx, PEXCEPTION_POINTERS ExceptionInfo )
{
    
bool found = false;
    
auto expCtx = ExceptionInfo->ContextRecord;

    
// Exception on execute
    
if (ExceptionInfo->ExceptionRecord->ExceptionInformation[0] == 8)
    {
        
expCtx->Nip = ctx.origPtrVal;
        return 
true;
    }

    
// Exception on read/write
#ifdef _M_AMD64
    
for (DWORD_PTR* pRegVal = &expCtx->Rax; pRegVal <= &expCtx->R15; pRegVal++) #else
    
for (DWORD_PTR* pRegVal = &expCtx->Ndi; pRegVal <= &expCtx->Nax; pRegVal++)   #endif
    
{
        
// Compare high address parts
        
if ((*pRegVal & ADDR_MASK) == (ExceptionInfo->ExceptionRecord->ExceptionInformation[1] & ADDR_MASK))
        {
            *
pRegVal = ctx.origPtrVal;
            
found = true;
        }
    }

    return 
found;
}
/// <summary>
/// Capture stack frames
/// </summary>
/// <param name="ip">Current instruction pointer</param>
/// <param name="sp">Current stack pointer</param>
/// <param name="results">Found frames.</param>
/// <param name="depth">Frame depth limit</param>
/// <returns>Number of found frames</returns>
size_t HookMgr::StackBacktrace( uintptr_t ip, uintptr_t sp, vecStackFrames& results, uintptr_t depth /*= 10 */ )
{
    
SYSTEM_INFO sysinfo = { 0 };
    
uintptr_t stack_base = (uintptr_t)((PNT_TIB)NtCurrentTeb())->StackBase;

    
GetNativeSystemInfo( &sysinfo );

    
// Store exception address
    
results.emplace_back( std::make_pair( 0, ip ) );

    
// Walk stack
    
for (uintptr_t stackPtr = sp; stackPtr < stack_base && results.size() <= depth; stackPtr += sizeof(void*))
    {
        
uintptr_t stack_val = *(uintptr_t*)stackPtr;
        
MEMORY_BASIC_INFORMATION meminfo = { 0 };

        
// Decode value
        
uintptr_t original = stack_val & HIGHEST_BIT_UNSET;

        
// Invalid value
        
if ( original < (uintptr_t)sysinfo.lpMinimumApplicationAddress ||
             
original > (uintptr_t)sysinfo.lpMaximumApplicationAddress)
        {
            continue;
        }

        
// Check if memory is executable
        
if (VirtualQuery( (LPVOID)original, &meminfo, sizeof(meminfo) ) != sizeof(meminfo))
            continue;

        if ( 
meminfo.Protect != PAGE_EXECUTE_READ &&
             
meminfo.Protect != PAGE_EXECUTE_WRITECOPY &&
             
meminfo.Protect != PAGE_EXECUTE_READWRITE)
        {
            continue;
        }

        
// Detect 'call' instruction
        
for (uintptr_t j = 1; j < 8; j++)
        {
            
DISASM info = { 0 };
            
info.EIP = original - j;

        
#ifdef _M_AMD64
            
info.Archi = 64;
        
#endif  

            
if (Disasm( &info ) > 0 && info.Instruction.BranchType == CallType)
            {
                
results.emplace_back( std::make_pair( stackPtr, stack_val ) );
                break;
            }
        }

    }

    return 
results.size();
}

}  

The code probably has some bugs and is subject to improve.