Windows PE文件的输入表

这篇文章基于Window PE文件格式,在阅读前建议了解PE文件格式中的内容

什么是输入表?

在编写一个程序的时候,我们会调用很多库来完成我们想要实现的功能。这些库中有一些是静态库,在编译的时候一起编入到程序中,和程序一起存储;但是有一些库是动态链接库,在程序加载到内存的时候,操作系统才会告诉程序动态库中的这些函数的所在内存。

Printf(), scanf(), pow()这些是C标准库函数,这些函数编译后回生成机器码保存在exe中

MessageBoxA(), wsprintfA()这些是windows内核提供的函数称为API,这些函数编译后并不会生成机器码保存在exe中,因为这些函数在windows启动后就已经载入内存了---节省内存

输入表就是储存了我们在程序中应用了哪些动态链接库以及调用了这些库中的哪些函数(在此处成为API)。这样在程序运行时,操作系统可以根据输入表中的信息,来告诉程序,其调用的这些动态库函数在内存的哪个地方,这个过程被称作API的重定位

PE文件头中的输入表信息

理论

PE+80: 输入表的内存偏移,4个字节

PE+84: 输入表的内存长度 ,4个字节

我们在QV中查看输入表或是在编程时需要找到输入表的文件偏移位置,但是PE+80处记录的是输入表的内存偏移,所以应该如何把内存偏移转换为文件偏移呢?具体步骤如下:

  1. 先找出输入表落在内存的哪一个section内,根据PE+F8+C处的节的内存偏移和PE+F8+8处节的内存长度来确定
  2. 计算输入表起始内存偏移地址与该节的起始内存偏移地址的差
  3. 再将这个差加上该节文件起始地址,就可以得到输入表在文件中的具体位置了。

实践

这个EXE文件的PE头位于d0处,因此输入表的内存长度和内存偏移的地址位于PE+80=150h处。

此时输入表的内存偏移为64BCh,输入表的内存长度为0028h

在QV中通过F8+F3来查看各个section的属性

此时可以看到,根据表中的RVA项,输入表位于.rdata这个段内,并且发现内存的偏移地址和文件偏移地址是一致的。所以输入表在文件中的偏移地址即为64BCh,QV中F5输入64BCh,可以跳转到输入表的具体内容所在的地方(可以看出,输入表并不位于文件头内,文件头只是记录了输入表的内存偏移地址和内存长度)。

输入表的格式

在一个EXE中,输入表可以有多份,输入表的份数和调用的dll的个数有关。每一个输入表都有14h个字节,并且以14h个0作为结束。每14个字节构成了输入表的一项,描述的是该EXE调用了哪个dll中的哪些API。

000064b0: ffff ffff 3652 4000 3a52 4000 e464 0000  ....6R@.:R@..d..
					 |
					 +----------输入表+00h:4字节的指针指向API名字指针表的位置
000064c0: 0000 0000 0000 0000 8268 0000 0060 0000  .........h...`..
                                |	 |
                                |	 +----------输入表+10h: 4字节的指针指向API地址表
                                +-------------------输入表+0C:2字节的指针指向DLL名字,DLL名字以00作为字符串的结束																										 
000064d0: 0000 0000 0000 0000 0000 0000 0000 0000  ................
000064e0: 0000 0000 9465 0000 a665 0000 b465 0000  .....e...e...e.
  • API名字指针表位于64e4h处:

    该表里的每一个元素以4字节为一个单位

    • 如果该4个字节的最高位等于0,表示这是一个指针,该指针指向API的序号和名字,格式如下xx, xx, "API名"xx,xx是两个字节的API序号
    • 如果该4个字节的最高位等于1,表示这是一个API的序号,去掉最高位就是API的序号,这个API只有序号没有名字

    该表以4个全0字节表示结束

    操作系统可以根据API的序号和名字获取到该API加载内存里的位置。

    可以调用p = GetProcAddress(h, "MessageBoxA");来根据API 名字获取API地址

    可以调用p = GetProcAddress(h, 0x019D);来根据API 序号获取API地址

  • API地址表位于6000h处:

    输入表+10h和输入表+00h在文件中的值是一样的,但是在内存里是不一样的

​ 这个表中的内容在exe载入内存时,会被替换指向真正的API函数所在地址

实践

现在我们来看找到一个API名字指针的具体过程。

  1. 在PE头+80h处找到输入表的内存偏移长度偏移地址和计算出其文件偏移长度,在这个情况下文件偏移和内存偏移一致,即输入表位于64BCh处来到输入表的具体内容处。

  1. 跳转到64BCh,即输入表的具体内容处,输入表+00指向API名字指针表,即API名字指针表位于内存地址64E4h

  1. 跳转64E4h, 查看API名字指针表的具体值,以该表的第一项为例,这一项也是一个指针(最高位为0),指向6594h处,该指针指向API的序号和名字。

  1. 跳转6594h处,查看该API的序号和名字,可以看到这个API的序号是108h,API的名字为GetCommandLineA,该名字以00h字节作为结尾。

本随笔是个人学习总结,如有错误欢迎指出!