0%

CS:GO Hacking Notes

学习 CS:GO Hacking 的笔记

HOOK 技术

Hook 技术又被称为钩子技术

在系统没有调用该函数之前,钩子程序就先捕获该消息,钩子函数先得到控制权,这时钩子函数既可以加工处理(改变)该函数的执行行为,还可以强制结束消息的传递

简单来说,Hook 是一种截取信息、更改程序执行流向、添加新功能的技术

通俗来说,就是一条高速公路,我们去追击一个罪犯(要截获的消息或事件),然后在他要经过的地方提前埋下埋伏,等到罪犯到来时,实现截获,并执行自己的操作

DOS 时代的 Hook 技术是指修改中断向量表中的中断地址,那时操作系统提供的编程接口称为中断服务向量(即中断向量,中断向量是中断服务程序的入口地址),它以数组的形式保存,这就是中断向量表。要捕获一个中断服务程序,首先修改中断向量表中该程序的中断向量,具体是保存原程序中断地址(原中断向量),然后替换为自己函数的地址。这样,当要调用该中断服务程序时,实际调用了自己编写的函数,在该函数中,可以做一些我们需要的工作,然后可以继续通过原中断向量调用原中断服务程序,完成其原有的功能

Windows 下,Hook 技术的方法比较多,常见的 Hook 方法有 Inline Hook、IAT Hook、EAT Hook、Windows 钩子等

Inline Hook 内联钩子

Inline Hook 的原理

API 函数保存在提供的 DLL 文件中,当在程序中调用某个 API 函数并运行程序后,程序会隐式地将 API 函数所在的 DLL 文件加载入进程中,这样,程序就会像调用自己的函数一样调用 API 函数。而 Inline Hook 就是通过一种暴力的方法直接地修改 API 函数在内存中的映像,从而对 API 函数进行 Hook,其方法是,直接使用汇编指令的 jmp 指令将其代码执行流程改变,进而执行自己的代码,使得原来的函数的流程改变了,执行完自己的流程后,可以选择性地继续执行或不执行原来的函数

由于这种方法是在程序流程中直接进行嵌入 jmp 指令来改变流程的,所以才将其称为 Inline Hook

Inline Hook 的实现

Inline Hook 是在程序中嵌入 jmp 汇编指令后跳转到流程处继续执行的,jmp 指令是一条无条件的跳转指令,其后跟随的参数是要跳转的目的地址,jmp 指令编译为机器码后,其长度为 5 个 byte,前一个字节为 jmp 对应的机器码(E9),后四个字节为目的地址相对于 jmp 指令的下一条指令地址的偏移量,即一个 32 位的偏移量

1
00402260	- E9 1334F411	jmp 12345678 	;偏移量为 11 F4 34 13

jmp 指令后的偏移量的计算可以使用如下公式:

jmp 后的偏移量 = 目标地址 - jmp 指令的下一条指令的地址 = 目标地址 - jmp 指令的地址 - 5

这样我们就得到了 Inline Hook 的流程:

  • 构造跳转指令
  • 在内存中找到要 HOOK 的函数地址,并保存要 HOOK 位置处的前 5 字节
  • 将构造的跳转指令写入需 HOOK 的位置处
  • 当被 HOOK 位置被执行时会跳转到自己的流程执行
  • 如果要执行原来的流程,那么取消 HOOK,也就是还原被修改的字节
  • 执行原来的流程
  • 继续 HOOK 住原来的位置

用 C++ 封装一个 Inline Hook 的类,类的头文件与实现文件分别是 ILHook.h 与 ILHook.cpp

ILHook.h
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
#pragma once
#include<Windows.h>

class CILHook
{
public:
CILHook(); // 构造函数
~CILHook(); // 析构函数

// Hook 函数
BOOL Hook(LPSTR pszModuleName, // Hook 的模块名称
LPSTR pszFuncName, // Hook 的 API 函数名称
PROC pfnHookFunc); // 要替换的函数名称(钩子函数)

// 取消 Hook 函数
VOID UnHook();

// 重新进行 Hook 函数
BOOL ReHook();

private:
PROC m_pfnOrig; // 函数地址
BYTE m_bOldBytes[5]; // 函数入口代码
BYTE m_bNewBytes[5]; // Inline 代码
};

最重要的函数是 CILHook::Hook,在该成员函数中,首先获得了被 Hook 函数的函数地址,接着保存了被 Hook 函数的前 5 个字节,最后用构造好的跳转指令来修改被 Hook 函数的前 5 个字节的内容

ILHook.cpp
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 "ILHook.h"

CILHook::CILHook()
{
// 对成员变量的初始化
m_pfnOrig = NULL;
ZeroMemory(m_bOldBytes, 5);
ZeroMemory(m_bNewBytes, 5);
}

CILHook::~CILHook()
{
// 取消 Hook
UnHook();

m_pfnOrig = NULL;
ZeroMemory(m_bOldBytes, 5);
ZeroMemory(m_bNewBytes, 5);
}

/*
对指定模块中的函数进行挂钩
**/
BOOL CILHook::Hook(LPSTR pszModuleName,
LPSTR pszFuncName,
PROC pfnHookFunc)
{
BOOL bRet = FALSE;

// 获取指定模块中函数的地址
m_pfnOrig = (PROC)GetProcAddress(
GetModuleHandle(pszModuleName),
pszFuncName);

if ( m_pfnOrig!= NULL )
{
// 保存该地址处 5 字节的内容
DWORD dwNum = 0;

ReadProcessMemory(GetCurrentProcess(), // 被读取进程(远程进程)的句柄
m_pfnOrig, // 读的(远程进程中)起始地址
m_bOldBytes, // (本地进程中)存放读取数据缓冲区
5, // 一次读取的字节数
&dwNum); // 实际读取的字节数,函数返回时报告

// 构造 JMP 指令
m_bNewBytes[0] = '\xe9'; // jmp Opcode
// pfnHookFunc 是 Hook 后的目标地址
// m_pfnOrig 是原来的地址
// 5 是指令长度
*(DWORD*)(m_bNewBytes + 1) = (DWORD)pfnHookFunc - (DWORD)m_pfnOrig - 5;

// 将构造好的地址写入该地址处
WriteProcessMemory(GetCurrentProcess(), // 参数表与 ReadProcessMemory 类似
m_pfnOrig,
m_bNewBytes,
5,
&dwNum);

bRet = TRUE;
}

return bRet;
}

/*
取消函数的挂钩
**/
VOID CILHook::UnHook()
{
if ( m_pfnOrig != NULL )
{
DWORD dwNum = 0;
WriteProcessMemory(GetCurrentProcess(),
m_pfnOrig,
m_bOldBytes,
5,
&dwNum);
}
}

/*
重新对函数进行挂钩
**/
BOOL CILHook::ReHook()
{
BOOL bRet = FALSE;

if ( m_pfnOrig != NULL )
{
DWORD dwNum = 0;
WriteProcessMemory(GetCurrentProcess(),
m_pfnOrig,
m_bNewBytes,
5,
&dwNum);

bRet = TRUE;
}

return bRet;
}

注意,在自己实现的 Hook 函数(要替换的函数,不是上面的 CILHook::Hook)中,如果要调用原来的 API 函数,需要恢复 Inline Hook(恢复 Hook 前的状态,即恢复原来的 5 个字节),否则会进入死循环,在调用完成后,再重新对函数进行 Hook

用于 CSGOhacking 项目的 inline_hook 类

inline_hook.hpp
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
#pragma once
#include <windows.h>

// jmp xx xx xx xx : 5 bytes
constexpr int byte_length = 5;

class inline_hook
{
private:
using uchar = unsigned char;
using dword = DWORD;

uchar m_original_byte[byte_length]; // 原始函数处的汇编指令
uchar m_self_byte[byte_length]; // 我们自己构造需要改写的汇编指令

int m_origin_address; // 原始函数的地址
int m_self_address; // 改写函数的地址

dword motify_memory_attributes(int address, dword attributes = PAGE_EXECUTE_READWRITE)
// 改变内存的属性
// PAGE_EXECUTE_READWRITE 需要将内存的属性设置为可读可写
// 返回内存原有的属性
{
dword old_attributes;
// VirtualProtect 是对应 Win32 函数的逻辑包装函数
// 它会在呼叫处理程序的虚拟位置空间里,变更认可页面区域上的保护
VirtualProtect(reinterpret_cast<void*>(address),
byte_length,
attributes,
&old_attributes);
return old_attributes;
}

public:
/*
构造函数
用初始化列表初始化
构造我们编写的跳转汇编指令 bytes,保存原汇编指令 bytes
**/
inline_hook(int origin_address, int self_address) :
m_origin_address(origin_address),
m_self_address(self_address)
{
m_self_byte[0] = '\xe9'; // \x 为转义字符,表示十六进制数
// e9 为汇编指令中的 jmp 指令

// 转移的位移计算
// 用转移指令的下一条指令的起始地址进行计算
int offset = self_address - (origin_address + byte_length);

// 构造跳转到我们函数的 byte
memcpy(&m_self_byte[1], &offset, byte_length - 1);

// 修改内存属性
dword attributes = motify_memory_attributes(origin_address);

// 保存原始函数地址的 byte
memcpy(m_original_byte, reinterpret_cast<void*>(origin_address), byte_length);

// 恢复内存属性
motify_memory_attributes(origin_address, attributes);
}

void motify_address()
{
dword attributes = motify_memory_attributes(m_origin_address);
// 写入我们构造的 byte 实现 inline hook
memcpy(reinterpret_cast<void*>(m_origin_address), m_self_byte, byte_length);
motify_memory_attributes(m_origin_address, attributes);
}

void restore_address()
{
dword attributes = motify_memory_attributes(m_origin_address);
// 恢复原始的 byte
memcpy(reinterpret_cast<void*>(m_origin_address), m_original_byte, byte_length);
motify_memory_attributes(m_origin_address, attributes);
}
};

参考资料

C++ 黑客编程揭秘与防范 : 第三版 / 冀云编著. —— 北京 : 人民邮电出版社,2019.2

CSDN : 王大碗Dw / Hook技术简介