学习 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
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(); BOOL Hook (LPSTR pszModuleName, LPSTR pszFuncName, PROC pfnHookFunc) ; VOID UnHook () ; BOOL ReHook () ; private : PROC m_pfnOrig; BYTE m_bOldBytes[5 ]; BYTE m_bNewBytes[5 ]; };
最重要的函数是 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() { 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 ) { DWORD dwNum = 0 ; ReadProcessMemory(GetCurrentProcess(), m_pfnOrig, m_bOldBytes, 5 , &dwNum); m_bNewBytes[0 ] = '\xe9' ; *(DWORD*)(m_bNewBytes + 1 ) = (DWORD)pfnHookFunc - (DWORD)m_pfnOrig - 5 ; WriteProcessMemory(GetCurrentProcess(), 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> 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) { dword old_attributes; VirtualProtect(reinterpret_cast <void *>(address), byte_length, attributes, &old_attributes); return old_attributes; } public : inline_hook(int origin_address, int self_address) : m_origin_address(origin_address), m_self_address(self_address) { m_self_byte[0 ] = '\xe9' ; int offset = self_address - (origin_address + byte_length); memcpy (&m_self_byte[1 ], &offset, byte_length - 1 ); dword attributes = motify_memory_attributes(origin_address); 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); 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); 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技术简介