ps:一下两篇文章代码都出现部分错误,稍加修改即可运行
原文地址http://blog.csdn.net/glliuxueke/article/details/2702608
1前言
本文主要介绍了如何实现替换Windows上的API函数,实现Windows API Hook(当然,对于socket的Hook只是其中的一种特例)。这种Hook API技术被广泛的采用在一些领域中,如屏幕取词,个人防火墙等。
这种API Hook技术并不是很新,但是涉及的领域比较宽广,要想做好有一定的技术难度。本文是采集了不少达人的以前资料并结合自己的实验得出的心得体会,在这里进行总结发表,希望能够给广大的读者提供参考,达到抛砖引玉的结果。
2问题
最近和同学讨论如何构建一个Windows上的简单的个人防火墙。后来讨论涉及到了如何让进程关联套接字端口,替换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章。不过我使用的方法与其不近相同,应该相对比较简单易懂。
3原理
我们知道,系统函数都是以DLL封装起来的,应用程序应用到系统函数时,应首先把该DLL加载到当前的进程空间中,调用的系统函数的入口地址,可以通过 GetProcAddress函数进行获取。当系统函数进行调用的时候,首先把所必要的信息保存下来(包括参数和返回地址,等一些别的信息),然后就跳转到函数的入口地址,继续执行。其实函数地址,就是系统函数“可执行代码”的开始地址。那么怎么才能让函数首先执行我们的函数呢?呵呵,应该明白了吧,把开始的那段可执行代码替换为我们自己定制的一小段可执行代码,这样系统函数调用时,不就按我们的意图乖乖行事了吗?其实,就这么简单。Very very简单。 :P
实际的说,就可以修改系统函数入口的地方,让他调转到我们的函数的入口点就行了。采用汇编代码就能简单的实现Jmp XXXX, 其中XXXX就是要跳转的相对地址。
我们的做法是:把系统函数的入口地方的内容替换为一条Jmp指令,目的就是跳到我们的函数进行执行。而Jmp后面要求的是相对偏移,也就是我们的函数入口地址到系统函数入口地址之间的差异,再减去我们这条指令的大小。用公式表达如下:
int nDelta = UserFunAddr – SysFunAddr - (我们定制的这条指令的大小);
Jmp nDleta;
为了保持原程序的健壮性,我们的函数里做完必要的处理后,要回调原来的系统函数,然后返回。所以调用原来系统函数之前必须先把原来修改的系统函数入口地方给恢复,否则,系统函数地方被我们改成了Jmp XXXX就会又跳到我们的函数里,死循环了。
那么说一下程序执行的过程。
我们的dll“注射”入被hook的进程 -> 保存系统函数入口处的代码 -> 替换掉进程中的系统函数入口指向我们的函数 -> 当系统函数被调用,立即跳转到我们的函数 -> 我们函数进行处理 -> 恢复系统函数入口的代码 -> 调用原来的系统函数 -> 再修改系统函数入口指向我们的函数(为了下次hook)-> 返回。
于是,一次完整的Hook就完成了。
好,这个问题明白以后,讲一下下个问题,就是如何进行dll“注射”?即将我们的dll注射到要Hook的进程中去呢?
很简单哦,这里我们采用调用Windows提供给我们的一些现成的Hook来进行注射。举个例子,鼠标钩子,键盘钩子,大家都知道吧?我们可以给系统装一个鼠标钩子,然后所有响应到鼠标事件的进程,就会“自动”(其实是系统处理了)载入我们的dll然后设置相应的钩子函数。其实我们的目的只是需要让被注射进程载入我们的dll就可以了,我们可以再dll实例化的时候进行函数注射的,我们的这个鼠标钩子什么都不干的。
4简单的例子OneAddOne
讲了上面的原理,现在我们应该实战一下了。先不要考虑windows系统那些繁杂的函数,我们自己编写一个API函数来进行Hook与被Hook的练习吧,哈哈。
第一步,首先编写一个add.dll,很简单,这个dll只输出一个API函数,就是add啦。
新建一个win32 dll工程,
add.cpp的内容:
#include "stdafx.h"
int WINAPI add(int a,int b){ //千万别忘记声明WINAPI 否则调用的时候回产生声明错误哦!
return a+b;
}
BOOL APIENTRY DllMain( HANDLE hModule,
DWORD ul_reason_for_call,
LPVOID lpReserved
)
{
return TRUE;
}
然后别忘了在add.def里面输出函数:
LIBRARY Add
DESCRIPTION "ADD LA"
EXPORTS
add @1;
编译,ok,我们获得了add.dll
第二步,编写1+1主程序
新建一个基于对话框的工程One,
在 OnOK()里面调用add函数:
ConeDlg.h里面加入一些变量的声明:
….
Public:
HINSTANCE hAddDll;
typedef int (WINAPI*AddProc)(int a,int b);
AddProc add;
…
ConeDlg.cpp里进行调用:
void COneDlg::OnOK()
{
// TODO: Add extra validation here
if (hAddDll==NULL)
hAddDll=::LoadLibrary("add.dll");
add=(AddProc)::GetProcAddress(hAddDll,"add");
int a=1;
int b=2;
int c=add(a,b);
CString tem;
temp.Format("%d+%d=%d",a,b,c);
AfxMessageBox(temp);
}
OK,编译运行,正确的话就会显示1+2=3咯
第3步,要动手Hook咯,爽阿
新建一个MFC的 dll工程,Hook
在Hook.dll工程里:
添加一个鼠标Hook MouseProc,鼠标hook什么也不做
LRESULT CALLBACK MouseProc(int nCode,WPARAM wParam,LPARAM lParam)
{
LRESULT RetVal= CallNextHookEx(hhk,nCode,wParam,lParam);
return RetVal;
}
添加鼠标钩子的安装和卸载函数:
BOOL InstallHook()
{
hhk=::SetWindowsHookEx(WH_MOUSE,MouseProc,hinst,0);
….
return true;
}
void UninstallHook()
{
::UnhookWindowsHookEx(hhk);
}
再实例化中获得一些参数
BOOL CHookApp::InitInstance()
{
获得dll 实例,进程句柄
hinst=::AfxGetInstanceHandle();
DWORD dwPid=::GetCurrentProcessId();
hProcess=OpenProcess(PROCESS_ALL_ACCESS,0,dwPid);
//调用注射函数
Inject();
return CWinApp::InitInstance();
}
好,最重要的注射函数:
void Inject()
{
if (m_bInjected==false)
{ //保证只调用1次
m_bInjected=true;
//获取add.dll中的add()函数
HMODULE hmod=::LoadLibrary("add.dll");
add=(AddProc)::GetProcAddress(hmod,"add");
pfadd=(FARPROC)add;
if (pfadd==NULL)
{
AfxMessageBox("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(); //可以开启钩子了
}
}
开启钩子的函数
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()函数
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
MouseProc
Myadd
UninstallHook
四个函数。
(全部的程序会贴在最后面的)
好到这里基本上大功告成咯
第4步,我们就可以修改前面的One的测试程序了
增加一个安装钩子的函数/按钮
void COneDlg::doHook()
{
hinst=LoadLibrary("hook.dll");
if(hinst==NULL)
{
AfxMessageBox("no hook.dll!");
return;
}
typedef BOOL (CALLBACK *inshook)();
inshook insthook;
insthook=::GetProcAddress(hinst,"InstallHook");
if(insthook==NULL)
{
AfxMessageBox("func not found!");
return;
}
DWORD pid=::GetCurrentProcessId();
BOOL ret=insthook();
}
别忘了退出时卸掉钩子
void COneDlg::OnCancel()
{
// TODO: Add extra cleanup here
typedef BOOL (CALLBACK *UnhookProc)();
UnhookProc UninstallHook;
UninstallHook=::GetProcAddress(hinst,"UninstallHook");
if(UninstallHook==NULL) UninstallHook();
if (hinst!=NULL)
{
::FreeLibrary(hinst);
}
if (hAddDll!=NULL)
{
::FreeLibrary(hAddDll);
}
CDialog::OnCancel();
}
好了,大功告成咯,现在运行一下One,
先点Ok,测试1+2=3 没问题,
然后点Hook,安装钩子,,显示installhook ok,然后再点一下ok,
哈哈,发现结果1+2=5 !!!???
Hook成功啦!
附上Hook.cpp的原码
/
Hook.cpp
// Hook.cpp : Defines the initialization routines for the DLL.
//
#include "stdafx.h"
#include "Hook.h"
#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif
/
// CHookApp
BEGIN_MESSAGE_MAP(CHookApp, CWinApp)
//{{AFX_MSG_MAP(CHookApp)
// NOTE - the ClassWizard will add and remove mapping macros here.
// DO NOT EDIT what you see in these blocks of generated code!
//}}AFX_MSG_MAP
END_MESSAGE_MAP()
/
// CHookApp construction
CHookApp::CHookApp()
{
// TODO: add construction code here,
// Place all significant initialization in InitInstance
}
/
// The one and only CHookApp object
CHookApp theApp;
//变量定义
//不同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 变量定义
//函数定义
void HookOn(); //开启钩子
void HookOff(); //关闭钩子
LRESULT CALLBACK MouseProc(int nCode,WPARAM wParam,LPARAM lParam); //鼠标钩子函数
void Inject(); //具体进行注射,替换入口的函数
int WINAPI Myadd(int a,int b); //我们定义的新的add()函数
BOOL InstallHook(); //安装钩子函数
void UninstallHook(); //卸载钩子函数
//end of 函数定义
BOOL CHookApp::InitInstance()
{
//获取自身dll句柄
hinst=::AfxGetInstanceHandle();
//获取所属进程id和句柄
DWORD dwPid=::GetCurrentProcessId();
hProcess=OpenProcess(PROCESS_ALL_ACCESS,0,dwid);
//调用函数注射
Inject();
return CWinApp::InitInstance();
}
int CHookApp::ExitInstance()
{
// 如果add钩子开着,则将其关闭
if (bHook)
HookOff();
return CWinApp::ExitInstance();
}
BOOL InstallHook()
{
hhk=::SetWindowsHookEx(WH_MOUSE,MouseProc,hinst,0);
if (hhk==NULL)
{
DWORD err=::GetLastError();
temp.Format("hook install failed!:%d",err);
AfxMessageBox(temp);
return false;
}
AfxMessageBox("hook installed!");
return true;
}
void UninstallHook()
{
if (hhk!=NULL)
::UnhookWindowsHookEx(hhk);
AfxMessageBox("Unhooked!");
}
void Inject()
{
if (m_bInjected==false)
{
m_bInjected=true;
//读取add.dll并查找到add()函数
HMODULE hmod=::LoadLibrary("add.dll");
add=(AddProc)::GetProcAddress(hmod,"add");
pfadd=(FARPROC)add;
if (pfadd==NULL)
{
AfxMessageBox("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();
}
}
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;
}
LRESULT CALLBACK MouseProc(
int nCode, // hook code
WPARAM wParam, // message identifier
LPARAM lParam // mouse coordinates
)
{
LRESULT RetVal= CallNextHookEx(hhk,nCode,wParam,lParam);
return RetVal;
}
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;
}
//
hook.def
LIBRARY "Hook"
DESCRIPTION 'Hook Windows Dynamic Link Library'
EXPORTS
; Explicit exports can Go here
InstallHook
MouseProc
Myadd
UninstallHook
5 实战Hook Win Socket API
如果对于上面这章的的理解没有什么问题的化,我想对于windows API的Hook应该也就是按部就班的做就可以了,不会有太大的问题,只要足够细心耐心,善于处理发现一些细节问题就行了。
当然,为了使我们的程序使用起来更灵活方便,必须要改进我们的程序结构,因为我们要Hook很多的API,不可能为每个API制定一套注射、截获的函数,
所以,我设计了一个类来储存某个API的一些信息:
class CHookFuncInfo
{
public:
CHookFuncInfo(LPCTSTR szLibName,LPCTSTR szOldname,LPCTSTR szNewName)
{
//构造函数必须设定三个参数
strcpy(LibraryName,szLibName);
strcpy(OldFuncName,szOldname);
strcpy(NewFuncName,szNewName);
Injected=false;
}
public:
HINSTANCE hinstLib; //该函数所在dll模块的实例句柄
FARPROC fpFunc; //指向原函数的指针
BYTE OldCode[5]; //原函数入口处的代码
BYTE NewCode[5]; //跳转到我们函数入口地址的代码Jmp xxxx
FARPROC fpMyFunc; //指向我们函数地址的指针
char LibraryName[256]; //该函数所属的dll模块的名称
char OldFuncName[256]; //原API函数名
char NewFuncName[256]; //我们程序中定义的新API函数名
bool Injected; //该函数是否被Hook
};
对某API函数的注射都可以用Inject()来处理
void Inject(CHookFuncInfo* phinfo);
开/关某个API可以用HookOn/HookOff来处理
void HookOff(CHookFuncInfo * phinfo);
void HookOn(CHookFuncInfo * phinfo);
需要注意的是,在汇编代码里,不可以直接对对象的指针进行调用
即 mov eax, phinfo->fpFunc;诸如此类的代码是不被允许的。
所以我们要加入新的变量,处理完毕,将变量值传回给传入的对象指针:
void Inject(CHookFuncInfo * phinfo)
{
…….
FARPROC fpFunc=phinfo->fpFunc;
BYTE OldCode[5],NewCode[5];
……
汇编代码
……
memcpy(phinfo->OldCode,OldCode,5);
memcpy(phinfo->NewCode,NewCode,5);
HookOn(phinfo);//开启钩子
return;
}
举个例子:
CHookFuncInfo hinfoSendto("wsock32.dll","sendto","MySendto");
//创建一个sendto函数的API钩子
然后注射-〉开启钩子-〉…
下面是我定义的MySendto()的代码:
int WINAPI MySendto(
SOCKET s,
const char FAR *buf,
int len,
int flags,
const struct sockaddr FAR *to,
int tolen
)
{
int ret;
HookOff(&hinfoSendto);//关钩子
//下面这段就是通过WM_COPYDATA传递消息的方法,
//将一些sendto()的数据通过字符串方式传递给
//名为”XsockSpy”的窗体。
//XsockSpy窗体只需要响应对WM_COPYDATA的处理和显示就OK了,很简单。
HWND hwnd=::FindWindow("#32770","XSockSpy");
CString temp;
temp.Format("pid:%d sending %d bytes",g_pid,len);
if(hwnd!=NULL)
{
COPYDATASTRUCT cpdata;
cpdata.dwData=0;
cpdata.lpData=temp.GetBuffer(0);
cpdata.cbData=temp.GetLength();
::SendMessage(hwnd,WM_COPYDATA,0,(LPARAM)&cpdata);
}
//调用真正的sendto()函数
ret=::sendto(s,buf,len,flags,to,tolen);
if (ret==SOCKET_ERROR
{
temp.Format("send error:%d",WSAGetLastError());
AfxMessageBox(temp);
}
//开钩子
HookOn(&hinfoSendto);
return ret;
};
好了,是不是很简单啊?
打得很累了,我想大家基本生可以明白了吧?
如果需要代码的可以向我来索要,发信到 yoyohon@etang.com注明“索要代码”就可以了。
不过由于时间关系,截获socket的代码我仅仅实现了sendto()这个函数的截获,而且代码可能也比较临乱吧,大家见谅了!
欢迎大家讨论,发表看法,提出异议!
最后感谢一下对本文又帮助的人:
pingfanxin
Win2K下的Api函数的拦截
http://www.yourblog.org/Data/20044/62890.html
TopLevel
有关API HOOK方面的一些浅释
http://dev.csdn.net/develop/article/27/27732.shtm
原文地址http://blog.csdn.net/maoenpei002/article/details/5358601
2012年12月13日补充:
这篇文章写的时候是我还在上学的时候,所以不管是从技术实现角度还是文笔都显得很嫩,在此向所有无意间看到这篇文章的人表示抱歉。我写了这篇文章之后2年有人想问我要源代码,唉,如果我下次写文章一定贴上源代码,不过那么老的代码我实在是不大情愿找出来了。
我希望这篇文章已经把实现原理的每一个细节都点到了,虽然讲了很多废话,但是细节都埋在废话当中。
我自己简单看了一下我自己的文章(说实话我也有点忘记了),然后把实现流程做一个归纳:
1、在想要收到通知的项目(简称“原项目”)中,至少要包含一个窗口用以接受消息,并且必须保证在别的进程中能找到这个窗口句柄。
2、创建一个dll项目(简称“新项目”),在dllMain函数中根据DLL_PROCESS_ATTACH和DLL_PROCESS_DETACH向原窗口发送消息。
3、新项目中导出两个函数,一个用来增加hook一个用来删除hook,这两个函数的实现就在代码中(StartupHook/CloseHook),这是实现hook的基本技术。
4、想一个办法加载一次这个dll,比方说在原项目中调用LoadLibrary,然后去调StartupHook,这样在运行时,windows系统会把这个dll强制插入到所有的进程中(除了系统认为没有权限插入dll的进程),以及会为新创建的进程插入此dll。
5、第一次加载dll是手动的,之后为绝大部分进程“注射”dll是由系统完成的,最后卸载也是手动的,此时系统会把此dll从那些强制注射的进程中卸载掉。这其实就是整个程序的全部执行流程了。
以下是原文章内容:
第二次写文章,不好意思,和第一次理由相同,就是网上没有找到符合此问题的满意答案。
在一开头,我们需要统一语言,本人使用C++开发(确切的说是练习开发)windows程序,不是MFC。
还有本人使用Visual Studio 2008作为集成开发环境。
不敢称此为技术,只能叫方法。前面的三点全是不同情况的分析(意味着废话),需要马上知道方法的请直接看第四点。
我的需求是制作一个任务管理器。
方法有几种,可供参考:
第一,用窗口的计时器机制,不断枚举进程。
(顺便提及一下,关于如何枚举进程,建议学习CreateToolhelp32Snapshot及相关函数的使用,网上亦有文可考,在此不做累述,因为不是重点。)
这么做当然很方便,但是会出现一些问题,首先,设置一个计时器(SetTimer)的开销比较大,一个任务管理器本来就是小程序,如果为了偶尔发生的一些CreateProcess函数而设置一个不停地做无用功的计时器,过于浪费系统资源;其次,你的计时器时间间隔设置成多少?如果时间长了,就不能及时的反应进程信息,如果时间短了,自然频率就上去了,开销增大。
所以此方法适合widows程序的初学者尝试学习,不应该作为一个软件成品的一部分。
第二,使用windows的消息钩子技术,钩取进程启动和关闭消息。
当然,此方法完全不能使用,只是个人在一段时期内曾经用过,但是比第一种方法还要差。
首先,消息钩子只适用与“存在消息发送和接受”的程序,换句话说,就是窗口程序,windows这个操作系统并没有提供一种机制,可以在内核模式下(进程一级)捕获消息,当然是出于安全性考虑。所以只能发送消息给窗口,那么就马上否定了能够抓到后台运行程序的可能性。
其次,对于一个窗口程序而言,什么消息代表它的建立和灭亡?显然WM_CREATE和WM_QUIT消息(或者相关消息),但是很明显,有一条消息不是windows钩子能够抓到的,那就是WM_CREATE消息,因为windows消息钩子只能钩那些存在于消息队列中的消息,而WM_CREATE并不进消息队列,所以适用范围又减小了一半。
前两种方法是我最开始使用的方法,2个方法结合了使用,所以既没能保住效率,也没能保住即时性。
第三种方法比较难理解,但是是一种“正确”的方法,就是说它完全可以实现我们需要的功能,就是使用API Hook技术。
当然,这个方法需要深刻理解PE格式还有windows的内核工作原理。
我们知道windows启动一个进程,一定会调用一个API函数就是CreateProcess(),而结束一个进程则可能用到TerminateProcess()和ExitProcess(),那么我只要知道程序什么时候调用了这些函数,就可以明确地知道打开进程、关闭进程事件。
(当然这里也没法详细讲解API Hook技术,因为不是本文重点,所以需要了解的朋友请自己参考《windows核心编程》上的内容,里面有详细的解释说明,网上也有,但是讲得都不是很能说明问题(个人观点),因此建议还是直接看书比较好。我的书是第四版,在第四部分,22章里面讲到这个技术。)
此技术当然也有问题,我挂接了一个API且不说很烦,我还需要手动把dll插到各个能够调用CreateProcess的函数中,还好我们一般运行程序都是用双击点开的,那么只要挂接explorer.exe就行了;但是万一有人使用命令行模式打开呢?还需要挂接cmd.exe和taskmgr.exe(默认的那个任务管理器也具有命令行功能)。
第四种方法不是我最近想到的,但是是我最近忽然意识到的,只能怪我原来对于windows钩子和dll的理解不够深刻,所以没能发现原来这么简单就可以了。
需要一节基本的预备知识:dll映射机制、钩子原理,当然还有windows进程如何调用一个dll和窗口机制。
1、把没用的先去掉,窗口机制是为了能把捕获到的事件发送给我需要的程序,我前面说了,windows并没有提供一种机制能够使得进程与进程之间能够自由通信,所以需要依赖窗口,好在任务管理器不可能没有窗口(不然拿什么现实给用户?总不好自己写显卡驱动吧^_^),所以只要捕获到之后对着窗口发送消息就OK了。
2、dll是一种“代码共享”机制,为了节省物理内存。dll本身不能执行任何代码,它属于“应用程序”,但是没有程序入口函数;它可以分配内存但是只能在映射到某一个进程地址空间之后才能分配,而分配的内存也不属于dll而是属于加载dll的进程和线程;可以说,当dll被加载了之后几乎失去了它作为dll的所有特征标志(引用《windows核心编程》原话)。我的理解就是如果dll没有被映射,它没有任何可利用价值。
3、钩子的原理也在核心编程中讲到,用一句话来简单描述,就是“一个强势插入的dll”。
比如对于一个消息钩子,如果消息队列中一条消息准备发送到某进程的某窗口,那么系统会首先检查是否为这个进程插入了消息钩子,如果有,那么检查是否将钩子所在的dll映射到了进程地址空间,如果没有,那么进行一次映射,如果有,那么需要检查数据一致性(这个由系统完成),接下来就是调用我们提供给消息钩子的那个回调函数,直到回调函数运行完毕,那么消息才最终发送给应用程序。
4、刚刚我已经讲了,dll不会“自动”地执行代码,而是只能由进程调用LoadLibrary()函数加载到地址空间里面之后才能够体现它的价值。但是虽然它没有入口函数,却有一个消息通知函数,函数原型为:BOOL WINAPI DllMain(HINSTANCE hinstDll, DWORD fdwReason, PVOID fImpLoad) 这个函数的源代码可以由集成开发环境自动生成。
这个函数的唯一功能就是提供了进程钩取、进程释放、线程钩取、线程释放4个事件的消息通知,此函数非常强大,在此不予多讲。
好了,关键在这第四点里,我们现在所需要的不就是进程启动通知吗,而一个进程加载一个dll的事件是可以被我们捕捉到的。那么自然可以想到:
我们的目的是:我希望设计一个程序,这个程序能够让操作系统做出一个改变,这个改变使得每当一个程序启动/关闭的时候都能够给我的程序发送一个消息以指明发生的启动/关闭事件。
有了第四点,这个问题等价于:我希望能够设计一个程序,这个程序能够让操作系统做出一种改变,这种改变使得每当一个程序启动的时候,都能够加载一个指定的dll,这下不就行了?
那么怎么保证后面说的那种情况呢?显然,“钩子”可以实现这个功能啊。
综上所述,最关键的一句话就是:我只要设计一个dll,让这个dll成为一个钩子,插入到所有的进程,当我在这个dll的进程钩取、释放通知里面实现各一个函数,这个函数的功能是发送一条消息到我所指定的窗口中去。
讲到现在,问题解决了,如果上面讲的话能够完全明白,以下的代码就没有用处了,以便于不浪费一些理解能力非常强的人的宝贵的时间。
//---------------------------DLL部分--------------------------------------------
//---------------------------ProcHook.h---------------------------------------
extern "C"
{
__declspec(dllexport)
void StartupHook(void);
__declspec(dllexport)
void CloseHook(void);
}
void ProcessHookUp();
void ProcessHookDelete();
//--------------------------End Of File----------------------------------------
//--------------------------dllmain.cpp---------------------------------------
#include"ProcHook.h"
int count = 0;
BOOL APIENTRY DllMain( HMODULE hModule,
DWORD ul_reason_for_call,
LPVOID lpReserved
)
{
switch (ul_reason_for_call)
{
case DLL_PROCESS_ATTACH:
ProcessHookUp();
break;
case DLL_THREAD_ATTACH:
case DLL_THREAD_DETACH:
break;
case DLL_PROCESS_DETACH:
ProcessHookDelete();
break;
}
return TRUE;
}
//--------------------------End Of File----------------------------------------
//--------------------------ProcHook.cpp--------------------------------
#include"ProcHook.h"
HHOOK hThis = NULL;
LRESULT CALLBACK ProcessClose(int nCode, WPARAM wParam, LPARAM lParam)
{
return CallNextHookEx(hThis, nCode, wParam, lParam);
}
void StartupHook(void)
{
hThis = SetWindowsHookExW(WH_GETMESSAGE, ProcessClose, GetModuleHandleW(L"ProcHook.dll"), 0);
}
void CloseHook(void)
{
UnhookWindowsHookEx(hThis);
}
void ProcessHookUp()
{
DWORD proId = GetCurrentProcessId();
HWND hWnd = FindWindow(L"MyClass", L"MyWindow");
if (hWnd)
PostMessage(hWnd, WM_WINDOW, 0, proId);
else
CloseHook();
}
void ProcessHookDelete()
{
DWORD proId = GetCurrentProcessId();
HWND hWnd = FindWindow(L"MyClass", L"MyWindow");
if (hWnd)
PostMessage(hWnd, WM_WINDOW, 1, proId);
else
CloseHook();
}
//--------------------------End Of File----------------------------------------
//--------------------------调用部分-------------------------------------------
#include"ProcHook.h"
#pragma comment(lib, "ProcHook.lib")
class A()
{
public:
A();
~A();
};
A::A()
{
StartupHook();
}
A::~A()
{
CloseHook();
}
//-------------------------End Of File----------------------------------------
最后在WinMain函数(或者其他的程序切入函数)中声明一个A类的对象即可,当函数执行完毕自动撤销。或者窗口被清除了也可以自动撤销。
注意:为了尽可能不让大家看到忒长的代码,我只是从我的程序代码中抽取了有用的部分,由于不是编译过的,所以难免会发生失误,语法错误或者保留了我自己的程序上面的一部分,使得有些地方看不明白什么用,如果发生了那也请大家原谅。