登录  | 立即注册

游客您好!登录后享受更多精彩

查看: 72|回复: 1

WindowsPE文件格式入门08.导出表

[复制链接]

67

主题

-6

回帖

34

积分

网站编辑

积分
34
发表于 2025-3-3 22:44:36 | 显示全部楼层 |阅读模式

通过cff , depends灯等软件可以看到dll,导出函数的信息,因为dll中本身就存了这些信息,存了dll中有哪些导出函数,导出函数的序号是什么,名字是什么,以及他们的地址是什么,这些东西都存在导出表里面

导出表发展历程

最开始的时候只有序号导出,没有名称导出,使用的时候都是通过序号
序号 地址
1 1005
2 2005
3 3005
4 4005
5 5005
6 6005
7 7005
8 8005

但是上面的时间复杂度比较高是线性阶,但是对数阶占的体积比较大,所以只能折中,用常量阶.把地址作为数组,序号作为索引,只存地址,这样不仅速度更加快了,而且体积更小了

但是所以是从0开始的,因此可以数组首项加一个空

img

这样可以通过序号,直接去找对应数组对应的索引的值

序号不从1开始

但是上面有一个缺陷,序号不一定从0开始,例如 从 1001开始,这样数组不能前面加1000项0,这样不仅浪费空间,而且还用不上,解决办法是加一个 基址 (最小序号), 后面序号就根据基址来取偏移

img

例如上面,要拿到 序号1006 的函数地址, 可以 获取 1006对 1001 的偏移 5 ,再去数组取 索引为5的地址

序号不连续

解决了上面基本不从0开始的情况,还有一个问题,就是 序号不连续 ,例如 1001 , 1002 , 1020,1021,1050

这种情况下,数组中间对应的序号就需要填充0,因此会使体积变大,微软并没有解决这个问题,因为这是写dll的人自己的问题

例如

  1. 不指定序号(这样序号连续)

img

img

可以看出大小是38kb

  1. 部分指定序号使序号不连续

img

img

可以看到此时dll大小变成了 346KB

名称导出

img

序号跟成名2个表是拆开的,并不在一起,因此 基数数 ,导出地址表 ,导出名称表 ,导出序号表 构成了导出表

导出表结构

导出表结构

名称和序号是一一对应的,因此导出名称个数 和导出序号个数 只需要保存一个就可以了

IMAGE_EXPORT_DIRECTORY

// IMAGE_EXPORT_DIRECTORY 导出表结构体,40B
typedef struct _IMAGE_EXPORT_DIRECTORY {
    DWORD   Characteristics;        // 无用
    DWORD   TimeDateStamp;          // 时间戳
    WORD    MajorVersion;           // 无用
    WORD    MinorVersion;           // 无用
    DWORD   Name;                   // 描述性字段:'模块的名字'
    //上面20字节没用,说明性的

    DWORD   Base;                   // * 序号base基数,序号导出函数的索引值值从Base开始递增
    DWORD   NumberOfFunctions;      // * 导出地址的个数
    DWORD   NumberOfNames;          // * 导出名称的个数


    DWORD   AddressOfFunctions;     // * 导出地址表的地址RVA
    DWORD   AddressOfNames;         // * 导出名称表的地址RVA,按照ASCII码固定排序
    DWORD   AddressOfNameOrdinals;  // * 导出序号表的地址RVA
} IMAGE_EXPORT_DIRECTORY, *PIMAGE_EXPORT_DIRECTORY;

定位导出表:

数据目录第一项指针指向导出表。(第二项是导入表),该项只能说明导出表所在地址,导出表有多大并不是该Size来决定的(**但是它是导出函数是否转发的判断条件之一**)。

img

img

img

导出地址表

img

img

导出名称表

img

img

img

可以看出跟我们导出的顺序是不一样的,他进行了 函数名 ascii 码排序

导出序号表

img

解析

正常情况

img

在通过cff去看

img

  1. 像上面如果我们找到处序号为4的函数信息
  • 拿到下标索引 序号 - base = 4 - 1 = 3
  • 根据索引取导出地址表拿到地址 11195
  • 用OD验证

img

img

  1. 拿到函数 foo 的地址
  • 在导出名称表遍历,根据该函数名在导出名称表的索引 2
  • 在根据上面索引在导出序号表拿到函数导出序号 2
  • 在根据序号在 导出地址表拿到导出函数地址 110cd

img

导出表序号不从0开始的情况

img

img

img

此时情况是

imgimg

导出表序号不连续的情况

img

img

img

img

img

  1. 例如要找序号为110的函数地址
  • 获取在导出地址指表的索引 110 -100 = 10
  • 根据索引在导出地址指表数组取值 110cd
  • OD验证

img

img

  1. 寻找test的函数地址
  • 先到导出名称表找到 test 在该数组索引 5
  • 再到 导出序号表找到对应索引的导出地址的索引
  • 再根据导出地址表的索引取导出地址表 获取 地址 1132f

img

  1. 找序号108 的 函数地址
  • 获取序号108对应的导出地址的索引 108-100 = 8
  • 再根据导出地址表的索引取导出地址表 获取 地址 0000 ,说明没有这个序号的导出函数
导出函数没有名称只有序号导出

img

img

img

img

img

这种情况OD可以看到 函数名是通过pdb文件知道的,删掉之后就不知道了

函数转发

img

img

img

img

img

img

img

img

可以看出转发的话,导出地址表存的是一个字符串的地址,而系统需要解析这个字符串拿出dll名和函数名,继续去查

而且该地址和其他地址不一样, 该地址只需要位于 导出表地址 和 导出表地址+导出表大小 中间 那么他就是一个转出函数,如果没有位于这中间,那么他就不是一个转发函数

如何判断转发函数,即地址指向应为字符串而不是实现?
  • 如果是指向的字符串(表示为转发函数),是与导出函数名放在一起的,所以应该先借助正常遍历获取到地址,然后借助导出表的地址和大小判断地址的范围是否在导出表的范围之内:
    • 不是则为正常函数,直接返回地址节课;
    • 是则为转发函数,进行转发函数的处理。
遇到转发函数应该如何处理?
  • 1.字符串为dll.函数名,所以要先分割解析,分别拿到dll名和函数名;
  • 2.调用LoadLibrary;
  • 3.再GetProcAddress递归遍历拿到地址。

模拟GetProcAddress

.586
.model flat,stdcall
option casemap:none

   include windows.inc
   include user32.inc
   include kernel32.inc
   include msvcrt.inc

   includelib user32.lib
   includelib kernel32.lib
   includelib msvcrt.lib


WinMain proto :DWORD,:DWORD,:DWORD,:DWORD


.data
   g_szDll db "user32.dll",0
   g_szFunc  db "MessageBoxA",0

.code

;参数:   句柄   导出函数名
MyGetProcAddress proc hMod:HMODULE, lpProcName:LPCSTR   

    LOCAL @pDosHdr:ptr IMAGE_DOS_HEADER          ;dos头
    LOCAL @pNTHdr:ptr IMAGE_NT_HEADERS           ;Nt头
    LOCAL @pExpDir:ptr IMAGE_EXPORT_DIRECTORY    ;导出表

    LOCAL @pAddrTbl:DWORD     ;导出地址表地址
    LOCAL @pNameTbl:DWORD     ;导出名称表地址
    LOCAL @pOrdTbl:DWORD      ;导出序号表地址

    ;解析
    ;dos 头
    mov eax, hMod
    mov @pDosHdr, eax

    ;nt头
    mov esi, @pDosHdr
    assume esi:ptr IMAGE_DOS_HEADER
    mov eax, hMod
    add eax, [esi].e_lfanew
    mov @pNTHdr, eax


    mov esi, @pNTHdr
    assume esi:ptr IMAGE_NT_HEADERS


    ;获取导出表
    mov esi, @pNTHdr
    assume esi:ptr IMAGE_NT_HEADERS

    mov eax, [esi].OptionalHeader.DataDirectory[0].VirtualAddress
    add eax, hMod
    mov @pExpDir, eax

    mov esi, @pExpDir
    assume esi:ptr IMAGE_EXPORT_DIRECTORY

    ;导出函数地址表
    mov eax, [esi].AddressOfFunctions
    add eax, hMod
    mov @pAddrTbl, eax

    ;导出函数名称表
    mov eax, [esi].AddressOfNames
    add eax, hMod
    mov @pNameTbl, eax


    ;导入序号表
    mov eax, [esi].AddressOfNameOrdinals
    add eax, hMod
    mov @pOrdTbl, eax


    ;判断是序号还是名称  (序号是一个 word,对于 dword来说高位都是0)
    .if lpProcName & ffff0000h   
        ;名称
        mov ebx, @pNameTbl
        xor ecx, ecx
        .while ecx < [esi].NumberOfNames
            ;获取名称地址
            mov eax, [ebx+ecx*4]
            add eax, hMod

            ;字符串比较
            push ecx
            invoke crt_strcmp, lpProcName, eax
            pop ecx
            .if eax == 0
                ;找到了, 从导出序号表取出函数地址下标
                mov edi, @pOrdTbl
                movzx eax, word ptr [edi+ecx*2]


                ;从导入地址表,下标寻址,获取导出函数地址
                mov ebx, @pAddrTbl
                mov eax, [ebx+eax*4]

                ;判断转发 。。。。解析函数名,递归判断


                ;返回地址
                .if eax != NULL
                    add eax, hMod
                    ret
                .endif

            .endif

            inc ecx
        .endw

    .else 
        ;序号
        mov eax, lpProcName
        sub eax, [esi].nBase ;获取索引值

        ;从导入地址表,下标寻址,获取导出函数地址
        mov ebx, @pAddrTbl
        mov eax, [ebx+eax*4]

        .if eax != NULL
            add eax, hMod
            ret
        .endif

    .endif

    xor eax, eax
    ret

MyGetProcAddress endp

start:
    invoke LoadLibrary, offset g_szDll
    invoke MyGetProcAddress,eax, offset g_szFunc

    push MB_OK
    push offset g_szDll
    push offset g_szFunc
    push NULL
    call eax

    invoke ExitProcess,0
end start

作业

1. 实现GetProcAddress,可以处理函数转发

2. PE工具添加解析导出表的功能

本帖被以下淘专辑推荐:

0

主题

120

回帖

58

积分

新手上路

积分
58
发表于 7 天前 | 显示全部楼层
努力学习,支持一下!
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

QQ|Archiver|手机版|小黑屋|断点社区 |网站地图

GMT+8, 2025-3-12 23:29 , Processed in 0.112152 second(s), 21 queries , Yac On.

Powered by XiunoBBS

Copyright © 2001-2025, 断点社区.

快速回复 返回顶部 返回列表