hook
文章目录
- hook
- 1. 原理
- 2. hook分类
- 2.1 消息hook
- msgbullet.dll
- msghook.cpp
- 2.2 自定义hook
- inline hook
- IAT hook
- 3. 代码示例
- 3.1 inline hook
- InlineHook.dll
- CallHook.cpp
- 3.2 IAT hook
- IAT_Hook.dll
- callHook.c
hook(钩子)技术:拦截程序原有的信息、数据、代码,进行处理,再交给原来的程序使用。
效果/目标:
- 查看、处理拦截的信息
- 修改程序部分功能
hook一般要搭配注入使用的,这里为了整理总结,直接主程序LoadLibrary()
。
1. 原理
windows消息钩子基于一个重要的api:
HHOOK
WINAPI
SetWindowsHookExW(
_In_ int idHook,
_In_ HOOKPROC lpfn,
_In_opt_ HINSTANCE hmod,
_In_ DWORD dwThreadId);
它可以截获:
- 所有窗口程序的消息
- 某一个线程的消息
复习一下:窗口的消息由创建这个窗口的线程获得(GetMessage)。线程在创建完窗口后,就变成了GUI线程。
idHook表示消息类型。
dwThreadId为
- 0,就截获所有线程的消息(危险);
- PID,就截获该线程的消息。
截获消息后,需要执行自己存在dll中的代码lpfn。设置好钩子之后,应该将dll注入目标进程,这个dll就是参数hmod。
这里要注意一下,注入和hook是两种技巧,这里搭配使用,不存在必须注入才能hook这种说法。
钩子用完后,用UnhookWindowsHookEx()
卸载。
程序可能有多个钩子,所有回调函数最后要CallNextHookEx()
:
LRESULT
CallNextHookEx(
_In_opt_ HHOOK hhk,
_In_ int nCode,
_In_ WPARAM wParam,
_In_ LPARAM lParam);
后三个参数就是回调函数的参数。
2. hook分类
两类:
- windows消息hook,截获消息,不常用;
- 自定义hook,传统认知上的hook。
2.1 消息hook
也是一种dll注入手段。同样依赖于SetWindowsHookExA()
。
应用时TID应该是通过遍历得到,这里为了方便就hook所有程序。
消息钩子是windows支持的一种钩子技术,内部维护着钩子链表。这里我们利用它拿来做dll注入。
msgbullet.dll
下面是dll代码,包括回调函数,设置和卸载hook:
回调函数可以决定是否继续传递消息。
第一个参数需要查询msdn。
#include <windows.h>
HHOOK g_hook = NULL;
HMODULE g_hDll = NULL;
LRESULT CALLBACK KeyboardProc(int nCode, WPARAM wParam, LPARAM lParam)
{
if (nCode == HC_ACTION &&
(lParam & 0x80000000))
{
char msg[3] = { (char)wParam, 0 ,'\n'};
OutputDebugStringA(msg);
}
return CallNextHookEx(g_hook, nCode, wParam, lParam);
}
#ifdef __cplusplus
extern "C"{
#endif
__declspec(dllexport)
void HookStart()
{
g_hook = SetWindowsHookEx(
WH_KEYBOARD,
KeyboardProc,
g_hDll,
0);
}
__declspec(dllexport)
void HookEnd()
{
UnhookWindowsHookEx(g_hook);
}
#ifdef __cplusplus
}
#endif
BOOL APIENTRY DllMain(HMODULE hModule,
DWORD ul_reason_for_call,
LPVOID lpReserved
)
{
switch (ul_reason_for_call)
{
case DLL_PROCESS_ATTACH:
g_hDll = hModule;
break;;
case DLL_THREAD_ATTACH:
break;;
case DLL_THREAD_DETACH:
break;
case DLL_PROCESS_DETACH:
break;
}
return TRUE;
}
OutputDebugString()
输出可以用dbgview查看。
msghook.cpp
#include <Shlwapi.h>
#pragma comment(lib, "shlwapi.lib")
#include <stdio.h>
#include <windows.h>
#include <direct.h>
#include <conio.h>
typedef void(*PFUN)();
int main()
{
char szDllPath[MAX_PATH] = { 0 };
HMODULE hDll = NULL;
DWORD dwPID = 0;
PFUN pHookStart = NULL, pHookEnd = NULL;
_getcwd(szDllPath, MAX_PATH);
PathRemoveFileSpecA(szDllPath);
strcat_s(szDllPath, MAX_PATH, "\\x64\\Debug\\msgbullet.dll");
hDll = LoadLibraryA(szDllPath);
if (!hDll) return 0;
pHookStart = (PFUN)GetProcAddress(hDll, "HookStart");
pHookEnd = (PFUN)GetProcAddress(hDll, "HookEnd");
pHookStart();
while (_getch() != 'q');
pHookEnd();
system("pause");
return 0;
}
但进程本身也被hook了。
除了用dbgview,还可以用OD附加,观察dll模块是否注入。
2.2 自定义hook
又细分为两类:
- inline hook:修改程序代码,执行自己的代码;
- IAT hook:修改存储函数地址的变量,容易被检测到(当然,所有hook都能检测到)。
内核层还有IDT-hook(修改IDT表)、SYSENTER-hook.
自定义hook很普遍,是通常所说的传统意义上的hook。
inline hook
在高危代码首地址处写入call, jmp, ret
等跳转指令,跳转到自己的代码执行。自己的代码要注意:
- 开头pushad,最后popad;
- 最后还要再执行一下被修改的指令,然后跳回去。
例如jmp公式:offset = target addr - (jmp addr + 5)
5:jmp xxxxxxxx
为5个字节,这里涉及jmp指令的opcode计算,不赘述。所以说,用jmp时要找另一个长度为5的指令,避免指令截断。
IAT hook
找到函数地址的方法:
- 文件IAT
- 文件INT
- 内存INT
注意,我们不能通过映射到内存中的IAT找到函数地址,因为没有函数名称。
由IAT hook可以想到,虚函数表也能hook。
3. 代码示例
3.1 inline hook
InlineHook.dll
#include <windows.h>
#define HOOKLEN 5
extern "C"
__declspec(dllexport)
void InstallHook();
extern "C"
__declspec(dllexport)
void UninstallHook();
BYTE g_oldCodes[HOOKLEN] = { 0 };
BYTE g_newCodes[HOOKLEN] = { 0xE9 };
int
MyMessageBoxW(
_In_opt_ HWND hWnd,
_In_opt_ LPCWSTR lpText,
_In_opt_ LPCWSTR lpCaption,
_In_ UINT uType)
{
UninstallHook();
int ret = MessageBoxW(hWnd, L"MessageBoxW() is hooked~", L"Ooops", uType);
InstallHook();
return ret;
}
extern "C"
__declspec(dllexport)
void InstallHook()
{
HMODULE hModule = GetModuleHandle(L"user32.dll");
LPVOID lpMsgBox = NULL;
if (hModule)
{
lpMsgBox = GetProcAddress(hModule, "MessageBoxW");
}
if (!lpMsgBox) return;
memcpy(g_oldCodes, lpMsgBox, HOOKLEN);
DWORD offset = (DWORD)MyMessageBoxW - (DWORD)lpMsgBox - HOOKLEN;
*(DWORD*)(g_newCodes + 1) = offset;
DWORD dwProtect = 0;
VirtualProtect(lpMsgBox, HOOKLEN, PAGE_EXECUTE_READWRITE, &dwProtect);
memcpy(lpMsgBox, g_newCodes, HOOKLEN);
VirtualProtect(lpMsgBox, HOOKLEN, dwProtect, &dwProtect);
}
extern "C"
__declspec(dllexport)
void UninstallHook()
{
HMODULE hModule = GetModuleHandle(L"user32.dll");
LPVOID lpMsgBox = NULL;
if (hModule)
{
lpMsgBox = GetProcAddress(hModule, "MessageBoxW");
}
if (!lpMsgBox) return;
DWORD dwProtect = 0;
VirtualProtect(lpMsgBox, HOOKLEN, PAGE_EXECUTE_READWRITE, &dwProtect);
memcpy(lpMsgBox, g_oldCodes, HOOKLEN);
VirtualProtect(lpMsgBox, HOOKLEN, dwProtect, &dwProtect);
}
/*
为了演示,先将DllMain注释。
*/
//BOOL APIENTRY DllMain(HMODULE hModule, DWORD dwReason, LPVOID lpReserved)
//{
// switch (dwReason)
// {
// case DLL_PROCESS_ATTACH:
// InstallHook();
// break;
// case DLL_PROCESS_DETACH:
// UninstallHook();
// break;
// default:
// break;
// }
// return TRUE;
//}
CallHook.cpp
#include <windows.h>
#include <direct.h>
#include <Shlwapi.h>
#pragma comment(lib, "shlwapi.lib")
typedef void (*PFUNC)();
int main()
{
char szDllPath[MAX_PATH] = { 0 };
HMODULE hModule = NULL;
PFUNC InstallHook = NULL, UninstallHook = NULL;
_getcwd(szDllPath, MAX_PATH);
PathRemoveFileSpecA(szDllPath);
strcat_s(szDllPath, MAX_PATH, "\\Debug\\InlineHook.dll");
//strcat_s(szDllPath, MAX_PATH, "\\Debug\\IATHook.dll");
hModule = LoadLibraryA(szDllPath);
InstallHook = (PFUNC)GetProcAddress(hModule, "InstallHook");
UninstallHook = (PFUNC)GetProcAddress(hModule, "UninstallHook");
MessageBoxW(NULL, L"Before InstallHook()", L"", 0);
InstallHook();
MessageBoxW(NULL, L"after hooked", L"", 0);
UninstallHook();
MessageBoxW(NULL, L"after UninstallHook()", L"", 0);
return 0;
}
这段代码虽然成功,但实际上调用被hook的原函数是有点问题的。
00FF17C8 8BF4 mov esi,esp
00FF17CA 6A 00 push 0x0
00FF17CC 68 6C6BFF00 push CallHook.00FF6B6C
00FF17D1 68 706BFF00 push CallHook.00FF6B70 ; UNICODE "Before InstallHook()"
00FF17D6 6A 00 push 0x0
00FF17D8 FF15 CCA0FF00 call dword ptr ds:[<&USER32.MessageBoxW>] ; apphelp.722C1E80
00FF17DE 3BF4 cmp esi,esp
00FF17E0 E8 33F9FFFF call CallHook.00FF1118
00FF17E5 8BF4 mov esi,esp
00FF17E7 FF95 DCFEFFFF call dword ptr ss:[ebp-0x124] ; InlineHo.InstallHook
00FF17ED 3BF4 cmp esi,esp
00FF17EF E8 24F9FFFF call CallHook.00FF1118
00FF17F4 8BF4 mov esi,esp
00FF17F6 6A 00 push 0x0
00FF17F8 68 6C6BFF00 push CallHook.00FF6B6C
00FF17FD 68 A46BFF00 push CallHook.00FF6BA4 ; UNICODE "after hooked"
00FF1802 6A 00 push 0x0
00FF1804 FF15 CCA0FF00 call dword ptr ds:[<&USER32.MessageBoxW>] ; apphelp.722C1E80
00FF180A 3BF4 cmp esi,esp
00FF180C E8 07F9FFFF call CallHook.00FF1118
注意,每次调用函数前后,都有mov esi, esp
和cmp esi, esp
,来检查栈平衡。
下面是hook之前的MB函数结尾:
722C1ECC 5E pop esi
722C1ECD 5B pop ebx
722C1ECE 5D pop ebp
722C1ECF C2 1000 retn 0x10
而我自己写的MyMessageBoxW的结尾:
0FFB161F 8BE5 mov esp,ebp
0FFB1621 5D pop ebp ; CallHook.<ModuleEntryPoint>
0FFB1622 C3 retn
并没有retn 10
,这就导致00FF180A
处检查出了堆栈不平衡,会报错。
原因!!!!!:
这是调用约定的问题,于是查看MessageBox声明,是__stdcall
自动清栈,而vs项目属性里的调用约定是__cdecl
。于是修改dll:
int __stdcall
MyMessageBoxW(
//....
所以说,被hook的函数和我们自己的函数调用方式一定要一致!!!!!!!。
3.2 IAT hook
IAT_Hook.dll
注意是如何区分32位和64位的。
#include <windows.h>
extern "C"
__declspec(dllexport)
void InstallHook();
extern "C"
__declspec(dllexport)
void UninstallHook();
typedef
int(__stdcall *PMessageBoxW)(
_In_opt_ HWND hWnd,
_In_opt_ LPCWSTR lpText,
_In_opt_ LPCWSTR lpCaption,
_In_ UINT uType);
PMessageBoxW g_pOriginMsgBoxW = NULL;
PIMAGE_THUNK_DATA g_pMsgBoxIAT = NULL;
int __stdcall
MyMessageBoxW(
_In_opt_ HWND hWnd,
_In_opt_ LPCWSTR lpText,
_In_opt_ LPCWSTR lpCaption,
_In_ UINT uType)
{
return g_pOriginMsgBoxW(hWnd, L"I'm the True MessageBoxW", L"Ooops", uType);
}
PIMAGE_THUNK_DATA GetDllFunc(const char* dllName, const char* funcName)
{
PIMAGE_DOS_HEADER pDos = (PIMAGE_DOS_HEADER)GetModuleHandle(0);
PIMAGE_NT_HEADERS pNt = (PIMAGE_NT_HEADERS)(pDos->e_lfanew + (ULONGLONG)pDos);
PIMAGE_IMPORT_DESCRIPTOR pIID = (PIMAGE_IMPORT_DESCRIPTOR)(pNt->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress
+ (ULONGLONG)pDos);
PIMAGE_THUNK_DATA pIAT = NULL, pINT = NULL;
char *pDllName = NULL;
PIMAGE_IMPORT_BY_NAME pImportName = NULL;
while (pIID->Name)
{
pIAT = (PIMAGE_THUNK_DATA)(pIID->FirstThunk + (ULONGLONG)pDos);
pINT = (PIMAGE_THUNK_DATA)(pIID->OriginalFirstThunk + (ULONGLONG)pDos);
pDllName = (char*)(pIID->Name + (ULONGLONG)pDos);
if (!_stricmp(pDllName, dllName))
{
while (pINT->u1.Ordinal)
{
// If imported by name
if (!(pINT->u1.Ordinal &
(1 << 8 * sizeof(pINT->u1.Ordinal) - 1)
))
{
pImportName = (PIMAGE_IMPORT_BY_NAME)(pINT->u1.AddressOfData
+ (ULONGLONG)pDos);
if (!strcmp(pImportName->Name, funcName))
{
return pIAT;
}
}
++pINT;
++pIAT;
}
}
++pIID;
}
return NULL;
}
BOOL APIENTRY DllMain(HMODULE hModule,
DWORD dwReason,
LPVOID lpReserved
)
{
switch (dwReason)
{
case DLL_PROCESS_ATTACH:
g_pMsgBoxIAT = GetDllFunc("user32.dll", "MessageBoxW");
if (g_pMsgBoxIAT)
{
g_pOriginMsgBoxW = (PMessageBoxW)
((sizeof(g_pMsgBoxIAT) == 8)
? *(ULONGLONG*)g_pMsgBoxIAT
: *(DWORD*)g_pMsgBoxIAT);
InstallHook();
}
break;
case DLL_PROCESS_DETACH:
if (g_pMsgBoxIAT)
{
UninstallHook();
}
break;
}
return TRUE;
}
extern "C"
__declspec(dllexport)
void InstallHook()
{
DWORD dwProtect = 0;
VirtualProtect(g_pMsgBoxIAT, sizeof(g_pMsgBoxIAT), PAGE_EXECUTE_READWRITE, &dwProtect);
//64 bit
if (sizeof(g_pMsgBoxIAT) == 8)
{
*(ULONGLONG*)g_pMsgBoxIAT = (ULONGLONG)MyMessageBoxW;
}
//32bit
else
{
*(DWORD*)g_pMsgBoxIAT = (DWORD)MyMessageBoxW;
}
VirtualProtect(g_pMsgBoxIAT, sizeof(g_pMsgBoxIAT), dwProtect, &dwProtect);
}
extern "C"
__declspec(dllexport)
void UninstallHook()
{
DWORD dwProtect = 0;
VirtualProtect(g_pMsgBoxIAT, sizeof(g_pMsgBoxIAT), PAGE_EXECUTE_READWRITE, &dwProtect);
//64 bit
if (sizeof(g_pMsgBoxIAT) == 8)
{
*(ULONGLONG*)g_pMsgBoxIAT = (ULONGLONG)g_pOriginMsgBoxW;
}
//32bit
else
{
*(DWORD*)g_pMsgBoxIAT = (DWORD)MyMessageBoxW;
}
VirtualProtect(g_pMsgBoxIAT, sizeof(g_pMsgBoxIAT), dwProtect, &dwProtect);
}
callHook.c
#include <windows.h>
#include <stdio.h>
#include <direct.h>
#include <Shlwapi.h>
#pragma comment(lib, "shlwapi.lib")
typedef void (*PFUNC)();
typedef
HANDLE
(*POpenProcess)(
_In_ DWORD dwDesiredAccess,
_In_ BOOL bInheritHandle,
_In_ DWORD dwProcessId
);
int main()
{
char szDllPath[MAX_PATH] = { 0 };
HMODULE hModule = NULL;
PFUNC InstallHook = NULL, UninstallHook = NULL;
_getcwd(szDllPath, MAX_PATH);
PathRemoveFileSpecA(szDllPath);
//strcat_s(szDllPath, MAX_PATH, "\\Debug\\InlineHook.dll");
strcat_s(szDllPath, MAX_PATH, "\\x64\\Debug\\IAT_Hook.dll");
hModule = LoadLibraryA(szDllPath);
MessageBoxW(NULL, L"not work", L"caption", 0);
return 0;
}
这里的路径是vs调试路径,OD调试的时候要在数据窗口改dll路径。