API Hook根基道理和实现
副标题#e#
hook是什么?
windows系统下的编程,动静message的通报是贯串其始终的。这个动静我们可以简朴领略为一个有特定意义的整数,正如我们看过的老故事片中的“长江长江,我是黄河”一个寄义。windows中界说的动静给初学者的印象好像是“不行胜数”的,常见的一部门动静在winuser.h头文件中界说。hook与动静有着很是密切的接洽,它的中文寄义是“钩子”,这样领略起来我们不可贵出“hook是动静处理惩罚中的一个环节,用于监控动静在系统中的通报,并在这些动静达到最终的动静处理惩罚进程前,处 理某些特定的动静”。这也是hook分为差异种类的原因。
hook的这个本事,使它可以或许将自身的代码“融入”被hook住的措施的历程中,成为方针历程的一个部门。我们也知道,在windows2000今后的系统中,普通用户措施的历程空间都是独立的,措施的运行互相间都不受滋扰。这就使我们但愿通过一个措施改变其他措施的某些行为的想法不能直接实现,可是hook的呈现给我们开辟了办理此类问题的阶梯。
api hook是什么?
在windows系统下编程,应该会打仗到api函数的利用,常用的api函数或许有2000个阁下。本日跟着控件,stl等高效编程技能的呈现,api的利用概率在普通的用户措施上就变得越来越小了。当诸如控件这些现成的手段不能实现的成果时,我们还需要借助api。最初有些人对某些api函数的成果不太满足,就发生了如何修改这些api,使之更好的处事于措施的想法,这样api hook就自然而然的呈现了。我们可以通过api hook,改变一个系统api的原有成果。根基的要领就是通过hook“打仗”到需要修改的api函数进口点,改变它的地点指向新的自界说的函数。api hook并不属于msdn上先容的13类hook中的任何一种。所以说,api hook并不是什么出格差异的hook,它也需要通过根基的hook提高本身的权限,超过差异历程间会见的限制,到达修改api函数地点的目标。对付自身历程空间下利用到的api函数地点的修改,是不需要用到api hook技能就可以实现的。
#p#副标题#e#
api hook和pe名目标干系
api hook技能的难点,并不在于hook技能,初学者借助于资料“照葫芦画瓢”可以或许很容易就把握hook的根基利用技能。可是如何修改api函数的进口地点?这就需要进修pe可执行文件(.exe,.dll等)如何被系统映射到历程空间中,这就需要进修pe名目标根基常识。windows已经提供了很大都据布局struct辅佐我们会见pe名目,借助它们,我们就不要本身计较名目标详细字节位置这些繁琐的细节。可是从api hook的实现来看,pe名目标会见部门仍然是整个编程实现中最巨大的一部门,对付常常crack的伴侣不在此列。
假设我们已经相识了pe名目,那么我们在那边修改api的函数进口点较量符合呢?这个就是输入标记表imported symbols table(间接)指向的输入标记地点。
下面临付pe名目标先容这一部门,对付没有打仗过pe名目进修的伴侣应该是看不太大白的,但我已经把英华部门提取出来了,进修了pe名目后再看这些就很容易了。
pe名目标根基构成
+-------------------+
| DOS-stub | --DOS-头
+-------------------+
| file-header | --文件头
+-------------------+
| optional header | --可选头
|- - - - - - - - - -|
| |
| data directories | --(可选头尾的)数据目次
| |
+-------------------+
| |
| section headers | --节头
| |
+-------------------+
| |
| section 1 | --节1
| |
+-------------------+
| |
| section 2 | --节2
| &// 本文转自 C++Builder 研究 - http://www.ccrun.com/article.asp?i=1036&d=cf6de2
nbsp; |
+-------------------+
| |
| ... |
| |
+-------------------+
| |
| section n | --节n
| |
+-------------------+
在上图中,我们需要从“可选头”尾的“数据目次”数组中的第二个元素——输入标记表的位置,它是一个IMAGE_DATA_DIRECTORY布局,从它中的VirtualAddress地点,“顺藤摸瓜”找到api函数的进口所在。
下图的简朴说明如下:
OriginalFirstThunk 指向IMAGE_THUNK_DATA布局数组,为利便只画了数组的一个元素,AddressOfData 指向IMAGE_IMPORT_BY_NAME布局。
IMAGE_IMPORT_DESCRIPTOR数组:每个引入的dll文件都对应数组中的一个元素,以全0的元素(20个bytes的0)暗示数组的竣事
IMAGE_THUNK_DATA32数组:同一组的以全0的元素(4个bytes的0)暗示数组的竣事,每个元素对应一个IMAGE_IMPORT_BY_NAME布局
IMAGE_IMPORT_BY_NAME:如[email protected]@initialization$qqrv. 暗示
Unmangled Borland C++ Function: qualified function __fastcall Consts::initialization()
#p#分页标题#e#
为了淘汰这个图的巨细,不得已将汇编和c++的布局都用上了。这个图是输入标记表初始化的景象,此时两个IMAGE_THUNK_DATA布局数组的对应元素都指向同一个IMAGE_IMPORT_BY_NAME布局。
措施加载到历程空间后,两个IMAGE_THUNK_DATA布局数组指向有所差异了。看下图:
初始化的,“两个布局都指向同一个IMAGE_IMPORT_BY_NAME”,此时还没有api函数地点
当PE文件筹备执行时,前图已转换成上图。一个布局指向稳定,另一个呈现api函数地点
假如PE文件从kernel32.dll中引入10个函数,那么IMAGE_IMPORT_DESCRIPTOR 布局的 Name1域包括指向字符串"kernel32.dll"的RVA,同时每个IMAGE_THUNK_DATA 数组有10个元素。(RVA是指相对地点,每一个可执行文件在加载到内存空间前,都以一个基址作为起点,其他地点以基址为准,均以相对地点暗示。这样系统加载措施到差异的内存空间时,都可以利便的算出地点)
上述这些布局可以在winnt.h头文件里查到。
详细编程实现
我将手上的vc示例代码举办了适当批改,修改了一些资源泄漏的小问题,移植到c++builder6 & update4上,颠末测试已经可以完成根基的api hook成果。有几个常识点说明一下:
1、dll中共享内存变量的实现
正常编译下的dll,它的变量利用到的内存是独立的。好比你同时运行两个挪用了某个dll的用户措施,试图对某一个在dll中界说的全局变量修改赋值的时候,两个措施里的变量值仍然是差异的。
共享的要领为:在.cpp文件(.h文件里如此配置会提示编译错误)的头部写上如上两行:
#pragma option -zRSHSEG // 改变缺省数据段名
#pragma option -zTSHCLASS // 改变缺省数据类名
HINSTANCE hdll = NULL; // 用来生存该动态毗连库的句柄
HHOOK hApiHook = NULL; // 钩子句柄
HHOOK hWndProc = NULL; // 窗口进程钩子用来拦截SendMessage
int threadId = 0;
别的成立一个与dll同名,差异后缀的def文件,如HookDll.def文件,写上:
LIBRARY HookDll.dll
EXPORTS
;...
SEGMENTS
SHSEG CLASS 'SHCLASS' SHARED
;end
这样配置后在.cpp文件中界说的变量,假如举办了初始化,将进入“SHCLASS”共享内存段(假如不初始化,将不改变其默认段属性)。
上述的共享对付本示例代码并不是必需的,只是稍微演示了一下。
2、api hook修改api函数进口点地点的机缘
很显然,我们必需通过hook进入方针历程的地点空间后,再在位于该地点空间里的hook动静处理惩罚进程里修改输入标记表“指向”的api函数进口点地点,退出hook前也必需在这个动静处理惩罚进程里规复本来的地点。只要我们紧记修改的进程产生在方针历程的地点空间中,就不会产生会见违例的错误了。
示例代码利用了WH_GETMESSAGE、WH_CALLWNDPROC两中hook来演示如何hook api,但WH_GETMESSAGE实际上并没有完成详细的成果。
为了让初学者尽快的把握重点,我将代码举办了简化,是一个不结实、不机动的演示示例。
3、函数的表里部表示形式
譬喻api函数MessageBox,这个形式是我们凡是用到的,但到了dll里,它的名字很大概呈现了两个形式,一个是MessageBoxA,另一个是MessageBoxW,这是因为系统需要适应Ansi和Unicode编码的两种形式,我们不在函数尾端添加“A”或“W”,是不能hook到需要的函数的。
4、帮助pe名目查察东西
PE Explorer是一个很是好的查察pe资源的东西,通过它可以验证本身手工计较的pe地点,可以更快的把握pe名目。
调试器ollydbg也长短常好的帮助东西,譬喻查察输入标记表中的api函数。
5、措施文件列表
dll根基文件:Hook.h,Hook.cpp,HookDll.def
client验证方根基文件:HookTest.h,HookTest.cpp,ApiHookTest.cpp
6、实现的成果
对记事本的MessageBoxW函数举办了hook,先执行自界说的
int WINAPI MyMessageBoxW(HWND hWnd, LPCWSTR M1, LPCWSTR M2, UINT M3)
{
return oldMessageBoxW(hWnd, M1, L"my api hook", M3);
}
#p#分页标题#e#
从这里可以看到,由于方针历程空间中的执行线程并不知道你已经改变了api函数的实际进口地点,它在挪用时仍旧将参数一成稳定的压入仓库(这个说法是汇编代码时看到的等价景象),事实上你已经提前吸收到了函数挪用的所有参数。这里就是篇首帖子的回覆了。
hook之前
hook今后
示例代码
1、client验证方的代码很是简朴。成立一个Application工程,在窗体上放一个memo(提示信息),两个button(一个SetHook,另一个RemoveHook)。
void __fastcall TForm1::Button1Click(TObject *Sender)
{
DWORD dwProcessId, dwThreadID;
HWND hWnd = FindWindow("Notepad", NULL);
if (!hWnd)
{
Memo1->Lines->Add("Nodepad is not found");
}
else
{
dwThreadID = GetWindowThreadProcessId(hWnd, &dwProcessId);
Memo1->Lines->Add(dwThreadID);
SetHook(dwThreadID);
}
}
//---------------------------------------------------------------------------
void __fastcall TForm1::Button2Click(TObject *Sender)
{
RemoveHook();
}
//---------------------------------------------------------------------------
2、api hook dll稍微巨大些,成立一个dll工程之后,修改之。代码中有一些函数并未用上,ReplaceApiAddress是焦点函数,文章末端有示例代码下载。