PE格式详解(七)

 

         首先介绍一下DLL相关内容。根据Win32 Programmer's Reference所述(自己翻译的):

“在Windows中,动态链接库(DLL)是含有函数与数据的模块。一个DLL在运行时期由其调用模块加载,当DLL被加载时,它会被映射到主叫进程的地址空间中去。

         DLL内有两类函数:输出(exported)与内部(internal)。输出函数是要被其他模块调用的函数,内部函数仅供DLL内部使用。尽管DLL可以输出数据,但通常数据也仅作内部使用。

         DLL可以使应用程序模块化,并且多个应用程序可以共享同一份内存中的DLL,因此也节省系统资源。Win32 API是被实现为一组DLL的,所以任何使用Win32 API的应用程序一定使用DLL。”

         DLL中函数(也常被叫做符号Symbols可以通过两种方式输出:通过名字与通过序号。一个序号通常是一个字大小的一个数,它在一个DLL中唯一标识一个函数,注意,这个序号仅在同一个DLL中唯一,不同DLL间序号不不唯一!

         如果说函数通过名字输出,那么当其他模块要调用它时,可以在GetProcAddress中或使用它的名字或使用它的序号来指定,GetProcAddress函数则会返回被调用函数的地址。注意:使用名字的话,它的拼写与大小写必须与源DLL的模块定义文件(.DEF)中的一模一样,并且序号可以不从1开始,如果GetProcAddress找不到对应函数,就会返回NULL。具体GetProcAddress信息请参考Win32 Programmer’s Reference,或者MSDN

         之所以GetProcAddress可以得到DLL的输出信息,是因为DLL中定义了输出目录表(Export Directory),还记得我们在(五)中讲的IMAGE_DATA_DIRECTORY数组吗?它的第0个元素就描述了Export Directory的虚拟地址与大小,这样我们就可以通过这些信息找到PE中对应的输出目录表的全部信息。

         输出目录表是用IMAGE_EXPORT_DIRECTORY这个结构定义的。

typedef struct _IMAGE_EXPORT_DIRECTORY {

    DWORD Characteristics;

    DWORD TimeDateStamp;

    WORD MajorVersion;

    WORD MinorVersion;

    DWORD Name;

    DWORD Base;

    DWORD NumberOfFunctions;

    DWORD NumberOfNames;

    DWORD AddressOfFunctions;

    DWORD AddressOfNames;

    DWORD AddressOfNameOrdinals;

} IMAGE_EXPORT_DIRECTORY,*PIMAGE_EXPORT_DIRECTORY;

    我们只关心其中几个成员:

    Name。模块的内部名称,如果DLL文件的名字被用户改了,那么PE加载器会使用这个内部名称。

    Base。前面说的序号的起始编号。比如一个函数的序号是4,起始序号Base2,就表示该函数位于输出地址列表第三个元素,输出地址列表中默认首元素对应序号1,而不是序号0序号的起始数字可以在.DEF文件中定义。

    NumberOfFunctions。顾名思义,就是该DLL输出函数的个数。

    NumberOfNames。就是通过名字输出的函数个数。

    AddressOfFunctions。输出地址列表(Export Address TableEAT,其实就是一个地址数组)的首地址。

    AddressOfNames。输出名字列表(Export Name Table, ENT)的首地址。

    AddressOfNameOrdinals。输出序号列表(Export Ordinal Table, EOT)的首地址。该数组的元素都是16-bit(一个字)长度的整数。

    我们看到IMAGE_EXPORT_DIRECTORY其实主要就是指向了三个数组。现在我们再看下怎么通过函数的名字来找到对应的函数地址。

    假设Base = 3, 且有以下的表格:

AddressOfNames  - ENT

AddressOfNameOrdinals  - EOT

Name1

3

Name2

4

Name3

5

AddressOfFunctions - EAT

索引

地址

1

0x400042

2

0x400156

3

0x401256

4

0x400520

5

0x401452

 

    比如我们传给GetProcAddress的名字是”Name3”,之后系统会先查找ENT这张表格,发现找到了,然后平行地看过去,发现在EOT中对应的序数是5,那么说明该函数的地址在EAT的第5个位置(从1开始数),取出地址0x401452,任务就完成了。

    如果只通过序号来查找函数地址那就很方便,只要读取EOT,然后直接在EAT里索引就可以了。但这样的做法一不利于记忆,二不利于维护与扩展(因为序号一变就得改许多用户源代码)。

    下面看一种新的情况,如果一个输出函数尽管出现在了EAT中,但没有出现在ENTEOT中,那该怎么办呢?那就只能通过排除法了,也就是要满足在EAT中而不在ENTEOT中。

    好了,最后一个事儿了,就是输出转送(Export Forwarding)。就比如我调用了kernal32.dll里的HeapAlloc函数,它本身并没有实现这个函数,而是把我的调用请求转送到了ntdll.dllRtlAllocHeap这个函数,这就是DLL输出转送,同样可以通过修改.DEF文件在链接时期进行。输出转送的引入,一来可以隔离通用的Win32 API与内核支持函数,达到屏蔽底层差异的目的,二来就是支持了操作系统的模式转换,划清用户模式与内核模式的界限。

    具体反应在输出目录表中是这样的,那个AddressOfNames指向的表格中,本来存的都是函数的名字,现在就换成“模块名.函数名”,比如上面的例子就是”NTDLL. RtlAllocHeap”,因此如果你看到了类似这样的名字,就说明这个函数调用被转送了。

    这次的文章没有图片实例,是因为咱们的testPE.exe没有输出表,而输出表本身也比较简单,我就懒得再去编一个DLL再提取16进制数据了,所以就抱歉啦!其实研究方法和前面几篇中的一模一样,就是先通过data directory找到输出表,然后读入IMAGE_EXPORT_DIRECTORY结构,得到三张表的地址,然后按上面的查表方法就可以了。

    OK!本文就到这。(八)将讲的是输入区段(The Import Section)。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值