windows 用户态逆向开发

sky123

基础

VS 导入第三方 lib 库

待更新

获取进程句柄

这里指的是如何通过进程名来获取目标进程 id 。这样做的好处是代码更具有通用性,因为通常每次程序启动 pid 会变化但进程名不变。

CreateToolhelp32Snapshot 函数用于创建一个包含系统中进程、线程、模块或堆的快照。我们使用它来获取当前系统中所有进程的快照。

1
2
3
4
cppCopy codeHANDLE CreateToolhelp32Snapshot(
DWORD dwFlags, // 想要获取的快照类型,这里我们使用TH32CS_SNAPPROCESS表示进程快照
DWORD th32ProcessID // 进程ID,如果设置为0,则获取所有进程的快照
);
  • dwFlags:指定快照的内容。对于进程快照,使用 TH32CS_SNAPPROCESS 。常用值含义如下:
    • TH32CS_SNAPPROCESS:表示进程快照。
    • TH32CS_SNAPTHREAD:表示线程快照。
    • TH32CS_SNAPMODULE:表示模块快照。
    • TH32CS_SNAPHEAPLIST:表示堆快照。
  • th32ProcessID:指定要快照的进程的 ID 。0 表示获取所有进程的快照。
  • 返回值:如果成功,返回快照的句柄;如果失败,返回 INVALID_HANDLE_VALUE

Process32First 函数用于获取进程快照中的第一个进程的信息。

1
2
3
4
cppCopy codeBOOL Process32First(
HANDLE hSnapshot, // 由CreateToolhelp32Snapshot返回的快照句柄
LPPROCESSENTRY32 lppe // 指向PROCESSENTRY32结构的指针,用于接收进程信息
);
  • hSnapshot:由 CreateToolhelp32Snapshot 返回的快照句柄。
  • lppe:指向 PROCESSENTRY32 结构的指针,用于接收第一个进程的信息。
  • 返回值:如果成功,返回非零值;如果失败或没有更多进程,返回零。

Process32Next 函数用于获取快照中的下一个进程的信息。

1
2
3
4
cppCopy codeBOOL Process32Next(
HANDLE hSnapshot, // 由CreateToolhelp32Snapshot返回的快照句柄
LPPROCESSENTRY32 lppe // 指向PROCESSENTRY32结构的指针,用于接收进程信息
);
  • hSnapshot:由 CreateToolhelp32Snapshot 返回的快照句柄。
  • lppe:指向 PROCESSENTRY32 结构的指针,用于接收下一个进程的信息。
  • 返回值:如果成功,返回非零值;如果失败或没有更多进程,返回零。

利用上述 API 可以枚举系统中的所有进程,以找到目标进程的进程 ID(PID)。注意,同一个程序可能会创建多个进程。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
#include <windows.h>
#include <tlhelp32.h>
#include <tchar.h>
#include <iostream>

// 获取目标进程的PID
DWORD GetTargetProcessId(const TCHAR *processName) {
DWORD processId = -1;
HANDLE hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
if (hSnapshot != INVALID_HANDLE_VALUE) {
PROCESSENTRY32 pe;
pe.dwSize = sizeof(pe);
if (Process32First(hSnapshot, &pe)) {
do {
if (_tcsicmp(pe.szExeFile, processName) == 0) {
processId = pe.th32ProcessID;
break;
}
} while (Process32Next(hSnapshot, &pe));
}
CloseHandle(hSnapshot);
}
return processId;
}

int _tmain() {
const TCHAR *targetProcessName = _T("target.exe");
std::cout << GetTargetProcessId(targetProcessName) << std::endl;
}

注入技术

远程线程注入

远程线程注入是一种将代码注入到目标进程的方法。它通过在目标进程中创建一个新的线程来执行我们指定的代码。

步骤

  1. 获取目标进程的句柄。
  2. 在目标进程的地址空间中分配内存。
  3. 将要执行的代码(如DLL路径)写入目标进程的内存中。
  4. 使用 CreateRemoteThread 函数在目标进程中创建一个新线程,使其执行我们指定的代码(如 LoadLibrary 函数)。
    img

API 介绍

OpenProcess 函数用于打开一个已存在的进程,并返回进程的句柄。

1
2
3
4
5
HANDLE OpenProcess(
DWORD dwDesiredAccess, // 想要获取的访问权限,这里我们使用PROCESS_ALL_ACCESS表示所有权限
BOOL bInheritHandle, // 如果为TRUE,句柄将被子进程继承
DWORD dwProcessId // 要打开的进程的ID
);
  • dwDesiredAccess:指定所需的访问权限。常用值:
    • PROCESS_ALL_ACCESS:表示所有可能的权限。
    • PROCESS_CREATE_THREAD:允许创建线程。
    • PROCESS_VM_OPERATION:允许虚拟内存操作。
    • PROCESS_VM_READ:允许读取进程的内存。
    • PROCESS_VM_WRITE:允许写入进程的内存。
  • bInheritHandle:指定新句柄是否可以被当前进程创建的子进程继承。如果为 TRUE ,则可以继承;否则不能。
  • dwProcessId:指定要打开的进程的 ID 。
  • 返回值:如果成功,返回进程的句柄;如果失败,返回 NULL

VirtualAllocEx 函数用于在目标进程的地址空间中分配内存。

1
2
3
4
5
6
7
cppCopy codeLPVOID VirtualAllocEx(
HANDLE hProcess, // 目标进程的句柄
LPVOID lpAddress, // 指定内存地址,可以为NULL
SIZE_T dwSize, // 要分配的内存大小
DWORD flAllocationType, // 内存分配类型,这里使用MEM_COMMIT
DWORD flProtect // 内存保护属性,这里使用PAGE_READWRITE
);
  • hProcess:目标进程的句柄。
  • lpAddress:指定内存地址,可以为 NULL ,表示由系统决定分配位置。
  • dwSize:要分配的内存大小。
  • flAllocationType:内存分配类型。常用值:
    • MEM_COMMIT:分配物理内存并将其初始化为零。
    • MEM_RESERVE:保留一块虚拟地址空间,但不分配物理内存。
  • flProtect:内存保护属性。常用值:
    • PAGE_READWRITE:可读写的内存。
    • PAGE_READONLY:只读内存。
    • PAGE_EXECUTE:可执行内存。
  • 返回值:如果成功,返回分配的内存地址;如果失败,返回NULL

WriteProcessMemory 函数用于将数据写入目标进程的内存中。

1
2
3
4
5
6
7
BOOL WriteProcessMemory(
HANDLE hProcess, // 目标进程的句柄
LPVOID lpBaseAddress, // 目标进程的内存地址
LPCVOID lpBuffer, // 要写入的数据缓冲区
SIZE_T nSize, // 要写入的数据大小
SIZE_T *lpNumberOfBytesWritten // 实际写入的数据大小,可以为NULL
);
  • hProcess:目标进程的句柄。
  • lpBaseAddress:目标进程的内存地址。
  • lpBuffer:要写入的数据缓冲区。
  • nSize:要写入的数据大小。
  • lpNumberOfBytesWritten:实际写入的数据大小,可以为 NULL
  • 返回值:如果成功,返回非零值;如果失败,返回零。

CreateRemoteThread 函数用于在目标进程中创建一个新线程。

1
2
3
4
5
6
7
8
9
HANDLE CreateRemoteThread(
HANDLE hProcess, // 目标进程的句柄
LPSECURITY_ATTRIBUTES lpThreadAttributes, // 线程安全属性,可以为NULL
SIZE_T dwStackSize, // 线程的堆栈大小,可以为0
LPTHREAD_START_ROUTINE lpStartAddress, // 新线程的起始地址
LPVOID lpParameter, // 传递给新线程的参数
DWORD dwCreationFlags, // 线程创建标志,这里使用0
LPDWORD lpThreadId // 返回新线程的ID,可以为NULL
);
  • hProcess:目标进程的句柄。
  • lpThreadAttributes:线程安全属性,可以为 NULL
  • dwStackSize:线程的堆栈大小,可以为 0 。
  • lpStartAddress:新线程的起始地址。
  • lpParameter:传递给新线程的参数。
  • dwCreationFlags:线程创建标志,常用的是 0 。
  • lpThreadId:返回新线程的 ID ,可以为 NULL
  • 返回值:如果成功,返回新线程的句柄;如果失败,返回 NULL

代码示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
#include <windows.h>
#include <tlhelp32.h>
#include <tchar.h>
#include <iostream>

// 获取目标进程的PID
DWORD GetTargetProcessId(const TCHAR* processName) {
DWORD processId = 0;
HANDLE hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
if (hSnapshot != INVALID_HANDLE_VALUE) {
PROCESSENTRY32 pe;
pe.dwSize = sizeof(pe);
if (Process32First(hSnapshot, &pe)) {
do {
if (_tcsicmp(pe.szExeFile, processName) == 0) {
processId = pe.th32ProcessID;
break;
}
} while (Process32Next(hSnapshot, &pe));
}
CloseHandle(hSnapshot);
}
return processId;
}

// 注入DLL到目标进程
BOOL InjectDLL(DWORD processId, const TCHAR *dllPath) {
HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, processId);
if (hProcess == NULL) {
_tprintf(_T("[-] Failed to open target process.\n"));
return FALSE;
}

// 将相对路径转换为完整路径
TCHAR fullDllPath[MAX_PATH];
if (!GetFullPathName(dllPath, MAX_PATH, fullDllPath, NULL)) {
_tprintf(_T("[-] Failed to get full path of DLL.\n"));
return FALSE;
}

// 分配内存
LPVOID pRemoteBuf = VirtualAllocEx(hProcess, NULL, (_tcslen(fullDllPath) + 1) * sizeof(TCHAR), MEM_COMMIT, PAGE_READWRITE);
if (pRemoteBuf == NULL) {
CloseHandle(hProcess);
_tprintf(_T("[-] Failed to allocate memory in target process.\n"));
return FALSE;
}

// 写入DLL路径
if (!WriteProcessMemory(hProcess, pRemoteBuf, (LPVOID) fullDllPath, (_tcslen(fullDllPath) + 1) * sizeof(TCHAR), NULL)) {
VirtualFreeEx(hProcess, pRemoteBuf, 0, MEM_RELEASE);
CloseHandle(hProcess);
_tprintf(_T("[-] Failed to write DLL path to target process memory.\n"));
return FALSE;
}

// 获取LoadLibrary的地址
HMODULE hKernel32 = GetModuleHandle(_T("Kernel32.dll"));
LPVOID pLoadLibrary = NULL;

#ifdef UNICODE
pLoadLibrary = (LPVOID) GetProcAddress(hKernel32, "LoadLibraryW");
#else
pLoadLibrary = (LPVOID) GetProcAddress(hKernel32, "LoadLibraryA");
#endif

if (pLoadLibrary == NULL) {
VirtualFreeEx(hProcess, pRemoteBuf, 0, MEM_RELEASE);
CloseHandle(hProcess);
_tprintf(_T("[-] Failed to get address of LoadLibrary.\n"));
return FALSE;
}

// 创建远程线程加载DLL
HANDLE hThread = CreateRemoteThread(hProcess, NULL, 0, (LPTHREAD_START_ROUTINE) pLoadLibrary, pRemoteBuf, 0, NULL);
if (hThread == NULL) {
VirtualFreeEx(hProcess, pRemoteBuf, 0, MEM_RELEASE);
CloseHandle(hProcess);
_tprintf(_T("[-] Failed to create remote thread in target process.\n"));
return FALSE;
}

// 等待远程线程结束
WaitForSingleObject(hThread, INFINITE);
VirtualFreeEx(hProcess, pRemoteBuf, 0, MEM_RELEASE);
CloseHandle(hThread);
CloseHandle(hProcess);
_tprintf(_T("[+] DLL injected successfully.\n"));
return TRUE;
}

int _tmain() {
const TCHAR* targetProcessName = _T("target_process.exe");
const TCHAR* dllPath = _T("inject_dll.dll");

DWORD processId = GetTargetProcessId(targetProcessName);
if (processId == 0) {
_tprintf(_T("[-] Target process not found.\n"));
return 1;
}

if (InjectDLL(processId, dllPath)) {
_tprintf(_T("[+] DLL injected successfully.\n"));
} else {
_tprintf(_T("[-] Failed to inject DLL.\n"));
}

return 0;
}

Hook 技术

Inline Hook

Inline Hook 的演变

首先最原始的 Inline Hook 是直接暴力的在 targetFunc 的开头 patch 一条跳转到 hookFunc 的汇编指令。这样程序在调用函数的时候会调用到我们的自己实现的 Hook 函数上。

然而这样存在 Hook 重入问题,即如果我们想在 hookFunc 中调用 targetFunc 会出现无限递归的情况,因此在 hookFunc 调用 targetFunc 前需要恢复 targetFunc 前面被 patch 的部分。而调用 targetFunc 后需要重新恢复 Hook 。

然而这种方法每次调用 targetFunc 前后都要修改 targetFunc 开头,因此如果多个线程同时调用 targetFunc 会出现多线程竞争问题。

因此可以借助跳板函数 originalFunc 来解决重入问题,这样就不需要多次修改 targetFunc,也就没有线程竞争问题。

originalFunc 中我们会执行 targetFunc 中被 Hook 点覆盖的指令,从而确保 targetFunc 的完整性,然而 targetFunc 前面对应跳转指令长度的指令不一定是完整的指令。然而由于 Hook 是 Windows 中常用的手段,因此微软在开发 Windows API 时会确保函数开头处的指令长度对应跳转指令长度。

例如在 32 位下,LoadLibraryW 函数开头通过添加 mov edi, edi 指令恰好凑出了总长度为 5 字节的指令,这也正是 32 位下跳转指令的长度。

1
2
3
4
KERNEL32!LoadLibraryWStub:
76f3d8a0 8bff mov edi,edi
76f3d8a2 55 push ebp
76f3d8a3 8bec mov ebp,esp

在 64 位下,LoadLibraryW 函数开头是一个跳转指令并且后面用 INT3 指令填充,因此我们可以使用 FF 25 的跳转指令来实现任意地址跳转。

1
2
3
4
5
6
7
8
9
KERNEL32!LoadLibraryWStub:
00007ff8`db4b8be0 48ff2539be0600 jmp qword ptr [KERNEL32!_imp_LoadLibraryW (00007ff8`db524a20)]
00007ff8`db4b8be7 cc int 3
00007ff8`db4b8be8 cc int 3
00007ff8`db4b8be9 cc int 3
00007ff8`db4b8bea cc int 3
00007ff8`db4b8beb cc int 3
00007ff8`db4b8bec cc int 3
00007ff8`db4b8bed cc int 3

FF 25 00 00 00 00 的跳转指令翻译过来是 jmp qword prt ds:[下一条指令地址],因此只要紧跟着 8 字节的要跳转到的地址就可以实现任意地址跳转,总指令长度为 14 字节。

这里简单实现了一下对 MessageBox 函数的 Hook 。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
#include "pch.h"
#include <iostream>
#include <tchar.h>
#include <windows.h>

typedef int(WINAPI *MessageBox_t)(HWND, LPCTSTR, LPCTSTR, UINT);
MessageBox_t OriginalMessageBox = nullptr;

#ifdef _WIN64
constexpr size_t byteCount = 14;
#else
constexpr size_t byteCount = 5;
#endif

int WINAPI HookedMessageBox(HWND hWnd, LPCTSTR lpText, LPCTSTR lpCaption, UINT uType) {
_tprintf(_T("[+] Hooked MessageBox called with text: %s\n"), lpText);
return OriginalMessageBox(hWnd, _T("Hooked!"), lpCaption, uType);
}

void InlineHook(void *targetFunc, void *hookFunc, void **originalFunc) {
DWORD oldProtect;

// 分配内存用于跳板函数
*originalFunc = VirtualAlloc(NULL, byteCount * 2, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);
if (*originalFunc == NULL) {
_tprintf(_T("[-] Failed to allocate memory for trampoline.\n"));
return;
}

// 修改目标函数的保护属性以允许写入
VirtualProtect(targetFunc, byteCount, PAGE_EXECUTE_READWRITE, &oldProtect);

// 复制原始字节到跳板函数
memcpy(*originalFunc, targetFunc, byteCount);

#ifdef _WIN64
// 64位跳转

// 设置跳板函数末尾的跳转指令,从跳板函数跳回目标函数未被覆盖的部分
*(BYTE *) ((DWORD64) *originalFunc + byteCount) = (BYTE) 0xFF;// JMP指令的前两个字节
*(BYTE *) ((DWORD64) *originalFunc + byteCount + 1) = 0x25;
*(DWORD *) ((DWORD64) *originalFunc + byteCount + 2) = 0x00000000; // 跳转地址为后续8字节
*(DWORD64 *) ((DWORD64) *originalFunc + byteCount + 6) = (DWORD64) targetFunc + byteCount;// 跳转地址

// 设置目标函数的跳转指令,从目标函数跳到钩子函数
*(BYTE *) targetFunc = (BYTE)0xFF;// JMP指令的前两个字节
*(BYTE *) ((DWORD64) targetFunc + 1) = 0x25;
*(DWORD *) ((DWORD64) targetFunc + 2) = 0x00000000; // 跳转地址为后续8字节
*(DWORD64 *) ((DWORD64) targetFunc + 6) = (DWORD64) hookFunc;// 跳转地址
#else
// 32位跳转
DWORD targetAddr = (DWORD) targetFunc + byteCount; // 计算目标函数下一指令的地址
DWORD trampolineAddr = (DWORD) *originalFunc + byteCount;// 计算跳板函数下一指令的地址

// 设置跳板函数末尾的跳转指令,从跳板函数跳回目标函数未被覆盖的部分
*(BYTE *) ((DWORD) *originalFunc + byteCount) = 0xE9; // JMP指令
*(DWORD *) ((DWORD) *originalFunc + byteCount + 1) = targetAddr - (trampolineAddr + byteCount);// 计算相对跳转地址

// 设置目标函数的跳转指令,从目标函数跳到钩子函数
*(BYTE *) targetFunc = 0xE9; // JMP指令
*(DWORD *) ((DWORD) targetFunc + 1) = (DWORD) hookFunc - ((DWORD) targetFunc + byteCount);// 计算相对跳转地址
#endif

VirtualProtect(targetFunc, byteCount, oldProtect, &oldProtect);

_tprintf(_T("[+] Inline hook installed successfully.\n"));
}

void Unhook(void *targetFunc, void *originalFunc) {
DWORD oldProtect;

VirtualProtect(targetFunc, byteCount, PAGE_EXECUTE_READWRITE, &oldProtect);
memcpy(targetFunc, originalFunc, byteCount);
VirtualProtect(targetFunc, byteCount, oldProtect, &oldProtect);
VirtualFree(originalFunc, 0, MEM_RELEASE);

_tprintf(_T("[+] Inline hook removed successfully.\n"));
}

void InstallHook() {
InlineHook((void *) MessageBoxW, (void *) HookedMessageBox, (void **) &OriginalMessageBox);
}

void UninstallHook() {
if (OriginalMessageBox) {
Unhook((void *) MessageBoxW, (void *) OriginalMessageBox);
}
}

BOOL APIENTRY DllMain(HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved) {
switch (ul_reason_for_call) {
case DLL_PROCESS_ATTACH:
_tprintf(_T("[+] DLL loaded.\n"));
InstallHook();
break;
case DLL_PROCESS_DETACH:
_tprintf(_T("[+] DLL unloaded.\n"));
UninstallHook();
break;
}
return TRUE;
}

然而在 64 位运行时会发现出错了,这是因为 64 位下的 MessageBox 的开头是如下代码,这里第三条指令如果只是复制到 originalFunc 没有重定位会导致访存错误。因此这种方法也是不完善的(需要假定微软预留了 Hook 的位置)。

1
2
3
4
USER32!MessageBoxW:
00007ff8`dae2b240 4883ec38 sub rsp,38h
00007ff8`dae2b244 4533db xor r11d,r11d
00007ff8`dae2b247 44391d4af00300 cmp dword ptr [USER32!gfEMIEnable (00007ff8`dae6a298)],r11d

Detours 库

Detours是由微软开发的一种用于拦截函数调用和重定向代码执行的动态链接库。它被广泛用于性能监控、调试、日志记录和安全检查等场景。Detours通过修改目标函数的入口点,实现对函数调用的拦截,并将调用重定向到用户定义的钩子函数。

Detours 库通过如下 API 完成 inline Hook 操作:

1
LONG DetourTransactionBegin();

DetourTransactionBegin 函数用于开始一个 Detours 事务。在事务中进行的所有操作都是原子的,也就是说,所有操作要么全部成功,要么全部失败,这样可以保证钩子操作的完整性和一致性。

  • 初始化一个全局变量来保存事务的状态。
  • 事务开始后,可以进行多个钩子操作(附加、分离等),直到事务提交或中止。
1
LONG DetourUpdateThread(HANDLE hThread);

DetourUpdateThread 函数将当前线程包含在 Detours 事务中,确保事务中的所有操作在同一个线程上下文中进行,防止多线程竞争问题。

  • 将当前线程的上下文信息保存到事务中。
  • 确保所有的钩子操作在同一个线程内执行,以避免多线程环境下的竞争和不一致。
1
PVOID DetourFindFunction(LPCSTR lpModule, LPCSTR lpFunction);

DetourFindFunction 函数用于查找指定模块中的函数地址。它帮助我们找到需要钩子的目标函数地址。

1
LONG DetourAttach(PVOID *ppPointer, PVOID pDetour);

DetourAttach 函数用于附加一个钩子函数,将目标函数重定向到钩子函数。

  • ppPointer:指向目标函数指针的指针。
  • pDetour:指向钩子函数的指针。
1
LONG DetourDetach(PVOID *ppPointer, PVOID pDetour);

DetourDetach 函数用于分离一个钩子函数,恢复目标函数的原始行为。

  • ppPointer:指向目标函数指针的指针。
  • pDetour:指向钩子函数的指针。
1
LONG DetourTransactionCommit();

DetourTransactionCommit 函数用于中止一个 Detours 事务,放弃所有未提交的钩子操作。

1
LONG DetourTransactionAbort();

DetourTransactionAbort 函数用于中止一个Detours事务,放弃所有未提交的钩子操作。

这里利用 Detours 库同样实现了对 MessageBox 的 Hook 。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
#include "pch.h"
#include <windows.h>
#include <tchar.h>
#include <iostream>
#include "detours.h"
#pragma comment(lib, "detours.lib")

// 定义原始MessageBoxW函数的类型
typedef int(WINAPI *MessageBox_t)(HWND, LPCTSTR, LPCTSTR, UINT);
MessageBox_t OriginalMessageBox = nullptr;

// 钩子函数
int WINAPI HookedMessageBox(HWND hWnd, LPCTSTR lpText, LPCTSTR lpCaption, UINT uType) {
_tprintf(_T("[+] Hooked MessageBox called with text: %s\n"), lpText);
return OriginalMessageBox(hWnd, _T("Hooked!"), lpCaption, uType);
}

// 安装钩子
void InstallHook() {
LONG error = DetourTransactionBegin();
if (error != NO_ERROR) {
_tprintf(_T("[-] DetourTransactionBegin failed: %d\n"), error);
return;
}

error = DetourUpdateThread(GetCurrentThread());
if (error != NO_ERROR) {
_tprintf(_T("[-] DetourUpdateThread failed: %d\n"), error);
DetourTransactionAbort();
return;
}

OriginalMessageBox = (MessageBox_t) DetourFindFunction("user32.dll", "MessageBoxW");
if (OriginalMessageBox == NULL) {
_tprintf(_T("[-] DetourFindFunction failed.\n"));
DetourTransactionAbort();
return;
}

error = DetourAttach(&(PVOID &) OriginalMessageBox, HookedMessageBox);
if (error != NO_ERROR) {
_tprintf(_T("[-] DetourAttach failed: %d\n"), error);
DetourTransactionAbort();
return;
}

error = DetourTransactionCommit();
if (error != NO_ERROR) {
_tprintf(_T("[-] DetourTransactionCommit failed: %d\n"), error);
return;
}

_tprintf(_T("[+] Hook installed successfully.\n"));
}

// 卸载钩子
void UninstallHook() {
LONG error = DetourTransactionBegin();
if (error != NO_ERROR) {
_tprintf(_T("[-] DetourTransactionBegin failed: %d\n"), error);
return;
}

error = DetourUpdateThread(GetCurrentThread());
if (error != NO_ERROR) {
_tprintf(_T("[-] DetourUpdateThread failed: %d\n"), error);
DetourTransactionAbort();
return;
}

error = DetourDetach(&(PVOID &) OriginalMessageBox, HookedMessageBox);
if (error != NO_ERROR) {
_tprintf(_T("[-] DetourDetach failed: %d\n"), error);
DetourTransactionAbort();
return;
}

error = DetourTransactionCommit();
if (error != NO_ERROR) {
_tprintf(_T("[-] DetourTransactionCommit failed: %d\n"), error);
return;
}

_tprintf(_T("[+] Hook removed successfully.\n"));
}

// DLL入口点
BOOL APIENTRY DllMain(HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved) {
switch (ul_reason_for_call) {
case DLL_PROCESS_ATTACH:
DetourRestoreAfterWith();
InstallHook();
break;
case DLL_PROCESS_DETACH:
UninstallHook();
break;
}
return TRUE;
}

以 64 位 Hook 为例,观察发现 Detour 库的实现基本原理和我们自己实现的 Inline Hook 基本相同,不过 Detour 库实现的 Hook 点长度更短。另外由于 Detour 是微软官方使用的,兼容性更强,同时还加入了大量的锁操作确保线程安全,因此推荐使用 Detour 来实现 Inline Hook 。

  • Title: windows 用户态逆向开发
  • Author: sky123
  • Created at : 2022-09-28 11:45:14
  • Updated at : 2025-06-01 23:25:42
  • Link: https://skyi23.github.io/2022/09/28/windows 用户态逆向开发/
  • License: This work is licensed under CC BY-NC-SA 4.0.
Comments