现在的位置: 首页 > 综合 > 正文

PE文件格式与API HOOK

2014年02月03日 ⁄ 综合 ⁄ 共 2960字 ⁄ 字号 评论关闭

http://blog.csdn.net/wltg2001/archive/2008/04/23/2317059.aspx

对于windows低层编程来说,进行API拦截始终是一件让人激动的事,用自己的代码来改变其它程序的行为,还有比这个更有趣吗?而且,在实现API拦截的过程中我们还有机会去熟悉许多在RAD编程环境中很少接触的东西,如DLL远程注入、内存管理,PE文件格式等知识。许多商业软件,如金山词霸等词典软件,各种即时汉化软件、甚至一些网络游戏的外挂中都用到了这种技术,各种调试工具中多多少少也要用到这种技术。

实现API拦截的一种方法是修改PE文件中的输入地址表。在32windows中,无论是.EXE文件,还是.DLL文件都是采用PE文件格式,PE文件格式将程序所有调用的API函数的地址信息存放在输入地址表中,而在程序码中,对API的调用使用的地址不是API函数的地址,而是输入地址表中该API函数对应的地址。我们只要修改输入地址表中函数地址就可以拦截API了。首先我们来熟悉一下PE文件格式,由于PE文件格式本身比较复杂,涉及到的数据类型较多,所以在这里只介绍一部分内容。我已经画了一幅示意图,大致描绘出PE文件格式,其中有的结构中的数据是一个RVA,凡是这样数据在图中都已注明。

 


PE文件是由一个DOS文件头开始的,紧接在它后面的是一个DOS stub,它们合在一起实际上是一个完整的DOS程序,在PE文件中提供它们最主要的目的是由于兼容性,如果我们在DOS中去执行一个win32程序,这个DOS程序就会显示出“This program can not run in dos mode”之类的语句。在它们的后面才是真正的PE文件头,所以这两个部分并不重要,但是由于每一个DOS stub的大小并不一样,所以我们必须要用DOS文件头中一个成员e_lfanew来定位PE文件头,DOS文件头被定义成IMAGE_DOS_HEADER结构。它的成员e_lfanew中含有PE文件头的“相对虚拟地址”(RVA)。

在这里我们要解释一下RVA(相对虚拟地址),在PE文件中经常见到这个名词,所谓RVA指的是相对于模块起始地址的偏移量,所以RVA必须要加上模块的起始地址才能得到真正的地址。之所以称它为“虚拟”的是因为在一个PE格式文件没有被装入内存之前,RVA是没有意义的,只有PE格式文件被装入内存后,RVA才是有意义的。

举例说明:如上图所示:

假设某个PE文件的装入虚拟地址(VA)为400000h,而这个PE文件中的DOS头中的成员e_lfanew的值为40hRVA)的话, 那么它所指的PE文件头的虚拟地址(VA)就是400040h

DOS stub后面才是我们感兴趣的PE文件头,它被定义成IMAGE_NT_HEADERS结构,这个结构中含有整个PE文件的信息,它的定义如下:( 这里用汇编语言定义,在winnt.h中有基于C语言的定义)

IMAGE_NT_HEADERS STRUCT

  Signature dd ?

  FileHeader IMAGE_FILE_HEADER <>

  OptionalHeader IMAGE_OPTIONAL_HEADER32<>

IMAGE_NT_HEADERS ENDS

而这个结构中,与我们API拦截有关的是最后一项OptionalHeader,它被定义成IMAGE_OPTIONAL_HEADER32结构,这个结构共有31个域,定义如下:(省略了一部分与API拦截无关的)

IMAGE_OPTIONAL_HEADER32 STRUCT

  NumberOfRvaSizes dd ?

  DataDirectory IMAGE_DATA_DIRECTORY 16 dup(<>)

IMAGE_OPTIONAL_HEADER32 STRUCT

其中我们需要的是最后的DataDirectory域,这个域被称为“数据目录”,它是由16IMAGE_DATA_DIRECTORY结构组成的数组,每个数组中存放了PE文件的一个重要的数据结构的信息,其中第二个元素称为“引入表”,在“引入表”中存放了PE文件所调用的DLL及外部函数的信息,包括引入函数所在DLL名,引入函数名,引入函数地址等。我们实现API拦截的方法就是要将“引入表”中的引入函数地址改成我们自已的函数地址。IMAGE_DATA_DIRECTORY定义如下:

IMAGE_DATA_DIRECTORY STRUCT

  VirtualAddress dd ?

  isize dd ?

IMAGE_DATA_DIRECTORY ENDS

其中VirtualAddress 是数据结构的相对虚拟地址,isize含有VirtualAddress所指向的数据结构的大小。举例来说,一个关于 “引入表”的IMAGE_DATA_DIRECTORY结构中,VirtualAddress包含了“引入表”的RVA。利用这个RVA我们就可以找到“引入表”。

“引入表”本身是一个由IMAGE_IMPORT_DESCRIPTOR结构组成的数组,数组中的每个IMAGE_IMPORT_DESCRIPTOR元素包含一个PE文件引用的DLL的信息,所以数组中元素个数与PE文件引用的DLL个数有关。这个数组以一个全0IMAGE_IMPORT_DESCRIPTOR结构结束。下面看一下IMAGE_IMPORT_DESCRIPTOR结构的定义:

IMAGE_IMPORT_DESCRIPTOR STRUCT

  union

     Characteristics dd ?

     OriginalFirstThunk dd ?

  ends

  TimeDataStamp dd ?

  ForarderChain dd ?

  Name1 dd ?

FirstThunk dd ?

IMAGE_IMPORT_DESCRIPTOR ENDS

这个结构中的成员并不是每一个都和我们讨论的API拦截有关,但是它实在是太有趣了,所以在这里介绍一下它的部分成员。

第一个成员是一个union子结构,这个子结构其实只是给OriginalFirstThunk加了个别名而已,该成员含有指向一个IMAGE_THUNK_DATA结构数组的RVA

那么什么是IMAGE_THUNK_DATA呢?它的定义如下:

IMAGE_THUNK_DATA STRUCT

    union u1

        ForwarderString dd  ?

        Function dd         ?

        Ordinal dd          ?

        AddressOfData dd    ?

    ends

IMAGE_THUNK_DATA ENDS

虽然看起来很复杂,其实它不过是一个DWORD型的变量,一般我们将它看作是一个指向IMAGE_IMPORY_BY_NAME结构的RVA。至于IMAGE_IMPORY_BY_NAME结构它存放了一个引入函数的信息。定义如下:

IMAGE_IMPORT_BY_NAME STRUCT

  Hint dw ?

  Name1 db ?

IMAGE_IMPORT_BY_NAME ENDS

其中Hint指示本函数在DLL的“引出表”中的索引号,而Name1含有函数名。(这个成员本来的定义应该是Name,但是Name

抱歉!评论已关闭.