• data directory(数据目录)之 引出表


    (一)引出表,是将dll中所有的函数引出,并提供查询。(个人理解)

    (二)DLL/EXE要引出一个函数给其他DLL/EXE使用,有两种实现方法: 通过函数名引出或者仅仅通过序数引出。比如某个DLL要引出名为"GetSysConfig"的函数,如果它以函数名引出,那么其他DLLs/EXEs若要调用这个函数,必须通过函数名,就是GetSysConfig。另外一个办法就是通过序数引出。什么是序数呢? 序数是唯一指定DLL中某个函数的16位数字,在所指向的DLL里是独一无二的。例如在上例中,DLL可以选择通过序数引出,假设是16,那么其他DLLs/EXEs若要调用这个函数必须以该值作为GetProcAddress调用参数。这就是所谓的仅仅靠序数引出。

    我们不提倡仅仅通过序数引出函数这种方法,这会带来DLL维护上的问题。一旦DLL升级/修改,程序员无法改变函数的序数,否则调用该DLL的其他程序都将无法工作。

    IMAGE_EXPORT_DIRECTORY STRUCT【导出表,共40字节】
    {
    +00 h DWORD Characteristics ; 未使用,总是定义为0
     +04 h DWORD TimeDateStamp ; 文件生成时间
     +08 h WORD MajorVersion     ; 未使用,总是定义为0
     +0A h WORD MinorVersion ; 未使用,总是定义为0
    +0C h DWORD Name     ; 模块的真实名称
     +10 h DWORD Base     ; 基数,加上序数就是函数地址数组的索引值
     +14 h DWORD NumberOfFunctions ; 导出函数的总数
     +18 h DWORD NumberOfNames ; 以名称方式导出的函数的总数
    +1C h DWORD AddressOfFunctions ; 指向输出函数地址的RVA +20 h DWORD AddressOfNames ; 指向输出函数名字的RVA +24 h DWORD AddressOfNameOrdinals ; 指向输出函数序号的RVA };IMAGE_EXPORT_DIRECTORY ENDS

    1、引出表的设计是为了方便PE装载器工作。首先,模块必须保存所有引出函数的地址以供PE装载器查询。模块已有两个模块: 名字数组和地址数组,但两者之间还没有联系的纽带。因此我们还需要一些联系函数名及其地址的东东。PE参考指出使用到地址数组的索引作为联接,因此PE装载器在名字数组中找到匹配名字的同时,它也获取了 指向地址表中对应元素的索引。 而这些索引保存在由AddressOfNameOrdinals域指向的另一个数组(最后一个)中。由于该数组是起了联系名字和地址的作用,所以其元素数目必定和名字数组相同,比如,每个名字有且仅有一个相关地址,反过来则不一定: 每个地址可以有好几个名字来对应。因此我们给同一个地址取"别名"。为了起到连接作用,名字数组和索引数组必须并行地成对使用,譬如,索引数组的第一个元素必定含有第一个名字的索引,以此类推。

    AddressOfNames AddressOfNameOrdinals
    |   |
    RVA of Name 1
    RVA of Name 2
    RVA of Name 3
    RVA of Name 4
    ...
    RVA of Name N
    <-->
    <-->
    <-->
    <-->
    ...
    <-->
    Index of Name 1
    Index of Name 2
    Index of Name 3
    Index of Name 4
    ...
    Index of Name N

    下面举一两个例子说明问题。如果我们有了引出函数名并想以此获取地址,可以这么做:

    1. 定位到PE header。
    2. 从数据目录读取引出表的虚拟地址。
    3. 定位引出表获取名字数目(NumberOfNames)。
    4. 并行遍历AddressOfNamesAddressOfNameOrdinals指向的数组匹配名字。如果在AddressOfNames 指向的数组中找到匹配名字,从AddressOfNameOrdinals 指向的数组中提取索引值。例如,若发现匹配名字的RVA存放在AddressOfNames 数组的第77个元素,那就提取AddressOfNameOrdinals数组的第77个元素作为索引值。如果遍历完NumberOfNames 个元素,说明当前模块没有所要的名字。
    5. AddressOfNameOrdinals 数组提取的数值作为AddressOfFunctions 数组的索引。也就是说,如果值是5,就必须读取AddressOfFunctions 数组的第5个元素,此值就是所要函数的RVA。

    2、

    现在我们在把注意力转向IMAGE_EXPORT_DIRECTORY 结构的nBase成员。您已经知道AddressOfFunctions 数组包含了模块中所有引出符号的地址。当PE装载器索引该数组查询函数地址时,让我们设想这样一种情况,如果程序员在.def文件中设定起始序数号为200,这意味着AddressOfFunctions 数组至少有200个元素,甚至这前面200个元素并没使用,但它们必须存在,因为PE装载器这样才能索引到正确的地址。这种方法很不好,所以又设计了nBase 域解决这个问题。如果程序员指定起始序数号为200,nBase 值也就是200。当PE装载器读取nBase域时,它知道开始200个元素并不存在,这样减掉一个nBase值后就可以正确地索引AddressOfFunctions 数组了。有了nBase,就节约了200个空元素。

    注意nBase并不影响AddressOfNameOrdinals数组的值。尽管取名"AddressOfNameOrdinals",该数组实际包含的是指向AddressOfFunctions 数组的索引,而不是什么序数啦。

    讨论完nBase的作用,我们继续下一个例子。
    假设我们只有函数的序数,那么怎样获取函数地址呢,可以这么做:

      1. 定位到PE header。
      2. 从数据目录读取引出表的虚拟地址。
      3. 定位引出表获取nBase值。
      4. 减掉nBase值得到指向AddressOfFunctions 数组的索引。
      5. 将该值与NumberOfFunctions作比较,大于等于后者则序数无效。
      6. 通过上面的索引就可以获取AddressOfFunctions 数组中的RVA了。

    (三)问题:

    如果一函数通过名字引出,那在GetProcAddress中可以使用名字或序数。但函数仅由序数引出情况又怎样呢? 现在就来看看。
    "一个函数仅由序数引出"意味着函数在
    AddressOfNames AddressOfNameOrdinals 数组中不存在相关项。记住两个域,NumberOfFunctionsNumberOfNames。这两个域可以清楚地显示有时某些函数没有名字的。函数数目至少等同于名字数目,没有名字的函数通过序数引出。比如,如果存在70个函数但AddressOfNames数组中只有40项,这就意味着模块中有30个函数是仅通过序数引出的。现在我们怎样找出那些仅通过序数引出的函数呢?这不容易,必须通过排除法,比如,AddressOfFunctions 的数组项在AddressOfNameOrdinals 数组中不存在相关指向,这就说明该函数RVA只通过序数引出。

  • 相关阅读:
    Windows下临界区的使用CRITICAL_SECTION
    MFC中消息映射的实现
    Oracle中提供的事件触发机制
    CreateEvent()详解
    内核参数优化之2-1 tcp/ip 标志位报文解析
    内核参数优化之1 keepalive解析
    python之7-3对象的信息/方法获取
    python之7-2类的继承与多态
    python之7-1类
    python之6-3嵌套函数
  • 原文地址:https://www.cnblogs.com/wang-can/p/3280565.html
Copyright © 2020-2023  润新知