API Hooking

一、介绍

API  hook(钩取)是一种用来拦截和修改 API 函数行为的技术。它常用于调试、逆向工程和游戏作弊。API 钩取涉及用一个自定义版本替换 API 函数的原始实现,该自定义版本在调用原始函数之前或之后执行一些附加动作。这允许人们在不修改程序源代码的情况下修改其行为 。

1.1 Trampolines(跳板

实现 API 劫持的传统方法是通过跳转。Trampolines是一种 shellcode,用于通过跳转(jmp指令)到进程地址空间内的另一个特定地址来改变代码执行路径。Trampolines shellcode 被插入函数开头,导致函数被劫持。当被劫持的函数被调用时,将Trampolines shellcode,并且执行流被传递并更改到另一个地址,从而导致执行其他函数。

1.2 Inline Hooking

Inline Hook是一种替代执行 API 挂载的方法,其工作原理类似于基于跳转的hook。不同之处在于hook会将执行完成后返回到合法功能,允许正常执行继续进行。虽然更复杂且更难维护,但Inline Hook更有效率。

二、API Hooking - Detours库

Detours是 Microsoft Research 开发的软件库,可用于拦截和重定向 Windows 中的函数调用。该库将特定函数的调用重定向到用户定义的替换函数,然后该函数可以执行其他任务或修改原始函数的行为。Detours 通常用于 C/C++ 程序,可用于 32 位和 64 位应用程序。Detours 库将目标函数(即要挂钩的函数)的前几条指令替换为无条件跳转到用户提供的 detour 函数(即要执行的函数)。

要使用 Detours 库函数,必须下载并编译 Detours 存储库,以获取编译所需的静态库文件(.lib)。此外,还应包含 detours.h头文件

下载https://github.com/microsoft/Detours/releases

使用x64/x86 Visual Studio 2022 Developer Command Prompt nmake命令分别编译

编译过程中会出现error不影响是编译samples出现的问题,看到bin\lib对应程序集就成功了

 添加包含目录修改库目录为对应的程序集

 Detours API 函数

在使用任何钩子方法时,第一步总是获取要钩住的 WinAPI 函数的地址。函数的地址是确定将放置跳转指令的位置的必要条件。在 本文MessageBoxA 函数将被用作要钩住的函数。

以下是 Detours 库提供的 API 函数:

  • DetourTransactionBeginopen - 开始一个新的事务用于附加或分离钩子。钩住和解钩时应首先调用此函数。
  • DetourUpdateThreadopen - 更新当前事务。Detours 库使用它将一个线程“登记”到当前事务。
  • DetourAttachopen - 在当前事务中,将钩子安装到目标函数。在调用 DetourTransactionCommit 之前不会提交此钩子。
  • DetourDetachopen- 在当前事务中,从目标函数中移除钩子。在调用 DetourTransactionCommit 之前不会提交此钩子。
  • DetourTransactionCommitopen - 提交当前附加或分离钩子的事务。

上述函数返回一个 LONG 值,用于理解函数执行的结果。Detours API 将在成功时返回 NO_ERROR(即 0),在失败时返回一个非零值。非零值可用作调试目的的错误代码。

 替换已经挂钩的 API

下一步是创建一个函数来替换已经挂钩的 API。替换函数应该具有相同的数据类型,并且可以选择使用相同参数,这允许检查或修改参数值。例如,以下函数可以用作 MessageBoxA 的钩子函数,允许检查原始参数值。

INT WINAPI MyMessageBoxA(HWND hWnd, LPCSTR lpText, LPCSTR lpCaption, UINT uType) {
  // 这里可以检查 hWnd - lpText - lpCaption - uType 参数
}

需要注意的是,替换函数可以使用较少参数,但不能使用比原始函数更多的参数,因为这样会访问无效地址,并抛出访问冲突异常。

 无限循环问题

当连接到一个函数并触发钩子时,会执行自定义函数。但是,为了继续执行,自定义函数必须返回原始挂钩函数应返回的有效值。一种简单的做法是在挂钩中通过调用原始函数来返回相同的值。这可能会导致问题,因为会调用替换函数,从而导致无限循环。这是一个通用的挂钩问题,而不是 Detours 库中的错误。

为了更好地理解这一点,下面的代码段展示了替换函数 MyMessageBoxA 调用 MessageBoxA。这样会导致无限循环。程序会陷入运行 MyMessageBoxA 的状态,这是因为 MyMessageBoxA 正在调用 MessageBoxA,而 MessageBoxA 又会再次指向 MyMessageBoxA 函数。 

INT WINAPI MyMessageBoxA(HWND hWnd, LPCSTR lpText, LPCSTR lpCaption, UINT uType) {
  // 打印原始参数值
  printf("Original lpText Parameter : %s\n", lpText);
  printf("Original lpCaption Parameter : %s\n", lpCaption);
   
  // 不要这样做
  // 更改参数值
  return MessageBoxA(hWnd, "different lpText", "different lpCaption", uType); // 调用 MessageBoxA(已挂钩)
}

解决方案一:全局原始函数指针

Detour 库可以通过在挂钩函数之前保存指向原始函数的指针来解决此问题。该指针可以存储在全局变量中,并在 detour 函数中调用,而不是挂钩函数。

// 用作在 `MyMessageBoxA` 中未挂钩的 MessageBoxA
fnMessageBoxA g_pMessageBoxA = MessageBoxA;
 
INT WINAPI MyMessageBoxA(HWND hWnd, LPCSTR lpText, LPCSTR lpCaption, UINT uType) {
  // 打印原始参数值
  printf("原始 lpText 参数:%s\n", lpText);
  printf("原始 lpCaption 参数:%s\n", lpCaption);
   
  // 更改参数值
  // 调用未挂钩的 MessageBoxA
  return g_pMessageBoxA(hWnd, "不同的 lpText", "不同的 lpCaption", uType);
}

解决方案 2 - 使用不同的 API

另一个值得一提的更通用的解决方法,是调用一个与目标函数具有相同功能的不同“未挂钩”函数。例如 MessageBoxA 和 MessageBoxWVirtualAlloc 和 VirtualAllocEx

INT WINAPI MyMessageBoxA(HWND hWnd, LPCSTR lpText, LPCSTR lpCaption, UINT uType) {
  // 打印原始参数值
  printf("原始 lpText 参数 :%s\n", lpText);
  printf("原始 lpCaption 参数:%s\n", lpCaption);
   
  // 修改参数值
  return MessageBoxW(hWnd, L"不同的 lpText", L"不同的 lpCaption", uType);
}

Detours 钩子函数

如前所述,Detours 库通过事务来工作,因此要钩取一个 API 函数,必须创建一个事务、提交一个操作(钩取/解钩)到事务,然后提交事务。下面的代码片段执行了这些步骤。

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
// 在 `MyMessageBoxA` 中用作未钩取的 MessageBoxA
// 并被 `DetourAttach` 和 `DetourDetach` 使用
fnMessageBoxA g_pMessageBoxA = MessageBoxA;
 
 
// 钩取后将在 MessageBoxA 代替运行的函数
INT WINAPI MyMessageBoxA(HWND hWnd, LPCSTR lpText, LPCSTR lpCaption, UINT uType) {
 
    printf("[+] 原始参数:\n");
    printf("\t - lpText:%s\n", lpText);
    printf("\t - lpCaption:%s\n", lpCaption);
 
    return g_pMessageBoxA(hWnd, "不同的 lpText", "不同的 lpCaption", uType);
}
 
 
BOOL InstallHook() {
     
    DWORD dwDetoursErr = NULL;
 
    // 创建事务并更新它
    if ((dwDetoursErr = DetourTransactionBegin()) != NO_ERROR) {
        printf("[!] DetourTransactionBegin 失败,错误为:%d\n", dwDetoursErr);
        return FALSE;
    }
     
    if ((dwDetoursErr = DetourUpdateThread(GetCurrentThread())) != NO_ERROR) {
        printf("[!] DetourUpdateThread 失败,错误为:%d\n", dwDetoursErr);
        return FALSE;
    }
     
    // 在 g_pMessageBoxA 代替执行 MyMessageBoxA,g_pMessageBoxA 就是 MessageBoxA
    if ((dwDetoursErr = DetourAttach((PVOID)&g_pMessageBoxA, MyMessageBoxA)) != NO_ERROR) {
        printf("[!] DetourAttach 失败,错误为:%d\n", dwDetoursErr);
        return FALSE;
    }
 
    // 实际的钩子会在 `DetourTransactionCommit` 之后安装——提交事务
    if ((dwDetoursErr = DetourTransactionCommit()) != NO_ERROR) {
        printf("[!] DetourTransactionCommit 失败,错误为:%d\n", dwDetoursErr);
        return FALSE;
    }
 
    return TRUE;
}

Detours 取消挂钩  

/ 用于作为在 `MyMessageBoxA` 中取消挂钩的 MessageBoxA
// 并由 `DetourAttach` 和 `DetourDetach` 使用
fnMessageBoxA g_pMessageBoxA = MessageBoxA;
 
 
// 在挂钩时将代替 MessageBoxA 运行的函数
INT WINAPI MyMessageBoxA(HWND hWnd, LPCSTR lpText, LPCSTR lpCaption, UINT uType) {
 
    printf("[+] 原始参数 : \n");
    printf("\t - lpText : %s\n", lpText);
    printf("\t - lpCaption  : %s\n", lpCaption);
 
    return g_pMessageBoxA(hWnd, "不同的 lpText", "不同的 lpCaption", uType);
}
 
 
BOOL Unhook() {
 
    DWORD   dwDetoursErr = NULL;
 
    // 创建事务并更新它
    if ((dwDetoursErr = DetourTransactionBegin()) != NO_ERROR) {
        printf("[!] DetourTransactionBegin 出错,错误码 : %d \n", dwDetoursErr);
        return FALSE;
    }
 
    if ((dwDetoursErr = DetourUpdateThread(GetCurrentThread())) != NO_ERROR) {
        printf("[!] DetourUpdateThread 出错,错误码 : %d \n", dwDetoursErr);
        return FALSE;
    }
 
    // 从 MessageBoxA 中移除挂钩
    if ((dwDetoursErr = DetourDetach((PVOID)&g_pMessageBoxA, MyMessageBoxA)) != NO_ERROR) {
        printf("[!] DetourDetach 出错,错误码 : %d \n", dwDetoursErr);
        return FALSE;
    }
 
    // 实际的挂钩移除发生在 `DetourTransactionCommit` 之后 - 提交事务
    if ((dwDetoursErr = DetourTransactionCommit()) != NO_ERROR) {
        printf("[!] DetourTransactionCommit 出错,错误码 : %d \n", dwDetoursErr);
        return FALSE;
    }
 
    return TRUE;
}
 
主函数
前面展示的挂钩和取消挂钩的例程不包括主函数。下面展示了主函数,它仅从挂钩和未挂钩版本调用 MessageBoxA。
 
int main() {
 
    // 直接运行,未挂钩
    MessageBoxA(NULL, "您如何看待恶意软件开发?", "原始 MsgBox", MB_OK | MB_ICONQUESTION);
 
 
//------------------------------------------------------------------
    //  挂钩
    if (!InstallHook())
        return -1;
 
//------------------------------------------------------------------   
    // 不会直接运行 - 将运行 MyMessageBoxA
    MessageBoxA(NULL, "恶意软件开发是错误的", "原始 MsgBox", MB_OK | MB_ICONWARNING);
 
 
//------------------------------------------------------------------
    //  取消挂钩
    if (!Unhook())
        return -1;
         
//------------------------------------------------------------------
    // 直接运行,已取消挂钩
    MessageBoxA(NULL, "正常 MsgBox 已恢复", "原始 MsgBox", MB_OK | MB_ICONINFORMATION);
   
    return 0;
}

完整代码

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
110
111
112
113
114
115
116
117
118
119
#include<stdio.h>
#include<windows.h>
#include "detours.h"
 
// 如果是64位系统,链接 detours 库
#ifdef _M_X64
#pragma comment (lib, "detours.lib")
#endif
 
// 定义一个函数指针类型 fnMessageBox,用于指向 MessageBoxA 函数
typedef INT(WINAPI* fnMessageBox)(HWND hWnd, LPCSTR lpText, LPCSTR lpCaption, UINT uType);
 
// 定义一个全局的函数指针变量 g_pMessageBoxA,用于保存原始的 MessageBoxA 函数地址
fnMessageBox g_pMessageBoxA = (fnMessageBox)MessageBoxA;
 
// 这是当钩子安装后替换 MessageBoxA 的函数
INT WINAPI MyMessageBoxA(HWND hWnd, LPCSTR lpText, LPCSTR lpCaption, UINT uType) {
 
    // 打印原始的 MessageBoxA 参数
    printf("[+] 原始参数 : \n");
    printf("\t - lpText : %s\n", lpText);
    printf("\t - lpCaption : %s\n", lpCaption);
 
    // 使用不同的文本和标题显示 MessageBox,而不是原来的文本
    return g_pMessageBoxA(hWnd, "different lpText", "different lpCaption", uType);
}
 
// 安装钩子函数,将 MessageBoxA 替换为 MyMessageBoxA
BOOL InstallHook() {
 
    DWORD dwDetoursErr = NULL;
 
    // 开始钩子事务
    if ((dwDetoursErr = DetourTransactionBegin()) != NO_ERROR) {
        printf("[!] DetourTransactionBegin 失败,错误码:%d \n", dwDetoursErr);
        return FALSE;
    }
 
    // 更新当前线程的钩子事务
    if ((dwDetoursErr = DetourUpdateThread(GetCurrentThread())) != NO_ERROR) {
        printf("[!] DetourUpdateThread 失败,错误码:%d \n", dwDetoursErr);
        return FALSE;
    }
 
    // 将原始的 MessageBoxA 替换为 MyMessageBoxA
    if ((dwDetoursErr = DetourAttach(&(PVOID&)g_pMessageBoxA, MyMessageBoxA)) != NO_ERROR) {
        printf("[!] DetourAttach 失败,错误码:%d \n", dwDetoursErr);
        return FALSE;
    }
 
    // 提交钩子事务,使钩子生效
    if ((dwDetoursErr = DetourTransactionCommit()) != NO_ERROR) {
        printf("[!] DetourTransactionCommit 失败,错误码:%d \n", dwDetoursErr);
        return FALSE;
    }
 
    return TRUE; // 钩子安装成功
}
 
// 卸载钩子函数,恢复 MessageBoxA 为原始版本
BOOL Unhook() {
 
    DWORD dwDetoursErr = NULL;
 
    // 开始钩子事务
    if ((dwDetoursErr = DetourTransactionBegin()) != NO_ERROR) {
        printf("[!] DetourTransactionBegin 失败,错误码:%d \n", dwDetoursErr);
        return FALSE;
    }
 
    // 更新当前线程的钩子事务
    if ((dwDetoursErr = DetourUpdateThread(GetCurrentThread())) != NO_ERROR) {
        printf("[!] DetourUpdateThread 失败,错误码:%d \n", dwDetoursErr);
        return FALSE;
    }
 
    // 移除钩子,将 MyMessageBoxA 恢复为原始的 MessageBoxA
    if ((dwDetoursErr = DetourDetach(&(PVOID&)g_pMessageBoxA, MyMessageBoxA)) != NO_ERROR) {
        printf("[!] DetourDetach 失败,错误码:%d \n", dwDetoursErr);
        return FALSE;
    }
 
    // 提交钩子事务,使钩子移除生效
    if ((dwDetoursErr = DetourTransactionCommit()) != NO_ERROR) {
        printf("[!] DetourTransactionCommit 失败,错误码:%d \n", dwDetoursErr);
        return FALSE;
    }
 
    return TRUE; // 钩子卸载成功
}
 
int main() {
 
    // 调用未被钩住的 MessageBoxA,显示原始的文本
    MessageBoxA(NULL, "What Do You Think About Malware Development ?", "Original MsgBox", MB_OK | MB_ICONQUESTION);
 
    //------------------------------------------------------------------
    // 安装钩子:替换 MessageBoxA 为 MyMessageBoxA
    if (!InstallHook()) {
        return -1; // 如果安装钩子失败,退出程序
    }
 
    //------------------------------------------------------------------
    // 调用 MessageBoxA 时,会触发 MyMessageBoxA,而不是原始的 MessageBoxA
    MessageBoxA(NULL, "Malware Development Is Bad", "Original MsgBox", MB_OK | MB_ICONWARNING);
    MessageBoxA(NULL, "Is Bad", " MsgBox", MB_OK | MB_ICONWARNING);
 
    //------------------------------------------------------------------
    // 卸载钩子:恢复 MessageBoxA 为原始版本
    if (!Unhook()) {
        return -1; // 如果卸载钩子失败,退出程序
    }
 
    //------------------------------------------------------------------
    // 卸载钩子后,MessageBoxA 会恢复为原始版本
    MessageBoxA(NULL, "Normal MsgBox Again", "Original MsgBox", MB_OK | MB_ICONINFORMATION);
 
    return 0; // 程序正常退出
}

 

posted @   aoaoaoao  阅读(20)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
· Manus爆火,是硬核还是营销?
· 终于写完轮子一部分:tcp代理 了,记录一下
· 别再用vector<bool>了!Google高级工程师:这可能是STL最大的设计失误
· 单元测试从入门到精通
点击右上角即可分享
微信分享提示