UE Hook函数

不改引擎源代码的情况下,如何修改一个函数?

如何Hook FMaterialEditor::PasteNodesHere 这个函数?

1.某些函数是导出的,某些函数是未导出的。
2.dll加载到内存后,整个dll的地址会发生变化。
3.函数地址是相对于dll基地址的。

第一个问题:不管函数有没有导出,在dll里面都可以获得函数的地址,秒了。

PasteNodesHere在.text段里面,位置是 00000001800B0860,
但是UE的dll 的 ImageBase都是000180000000,
于是能算出 函数基于ImageBase的偏移值,
0001800B0860 - 000180000000 = B0860,转为十进制是 723040,
PasteNodesHere 的偏移值是 B0860,十进制为723040。
实际的函数地址还需要dll在内存中的首地址 即可计算出PasteNodesHere在内存中的地址,
例如: DLL首地址 + 723040 = PasteNodesHere。

我说的对,但是首地址怎么获取?
GetModuleHandleA 是 Windows API 中的一个函数,用于获取指定模块(如 DLL 或 EXE)的句柄。

句柄可能在底层通过指针实现(如句柄指向内核对象的内存地址),但对外完全隐藏了细节。

示例: 获取UnrealEditor-MaterialEditor.dll的首地址

constexpr const char* MaterialDll = "UnrealEditor-MaterialEditor.dll";
HMODULE MatModule = GetModuleHandleA(MaterialDll);
uintptr_t BaseAddress = reinterpret_cast<uintptr_t>(HModule);

BaseAddress是这个dll的首地址

PasteNodesHere 的偏移值是 B0860,十进制为723040。
实际的函数地址还需要dll在内存中的首地址 即可计算出PasteNodesHere在内存中的地址,
例如: DLL首地址 + 723040 = PasteNodesHere。

所以 PasteNodesHere的实际内存地址是:
FunctionAddress = BaseAddress + 723040 = PasteNodesHere.
增加计算FunctionAddress :

constexpr const char* MaterialDll = "UnrealEditor-MaterialEditor.dll";
HMODULE MatModule = GetModuleHandleA(MaterialDll);

uint64 FunctionOffset = 723040;

uintptr_t BaseAddress = reinterpret_cast<uintptr_t>(HModule);
void* FunctionAddress = reinterpret_cast<void*>(BaseAddress + FunctionOffset);

函数地址已经得到了,然后呢? 如何截胡这个函数?
正常执行流程:
Call PasteNodesHere()---> 原函数执行 ---> 执行完毕
截胡后的流程:
Call PasteNodesHere()---> 执行自定义函数 --->原函数执行 ---> 执行完毕
Call PasteNodesHere()---> 原函数执行 ---> 执行自定义函数 --->执行完毕
Call PasteNodesHere()---> 执行自定义函数 --->执行完毕
Call PasteNodesHere()---> 执行完毕
这几种截胡都可能实现,首要目标是获取原函数地址,目标完成了,那到底怎么实现截胡?

微软的 Detours 库是一个用于拦截和修改函数调用的高效工具,广泛应用于调试、性能分析、安全检测、API 扩展等场景。
● 高效稳定:直接操作二进制代码,性能开销低。
● 灵活性高:支持动态挂载与卸载,适应多种场景。
● 开源免费:社区活跃,文档和示例丰富。

  1. Detours 库的核心功能
    ● 函数钩子(Hook):拦截目标函数的执行,将控制流重定向到自定义的替换函数。
    ● 动态代码修补:在运行时修改二进制代码,支持对已编译程序的无缝扩展。
    ● 多平台支持:兼容 Windows x86/x64 架构,适用于用户模式和内核模式编程。

  2. 工作原理
    Detours 通过以下步骤实现函数拦截:

  3. 备份原函数入口:保存目标函数的前几条指令(通常为跳转或调用指令)。

  4. 插入跳转指令:在目标函数入口处写入跳转指令(如 JMP),将执行流转向自定义的钩子函数。

  5. 执行替换逻辑:在钩子函数中处理自定义逻辑,可选择调用原始函数(通过备份的指令)或完全替代其行为。

  6. 恢复原函数:在需要时,可移除钩子并恢复原始代码。

通过 Detours 库,无需修改源码或重新编译目标程序,即可实现强大的运行时行为监控和扩展。

DetourAttach 通常需要配合以下函数实现线程安全:
● DetourTransactionBegin():开始一个事务,暂停所有线程的执行。
● DetourUpdateThread(GetCurrentThread()):更新当前线程的上下文,确保指令缓存一致性。
● DetourTransactionCommit():提交事务,恢复线程并应用所有修改。

Build.cs配置

PasteNodesHere函数的Hook流程:

void HookFunction(HMODULE HModule, uintptr_t FunctionOffset, PVOID& OriginalFunction,PVOID InHookFunction,Hook_Normal_Tag )
{
    if (!HModule) {
        return ;
    }

    uintptr_t BaseAddress = reinterpret_cast<uintptr_t>(HModule);
    void* FunctionAddress = reinterpret_cast<void*>(BaseAddress + FunctionOffset);
    OriginalFunction = FunctionAddress;
    DetourTransactionBegin();
    DetourUpdateThread(GetCurrentThread());
    DetourAttach(&(PVOID&)OriginalFunction, InHookFunction);
    DetourTransactionCommit();
}

//FMaterialEditor::PasteNodesHere(const FVector2D& Location, const class UEdGraph* Graph)
using PasteNodesHereFuncType = void(__fastcall*)(FMaterialEditor* , const FVector2D& , const UEdGraph* );
PasteNodesHereFuncType OriginalPasteNodesHere = nullptr;
void __fastcall HookPasteNodesHere(FMaterialEditor* This, const FVector2D& Location, const UEdGraph* Graph)
{
	OriginalPasteNodesHere(This, Location, Graph);
	
	//Hook
	auto NewNodes = This->GetSelectedNodes();
	for (const auto Node : NewNodes)
	{
		UMaterialGraphNode* MatNode = Cast<UMaterialGraphNode>(Node);
		if (Cast<UMaterialExpressionMaterialFunctionCall>(MatNode->MaterialExpression))
		{
			MatNode->RecreateAndLinkNode();
		}
		This->AddToSelection(MatNode->MaterialExpression);
	}
}

void HookMatEditor()
	{
		constexpr const char* MaterialDll = "UnrealEditor-MaterialEditor.dll";
		HMODULE MatModule = GetModuleHandleA(MaterialDll);
		const UMatHelperSettings* Settings = GetDefault<UMatHelperSettings>();
		
		HookFunction(MatModule, 723040, (PVOID&)OriginalPasteNodesHere, HookPasteNodesHere);
	}

分析:
0.定义要替换的函数
OriginalPasteNodesHere 用来保存原始函数
HookPasteNodesHere 是要用来做替换的函数,
Hook成功以后,再执行PasteNodesHere 就执行到了 HookPasteNodesHere 函数。
这个代码里面 Hook后的执行流程:
Call PasteNodesHere()---> 原函数执行 ---> 执行自定义函数 --->执行完毕

//FMaterialEditor::PasteNodesHere(const FVector2D& Location, const class UEdGraph* Graph)
using PasteNodesHereFuncType = void(__fastcall*)(FMaterialEditor* , const FVector2D& , const UEdGraph* );
PasteNodesHereFuncType OriginalPasteNodesHere = nullptr;
void __fastcall HookPasteNodesHere(FMaterialEditor* This, const FVector2D& Location, const UEdGraph* Graph)
{
    OriginalPasteNodesHere(This, Location, Graph);

    //Hook
    auto NewNodes = This->GetSelectedNodes();
    for (const auto Node : NewNodes)
        {
            UMaterialGraphNode* MatNode = Cast<UMaterialGraphNode>(Node);
            if (Cast<UMaterialExpressionMaterialFunctionCall>(MatNode->MaterialExpression))
            {
                MatNode->RecreateAndLinkNode();
            }
            This->AddToSelection(MatNode->MaterialExpression);
        }
}

1.计算函数在内存中的实际位置.

uintptr_t BaseAddress = reinterpret_cast<uintptr_t>(HModule);
void* FunctionAddress = reinterpret_cast<void*>(BaseAddress + FunctionOffset);

2.保存原函数
PVOID就是void*,通过PVOID&引用传递,就能在函数体内修改外部传进来的函数指针,
最终让外部传来的函数指针 指向原始函数.

void HookFunction(HMODULE HModule, uintptr_t FunctionOffset, PVOID& OriginalFunction,PVOID InHookFunction,Hook_Normal_Tag )
{
    if (!HModule) {
        return ;
    }

    uintptr_t BaseAddress = reinterpret_cast<uintptr_t>(HModule);
    void* FunctionAddress = reinterpret_cast<void*>(BaseAddress + FunctionOffset);
    OriginalFunction = FunctionAddress;
}

3.Hook
核心在于Detour函数,替换执行逻辑
DetourAttach(&(PVOID&)OriginalFunction, InHookFunction);
连续调用4个Detour开头的函数,问就是 公式做题就是快。

void HookFunction(HMODULE HModule, uintptr_t FunctionOffset, PVOID& OriginalFunction,PVOID InHookFunction,Hook_Normal_Tag )
	{
		if (!HModule) {
			return ;
		}

		uintptr_t BaseAddress = reinterpret_cast<uintptr_t>(HModule);
		void* FunctionAddress = reinterpret_cast<void*>(BaseAddress + FunctionOffset);
		OriginalFunction = FunctionAddress;
		DetourTransactionBegin();
		DetourUpdateThread(GetCurrentThread());
		DetourAttach(&(PVOID&)OriginalFunction, InHookFunction);
		DetourTransactionCommit();
	}

Hook完成了。。。

封装Hook函数库:
FunctionHook.h

#pragma once
#include "Windows/AllowWindowsPlatformTypes.h"
#include <detours.h>
#include "Windows/HideWindowsPlatformTypes.h"

namespace FunctionHook
{
    struct Hook_Debug_Tag {};  
    struct Hook_Normal_Tag {};
    void HookFunction(HMODULE HModule, uintptr_t FunctionOffset, void*& OriginalFunction, void* InHookFunction, Hook_Normal_Tag = Hook_Normal_Tag{});
    void HookFunction(HMODULE HModule, uintptr_t FunctionOffset, void*& OriginalFunction, void* InHookFunction, Hook_Debug_Tag);
    void HookFunction(const char* ModuleName, uintptr_t FunctionOffset, void*& OriginalFunction, void* InHookFunction, Hook_Normal_Tag Tag = Hook_Normal_Tag{});
    void HookFunction(const char* ModuleName, uintptr_t FunctionOffset, void*& OriginalFunction, void* InHookFunction, Hook_Debug_Tag Tag);

    template<typename MODULE, typename T>
    void HookFunction(MODULE Module, uintptr_t Offset, T*& Original, T* Hook, Hook_Normal_Tag = Hook_Normal_Tag{})
    {
        HookFunction(Module, Offset, (void*&)Original, (void*)Hook);
    }

    template<typename MODULE, typename T>
    void HookFunction(MODULE Module, uintptr_t Offset, T*& Original, T* Hook, Hook_Debug_Tag Tag)
    {
        HookFunction(Module, Offset, (void*&)Original, (void*)Hook, Tag);
    }
}


#define DEFINE_HOOK_TYPE(HookName, RetType, CallConv, ...)               \
using HookName##FuncType = RetType(CallConv*)(__VA_ARGS__);              \
HookName##FuncType Original##HookName = nullptr;                         \
RetType CallConv Hook##HookName(__VA_ARGS__)

FunctionHook.cpp

// ReSharper disable CppCStyleCast
#include "FunctionHook.h"

namespace FunctionHook
{
    void HookFunction(HMODULE HModule, uintptr_t FunctionOffset, PVOID& OriginalFunction,PVOID InHookFunction,Hook_Normal_Tag )
    {
        if (!HModule) {
            return ;
        }

        uintptr_t BaseAddress = reinterpret_cast<uintptr_t>(HModule);
        void* FunctionAddress = reinterpret_cast<void*>(BaseAddress + FunctionOffset);
        OriginalFunction = FunctionAddress;
        DetourTransactionBegin();
        DetourUpdateThread(GetCurrentThread());
        DetourAttach(&(PVOID&)OriginalFunction, InHookFunction);
        DetourTransactionCommit();
    }


    void HookFunction(HMODULE HModule, uintptr_t FunctionOffset, PVOID& OriginalFunction,PVOID InHookFunction,Hook_Debug_Tag)
    {
        if (!HModule) {
            UE_LOG(LogTemp, Warning, TEXT("Failed to get module handle"));
            return ;
        }

        uintptr_t BaseAddress = reinterpret_cast<uintptr_t>(HModule);
        void* FunctionAddress = reinterpret_cast<void*>(BaseAddress + FunctionOffset);
        OriginalFunction = FunctionAddress;
        LONG Error = DetourTransactionBegin();
        if (Error != NO_ERROR) {
            UE_LOG(LogTemp, Warning, TEXT("DetourTransactionBegin failed with error code: %d"), Error);
            return ;
        }

        Error = DetourUpdateThread(GetCurrentThread());
        if (Error != NO_ERROR) {
            UE_LOG(LogTemp, Warning, TEXT("DetourUpdateThread failed with error code: %d"), Error);
            DetourTransactionAbort();
            return ;
        }

        Error = DetourAttach(&(PVOID&)OriginalFunction, InHookFunction);
        if (Error != NO_ERROR) {
            UE_LOG(LogTemp, Warning, TEXT("DetourAttach failed with error code: %d"), Error);
            DetourTransactionAbort();
            return ;
        }

        Error = DetourTransactionCommit();
        if (Error != NO_ERROR) {
            UE_LOG(LogTemp, Warning, TEXT("DetourTransactionCommit failed with error code: %d"), Error);
            return ;
        }
    }

    void HookFunction(const char* ModuleName, uintptr_t FunctionOffset, PVOID& OriginalFunction,PVOID InHookFunction,Hook_Normal_Tag Tag)
    {
        HMODULE HModule = GetModuleHandleA(ModuleName);
        HookFunction(HModule,FunctionOffset,OriginalFunction,InHookFunction,Tag);
    }


    void HookFunction(const char* ModuleName, uintptr_t FunctionOffset, PVOID& OriginalFunction,PVOID InHookFunction,Hook_Debug_Tag Tag)
    {
        HMODULE HModule = GetModuleHandleA(ModuleName);
        HookFunction(HModule,FunctionOffset,OriginalFunction,InHookFunction,Tag);
    }
}

使用封装的Hook函数:

DEFINE_HOOK_TYPE(PasteNodesHere, void, __fastcall, FMaterialEditor* This, const FVector2D& Location, const UEdGraph* Graph)
{
    OriginalPasteNodesHere(This, Location, Graph);

    //Hook
    auto NewNodes = This->GetSelectedNodes();
    for (const auto Node : NewNodes)
        {
            UMaterialGraphNode* MatNode = Cast<UMaterialGraphNode>(Node);
            if (Cast<UMaterialExpressionMaterialFunctionCall>(MatNode->MaterialExpression))
            {
                MatNode->RecreateAndLinkNode();
            }
            This->AddToSelection(MatNode->MaterialExpression);
        }
}

namespace MatHelperHook
{
	using namespace FunctionHook;

	void HookMatEditor()
	{
		constexpr const char* MaterialDll = "UnrealEditor-MaterialEditor.dll";
		HMODULE MatModule = GetModuleHandleA(MaterialDll);
		const UMatHelperSettings* Settings = GetDefault<UMatHelperSettings>();
		
		HookFunction(MatModule, Settings->PasteNodeHereOffset, OriginalPasteNodesHere, HookPasteNodesHere);
	}
}
posted @ 2025-03-31 18:06  Axsaw  阅读(60)  评论(0)    收藏  举报