Hook 技术常被叫做挂钩技术,挂钩技术其实早在DOS时代就已经存在了,该技术是Windows系统用于替代DOS中断机制的具体实现,钩子的含义就是在程序还没有调用系统函数之前,钩子捕获调用消息并获得控制权,在执行系统调用之前执行自身程序,简单来说就是函数劫持.
HOOK技术的实现方法比较多,常见的HOOK方法有 Inline Hook、IAT Hook、EAT Hook 这三种,钩子的应用范围非常广泛,比如输入监控、API拦截、消息捕获、改变程序执行流程等,杀毒软件也会HOOK钩住一些特殊的API函数,起到监控系统运行状态的目的,黑客们也会通过钩子技术截获一些有价值的数据,例如键盘消息等.
该笔记是针对32位Hook的简易封装,自己留着也没什么意思,还是分享出来吧,转载请加出处,谢谢!
Hook 实现去弹窗: 首先我们来实现一个小功能,这里有一个小程序,当我们点击弹窗时会自动的弹出一个MessageBox
提示,我们的目标是通过注入DLL的方式Hook钩挂住MessageBox
从而实现去除这个弹窗的目的,先来看一下Hook的思路:
1.调用 GetModuleHandle 来获取到user32.dll模块的基址
2.调用 GetProcAddress 获取到MessageBoxA弹窗的基址
3.调用 VirtualProtect 来修改MsgBox前5个字节内存属性
4.计算 Dest - MsgBox - 5 重定位跳转地址,并写入JMP跳转指令
5.计算 Dest + Offset + 5 = MsgBox +5 得到需要跳转回ret的位置
6.最后调用 VirtualProtect 来将内存属性修改为原始状态
首先我们载入带有MsgBox
弹窗的程序,然后在X64DBG上按下Ctrl+G
输入MessageBoxA
找到我们需要Hook的地方,如下所示我们为了完成弹窗转向功能,只需要在函数开头写入jmp
无条件跳转指令即可,在32位系统中JMP指令默认占用5个字节,前三条指令恰好5个字节,为了能够保持堆栈平衡,我们需要记下前三条指令,并在自己的中转函数中补齐.
759F1F70 | 8BFF | mov edi,edi | Src 替换为 jmp xxxx 759F1F72 | 55 | push ebp | 替换为 jmp xxxx 759F1F73 | 8BEC | mov ebp,esp | 替换为 jmp xxxx 759F1F75 | 6A FF | push 0xFFFFFFFF | 759F1F77 | 6A 00 | push 0x0 | 759F1F79 | FF75 14 | push dword ptr ss:[ebp+0x14] | 759F1F7C | FF75 10 | push dword ptr ss:[ebp+0x10] | 759F1F7F | FF75 0C | push dword ptr ss:[ebp+0xC] | 759F1F82 | FF75 08 | push dword ptr ss:[ebp+0x8] | 759F1F85 | E8 D6010000 | call <MessageBoxTimeoutA> | 759F1F8A | 5D | pop ebp | Dest 759F1F8B | C2 1000 | ret 0x10 |
|
我们还需要计算出程序的返回地址,使用759F1F8A - 772A1F70 = 1A
从而得出返回地址就是基址加上1A
,这里的返回地址其实就是返回到原MessageBox
弹窗的ret 0x10
的位置759F1F8B
,从这里可以看出屏蔽弹窗的原理就是通过中转函数跳过了弹窗函数的执行,我们直接编译这段代码,并注入到弹窗程序测试,会发现弹窗被去除了.
#include <Windows.h> #include <stdio.h>
DWORD jump = 0;
__declspec(naked) void Transfer(){ __asm{ mov edi, edi push ebp mov ebp, esp mov ebx, jump jmp ebx } }
bool APIENTRY DllMain(HANDLE handle, DWORD dword, LPVOID lpvoid) { HMODULE hwnd = GetModuleHandle(TEXT("user32.dll")); DWORD base = (DWORD)GetProcAddress(hwnd, "MessageBoxA"); DWORD oldProtect = 0; if (VirtualProtect((LPVOID)base, 5, PAGE_EXECUTE_READWRITE, &oldProtect)) { DWORD value = (DWORD)Transfer - base - 5; jump = base + 0x1a; __asm{ mov eax, base mov byte ptr[eax], 0xe9 inc eax mov ebx, value mov dword ptr[eax], ebx } VirtualProtect((LPVOID)base, 5, oldProtect, &oldProtect); } return true; }
|
Hook 实现改标题: 通常情况下,程序设置标题会调用SetWindowTextA
这个API函数,我们可以拦截这个函数,并传入自定义的窗口名称,从而实现修改指定窗口的标题的目的,代码只是在上面代码的基础上稍微改一下就能实现效果.
#include <Windows.h> #include <stdio.h>
DWORD jump = 0;
__declspec(naked) bool _stdcall Transfer(HWND hwnd, LPCSTR lpString){ __asm{ mov edi, edi push ebp mov ebp, esp mov ebx, jump jmp ebx } }
bool __stdcall MySetWindowTextA(HWND hwnd, LPCSTR lpString){ char * lpText = "LyShark 破解版"; return Transfer(hwnd, lpText); }
bool APIENTRY DllMain(HANDLE handle, DWORD dword, LPVOID lpvoid) { HMODULE hwnd = GetModuleHandle(TEXT("user32.dll")); DWORD base = (DWORD)GetProcAddress(hwnd, "SetWindowTextA"); DWORD oldProtect = 0;
if (VirtualProtect((LPVOID)base, 5, PAGE_EXECUTE_READWRITE, &oldProtect)) { DWORD value = (DWORD)MySetWindowTextA - base - 5; jump = base + 5; __asm{ mov eax, base mov byte ptr[eax], 0xe9 inc eax mov ebx, value mov dword ptr[eax], ebx } VirtualProtect((LPVOID)base, 5, oldProtect, &oldProtect); } return true; }
|
针对Hook代码封装: 上面代码并不具备通用性,这里我们可以使用C++将其封装成类,这样使用会更方便,通常封装类都会存在两个文件,这里我们将头文件定义为hook.h
将实现文件定义为hook.cpp
分别实现这两个文件代码逻辑.
#pragma once #include <Windows.h>
#ifdef __cplusplus extern "C"{ #endif
class MyHook { public: PROC m_pfnOrig; BYTE m_bOldBytes[5]; BYTE m_bNewBytes[5]; public: MyHook(); ~MyHook();
BOOL Hook(LPSTR pszModuleName, LPSTR pszFuncName, PROC pfnHookFunc); BOOL UnHook(); BOOL ReHook(); }; #ifdef __cplusplus } #endif
|
如下是代码实现部分MyHook()
构造函数用来初始化,析构函数用来清空并恢复钩子,Hook
则是具体实现挂钩的细节,在Hook()成员函数中完成了3项工作,首先是获得了被HOOK函数的函数地址,接下来是保存了被HOOK函数的前5字节,最后是用构造好的跳转指令来修改被HOOK函数的前5字节的内容.
#include "hook.h"
MyHook::MyHook() { m_pfnOrig = NULL; ZeroMemory(m_bOldBytes, 5); ZeroMemory(m_bNewBytes, 5); }
MyHook::~MyHook() { UnHook(); m_pfnOrig = NULL; ZeroMemory(m_bOldBytes, 5); ZeroMemory(m_bNewBytes, 5); }
BOOL MyHook::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; }
BOOL MyHook::UnHook() { if (m_pfnOrig != 0) { DWORD dwNum = 0; WriteProcessMemory(GetCurrentProcess(),m_pfnOrig,m_bOldBytes,5,&dwNum); } return TRUE; }
BOOL MyHook::ReHook() { BOOL bRet = FALSE; if (m_pfnOrig != 0) { DWORD dwNum = 0; WriteProcessMemory(GetCurrentProcess(),m_pfnOrig,m_bNewBytes,5,&dwNum); bRet = TRUE; } return bRet; }
|
到此为止整个Inline Hook
的封装已经完成了,在后面的代码中,可以很容易地实现对函数的HOOK功能,这里我再多说一句,如果我们需要在自己实现的MessageBox
函数中要调用原始的API函数,则需要恢复Inline Hook
,否则程序将会崩溃.
#include <Windows.h> #include "hook.h"
MyHook MsgHook;
int WINAPI MyMessageBoxA(HWND hWnd, LPCSTR lpText, LPCSTR lpCaption, UINT uType) { MsgHook.UnHook();
MessageBoxA(hWnd, "hook inject", lpCaption, uType);
MsgHook.ReHook(); return 0; }
int main(int argc, char * argv[]) { MsgHook.Hook("user32.dll", "MessageBoxA", (PROC)MyMessageBoxA); MessageBoxA(NULL, "hello lyshark", "Msg", MB_OK); MsgHook.UnHook(); return 0; }
|
第二种封装方式: 该封装方式直接将定义与实现写到hook.h
头文件中,使用时直接包含一个文件即可.
#pragma once #include <Windows.h>
#ifdef __cplusplus extern "C"{ #endif
#pragma once class MyHook { public: static DWORD Hook(LPCWSTR lpModule, LPCSTR lpFuncName, PROC lpFunction) { DWORD dwAddr = (DWORD)GetProcAddress(GetModuleHandle(lpModule), lpFuncName); BYTE jmp[] = { 0xe9, 0x00, 0x00, 0x00, 0x00, 0xc3 };
ReadProcessMemory(GetCurrentProcess(), (LPVOID)dwAddr, MemoryAddress(), 6, 0); DWORD dwCalc = ((DWORD)lpFunction - dwAddr - 5); memcpy(&jmp[1], &dwCalc, 4); WriteProcessMemory(GetCurrentProcess(), (LPVOID)dwAddr, jmp, 6, 0);
return dwAddr; }
static BOOL UnHook(LPCWSTR lpModule, LPCSTR lpFuncName) { DWORD dwAddr = (DWORD)GetProcAddress(GetModuleHandle(lpModule), lpFuncName); if (WriteProcessMemory(GetCurrentProcess(), (LPVOID)dwAddr, MemoryAddress(), 6, 0)) return TRUE; return FALSE; }
static BYTE* MemoryAddress() { static BYTE backup[6]; return backup; } }; #ifdef __cplusplus } #endif
|
#include "hook.h"
MyHook MsgHook;
int WINAPI MyMessageBoxA(HWND hWnd, LPCSTR lpText, LPCSTR lpCaption, UINT uType) { MsgHook.UnHook(L"user32.dll","MessageBoxA");
MessageBoxA(hWnd, "hook inject", lpCaption, uType);
MsgHook.Hook(L"user32.dll", "MessageBoxW", (PROC)MyMessageBoxA); return 0; }
int main(int argc, char * argv[]) { MsgHook.Hook(L"user32.dll", "MessageBoxA", (PROC)MyMessageBoxA);
MessageBoxA(NULL, "hello lyshark", "Msg", MB_OK);
MsgHook.UnHook(L"user32.dll", "MessageBoxA"); return 0; }
|