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_DWORDint))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_DWORDint))v40)(v1LODWORD(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__stdcallfnEndScene )(IDirect3DDevice9*); fnEndScene pEndScene nullptr; fnEndScene pBeginScene nullptr;
HRESULT __stdcall hkEndSceneIDirect3DDevice9arg )
{
    
OutputDebugStringA"hkEndScene called\n" );
    return 
pEndScenearg );
}
HRESULT __stdcall hkBeginSceneIDirect3DDevice9arg )
{
    
OutputDebugStringA"hkBeginScene called\n" );
    return 
pBeginScenearg );
}
void Hook()
{
    
uintptr_t hMain = (uintptr_t)GetModuleHandleWNULL );
    
uint8_tpDxStorage = *(uint8_t**)(hMain STORAGE_OFST);
    
uintptr_tpDxDevice = *(uintptr_t**)(pDxStorage sizeof(void*));

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

    
HookContext::vecState pathTS_StepOut, { TS_StepIntohMain FUNC_PTR }, TS_Step };

    
HookMgr::Instance().ApplyHookpEndScene, &hkEndScenepDxStorage HOOK_OFSTpath );
    
HookMgr::Instance().ApplyHookpBeginScene, &hkBeginScenepDxStorage HOOK_OFST );
}
void Unhook()
{
    
HookMgr::Instance().RemoveHookpEndScene );
    
HookMgr::Instance().RemoveHookpBeginScene );

    
Sleep100 );
}
BOOL APIENTRY DllMainHMODULE /*hModule*/DWORD ul_reason_for_callLPVOID /*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;

    
PathNodeTraceState  _actionuintptr_t _arg )
        : 
action_action )
        , 
arg_arg ) { }
};

/// <summary>
/// Hook-related data
/// </summary>
struct HookContext {
    
typedef std::unordered_map<uintptr_tstd::pair<uintptr_tbool>> mapHooks;
    
typedef std::vector<PathNodevecState;

    
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 (autoitem hooks)
            
item.second.second false;
    }
};

class 
HookMgr {

public:
    
typedef std::map<uintptr_tHookContextmapContext;
    
typedef std::vector <std::pair<uintptr_tuintptr_t>> vecStackFrames;

public:
    ~
HookMgr();
    static 
HookMgrInstance();

    
/// <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 ApplyHookvoidtargetFunc
                    
voidhookFunc
                    
voidptrAddress
                    const 
HookContext::vecStatetracePath HookContext::vecState(),
                    
voidcheckIP );

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

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

    
//
    // Exception handlers
    //
    
static LONG __stdcall VecHandlerPEXCEPTION_POINTERS ExceptionInfo );
    
LONG VecHandlerPPEXCEPTION_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 StackBacktraceuintptr_t ipuintptr_t spvecStackFramesresultsuintptr_t depth 10 );

    
/// <summary>
    /// Setup exception upon function return
    /// </summary>
    /// <param name="ExceptionInfo">The exception information</param>
    
inline void BreakOnReturnuintptr_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 HookContextctxuintptr_t ipuintptr_t sp );

    
/// <summary>
    /// Handle branching
    /// </summary>
    /// <param name="ctx">Current hook context</param>
    /// <param name="exptContex">Thread context</param>
    
void HandleBranchHookContextctxPCONTEXT 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 HookContextctxPEXCEPTION_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 );
}
HookMgrHookMgr::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::ApplyHookvoidtargetFunc
                         
voidhookFunc
                         
voidptrAddress
                         const 
HookContext::vecStatetracePath /*= HookContext::vecState()*/,
                         
voidchekcIP /*= 0*/)
{
    
// Pointer already present
    
if (_contexts.count( (uintptr_t)ptrAddress ))
    {
        
HookContextctx _contexts[(uintptr_t)ptrAddress];

        
// Already hooked
        
if (ctx.hooks.count( (uintptr_t)targetFunc ))
            return 
false;
        else
            
ctx.hooks.emplacestd::make_pair( (uintptr_t)targetFuncstd::make_pair( (uintptr_t)hookFuncfalse ) ) );
    }
    
// 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.emplacestd::make_pair( (uintptr_t)targetFuncstd::make_pair( (uintptr_t)hookFuncfalse ) ) );
        
_contexts.emplacestd::make_pair( (uintptr_t)ptrAddressstd::movectx ) ) );

        if (
_pExptHandler == nullptr)
            
_pExptHandler AddVectoredExceptionHandler0, &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::RemoveHookvoidtargetFunc )
{
    
auto findfn = [targetFunc]( const mapContext::value_typeval ) { 
        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())
    {
        
autoctx 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.eraseiter );
        }

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

        
// 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::VecHandlerPEXCEPTION_POINTERS ExceptionInfo )
{
    return 
Instance().VecHandlerPExceptionInfo );
}
/// <summary>
/// Exception handler
/// </summary>
/// <param name="ExceptionInfo">The exception information</param>
/// <returns>Handling status</returns>
LONG HookMgr::VecHandlerPPEXCEPTION_POINTERS ExceptionInfo )
{
    
auto exptContex ExceptionInfo->ContextRecord;
    
auto exptRecord ExceptionInfo->ExceptionRecord;
    
auto exptCode   exptRecord->ExceptionCode;

    
// TODO: Somehow determine current hook context
    
HookContextctx = &_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 != && exptContex->Nip != ctx->checkIP)
        {
            
exptContex->EFlags |= SingleStep;

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

                
RestorePtr( *ctxExceptionInfo );
                return 
VecHandlerPExceptionInfo );
            }
            break;

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

        
// Step out from function
        
case TS_StepOut:
            {
                
// Get current stack frame
                
vecStackFrames frames;
                
StackBacktraceexptContex->NipexptContex->Nspframes);

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

        
// Step into specific function
        
case TS_StepInto:
            {
                
// Check if step into path function has occurred
                
if (CheckBranching( *ctxexptContex->NipexptContex->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 HookContextctxuintptr_t ipuintptr_t sp )
{
    
// Not yet initialized
    
if (ctx.lastIP == || ctx.lastSP == 0)
        return 
false;

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

    
#ifdef _M_AMD64
        
info.Archi 64;
    
#endif  

        // Double-check call instruction using disasm
        
if (Disasm( &info ) > && 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::HandleBranchHookContextctxPCONTEXT exptContex )
{
    
// Mark this hook as called
    
ctx.hooks[exptContex->Nip].second true;

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

    
// Break after hook execution
    
if (iter != ctx.hooks.end())
    {
        
ctx.state TS_WaitReturn;
        
BreakOnReturnexptContex->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::BreakOnReturnuintptr_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 HookContextctxPEXCEPTION_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_PTRpRegVal = &expCtx->RaxpRegVal <= &expCtx->R15pRegVal++) #else
    
for (DWORD_PTRpRegVal = &expCtx->NdipRegVal <= &expCtx->NaxpRegVal++)   #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::StackBacktraceuintptr_t ipuintptr_t spvecStackFramesresultsuintptr_t depth /*= 10 */ )
{
    
SYSTEM_INFO sysinfo = { };
    
uintptr_t stack_base = (uintptr_t)((PNT_TIB)NtCurrentTeb())->StackBase;

    
GetNativeSystemInfo( &sysinfo );

    
// Store exception address
    
results.emplace_backstd::make_pair0ip ) );

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

        
// 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, &meminfosizeof(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 18j++)
        {
            
DISASM info = { };
            
info.EIP original j;

        
#ifdef _M_AMD64
            
info.Archi 64;
        
#endif  

            
if (Disasm( &info ) > && info.Instruction.BranchType == CallType)
            {
                
results.emplace_backstd::make_pairstackPtrstack_val ) );
                break;
            }
        }

    }

    return 
results.size();
}

}  

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