推荐置顶:截获 Windows socket API

yoyohon 2004-06-15 06:00:03
加精
截获socket API
-- 徐鸿飞 Yoyohon

关键词:Hook, Windows API , socket, 屏幕取词

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 temp;
temp.Format("%d+%d=%d",a,b,c);
AfxMessageBox(temp);
}

OK,编译运行,正确的话就会显示1+2=3咯

...全文
1861 63 打赏 收藏 转发到动态 举报
写回复
用AI写文章
63 条回复
切换为时间正序
请发表友善的回复…
发表回复
文飞扬 2004-07-04
  • 打赏
  • 举报
回复
up
yoyohon 2004-07-01
  • 打赏
  • 举报
回复
呵呵结贴
kugou123 2004-06-30
  • 打赏
  • 举报
回复
佩服各位高人,看你们提到了微软的detours,是个什么东东?有没有相关的资料,请提供给我一下。谢谢。
betsyalan 2004-06-30
  • 打赏
  • 举报
回复
mark
nineclock 2004-06-29
  • 打赏
  • 举报
回复
mark
verybigbug 2004-06-28
  • 打赏
  • 举报
回复
Hook API根本就不是用来作为商业程序应用的,一般是为调查应用的。
没有什么软件会用来作为商业应用---不要谈屏幕取词,因为Hook API的目的根本就不是
为这个用途的,而且屏幕取词也没有什么其他的商业意义。

作为一个调查的应用,你会关心被你调查的程序会不会报错?我不会关心这些的,我只是
关心我调查的那些函数是如何进行工作的,所以,我不会用UnhookWindowsHookEx来退出
Hook功能,因为我根本就没有打算退出Hook,达到调查的目的后,已经可以重新启动机器
了,所以,UnhookWindowsHookEx的调用是多余的。。。

相对于调查跟踪而言,我更加关心多线程和递归,调用参数等一些有用的功能,UnHook?
没有必要的功能就不要了。
jackyspy 2004-06-28
  • 打赏
  • 举报
回复
我想研究HOOK API的朋友大多也不是为了做商业软件,但是作为对技术的研究,不应该对出现的问题进行回避和予以忽视
mahatma_cn 2004-06-28
  • 打赏
  • 举报
回复
另外hook api技术好像在《windows核心编程》中有详细论述,我再回去翻一翻!
mahatma_cn 2004-06-28
  • 打赏
  • 举报
回复
转入我的blog,谢谢!!!
月吻长河 2004-06-27
  • 打赏
  • 举报
回复
高手
jackyspy 2004-06-26
  • 打赏
  • 举报
回复
to verybigbug:
我也赞成用IAT的方法,不过有这么一个问题,不知道你发现没有,当你unhook的时候,UnhookWindowsHookEx函数将所有引用hookdll的进程中hookdll计数值减1,如果不是正在调用HookProc,hookdll的计数器减至0,系统将把hookdll从该进程中释放,也就是说,再也无法对hookdll中任何东西进行访问,如果此时hookdll中的函数仍然在被其他进程调用,正在运行中的函数将会发生内存异常错误。
从《Windows 核心编程》中的LastMsgBoxInfo例子可以观察到这个状况。运行LastMsgBoxInfo程序,打开一个记事本窗口,随便写一些东西后关闭,弹出对话框提示需要保存,这时不要点击任何按钮,关闭LastMsgBoxInfo程序,回到刚才的msgbox,随便点击一个按钮,立即出现内存访问错误。
跟踪了一下,发觉自定义的Hook_MessageBoxA(W)中有一个对MessageBoxA(W)的调用进入到系统user32.dll模块;关闭LastMsgBoxInfo程序后,记事本程序立即减少对hookdll的计数至0,并立即从进程中释放,到这里都没有什么问题,但是接下来,当我关闭记事本弹出的对话框时,线程试图结束MessageBoxA(W)的调用并从user32.dll模块跳转到调用模块,即企图返回Hook_MessageBoxA(W)中对其调用的位置,但是此时hookdll早已经从记事本进程中释放,返回地址实际上是无效的地址,因而报错。
对于这种情况,我想应该在自己定义的API替换函数中进行自我保护,防止在调用返回前被调用进程释放。我所想到的方法是在函数的开头增加对hookdll的引用,返回前减1,这样可以保证在函数正确返回前hookdll存在于调用进程中,关于增加减少引用计数,我暂时只能想到LoadLibrary和FreeLibrary函数,不知道是否存在其他更简洁、更适合的API函数以及更为简单的方法。另外,这样一来,就不能在UnhookWindowsHookEx调用后立即修复IAT,假设有这么一个多线程进程,每时刻至少保证有一个线程处于我们的API替代函数中,那么该hookdll在进程结束之前将永远无法释放,IAT也无法修复,也就不能及时的停止对指定API的hook。虽然这种情况出现的机会可能很小,但是对于程序员来说应该是要考虑的吧,呵呵。所以我认为应该想方法在UnhookWindowsHookEx调用后立即得到通知并及时修复IAT,而不是等到hookdll释放的时候才去做这项工作,对于这点目前我没有找到解决方法。
hundlom 2004-06-26
  • 打赏
  • 举报
回复
鼓励,支持。
大家努力相信会有更多的原创作品
elabs 2004-06-26
  • 打赏
  • 举报
回复
嗯,不错,标记一下
zxyjyzxyjy 2004-06-26
  • 打赏
  • 举报
回复
学习
seanbcliu 2004-06-26
  • 打赏
  • 举报
回复
顶。
skyupsky 2004-06-25
  • 打赏
  • 举报
回复
厉害!
能不能提供源码的下载呀!那样方便多了!
叁哥_ 2004-06-25
  • 打赏
  • 举报
回复
有没有更安全的方法?
Justin2003 2004-06-24
  • 打赏
  • 举报
回复
學習ing
Summer1314 2004-06-24
  • 打赏
  • 举报
回复
好棒啊
verybigbug 2004-06-24
  • 打赏
  • 举报
回复
Hook API 一般都是用修改IAT的方法,在32编程上已经没有这种修改jmp再次修改回去的方法了。
比如detours 也用修改jmp的方法,不过并没有修改回去的处理。

因为修改回去的处理在多线程会非常不可靠的。避开多线程不谈,那么递归程序又如何。
比如:WndProc函数就一直在递归了。还有,CreateWindowEx和LoadLibrary函数也经常
使用递归的。其他的SendMessage和很多函数。。。你知道会发生什么事情吗?
如果你Hook系统API函数的话,你怎么知道该函数有没有在递归使用。
如果只是Hook自己的程序---自己的程序有必要Hook吗?
Hook其他人的程序的话,规定别人的程序不能用多线程和递归的又有什么用?
加载更多回复(43)

18,356

社区成员

发帖
与我相关
我的任务
社区描述
VC/MFC 网络编程
c++c语言开发语言 技术论坛(原bbs)
社区管理员
  • 网络编程
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告
暂无公告

试试用AI创作助手写篇文章吧