Windows API Hook

原文地址:http://blog.sina.com.cn/s/blog_628821950100xmuc.html

原文对我的帮助极大,正是由于看了原文。我才学会了HOOK。鉴于原文的排版不是非常好,

又没有原project样例源代码下载,因此我决定对其又一次整理,文章后面附有我測试时的project源代码下载地址。

注:我測试的环境为Win7+VS2008+MFC

原文出处,好像是这篇:http://blog.csdn.net/glliuxueke/article/details/2702608      //后来才看到的

----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

前言
       本文主要介绍了怎样实现替换Windows上的API函数,实现Windows API Hook

(当然。对于socket的Hook仅仅是当中的一种特例)。这样的Hook API技术被广泛的採用在一些领域中,

如屏幕取词,个人防火墙等。这样的API Hook技术并非非常新,可是涉及的领域比較宽广,

要想做好有一定的技术难度。本文是採集了不少达人的曾经资料并结合自己的实验得出的心得体会,

在这里进行总结发表,希望可以给广大的读者提供參考。达到抛砖引玉的结果。


-------------------------------------------------------------------------------------------------------------------------------------------------------------

问题
       近期和同学讨论怎样构建一个Windows上的简单的个人防火墙。后来讨论涉及到了怎样让进程关联套接字port,

替换windows API,屏幕取词等技术。当中基本的问题有:

1) 採用何种机制来截获socket的调用?

一般来说。实现截获socket的方法有非常多非常多,最主要的。能够写驱动,驱动也有非常多种,TDI驱动, NDIS驱动,Mini port驱动…

因为我使用的是Win2000系统。所以截获socket也能够用Windows SPI来进行。

第二种就是Windows API Hook技术。

因为我没什么硬件基础,不会写驱动,所以第一种方法没有考虑。而用SPI相对照较简单。

可是后来认为Windows API Hook适应面更广,并且认为自己动手能学到不少东西,

就决定用Windows API Hook来尝试做socket Hook.
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
2) API Hook的实现方法?

       实际上就是对系统函数的替换,当然实现替换的方法大概不下5,6种吧,能够參考《Windows核心编程》第22章。

只是我使用的方法与其不近同样。应该相对照较简单易懂。


-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
原理

       我们知道,系统函数都是以DLL封装起来的,应用程序应用到系统函数时,应首先把该DLL载入到当前的进程空间中,

调用的系统函数的入口地址,能够通过 GetProcAddress函数进行获取。

当系统函数进行调用的时候,

首先把所必要的信息保存下来(包含參数和返回地址。等一些别的信息)。然后就跳转到函数的入口地址。继续运行。

事实上函数地址。就是系统函数“可运行代码”的開始地址。那么怎么才干让函数首先运行我们的函数呢?

呵呵。应该明确了吧,把開始的那段可运行代码替换为我们自己定制的一小段可运行代码,这样系统函数调用时,

不就按我们的意图乖乖行事了吗?事实上,就这么简单。Very very简单。

:P

       实际的说。就能够改动系统函数入口的地方,让他调转到我们的函数的入口点即可了。

採用汇编代码就能简单的实现Jmp XXXX, 当中XXXX就是要跳转的相对地址。

我们的做法是:把系统函数的入口地方的内容替换为一条Jmp指令。目的就是跳到我们的函数进行运行。

而Jmp后面要求的是相对偏移,也就是我们的函数入口地址到系统函数入口地址之间的差异。再减去我们这条指令的大小。

用公式表达例如以下:(1)int nDelta = UserFunAddr – SysFunAddr - (我们定制的这条指令的大小);(2)Jmp nDleta;

为了保持原程序的健壮性,我们的函数里做完必要的处理后,要回调原来的系统函数。然后返回。

所以调用原来系统函数之前必须先把原来改动的系统函数入口地方给恢复,否则,

系统函数地方被我们改成了Jmp XXXX就会又跳到我们的函数里。死循环了。


--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
那么说一下程序运行的过程。
       我们的dll“注射”入被hook的进程 -> 保存系统函数入口处的代码 -> 替换掉进程中的系统函数入口指向我们的函数 -> 当系统函数被

调用,马上跳转到我们的函数 -> 我们函数进行处理 -> 恢复系统函数入口的代码 -> 调用原来的系统函数 -> 再改动系统函数入口指向

我们的函数(为了下次hook)-> 返回。于是。一次完整的Hook就完毕了。
---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
        好,这个问题明确以后,讲一下下个问题。就是怎样进行dll“注射”?即将我们的dll注射到要Hook的进程中去呢?

非常easy哦,这里我们採用调用Windows提供给我们的一些现成的Hook来进行注射。

举个样例,鼠标钩子。

键盘钩子大家都知道吧?我们能够给系统装一个鼠标钩子,然后全部响应到鼠标事件的进程。

就会“自己主动”(事实上是系统处理了)加载我们的dll然后设置对应的钩子函数。事实上我们的目的仅仅是须要让被注射进程

加载我们的dll就能够了,我们能够再dll实例化的时候进行函数注射的,我们的这个鼠标钩子什么都不干的。

---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

简单的样例OneAddOne

      讲了上面的原理。如今我们应该实战一下了。

先不要考虑windows系统那些繁杂的函数,

我们自己编写一个API函数来进行Hook与被Hook的练习吧。哈哈。

第一步。首先编写一个Add.dll。非常easy,这个dll仅仅输出一个API函数,就是add啦。
新建一个win32 dllproject,


dllmain.cpp的内容:

//千万别忘记声明WINAPI,否则调用的时候回产生声明错误哦!

int WINAPI add(int a,int b) { return a+b; } BOOL APIENTRY DllMain( HANDLE hModule, DWORD ul_reason_for_call, LPVOID lpReserved ) { return TRUE; }

--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

然后别忘了在add.def里面输出函数add:
LIBRARY  Add
DESCRIPTION "ADD LA"
EXPORTS
 add  @1;


建完project后。你会发现没有Add.def文件,这时我们自己新建一个Add.def文件。然后加入到project中就可以,

加入Add.def文件到project后,我们还须要设置project的属性,将Add.def加入到【项目】-->【Add属性】-->

【链接器】-->【输入】-->【模块定义文件】。例如以下图所看到的,不这样设置的话,我们加入的Add.def文件是

不起作用的哦。


设置好后,编译,ok,我们获得了Add.dll

-----------------------------------------------------------------------------------------------------------------------------------------------------

得到Add.dll后。我们能够用一个小工具【dll函数查看器】来打开我们的Add.dll文件,假设函数导出成功的话,我们就能够

在里面看到导出的函数名字了,例如以下图所看到的:


该工具下载地址:http://download.csdn.net/detail/friendan/6347455       //dll函数查看器

----------------------------------------------------------------------------------------------------------------------------------------------------------

有了dll文件后。接下来我们新建一个MFC对话框程序来调用该dll中导出的函数add,

程序界面即执行效果截图例如以下:


主要代码例如以下:

//调用dll函数 add(int a,int b)
void CCallAddDlg::OnBnClickedBtnCallAdd()
{
	HINSTANCE hAddDll=NULL;
	typedef int (WINAPI*AddProc)(int a,int b);//函数原型定义
	AddProc add;
	if (hAddDll==NULL)
	{
		hAddDll=::LoadLibrary(_T("Add.dll"));//载入dll
	}
	add=(AddProc)::GetProcAddress(hAddDll,"add");//获取函数add地址

	int a=1;
	int b=2;
	int c=add(a,b);//调用函数
	CString tem;
	tem.Format(_T("%d+%d=%d"),a,b,c);
	AfxMessageBox(tem);
}

---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

接下来我们进行HOOK,即HOOK我们的Add.dll文件里的函数int add(int a,int b)

新建一个MFC的 dllproject。project名为Hook。然后我们在Hook.cpp文件中面编写代码例如以下:

首先在头部声明例如以下变量:

//变量定义
//不同Instance共享的该变量
#pragma data_seg("SHARED")
static HHOOK  hhk=NULL; //鼠标钩子句柄
static HINSTANCE hinst=NULL; //本dll的实例句柄 (hook.dll)
#pragma data_seg()
#pragma comment(linker, "/section:SHARED,rws")
//以上的变量共享哦!

CString temp; //用于显示错误的暂时变量
bool bHook=false; //是否Hook了函数
bool m_bInjected=false; //是否对API进行了Hook
BYTE OldCode[5]; //老的系统API入口代码
BYTE NewCode[5]; //要跳转的API代码 (jmp xxxx)
typedef int (WINAPI*AddProc)(int a,int b);//add.dll中的add函数定义
AddProc add; //add.dll中的add函数
HANDLE hProcess=NULL; //所处进程的句柄
FARPROC pfadd;  //指向add函数的远指针
DWORD dwPid;  //所处进程ID
//end of 变量定义
---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

编写鼠标钩子安装、卸载和处理函数:

//鼠标钩子过程,什么也不做,目的是注入dll到程序中
LRESULT CALLBACK MouseProc(int nCode,WPARAM wParam,LPARAM lParam)
{
	return CallNextHookEx(hhk,nCode,wParam,lParam);
}

//鼠标钩子安装函数:
BOOL InstallHook()
{

	hhk=::SetWindowsHookEx(WH_MOUSE,MouseProc,hinst,0);

    return true;
}

//卸载鼠标钩子函数
void UninstallHook()
{
	::UnhookWindowsHookEx(hhk);
}

--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

在dll实例化函数InitInstance()中,初始化变量和进行注入:

//在dll实例化中获得一些參数
BOOL CHookApp::InitInstance()
{
	CWinApp::InitInstance();

	//获得dll 实例,进程句柄
	hinst=::AfxGetInstanceHandle();
	DWORD dwPid=::GetCurrentProcessId();
	hProcess=OpenProcess(PROCESS_ALL_ACCESS,0,dwPid); 
	//调用注射函数
	Inject();
	return TRUE;
}

-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

编写注射函数。即HOOK函数Inject()了:

//好,最重要的HOOK函数:
void Inject()
{

	if (m_bInjected==false)
	{ //保证仅仅调用1次
		m_bInjected=true;

		//获取add.dll中的add()函数
		HMODULE hmod=::LoadLibrary(_T("Add.dll"));
		add=(AddProc)::GetProcAddress(hmod,"add");
		pfadd=(FARPROC)add;

		if (pfadd==NULL)
		{
			AfxMessageBox(L"cannot locate add()");
		}

		// 将add()中的入口代码保存入OldCode[]
		_asm 
		{ 
			lea edi,OldCode 
				mov esi,pfadd 
				cld 
				movsd 
				movsb 
		}

		NewCode[0]=0xe9;//实际上0xe9就相当于jmp指令
		//获取Myadd()的相对地址
		_asm 
		{ 
			lea eax,Myadd
				mov ebx,pfadd 
				sub eax,ebx 
				sub eax,5 
				mov dword ptr [NewCode+1],eax 
		} 
		//填充完成,如今NewCode[]里的指令相当于Jmp Myadd
		HookOn(); //能够开启钩子了
	}
}

--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

编写HOOK开启和停止函数HookOn()和HookOff()

//开启钩子的函数
void HookOn() 
{ 
	ASSERT(hProcess!=NULL);

	DWORD dwTemp=0;
	DWORD dwOldProtect;

	//将内存保护模式改为可写,老模式保存入dwOldProtect
	VirtualProtectEx(hProcess,pfadd,5,PAGE_READWRITE,&dwOldProtect); 
	//将所属进程中add()的前5个字节改为Jmp Myadd 
	WriteProcessMemory(hProcess,pfadd,NewCode,5,0);
	//将内存保护模式改回为dwOldProtect
	VirtualProtectEx(hProcess,pfadd,5,dwOldProtect,&dwTemp);

	bHook=true; 
}
//关闭钩子的函数
void HookOff()//将所属进程中add()的入口代码恢复
{ 
	ASSERT(hProcess!=NULL);

	DWORD dwTemp=0;
	DWORD dwOldProtect;

	VirtualProtectEx(hProcess,pfadd,5,PAGE_READWRITE,&dwOldProtect); 
	WriteProcessMemory(hProcess,pfadd,OldCode,5,0); 
	VirtualProtectEx(hProcess,pfadd,5,dwOldProtect,&dwTemp); 
	bHook=false; 
}

-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

编写我们自己的Myadd函数()

//然后,写我们自己的Myadd()函数
int WINAPI Myadd(int a,int b)
{
	//截获了对add()的调用。我们给a,b都加1
	a=a+1;
	b=b+1;

	HookOff();//关掉Myadd()钩子防止死循环

	int ret;
	ret=add(a,b);

	HookOn();//开启Myadd()钩子

	return ret;
}
---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

然后别忘记在hook.def里面导出我们的两个函数 :

InstallHook  
UninstallHook 


----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

接下来就能够进行HOOK的測试了,给前面的对话框程序,再加入两个button。一个用于安装钩子,还有一个用于卸载钩子,

程序和执行效果截图例如以下:


//未HOOK之前


//HOOK之后


-------------------------------------------------------------------------------------------------------------------------------------------------------------------

安装钩子和卸载钩子主要代码例如以下:

HINSTANCE hinst=NULL;
//安装鼠标钩子,进行HOOK
void CCallAddDlg::OnBnClickedBtnStartHook()
{
	typedef BOOL (CALLBACK *inshook)(); //函数原型定义
	inshook insthook;
	
	hinst=LoadLibrary(_T("Hook.dll"));//载入dll文件
	if(hinst==NULL)
	{
		AfxMessageBox(_T("no Hook.dll!"));
		return;
	}
	insthook=::GetProcAddress(hinst,"InstallHook");//获取函数地址
	if(insthook==NULL)
	{
		AfxMessageBox(_T("func not found!"));
		return;
	}
	insthook();//開始HOOK
}

//卸载鼠标钩子,停止HOOK
void CCallAddDlg::OnBnClickedBtnStopHook()
{
	if (hinst==NULL)
	{
		return;
	}
	typedef BOOL (CALLBACK *UnhookProc)(); //函数原型定义
	UnhookProc UninstallHook;

	UninstallHook=::GetProcAddress(hinst,"UninstallHook");//获取函数地址
	if(UninstallHook!=NULL) 
	{
		UninstallHook();
	}
	if (hinst!=NULL)
	{
		::FreeLibrary(hinst);
	}
}

--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

以上就是之前我看的那篇文章的主要内容了,关于HOOK系统API,我会在其他的文章里面进行说明。

这里再说一下原文的缺点,我觉得其有两个缺点:

1.停止HOOK时,没有恢复被HOOK函数的入口。

2.没有处理dll退出事件,没有在dll退出事件中恢复被HOOK函数入口。

以上两个缺点。非常easy导致程序的崩溃,因此在我的样例程序中,都对它们进行了处理:

//卸载鼠标钩子函数
void UninstallHook()
{
	if (hhk!=NULL)
	{
		::UnhookWindowsHookEx(hhk);
	}
	HookOff();//记得恢复原函数入口
}

//dll退出时
int CHookApp::ExitInstance()
{
	HookOff();//记得恢复原函数入口
	return CWinApp::ExitInstance();
}

----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

以上我这个样例project的下载地址:hook dll文件里的函数add.zip

http://download.csdn.net/detail/friendan/6348209

友情提示:我在Debug模式执行程序时,HOOK会失败,在Release模式执行程序则HOOK成功。

--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

posted @ 2017-06-15 09:44  wzjhoutai  阅读(213)  评论(0编辑  收藏  举报