C/C++:Windows编程—Inline Hook内联钩子(上)

前言

先介绍下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 ;**知道这个公式之后,我们就可以梳理一下 内联钩子的流程了。

  1. 找到需要 Hook的函数地址,并保存该地址的前5个字节
  2. 构造jmp指令(实际是构造对应的机器码指令),并将其写入到 需要Hook的函数地址处
  3. 当被Hook的函数被调用的时候,就会跳转到我们自己的流程了,这里可以执行我们的逻辑
  4. 如果还想执行原来的流程,就需要先取消Hook(卸载钩子),也就是还原被修改的字节。(必须先还原,不然会进入死循环 一直进入到我们自己的流程)
  5. 执行完原来的流程
  6. 继续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下载最新代码,如果可以的话,帮忙点个星星哟。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值