前言
先介绍下Windows中的Hook技术。Hook是Windows中提供的一种用以替换DOS下“中断”的系统机制,中文译为“挂钩”或“钩子”。在对特定的系统事件进行hook后,一旦发生已hook事件,对该事件进行hook的程序就会收到系统的通知,这时程序就能在第一时间对该事件做出响应。Windows中的Hook技术的方法较多,常见的有Inline Hook、IAT Hook、EAT Hook 、Windows 钩子…等。本片博文将主要介绍 Inline Hook(内联钩子)。本篇博文介绍的是5字节的Inline Hook,7字节的Inline Hook点击这里。
Inline Hook介绍
Windows的API函数都是保存在系统提供的DLL文件中。当程序调用某个API函数并运行程序的时候,程序会将DLL文件加载到进程中。举例,比如当我们调用CreateFile这个函数的时候,如下图
而我们的内联钩子的流程如下图
当我们想要勾住CreateFile函数,首先要找到CreateFile函数地址,将其首地址代码改为jmp MyHookProc,这样当调用CreateFile函数时 就会跳转到我们写的 MyHookProc函数了,当执行我们自己写的代码后 如果还需要继续执行之前的CreateFile,这时就需要将 CreateFile函数地址的首地址代码改回来,这样我们再次调用CreateFile时 就会走原来的流程了。
Inline Hook实现
在Inline Hook的时候我们介绍了,Hook过程主要是修改 被hook函数的首地址处的代码,将其改为jmp 目标地址即可。那么问题来了,怎样改?
我们用OD随便打开一个exe程序,如下图,我们看到的jmp汇编指令是占5个字节的,其中jmp对应的机器码 是E9(针对长转移了说) 占一个字节,其后的目标地址占4字节。
我们知道内存的内容 都是以二进制存储的,程序在编译的时候 先会通过汇编阶段 转为汇编语言,然后再通过编译阶段转为 机器码,最后是链接阶段。这里直接说结论了,jmp 目标地址 经过编译之后 会变为 E9 偏移量。那么偏移量怎么算?
前面我们知道这里的jmp指令是占5字节,**E9后的偏移量 = 目标地址 - 源地址 -5 ;**知道这个公式之后,我们就可以梳理一下 内联钩子的流程了。
- 找到需要 Hook的函数地址,并保存该地址的前5个字节
- 构造jmp指令(实际是构造对应的机器码指令),并将其写入到 需要Hook的函数地址处
- 当被Hook的函数被调用的时候,就会跳转到我们自己的流程了,这里可以执行我们的逻辑
- 如果还想执行原来的流程,就需要先取消Hook(卸载钩子),也就是还原被修改的字节。(必须先还原,不然会进入死循环 一直进入到我们自己的流程)
- 执行完原来的流程
- 继续Hook原来的位置(把钩子继续勾上,下次调用被Hook的函数 依旧会进入我们的流程)
示例效果
这是卸载钩子后,正常弹框
下钩子后
代码实现
InlineHook.h
#pragma once
#include <Windows.h>
class CInlineHook
{
public:
CInlineHook(void);
~CInlineHook(void);
bool Hook(LPSTR strModuleName,LPSTR strHookFnName,FARPROC strHookCallFnName);
bool UnHook();
bool ReHook();
private:
FARPROC m_pFnOrign; // 要Hook的函数地址
BYTE m_bOld[5]; // 要Hook的函数 前5个字节
BYTE m_bNew[5]; // 要Hook的函数 修改后的5个字节
};
InlineHook.cpp
#include "StdAfx.h"
#include "InlineHook.h"
CInlineHook::CInlineHook(void)
{
m_pFnOrign = NULL;
memset(m_bOld,0,5);
memset(m_bNew,0,5);
}
CInlineHook::~CInlineHook(void)
{
UnHook();
}
bool CInlineHook::Hook(LPSTR strModuleName,LPSTR strHookFnName,FARPROC strTargetFnAddr)
{
bool ret = false;
HMODULE hModule = GetModuleHandleA(strModuleName);
if( hModule == NULL )
goto end;
m_pFnOrign = (FARPROC)GetProcAddress(hModule,strHookFnName);
if( m_pFnOrign == NULL )
goto end;
SIZE_T numByte;
// 保存被Hook函数的前5个字节
if( ReadProcessMemory(GetCurrentProcess(),m_pFnOrign,m_bOld,5,&numByte) == 0 )
goto end;
// 构造jmp指令:jmp 目标地址,对应的机器码:e9 4字节的偏移量
m_bNew[0] = 0xe9;
// 剩余4字节 放偏移量
* (DWORD*)(&m_bNew[0]+1) = (DWORD)strTargetFnAddr - (DWORD)m_pFnOrign -5;
// 修改被Hook函数的前5个字节 改变其执行流程
if( WriteProcessMemory(GetCurrentProcess(),m_pFnOrign,m_bNew,5,&numByte) == 0 )
goto end;
end:
return ret;
}
bool CInlineHook::UnHook()
{
bool ret = false;
// 卸载钩子,实际上即是将更改的5个字节还原
SIZE_T numByte;
if( m_pFnOrign != NULL && WriteProcessMemory(GetCurrentProcess(),m_pFnOrign,m_bOld,5,&numByte) != 0)
ret = true;
return ret;
}
bool CInlineHook::ReHook()
{
bool ret = true;
// 再次装钩子,更改要Hook的函数的前5字节 让其跳转到目标函数
SIZE_T numByte;
if( m_pFnOrign != NULL && WriteProcessMemory(GetCurrentProcess(),m_pFnOrign,m_bNew,5,&numByte) != 0)
ret = true;
return ret;
}
目标函数代码
// WINAPI 一定要声明,指明函数调用方式,这里和MessageBox保持一致
int WINAPI MyMessageBox(HWND hWnd, LPCTSTR lpText, LPCTSTR lpCaption, UINT uType)
{
g_inlineHookObj.UnHook(); // 必须先卸载钩子 再才可以再次调用被Hook的函数,不然会进入死循环
::MessageBoxW(hWnd ,_T("进入MyMessageBox了"),_T("被Hook了,5字节InlineHook"),MB_OK);
::MessageBoxW(hWnd ,lpText,lpCaption,MB_OK);
g_inlineHookObj.ReHook();
return 0;
}
完整项目
完整项目请在这里下载,没分也可以在这里github下载最新代码,如果可以的话,帮忙点个星星哟。