inline hook 原理 教程


inline hook 原理&教程

2021年5月24日

  • <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 \n", lpFileName);
	return NULL;
}

int main()
{

	HANDLE hFile;
	printf("第一次調用CreateFileA \n");
	hFile = CreateFileA(
		"abc.txt",
		GENERIC_WRITE,
		0,
		NULL,
		CREATE_ALWAYS,
		FILE_ATTRIBUTE_NORMAL,
		NULL);

	if (hFile == NULL) {
		printf("hFile==NULL\n");
	}
	else if (hFile != INVALID_HANDLE_VALUE) {
		printf("CreateFileA success\n");
		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\n");
		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 \n");
	hFile = CreateFileA(
		"abc.txt",
		GENERIC_WRITE,
		0,
		NULL,
		CREATE_ALWAYS,
		FILE_ATTRIBUTE_NORMAL,
		NULL);

	if (hFile == NULL) {
		printf("hFile==NULL\n");
	}
	else if (hFile != INVALID_HANDLE_VALUE) {
		CloseHandle(hFile);
	}

	std::cout << "Hello World!\n";
}

執行結果輸出為

第一次調用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 \n", 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 \n";
}

輸出:

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 \n", 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 \n");
		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 \n");
			assert(false);
		}
		else if ((hs.opcode & 0xFD) == 0xE9) {
			printf("JMP (EB or E9) \n");
			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 地址修復 \n");
			}
			else
				assert(false);

		}
		else if ((hs.opcode & 0xF0) == 0x70
			|| (hs.opcode & 0xFC) == 0xE0
			|| (hs.opcode2 & 0xF0) == 0x80) {
			printf(" Jcc \n");
			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 \n", 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 \n", 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\n");
		CloseHandle(hFile);
	}

	printf("end \n");
}


輸出:

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,很大不同,原因在於vcthiscall會固定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\n", __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\n", __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\n", &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) 【轉載請注明出處】。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM