HOOK
- 什么是 HOOK:
-
- 消息hook
- C++中各种句柄的区别
- IAT HOOK
-
- hookmain.cpp
- hookmain.h
- test main.cpp
- Inline hook
-
- 思路
- 硬编码
- main.h
- main.cpp
- 调试DLL
什么是 HOOK:
Windows操作系统是事件驱动的。事件被包装了消息发送给窗口,比如点击菜单,按钮,移动窗口等等~
消息处理过程:例如:
1、当按下键盘时,会产生一个键盘按下的消息,这个消息首先被加入到系统消息队列2、操作系统从消息队列中取出消息,添加到相应的程序的消息队列中
3、程序自身通过GetMessage获取消息,DispatchMessage分发消息,通过消息回调函数处理消息。
HOOK就是从系统消息队列到应用程序消息队列之前对消息进行处理,处理之后在扔给下一个消息钩子(HOOK)或者扔到应用程序的消息队列中。
在操作系统中,操作系统会从系统消息队列中找到不同程序的消息队列分发消息,hook就是在传递这条线上截取相应的消息,对消息进行处理,处理后按顺序从上到下让hook或者程序消息队列处理。
消息hook
编写消息钩子需要将设置钩子的函数写到dll里面,当勾住一个线程后,产生消息时,假如系统发现包含钩子的dll不在本进程当中,系统会将dll强行加载进去,这一是一种dlI注入的手段。
C++中各种句柄的区别
- HMODULE表示模块句柄
- Handle 是代表 系统的内核对象,如 文件句柄,线程句柄,进程句柄。
- HMODULE 是代表应用 程序载入的模块,win32系统下通常是被载入模块的 线性地址。
-
HINSTANCE 表示
实例句柄。在win32下与HMODULE是相同的东西,在Win32下还存在主要是因为win16程序使用HINSTANCE来区别task。
- HWND 是 窗口句柄
在头文件中HMODULE定义如下:
typedef HINSTANCE HMODULE;
再看看HINSTANCE定义,typedef HANDLE HINSTANCE;
再看看HANDLE定义,typedef PVOID HANDLE;
再看看PVOID定义,typedef void *PVOID;
其实这些都可以称为句柄,为了表述的方便,所以对于不同类型的句柄都用不同样式的typedef,比如说HINSTANCE表示 实例句柄,HMODULE是 模块句柄,实际上他们本质上都是VOID 指针,是可以指向任何类型的指针。
在虚拟机中运行需要改运行库,因为虚拟机可能有很多文件没有。
IAT HOOK
hookmain.cpp
#include"hookMain.h"
int WINAPI HookMessageBoxW(
HWND hWnd,
LPCWSTR lpText,
LPCWSTR lpCaption,
UINT uType
)
{
int result = MessageBoxA(0, "51HOOK", "提示", MB_OK);
return result;
}
//安装钩子
BOOL InstallHook()
{
DWORD dwOldProtect = 0;//修改之前的属性
VirtualProtect(g_iatAddr, 4, PAGE_EXECUTE_READWRITE, & dwOldProtect);
*g_iatAddr = (DWORD)HookMessageBoxW;//因为我们HookMessageBoxW函数的文件属性不一定是可写的,所以我们要在上面改可写属性
VirtualProtect(g_iatAddr, 4, dwOldProtect, &dwOldProtect);
return TRUE;
}
//卸载钩子
BOOL UnInstallHook()
{
DWORD dwOldProtect = 0;//修改之前的属性
VirtualProtect(g_iatAddr, 4, PAGE_EXECUTE_READWRITE, &dwOldProtect);
*g_iatAddr = (DWORD)g_unHookAddr;//因为我们HookMessageBoxW函数的文件属性不一定是可写的,所以我们要在上面改可写属性
VirtualProtect(g_iatAddr, 4, dwOldProtect, &dwOldProtect);
return TRUE;
}
DWORD* GetIatAddr(const char* dllName,const char* dllFunName)
{
//获取当前EXE文件模块句柄
HMODULE hModule = GetModuleHandleA(0);
DWORD dwhModule = (DWORD)hModule;
//获取当前dos头
PIMAGE_DOS_HEADER pDosHeader = (PIMAGE_DOS_HEADER)dwhModule;
//获取当前NT头
PIMAGE_NT_HEADERS pNtHeaders = (PIMAGE_NT_HEADERS)(pDosHeader->e_lfanew + dwhModule);
//获取可选PE头
PIMAGE_OPTIONAL_HEADER pOptionalHeader = &pNtHeaders->OptionalHeader;
//获取数据目录表
IMAGE_DATA_DIRECTORY dataDirectory = pOptionalHeader->DataDirectory[1];
//获取导入表
PIMAGE_IMPORT_DESCRIPTOR pImageImageTable = (PIMAGE_IMPORT_DESCRIPTOR)(dataDirectory.VirtualAddress + dwhModule);
//遍历导入表获取符合条件的函数
while (pImageImageTable->Name)
{
char* iatDllName = (char*)(pImageImageTable->Name + dwhModule);
if (_stricmp(iatDllName, dllName) == 0)//判断dllName是否和iatDllName相同 查网dll遍历与函数原型,这里dll遍历是不区分大小写 不用完全匹配 函数名是区分的 要完全匹配
{
//获取导入名称表
PIMAGE_THUNK_DATA pINT = (PIMAGE_THUNK_DATA)(pImageImageTable->OriginalFirstThunk + dwhModule);
//获取导入地址表
PIMAGE_THUNK_DATA pIAT = (PIMAGE_THUNK_DATA)(pImageImageTable->FirstThunk + dwhModule);
while (pINT->u1.Function)//遍历名称表
{
if ((pINT->u1.Ordinal & 0x80000000)==0)//确定INT是按名称导入的
{
PIMAGE_IMPORT_BY_NAME pImportName = (PIMAGE_IMPORT_BY_NAME)(pINT->u1.Function + dwhModule);
if (strcmp(pImportName->Name, dllFunName) == 0)//按不区分大小写的方式比较名称 查网dll遍历与函数原型,这里dll遍历是不区分大小写 不用完全匹配 函数名是区分的 要完全匹配
{
return (DWORD*)pIAT;//获取到要替换函数的地址
}
}
pINT++;
pIAT++;
}
}
pImageImageTable++;
}
}
BOOL WINAPI DllMain(HINSTANCE hInstance, DWORD callReason, LPVOID lpReversed)
{
if (callReason == DLL_PROCESS_ATTACH)
{
//获取IAT表
g_iatAddr = GetIatAddr("user32.dll","MessageBoxW");
//保护要HOOK的函数地址,最后卸载钩子要能够找到
g_unHookAddr = (DWORD*)*g_iatAddr;
//安装钩子
InstallHook();
}
else if (callReason==DLL_PROCESS_DETACH)
{
UnInstallHook();
}
return TRUE;
}
hookmain.h
#pragma once
#include <Windows.h>
DWORD* g_iatAddr = NULL;
DWORD* g_unHookAddr = NULL;
int WINAPI HookMessageBoxW(
HWND hWnd,
LPCWSTR lpText,
LPCWSTR lpCaption,
UINT uType
);
BOOL InstallHook();
BOOL UnInstallHook();
DWORD* GetIatAddr(const char* dllName, const char* dllFunName);
test main.cpp
#include<Windows.h>
int main()
{
HMODULE hModule = LoadLibraryA("IATHook.dll");
if (hModule)
{
MessageBoxW(0, L"好喽", L"结束折磨", MB_OK);
}
return 0;
}
编写完成后我们通过代码注入器进行注入。
Inline hook
如果我们想要操控的函数不在IAT表中我们该如何hook?
可以使用内联hook。
思路
1、找到我们要HOOK函数地址
2、保存要HOOK的函数的前5个字节
3、计算目标函数距离jmp指令的一条指令的偏移offset
4、改变函数的前5个字节改成OXE9 offset
正常的API函数调用流程应该是由调用者(进程)通过函数名去调用已加载动态链接库中的导出函数。那么InlineHook的流程一般是在被调用API的头部,插入跳转指令的方式,劫持函数执行流程。
下图就是MessageBoxA的函数实现:
那么,我们可以在头部做出如下修改,进而实现HOOK:
上图中的12345678就代指的是我们自己的函数,通过这种方法劫持到我们的流程中执行。下面,我们采用MessageBoxW的Hook作为例子,实际体验一下InlineHook的实现方式。
硬编码
之所以要对前五个字节进行操控,是因为五个字节为jmp命令需要占五个字节,硬编码中jmp指令为 E9 xxxxx。
汇编中要jmp跳转的目标地址=xxxxx+5+jmp指令初地址
即上图12345678-= 9C5421A3+5+75E034D0
所以E9后面xxxxx就是跳转目标地址距JMP指令下一个指令的偏移
。
main.h
#pragma once
#include<iostream>
#include<Windows.h>
int WINAPI MyMessageBoxW(
_In_opt_ HWND hWnd,
_In_opt_ LPCWSTR lpText,
_In_opt_ LPCWSTR lpCaption,
_In_ UINT uType);
BOOL InstallHook();
BOOL UnInstallHook();
main.cpp
#include"main.h"
DWORD g_unhookfun = NULL;
char g_oldcode[5] = { 0 };//函数的前五个字节也就是旧函数的地址
char g_newcode[5] = { 0xE9 };
//思路
//1、找到我们要HOOK函数地址
//2、保存要HOOK的函数的前5个字节
//3、计算目标函数距离jmp指令的一条指令的偏移offset
//4、改变函数的前5个字节改成OXE9 offset
//1、初始化函数:进行hook的初始工作找到hook函数保存函数的前五个字节,计算出偏移值。保存改变后的前5个字节
//2、安装钩子
//3、卸载钩子
//4、自定义函数
int WINAPI MyMessageBoxW(
_In_opt_ HWND hWnd,
_In_opt_ LPCWSTR lpText,
_In_opt_ LPCWSTR lpCaption,
_In_ UINT uType)
{
UnInstallHook();
int result = MessageBoxW(hWnd, L"51HOOK", lpCaption, uType);
InstallHook();
return result;
}
//安装钩子
BOOL InstallHook()
{
DWORD oldProtect = 0;
if (g_unhookfun==0)
{
return FALSE;
}
VirtualProtect((DWORD*)g_unhookfun, 5, PAGE_EXECUTE_READWRITE, &oldProtect);
memcpy((DWORD*)g_unhookfun, g_newcode, 5);
VirtualProtect((DWORD*)g_unhookfun, 5, oldProtect, &oldProtect);
return TRUE;
}
//卸载钩子
BOOL UnInstallHook()
{
DWORD oldProtect = 0;
if (g_unhookfun == 0)
{
return FALSE;
}
VirtualProtect((DWORD*)g_unhookfun, 5, PAGE_EXECUTE_READWRITE, &oldProtect);
memcpy((DWORD*)g_unhookfun, g_oldcode, 5);
VirtualProtect((DWORD*)g_unhookfun, 5, oldProtect, &oldProtect);
return TRUE;
}
//初始化函数
BOOL InitHook()
{
//找到要Hook函数的地址
HMODULE hModule = LoadLibraryA("user32.dll");
if (hModule == 0)
{
return 0;
}
g_unhookfun = (DWORD)GetProcAddress(hModule, "MessageBoxW");
//保存函数的前五个字节(旧函数的地址)
memcpy(g_oldcode, (char*)g_unhookfun, 5);
//计算偏移
DWORD offset = (DWORD)MyMessageBoxW - (g_unhookfun + 5);
//保存hook后的五个字节(新函数的地址)
memcpy(&g_newcode[1], &offset, 4);
return TRUE;
}
BOOL WINAPI DllMain(HINSTANCE hInstance, DWORD callReason, LPVOID lpReserved)
{
if (callReason == DLL_PROCESS_ATTACH)
{
InitHook();
InstallHook();
}
else if (callReason == DLL_PROCESS_DETACH)
{
UnInstallHook();
}
return TRUE;
}
调试DLL
有时候我们没办法调试,不知道dll文件哪里有问题,我们可以在项目属性里,浏览要注入的exe文件,选中后应用确定。直接调试,我们可以看到关联的exe文件也运行了,这时打开dll注入工具注入dll,即可进行调试。