@会网络的老鼠

涂飞平的博客空间

如何采用陷阱方式钩住API [原]

12 年前 0

前几天,一个朋友问我如何限制用户对文件的操作(他在做一个类似网吧安全管理的软件),如果用户删除文件夹或者文件的时候阻止他的行为!当时我草草回答他:使用ICopyHook接口来实现,后来一看资料才知道这个接口只是在用户对文件夹的操作时候才会被调用,对于文件的操作这个函数没有用。(这里用户的操作都是指在Explorer.exe中操作),后来一想,既然使用电脑的用户无非就是使用资源管理器对文件进行操作,所以直接钩住Explorer.exe中关于文件的操作就可以了,然后就向这个朋友建议:使用APIHook钩住CreateFile,DeleteFile,MoveFile等函数就可以了,后来朋友说按照这个方法DeleteFile好像没有作用。所以再次考虑,发现所有文件删除操作在Explorer.exe中都会出现一个“是否删除”的确认框,这个效果可不是DeleteFile所具有的,它是由Shell32.dll中的SHFileOperation函数提供的,所以应该钩住它就可以了。
好了,进入主题了,既然知道应该钩住什么函数,那么就可以采用安装钩子的方法使得我们的DLL进入到目的进程地址空间,但钩API有两种常用的方法:1,改引入表式;2,陷阱式。改引入表式的方法具有更大的通用性,因为不管在什么平台上PE文件都可以被支持此平台的window系统识别,而陷阱式由于直接改写指令,可能会引起无效指令的错误。但凡事都有两面性,改引入表式要遍历当前所有的模块,然后还要遍历模块中的IAT(引入地址表),看是否有对于某个函数的调用,而且还有两个问题:就是当前进程建立一个新的线程的时候,我们必须重新这个工作,而且,如果程序采用GetProcAddress函数来得到地址的话,我们的方法就没有用,所以还得钩住GetProcAddress函数(是不是很麻烦?)!但陷阱式方式却不存在这个问题,它直接更改这个函数的进口处的指令,这样:不管多少模块对他进行调用(一个DLL只会在进程中映象一次),也不管以后是否有新建立的线程有对它的调用,或者对方使用GetProcAddress函数,都不会需要我们使用更多的操作,这种方法很简单,虽然失去了一点可移植性能,但我觉得值得!!
下面是一段示意性代码:(假设对于某个函数已经开始调用)
建立一个替换的结构:

TSundyJmp=packed record
OP1:Byte;//B8
Addr: DWORD;//函数的地址,我们也是通过GetProcAddress来得到这个地址
OP2:WORD;//$E0FF
Reserved:Byte;//为了CPU的效率,所以补充一个直接来使整个结构为8字节
end;
这里简单介绍一下这个结构的作用:
假设我们的地址是$12345,填充地址到结构中后我们可以得到它在内存中布局:
B8 $12345 //对应的汇编代码为:MOV EAX,$12345
$E0FF //对应的汇编代码为:JMP EAX
这样就知道结构中各个域的作用了吧^_^
下面是过程:
1.查找是否有我们要钩住函数的宿主DLL
这里不要采用载入DLL的操作Loadlibrary,因为如果没有这个DLL,我们就没有必要做挂钩操作了,所以这里我使用了GetModuleHandle函数,如果返回为NULL的话,我们就退出不做任何事情了!
2.查找函数地址:
没有什么好说的,直接使用GetProcAddress就可以了。
3.改写函数:
先将得到地址内存块的前8个字节存起来,因为正常调用的时候还要把它写回去,使用ReadProcessMemory函数即可!然后将我们函数的地址填入我们的结构中的Addr,再使用WriteProcessMemory直接将我们的结构写入这个地址的内存块中即可(这两个函数非常好用,它可以忽略内存块中的读写属性。代码页面一般都是只读的,靠这个函数我们就可以写操作了^_^,这个特性估计是window自己留的后门吧,方便为系统打补丁或者开发更多的系统优化程序??不得而知)。
4.自己函数的操作:
我们的函数声明必须和要钩住的函数采用一样的声明方式,参数的个数,参数的类型(涉及到参数的长度),参数的传递方式都要一致,因为我们采用的是JMP方式,所以在__STDCALL方式中退栈是由我们程序完成的,这个地方如果出错了,指令就回不去了^_^。还有一个问题就是当我们允许用户正常调用原来的功能的话,我们还要再次做第三步的工作,不过这次应该将我们开始保存的8个字节写回去,然后再调用原来的函数,得到返回值后再将我们的结构写回来,继续监视函数的调用。
好了,说了这么多,真是累。今天是端午节,祝自己节日快乐!^_^

编写评论