zoukankan      html  css  js  c++  java
  • inline hook 原理 教程

    inline hook 原理&教程

    2021年5月24日 qq: base64(MTcxMjgzNjQ0)

    • <1> inline hook 是什么
    • <2> inline hook 基本原理
    • <3> inline hook 跳板函数
    • <4> inline hook 线程安全
    • <5> inline hook 推荐库
    • <6> thiscall hook的方法
    • <7> hook现有进程的其他事项

    <1> inline hook 是什么

    当我们想要拦截现有运行中的进程内某个现有的汇编函数体,最常用的办法就是 inline hook

    它可以在权限允许内,通过修改程序运行中的内存代码段汇编,以达到拦截任何函数的目的,包括系统api(只限非内核态的函数体,要hook内核函数需要进内核态),以及程序内部现有的任何函数体。

    比如想拦截系统APICreateFileW的调用,修改原调用参数并继续执行CreateFileW原函数逻辑,获得返回值,或者直接拦截返回NULL失败,或者拦截程序本身代码汇编的函数体,用 inline hook都可以做到。

    <2>inline hook 基本原理

    在windows下,程序执行的时候会把dll和exe的代码段 text 以及其他数据整理后加载进内存,以顺序排列在指定的虚拟内存空间内。

    xx.exe, 0xc80000
    abseil_dll.dll, 0x6ca0000
    AcLayers.dll, 0x7b9d0000
    AddrSearch.dll, 0x46a0000
    advapi32.dll, 0x75b80000
    advapi32.dll.mui, 0x201b0000
    Advertisement.dll, 0x1e560000
    AdvVideoDev.dll, 0x7c930000
    AFBase.dll, 0x3a80000
    AFCtrl.dll, 0x7c9b0000
    AFUtil.dll, 0x7c2b0000
    AppCenter.dll, 0x7b9b0000
    AppFramework.dll, 0x796f0000
    ...
    

    其中,dll或者exe的内存空间首地址,被称为基地址,此时xx.exe的基地址就是0xc80000。

    既然在内存内,那就意味着一个exe或者dll的代码段 text或者其他段的运行时数据是可能被修改的?

    是的,windows下可以使用VirtualProtect函数,修改虚拟内存地址块的保护属性,标记为可读写

    请看下面的代码

    
    <此代码只适用32位程序>
    
    #include <Windows.h>
    #include <iostream>
    
    //构造了一个 参数 与原CreateFileA一样的的函数
    
    HANDLE __stdcall MY_CreateFileA(
    	LPCSTR lpFileName,
    	DWORD dwDesiredAccess,
    	DWORD dwShareMode,
    	LPSECURITY_ATTRIBUTES lpSecurityAttributes,
    	DWORD dwCreationDisposition,
    	DWORD dwFlagsAndAttributes,
    	HANDLE hTemplateFile) {
    
    	printf("MY_CreateFileA: %s 
    ", lpFileName);
    	return NULL;
    }
    
    int main()
    {
    
    	HANDLE hFile;
    	printf("第一次调用CreateFileA 
    ");
    	hFile = CreateFileA(
    		"abc.txt",
    		GENERIC_WRITE,
    		0,
    		NULL,
    		CREATE_ALWAYS,
    		FILE_ATTRIBUTE_NORMAL,
    		NULL);
    
    	if (hFile == NULL) {
    		printf("hFile==NULL
    ");
    	}
    	else if (hFile != INVALID_HANDLE_VALUE) {
    		printf("CreateFileA success
    ");
    		CloseHandle(hFile);
    	}
    
    
    	//原CreateFileA的函数内存地址 或者直接用&CreateFileA
    	char* target = (char*)GetProcAddress(GetModuleHandleA("Kernel32.dll"), "CreateFileA");
    
    	//MY_CreateFileA的内存地址
    	char* detour = (char*)&MY_CreateFileA;
    
    
    	DWORD  oldProtect;
    
    	//修改CreateFileA的内存地址块 5个大小为可读写
    	if (!VirtualProtect(target, 5, PAGE_EXECUTE_READWRITE, &oldProtect)) {
    		printf("VirtualProtect false
    ");
    		return 0;
    	}
    
    	//0xe9为汇编代码jmp的二进制值
    	unsigned char jmp_0xe9 = 0xE9;
    
    	//这是一个jmp用的偏移地址,从CreateFileA位置跳转到MY_CreateFileA
    	unsigned int jmp_addr = detour - (target + 5);
    
    	//将target 原CreateFileA 的内存前五个 改写,覆盖为一个jmp指令,和一个jmp需要的偏移地址,刚好五个字节大小
    	memcpy(target, &jmp_0xe9, 1);
    	memcpy(target + 1, &jmp_addr, 4);
    
    	//恢复原内存保护属性
    	VirtualProtect(target, 5, oldProtect, &oldProtect);
    
    	printf("第二次调用CreateFileA 
    ");
    	hFile = CreateFileA(
    		"abc.txt",
    		GENERIC_WRITE,
    		0,
    		NULL,
    		CREATE_ALWAYS,
    		FILE_ATTRIBUTE_NORMAL,
    		NULL);
    
    	if (hFile == NULL) {
    		printf("hFile==NULL
    ");
    	}
    	else if (hFile != INVALID_HANDLE_VALUE) {
    		CloseHandle(hFile);
    	}
    
    	std::cout << "Hello World!
    ";
    }
    

    执行结果输出为

    第一次调用CreateFileA
    CreateFileA success
    第二次调用CreateFileA
    MY_CreateFileA: abc.txt
    hFile==NULL
    Hello World!
    
    

    可以很明显的看到,第二次调用CreateFileA被拦截到MY_CreateFileA,并成功获取到了原调用参数!

    这其中的原理就是,修改了CreateFileA前五个字节的汇编,覆盖为jmp 0x 0x 0x 0x, 四字节的0x 0x 0x 0xMY_CreateFileA的函数地址!

    [---原函数---]
    [[ jmp 0x 0x 0x 0x ]-被修改的函数---](头五个字节汇编被覆盖)
    

    当执行CreateFileA,就会执行jmp 指令,跳转到给定的函数地址,所以就跳转到了MY_CreateFileA

    0xE9 jmp的汇编之后的四个字节,是偏移的地址,通用公式为:要跳转的地址-(jmp下一行汇编的地址),即detour - (target + 5)

    上面的代码完成了32位下对某一个函数的简单拦截。

    思考几个问题?

    1:为什么要一个参数和调用方式与原函数一样参数一样调用方式MY_CreateFileA函数?

    答:因为退栈的原则,CreateFileAstdcall调用,参数全是push压栈,且,清理栈还原esp值责任也全在目标函数CreateFileA!所以,有一个参数与CreateFileA一样的函数,MY_CreateFileA正常退栈esp值,才能保证正常esp值,否则函数执行完,esp值并没有还原到 call CreateFileA之前的状态,造成程序错乱异常甚至崩溃!

    2:为什么直接jmpMY_CreateFileA能获取到参数?

    答:因为call之后跳转到CreateFileA中途只有一个jmp,栈是传参后的原样没有改变,MY_CreateFileACreateFileA的调用方式都是__stdcall,所以MY_CreateFileA去获取参数的时候,就能获取到原本的传参。

    3:MY_CreateFileA执行完成为什么会成功跳转到原来的调用代码?

    答:原本执行的过程是 call CreateFileA -> 在CreateFileA函数结束retcallret是成对工作的,call 会压栈一个ip地址,而ret会退栈一个值,并跳转到这个值地址,从而回到call的下一行汇编。当覆盖原CreateFileA前五个字节为jmp,并没有改变栈的状态,所以跳转到MY_CreateFileA,不仅能够获取到原本的参数,而且ret同样能跳转到原来call CreateFileA的代码ip位置。

    思考这些汇编实现问题很重要,如果不能想通,就需要去补习函数调用增栈退栈和传参的实现原理。

    现在已经完成了对某一个函数的拦截,那么如何成功的调用原函数呢?

    <3>inline hook 跳板函数

    CreateFileA已被破坏了,因为前5个字节的汇编代码都被覆盖了。无法正常调用!

    如何才能正常的调用原函数?

    可以尝试这样

    <此代码只适用于32位>
    
    #include <Windows.h>
    #include <iostream>
    
    char backups_asm[5];
    
    int __stdcall MY_MessageBoxA(
    	HWND hWnd,
    	LPCSTR lpText,
    	LPCSTR lpCaption,
    	UINT uType);
    
    //拦截,并备份
    void Intercept()
    {
    	char* target = (char*)&MessageBoxA;
    	char* detour = (char*)&MY_MessageBoxA;
    
    	DWORD  oldProtect;
    	VirtualProtect(target, 5, PAGE_EXECUTE_READWRITE, &oldProtect);
    
    	//----------------备份原来的5个字节------------------------------
    	memcpy(backups_asm, target, 5);
    	//----------------备份原来的5个字节------------------------------
    
    	unsigned char jmp_0xe9 = 0xE9;//jmp
    	unsigned int jmp_addr = detour - (target + 5);//jmp 地址
    
    	//覆盖原函数5个字节为 jmp
    	memcpy(target, &jmp_0xe9, 1);
    	memcpy(target + 1, &jmp_addr, 4);
    
    	VirtualProtect(target, 5, oldProtect, &oldProtect);
    }
    
    int __stdcall MY_MessageBoxA(
    	HWND hWnd,
    	LPCSTR lpText,
    	LPCSTR lpCaption,
    	UINT uType)
    {
    	printf("MY_MessageBoxA: %s 
    ", lpText);
    
    	//------在这里通过备份恢复原函数------
    
    	char* target = (char*)&MessageBoxA;
    	DWORD  oldProtect;
    	VirtualProtect(target, 5, PAGE_EXECUTE_READWRITE, &oldProtect);
    
    	//----------------恢复原来的5个字节------------------------------
    	memcpy(target, backups_asm, 5);
    	//----------------恢复原来的5个字节------------------------------
    
    	VirtualProtect(target, 5, oldProtect, &oldProtect);
    
    	//继续调用原函数
    	return MessageBoxA(hWnd, lpText, lpCaption, uType);
    }
    
    int main()
    {
    	Intercept();
    	MessageBoxA(NULL, "this my text!", "title", MB_OK);
    	std::cout << "end 
    ";
    }
    
    

    输出:

    MY_MessageBoxA: this my text!
    end
    

    可以看到,MY_MessageBoxA已拦截到,同时也正确执行了MessageBoxA原函数。

    但这种方式很别扭,每次都需要进行memcpy,不停改变代码段汇编,并不是线程安全的,不太实用。

    有一种更好的办法,那就是创建一个跳板函数。

    步骤如下,在虚拟内存内开辟一块可被执行的内存,将原函数的前5个字节复制到这里,然后在尾部再加上一个往原函数地址jmp,接着逻辑继续执行,如果执行到这个地址,那么先会执行备份的5个字节,然后jmp到原来函数的逻辑,就能成功调用原函数了。

    开辟的新可执行内存空间 跳板函数()
    {
    备份复制原函数的5字节汇编
    jmp 到原函数5字节之后位置
    }
    

    但这里有一个问题,汇编指令集不是一直为5个字节大小,有各种长度的,如果贸然只备份5个字节,可能会切断原本有的汇编指令,从而无法完整执行正常的代码段,造成程序崩溃。

    所以,这里这里在备份原函数的时候,需要读取汇编指令,从而备份的一个大于5字节的完整汇编段

    还有一个问题,当备份汇编字节的汇编有各种跳转指令的时候,copy到跳板函数内存区域,这些偏移地址也要进行修改。 比如原函数 (0xe9)jmp 0x 0x 0x 0x相对跳转指令,复制到跳板函数的时候就需要重新计算跳转偏移。
    需要进行修改的跳转指令大概有这些,call jmp jcc 等,当然如果是0xFF 0x25 jmp这样的绝对跳转,地址是不用变的。

    如何获得完整的汇编指令大小和值,可以通过开源代码实现,hde32_disasmhde64_disasm

    创建跳板Trampoline函数示例如下

    <此代码只适用于32位>
    
    #include <Windows.h>
    #include <iostream>
    
    #include "./hde/hde32.h"
    #include <cassert>
    
    typedef HANDLE(__stdcall* FN_PTR_CreateFileA)(
    	LPCSTR lpFileName,
    	DWORD dwDesiredAccess,
    	DWORD dwShareMode,
    	LPSECURITY_ATTRIBUTES lpSecurityAttributes,
    	DWORD dwCreationDisposition,
    	DWORD dwFlagsAndAttributes,
    	HANDLE hTemplateFile);
    
    FN_PTR_CreateFileA CreateFileATrampoline = NULL;
    
    HANDLE __stdcall MY_CreateFileA(
    	LPCSTR lpFileName,
    	DWORD dwDesiredAccess,
    	DWORD dwShareMode,
    	LPSECURITY_ATTRIBUTES lpSecurityAttributes,
    	DWORD dwCreationDisposition,
    	DWORD dwFlagsAndAttributes,
    	HANDLE hTemplateFile) {
    
    	printf("MY_CreateFileA: %s 
    ", lpFileName);
    
    	//执行原函数 跳板函数
    	return CreateFileATrampoline(lpFileName,
    		dwDesiredAccess,
    		dwShareMode,
    		lpSecurityAttributes,
    		dwCreationDisposition,
    		dwFlagsAndAttributes,
    		hTemplateFile);
    }
    
    //将target前面第一条汇编改成 jmp跳转到detour,所需5个字节
    void Hook(char* target, char* detour)
    {
    
    	DWORD  oldProtect;
    	VirtualProtect(target, 5, PAGE_EXECUTE_READWRITE, &oldProtect);
    
    	unsigned char jmp_0xe9 = 0xE9;//jmp
    	unsigned int jmp_addr = detour - (target + 5);//jmp 地址
    
    	memcpy(target, &jmp_0xe9, 1);
    	memcpy(target + 1, &jmp_addr, 4);
    
    	VirtualProtect(target, 5, oldProtect, &oldProtect);
    }
    
    //创建跳板函数,备份至少5字节汇编指令的完整汇编,并在末尾补充跳转到原函数
    void* CreateTrampoline(char* target)
    {
    	DWORD  oldProtect;
    	VirtualProtect(target, 5, PAGE_EXECUTE_READWRITE, &oldProtect);
    
    	UINT  asm_size;
    	hde32s hs;
    	UINT  trampoline_size = 0;
    	char* hde_target = target;
    
    	//开辟一个足够大的空间 +5,是因为末尾还需要jmp指令,jmp到原函数
    	char* TrampolineMem = (char*)VirtualAlloc(NULL, trampoline_size + 10 + 5,
    		MEM_COMMIT, PAGE_EXECUTE_READWRITE);
    	if (TrampolineMem == NULL) {
    		printf("VirtualAlloc Error 
    ");
    		exit(0);
    	}
    
    	do {
    		//通过hde32_disasm 读取下一条完整汇编
    		asm_size = hde32_disasm(hde_target, &hs);
    
    		//备份一条汇编
    		memcpy(TrampolineMem + trampoline_size, hde_target, asm_size);
    
    		//当汇编指令是以下的相对偏移指令,需要重新修改偏移跳转值,此代码!未实现完整!!!
    		if (hs.opcode == 0xE8) {
    			printf("CALL 
    ");
    			assert(false);
    		}
    		else if ((hs.opcode & 0xFD) == 0xE9) {
    			printf("JMP (EB or E9) 
    ");
    			if (hs.opcode == 0xe9) {
    				uint32_t* jmp_addr = (uint32_t*)(TrampolineMem + trampoline_size + asm_size - 4);
    
    				*jmp_addr = *jmp_addr + (hde_target + asm_size) - (TrampolineMem + trampoline_size + asm_size);
    
    				printf("0xe9 地址修复 
    ");
    			}
    			else
    				assert(false);
    
    		}
    		else if ((hs.opcode & 0xF0) == 0x70
    			|| (hs.opcode & 0xFC) == 0xE0
    			|| (hs.opcode2 & 0xF0) == 0x80) {
    			printf(" Jcc 
    ");
    			assert(false);
    		}
    
    		trampoline_size += asm_size;
    		hde_target += asm_size;
    
    
    	} while (trampoline_size < 5);//至少备份5个字节的汇编
    
    
    	//已将大于5字节的汇编备份到TrampolineMem,然后在TrampolineMem末尾加jmp
    	unsigned char jmp_0xe9 = 0xE9;//jmp
    	unsigned int jmp_addr = (target + trampoline_size) - (TrampolineMem + trampoline_size + 5);//jmp 到原函数
    
    	memcpy(TrampolineMem + trampoline_size, &jmp_0xe9, 1);
    	memcpy(TrampolineMem + trampoline_size + 1, &jmp_addr, 4);
    
    	VirtualProtect(target, 5, oldProtect, &oldProtect);
    
    	return TrampolineMem;
    };
    
    typedef int(*FN_add)(int a, int b);
    FN_add old_add = NULL;
    int add(int a, int b)
    {
    	return a + b + 100;
    }
    
    int my_add(int a, int b)
    {
    	printf("add %d %d 
    ", a, b);
    	return old_add(a + 1, b);
    }
    
    void main()
    {
    
    	//先在hook破坏原函数前创建跳板函数
    	old_add = (FN_add)CreateTrampoline((char*)&add);
    	Hook((char*)&add, (char*)&my_add);
    	int x = add(1, 2);
    	printf("add(1, 2) x : %d 
    ", x);
    
    
    	//先在hook破坏原函数前创建跳板函数
    	CreateFileATrampoline = (FN_PTR_CreateFileA)CreateTrampoline((char*)&CreateFileA);
    	Hook((char*)&CreateFileA, (char*)&MY_CreateFileA);
    	HANDLE hFile;
    	hFile = CreateFileA("abc.txt",
    		GENERIC_WRITE,
    		0,
    		NULL,
    		CREATE_ALWAYS,
    		FILE_ATTRIBUTE_NORMAL,
    		NULL);
    
    	if (hFile != INVALID_HANDLE_VALUE) {
    		printf("CreateFileA success
    ");
    		CloseHandle(hFile);
    	}
    
    	printf("end 
    ");
    }
    
    
    

    输出:

    JMP (EB or E9)
    0xe9 地址修复
    add 1 2
    add(1, 2) x : 104
    
    MY_CreateFileA: abc.txt
    CreateFileA success
    end
    

    <4>inline hook 线程安全

    知道了hook原理和创建跳板函数的大概流程,但现在还有一个问题,那就是线程安全

    当hook时候,hook流程正在覆盖目标函数起始的几个汇编字节的时候,如果有其他线程正在执行这个函数,也恰好正在执行起始位置,贸然覆盖汇编代码,可能会造成程序崩溃!

    解决方法:在附加hook的时候,暂停当前进程内 除当前线程外的其他所有线程,再继续执行附加hook的逻辑,附加hook完成之后,判断其他所有线程的eip,就是执行的代码地址,是否为目标函数的前几个覆盖的字节,如果是,需要把eip重新设置到跳板函数对应的位置。最后重新启动其他所有线程。

    多线程下安全的 attach hook 步骤
    
    1> 遍历当前进程的所有线程,暂停当前线程以外的所有线程。
    (遍历所有线程可以用TH32CS_SNAPTHREAD,暂停线程函数为SuspendThread。)
    
    2>执行attach hook流程。
    (覆盖目标函数头jmp到detour函数。创建跳板函数。)
    
    3>判断其他所有线程的执行的代码ip地址,如果正在执行目标函数,且恰好正在执行起始几个覆盖的汇编,则将此线程的ip地址重新设置到trampoline跳板函数对应的地址。
    (获取线程ip地址的函数为GetThreadContext,32eip 64rip,重新设置为SetThreadContext)
    
    4>恢复其他所有线程。
    (ResumeThread)
    
    

    卸载hook也需要线程安全,同样需要在detach hook前后暂停和恢复线程。但缺无法准确判断一个线程ip是不是正在执行hook代码段,因为有可能detour正在执行其他另外的函数。所以这是一个问题。只能尽可能的保证线程安全,稍晚一点释放跳板函数。

    <5>inline hook 推荐库

    要实现整个hook的流程,保证通用性和稳定性,这是一项不小的工作量,有两个推荐的开源库。

    Detours

    微软开源的库,支持x86/x64,arm。针对windows api适配得很好,同样也可以hook普通函数。有比较好的稳定性。

    https://github.com/microsoft/Detours

    使用流程&问题

    	DetourRestoreAfterWith();
    	DetourTransactionBegin();
    	DetourSetIgnoreTooSmall(TRUE);
    
    	DetourUpdateThread() 将某个线程加入休眠队列
    
    	-----------开始执行-----------
    	DetourAttachEx 创建hook
    
    	DetourDetach 移除分离hook
    	----------------------
    
    	DetourTransactionCommit() 提交hook 并且会恢复由DetourUpdateThread休眠的线程
    

    这里有个问题需要注意,

    	DetourUpdateThread函数内部代码
    
        // Silently (and safely) drop any attempt to suspend our own thread.
        if (hThread == GetCurrentThread()) {
            return NO_ERROR;
        }
    

    GetCurrentThread是一个伪句柄,由TH32CS_SNAPTHREAD得到的线程id再OpenThread,和这个伪句柄是对不上的。

    所以你需要自己在外部过滤掉当前线程,不能把由OpenThread的当前线程HANDLE传进去!!也许这是一个bug吧。

    	CreateToolhelp32Snapshot TH32CS_SNAPTHREAD 
    	这里得到的线程是当前操作系统所有的线程!!无论传不传进程id都是。
    
    	HANDLE hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, 0);
    	DWORD currentthreadid = GetCurrentThreadId();
    	DWORD processid = GetCurrentProcessId();
        if (hSnapshot != INVALID_HANDLE_VALUE)
        {
            THREADENTRY32 te;
            te.dwSize = sizeof(THREADENTRY32);
            if (Thread32First(hSnapshot, &te))
            {
                do
                {
    			if (processid == thread_entry32.th32OwnerProcessID
    				&& currentthreadid != thread_entry32.th32ThreadID) {
    					既是当前进程,又不是当前线程
    					调用DetourUpdateThread(OpenThread);
    				}
                   
                } while (Thread32Next(hSnapshot, &te));
            }
            CloseHandle(hSnapshot);
        }
    
    

    再推荐另外一个库,使用更便捷,api也简单易懂。不需要解决这个当前线程伪句柄的问题。

    minhook 同样支持线程安全。支持x86/x64。个人推荐。

    https://github.com/TsudaKageyu/minhook

    MH_Initialize()
    
    MH_CreateHook()
    
    //启用hook,这里内部会暂停其它线程和恢复线程
    MH_EnableHook()
    
    MH_DisableHook()
    
    MH_RemoveHook();
    
    MH_Uninitialize()
    
    当hook数量比较多的时候,最好用MH_ALL_HOOKS
    MH_EnableHook(MH_ALL_HOOKS);
    MH_DisableHook(MH_ALL_HOOKS);
    这样不用每次单独一个hook执行MH_EnableHook都会暂停和恢复线程。
    
    

    例子

    #include "MinHook.h"
    #if defined _M_X64
    #pragma comment(lib, "libMinHook.x64.lib")
    #elif defined _M_IX86
    #pragma comment(lib, "libMinHook.x86.lib")
    #endif
    
    #include <iostream>
    
    int Function(int x)
    {
    	x++;
    	std::cout << "real function" << std::endl;
    	return x;
    }
    
    typedef int (*FUNCTION)(int x);
    FUNCTION fpFunction = NULL;
    
    int DetourFunction(int x)
    {
    	x--;
    	std::cout << "fake function" << std::endl;
    	return x;
    }
    
    int main()
    {
    	int value = 0;
    	if (MH_Initialize() != MH_OK) {
    		return 1;
    	}
    
    	if (MH_CreateHook(&Function, &DetourFunction,
    		reinterpret_cast<LPVOID*>(&fpFunction)) != MH_OK) {
    		return 1;
    	}
    
    	if (MH_EnableHook(&Function) != MH_OK) {
    		return 1;
    	}
    
    	value = Function(123);
    
    	if (MH_DisableHook(&Function) != MH_OK) {
    		return 1;
    	}
    
    	value = Function(123);
    
    	if (MH_Uninitialize() != MH_OK) {
    		return 1;
    	}
    
    	return 0;
    }
    

    <6> thiscall hook的方法

    关于thiscall,就是c++的成员函数的调用方式,thiscall__cdecl, __stdcall,很大不同,原因在于vc的thiscall会固定this参数在ecx寄存器。

    如果构建一个thiscall or cdecl fake(void* this,...args)行不行呢?那肯定不行,因为thisecx寄存器传参,这里的this却是thiscall用栈传参(64位用寄存器),所以不能用c++语法的方式去写代码,要用汇编的角度去思考。

    而且thiscall不能标注在普通非成员函数的方法上,所以最好去创建一个类成员函数的指针,当调用类成员函数的指针,就会自己处理this ecx 函数参数的相关流程。

    class TestA
    {
    public:
    	TestA() {}
    	~TestA() {}
    
    public:
    	// 这是需要hook的函数
    	void ClassMemberFunction(void* arg)
    	{
    		printf("%s  this = %p arg = %p
    ", __FUNCTION__, this, arg);
    	}
    };
    
    class FakerClass;
    typedef void(__thiscall* mfunc)(FakerClass*, void*);
    mfunc org_mfunc = nullptr;
    
    struct FakerClass
    {
    	// 这是拦截的伪函数
    	void Mfunc(void* arg)
    	{
    		printf("%s  this = %p arg = %p
    ", __FUNCTION__, this, arg);
    		//调用原函数
    		org_mfunc(this, arg);
    	}
    };
    
    int main(int argc, char** argv)
    {
    	MH_Initialize();
    
    	//asMETHOD .ptr.f.func 的作用是获得成员函数的函数地址,当然你可以用其他方法去做
    
    	auto f = asMETHOD(TestA, ClassMemberFunction);
    	auto ff = asMETHOD(FakerClass, Mfunc);
    
    	auto s = MH_CreateHook(f.ptr.f.func, ff.ptr.f.func,
    		(void**)&org_mfunc);
    
    	if (s == MH_OK){
    		MH_EnableHook(MH_ALL_HOOKS);
    	}
    
    	TestA t;
    	void* arg = (void*)0x88888;
    	printf("t = %p
    ", &t);
    	t.ClassMemberFunction(arg);
    
    	return getchar();
    }
    
    
    t = 00F3FCEB
    FakerClass::Mfunc  this = 00F3FCEB arg = 00088888
    TestA::ClassMemberFunction  this = 00F3FCEB arg = 00088888
    

    <7> hook现有进程的其他事项

    当你需要对运行中的目标进程进行hook,你可能先需要知道目标函数的地址,这个地址应该是rva地址,就是相对于当前模块的偏移地址,rva地址+模块基地址=最终的函数在内存中的地址,因为模块在内存中的位置有可能每次启动都不一样,所以偏移地址+当前基地址才是正确的做法。

    你还需要注入逻辑,把自己的代码dll,注入到目标程序,否则无法方便操作,当前篇幅不涉及。

    <完> 2021年5月26日 qq: base64(MTcxMjgzNjQ0) 【转载请注明出处】。

  • 相关阅读:
    JS——jquery UI
    js——正则表达式
    jsonp——使用公共接口获取数据
    JS——json、ajax、jsonp
    [HNOI2011]括号修复 / [JSOI2011]括号序列
    [HNOI 2016] 树
    luogu_P3313 [SDOI2014]旅行
    无旋Treap模板
    [CF 718C] Sasha and Array
    [洛谷 P4556] 雨天的尾巴
  • 原文地址:https://www.cnblogs.com/luconsole/p/14813573.html
Copyright © 2011-2022 走看看