写这篇文章是为了复习一些知识,最近在做毕业设计,之中大量地使用了这种技术,主要是用在拦截winsock函数,对于其他系统api,其效果也是一样的.
前段时间写了一个HookSend的小程序,其实就是用的这里所说的方法.只不过这里讲得更详细一点,熟悉的就不用看了,另外欢迎大虾指出错误,不胜感激@_@
拦截api的技术有很多种,大体分为用户层和内核层的拦截.这里只说说用户层的拦截.而用户层也分为许多种:修改PE文件导入表,直接修改要拦截的api的内存(从开始到最后,使程序跳转到指定的地址执行).不过大部分原理都是修改程序流程,使之跳转到你要执行的地方,然后再返回到原地址.原来api的功能必须还能实现.否则拦截就失去作用了.修改文件导入表的方法的缺点是如果用户程序动态加载(使用LoadLibrary和GetProcAddress函数),拦截将变得复杂一些.所以这里介绍一下第二种方法,直接修改api,当然不是全局的.(后面会说到)
需要了解的一些知识:
1.windows内存的结构属性和进程地址空间
2.函数堆栈的一些知识
一:win2000和xp的内存结构和进程地址空间
windows采用4GB平坦虚拟地址空间的做法。即每个进程单独拥有4GB的地址空间。每个进程只能访问自己的这4GB的虚拟空间,而对于其他进程的地址空间则是不可见的。这样保证了进程的安全性和稳定性。但是,这4GB的空间是一个虚拟空间,在使用之前,我们必须先保留一段虚拟地址,然后再为这段虚拟地址提交物理存储器。可是我们的内存大部分都还没有1GB,那么这4GB的地址空间是如何实现的呢?事实上windows采用的内存映射这种方法,即把物理磁盘当作内存来使用,比如我们打开一个可执行文件的时候,操作系统会为我们开辟这个4GB的地址空间:0x00000000--0xffffffff。其中0x00000000--0x7fffffff是属于用户层的空间.0x80000000--0xffffffff则属于共享内核方式分区,主要是操作系统的线程调度,内存管理,文件系统支持,网络支持和所有设备驱动程序。对于用户层的进程,这些地址空间是不可访问的。任何访问都将导致一个错误。开辟这4GB的虚拟地址空间之后,系统会把磁盘上的执行文件映射到进程的地址空间中去(一般是在地址0x00400000,可以通过修改编译选项来修改这个地址)而一个进程运行所需要的动态库文件则一般从0x10000000开始加载。但是如果所有的动态库都加载到这个位置肯定会引起冲突。因此必须对一些可能引起冲突的dll编译时重新修改基地址。但是对于所有的操作系统所提供的动态库windows已经定义好了映射在指定的位置。这个位置会随着版本的不同而会有所改变,不过对于同一台机器上的映射地址来说都是一样的。即在a进程里映射的kernel32.dll的地址和在进程b里的kernel32.dll的地址是一样的。对于文件映射是一种特殊的方式,使得程序不需要进行磁盘i/o就能对磁盘文件进行操作,而且支持多种保护属性。对于一个被映射的文件,主要是使用CreateFileMapping函数,利用他我们可以设定一些读写属性:PAGE_READONLY,PAGE_READWRITE,PAGE_WRITECOPY.第一参数指定只能对该映射文件进行读操作。任何写操作将导致内存访问错误。第二个参数则指明可以对映射文件进行读写。这时候,任何对文件的读写都是直接操作文件的。而对于第三个参数PAGE_WRITECOPY顾名思义就是写入时拷贝,任何向这段内存写入的操作(因为文件是映射到进程地址空间的,对这段空间的读写就相当于对文件进行的直接读写)都将被系统捕获,并重新在你的虚拟地址空间重新保留并分配一段内存,你所写入的一切东西都将在这里,而且你原先的指向映射文件的内存地址也会实际指向这段重新分配的内存,于是在进程结束后,映射文件内容并没有改变,只是在运行期间在那段私有拷贝的内存里面