windows下的内联hook实现

20 篇文章 1 订阅
17 篇文章 1 订阅

HOOK技术正如其名,就像是代码中放下的一个“钩子”,它在静静地等待捕获系统中的某个消息或动作。在编程技术中,钩子技术在DOS时代就已经存在了。在windows下,钩子按照实现技术的不同和挂钩位置的不同,其种类也是越来越多,但是设置钩子的本质却是始终不变的。

那么钩子究竟有什么用?它能干的事非常多,例如输入监控、API拦截、消息捕获、改变程序执行流程等。杀毒软件会用HOOK技术钩住一些API函数,比如注册表读写函数,从而防止病毒对注册表的操作;病毒使用HOOK技术有针对性的捕获键盘的输入,从而记录用户的密码;文件系统通过HOOK技术在不改变用户操作的情况下对用户的文件进行透明加密。这些都属于HOOK的技术范畴。

内联HOOK的原理

API函数都保存在操作系统提供的DLL文件中。当在程序中调用某个API函数并运行程序后,程序会隐式地将API函数所在的DLL文件加载入进程中。这样,程序就可以像调用自己的函数一样调用API。大体过程如下图所示:


CreateProcessW()是API 函数,API函数也是程序员的代码编译而成,也有其对应的二进制代码。既然是代码,就可以被修改。通过一种强制性的手段修改API函数在内存中的映像,从而对API 函数进行hook。使用的方法是,直接使用汇编指令的jmp指令将其的代码执行流程改变,进而执行自己的代码。执行完自己的代码后可以选择性地执行其它的代码。

在Windows下,大部分应用程序都是由Explorer.exe进程来创建的。那么只要把Explorer.exe中创建进程的函数CreateProcessW()  hook住,就可以自主控制是否让它创建摸个特定进程。执行流程如下:


由于这种方法是直接在程序流程中嵌入jmp指令来改变流程的,所以把它叫做Inline  Hook。

在二进制文件中,代码部分都是CPU可以用来执行的机器码,机器码和汇编指令是一一对应的。使用 :jmp  目的地址,该汇编指令的长度为5个字节。具体可以用OD打开任意程序,修改某条指令为jmp格式的指令即可看到效果。其中jmp指令后跟着的是从当前地址到目的地址的偏移量。如下图所示:

准备修改00402390地质处的反汇编代码为jmp指令:


修改后的反汇编代码:


下一个指令的末尾是95,由此可见该jmp命令的长度是5字节。

jmp后的偏移量 = 目标地址 – 原地址 – jcc的指令长度。

下图是,目标地址 – 原地址后的值,由于本次我们使用的jmp指令长度是5,所以在减去5,最后 偏移量 = 11F432E3,正是上面所给出的值(主机使用的是小端字节序)。


注:

小端字节序:低字节存于内存低地址;高字节存于内存高地址。

大端字节序:高字节存于内存低地址;低字节存于内存高地址。

网络字节序:就是大端字节序。规定不同系统间通信一律采用网络字节序。

总结一下,InlineHook的流程大致如下:

1.     构造跳转指令

2.   在内存中找到要HOOK的函数地址,并保存要HOOK位置处的前指令长度字节数内容。

3.   将构造的跳转指令写入需要HOOK 的位置处。

4.   当被HOOK位置被执行时会转到自己的流程中执行。

5.   如果要执行原来的流程,那么取消HOOK,也就是还原被修改的字节。

6.   执行原来的流程。

7.   继续HOOK原来的位置。

下面是一个简单的例子:

首先,新建一个win32下的DLL工程。在工程中添加一个类,用于操作钩子。

.h文件中的主要代码如下:

#include<Windows.h>

class CILHOOK
{
public:
	CILHOOK();			//构造
	~CILHOOK();			//析构

	//HOOK函数
	BOOL Hook(LPSTR pszModuleName,
				LPSTR pszFuncName,
				PROC pfnHookFunc);
	
	//取消HOOK函数
	VOID UnHook();

	//重新进行HOOK
	BOOL ReHook();

private:
	PROC	m_pfnOrig;			//函数地址
	BYTE	m_bOldBytes[5];		//函数入口代码
	BYTE	m_bNewBytes[5];		//Inline代码
};

.cpp文件中的主要代码如下:

CILHOOK::CILHOOK()
{
	m_pfnOrig = NULL;
	ZeroMemory(m_bOldBytes, 5);
	ZeroMemory(m_bNewBytes, 5);
}

CILHOOK::~CILHOOK()
{
	//取消HOOK
	UnHook();

	m_pfnOrig = NULL;
	ZeroMemory(m_bOldBytes, 5);
	ZeroMemory(m_bNewBytes, 5);
}

/*
函数名:Hook
函数功能:对指定模块的函数进行挂钩
参数说明:
pszModuleName:模块名称
pszFuncName:函数名称
pfnHookFunc:钩子函数
*/
BOOL CILHOOK::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);

		//构造jmp指令
		m_bNewBytes[0] = '\xe9';		//jmp Opcode
		//pfnHookFunc是HOOK后的目标地址
		//m_pfnOrig是原来的地址
		//5是指令长度
		*(DWORD *)(m_bNewBytes + 1) = (DWORD)pfnHookFunc - (DWORD)m_pfnOrig - 5;

		//将构造好的地址写入该地址处
		WriteProcessMemory(GetCurrentProcess(), m_pfnOrig, m_bNewBytes, 5, &dwNum);

		bRet = TRUE;
	}
	return bRet;
}


/*
函数名称:UnHook
函数功能:取消函数的挂钩
*/
VOID CILHOOK::UnHook()
{
	if(m_pfnOrig != 0)
	{
		DWORD dwNum = 0;
		//将原来的内容写回hook地址
		WriteProcessMemory(GetCurrentProcess(), m_pfnOrig, m_bOldBytes, 5, &dwNum);
	}
}

/*
函数名称:ReHook
函数功能:重新对函数进行挂钩
*/
BOOL CILHOOK::ReHook()
{
	BOOL bRet = FALSE;

	if(m_pfnOrig != 0)
	{
		DWORD dwNum = 0;
		//写入挂钩地址
		WriteProcessMemory(GetCurrentProcess(), m_pfnOrig, m_bNewBytes, 5, &dwNum);
		bRet = TRUE;
	}
	return bRet;
}

DLL中的cpp主要代码:

//全局HOOK对象
CILHOOK CreateProcessHook;

BOOL WINAPI MyCreateProcessW(LPCWSTR lpApplicationName,
	LPWSTR lpCommandLine,
	LPSECURITY_ATTRIBUTES lpProcessAttributes,
	LPSECURITY_ATTRIBUTES lpThreadAttributes,
	BOOL bInheritHandles,
	DWORD dwCreationFlags,
	LPVOID lpEnvironment,
	LPCWSTR lpCurrentDirectory,
	LPSTARTUPINFOW lpStartupInfo,
	LPPROCESS_INFORMATION lpProcessInformation)
{

	BOOL bRet = FALSE;
	
	//弹出被创建的进程名
	if(MessageBoxW(NULL, lpApplicationName, lpCommandLine, MB_YESNO) == IDYES)
	{
	
		//自己调用函数前先去掉钩子,否则会进入死循环
		CreateProcessHook.UnHook();

		bRet = CreateProcessW( lpApplicationName,
		lpCommandLine,
		lpProcessAttributes,
		lpThreadAttributes,
		bInheritHandles,
		dwCreationFlags,
		lpEnvironment,
		lpCurrentDirectory,
		lpStartupInfo,
		lpProcessInformation);

		CreateProcessHook.ReHook();

	}else{
		MessageBox(NULL, "您启动的程序被拦截", "提示", MB_OK);
	}

	return bRet;

}



BOOL APIENTRY DllMain( HANDLE hModule, 
                       DWORD  ul_reason_for_call, 
                       LPVOID lpReserved
					 )
{
	switch (ul_reason_for_call)
	{
	case DLL_PROCESS_ATTACH:
		  {
			  //HOOK CreateProcessW()函数
			  CreateProcessHook.Hook("kernel32.dll", "CreateProcessW", (PROC)MyCreateProcessW);
			  break;
		  }
	case DLL_PROCESS_DETACH:
		{
			CreateProcessHook.UnHook();
			break;
		}
	}
    return TRUE;
}

接下来用我前面文章中用到的DLL注入器进行注入相应的DLL即可。

下面是运行时的界面:

注入explorer.exe进程,然后点击打开IE,此时会hook到我们指定的函数。选择是,可以打开IE;选择否,将不打开IE程序。选否时如下图所示:



  • 6
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 5
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值