WindowsPE文件格式入门08.导出表
https://bpsend.net/thread-377-1-1.html
通过cff , depends灯等软件可以看到dll,导出函数的信息,因为dll中本身就存了这些信息,存了dll中有哪些导出函数,导出函数的序号是什么,名字是什么,以及他们的地址是什么,这些东西都存在导出表里面
导出表发展历程
最开始的时候只有序号导出,没有名称导出,使用的时候都是通过序号
| 序号 | 地址 |
|---|---|
| 1 | 1005 |
| 2 | 2005 |
| 3 | 3005 |
| 4 | 4005 |
| 5 | 5005 |
| 6 | 6005 |
| 7 | 7005 |
| 8 | 8005 |
但是上面的时间复杂度比较高是线性阶,但是对数阶占的体积比较大,所以只能折中,用常量阶.把地址作为数组,序号作为索引,只存地址,这样不仅速度更加快了,而且体积更小了
但是所以是从0开始的,因此可以数组首项加一个空

这样可以通过序号,直接去找对应数组对应的索引的值
序号不从1开始
但是上面有一个缺陷,序号不一定从0开始,例如 从 1001开始,这样数组不能前面加1000项0,这样不仅浪费空间,而且还用不上,解决办法是加一个 基址 (最小序号), 后面序号就根据基址来取偏移

例如上面,要拿到 序号1006 的函数地址, 可以 获取 1006对 1001 的偏移 5 ,再去数组取 索引为5的地址
序号不连续
解决了上面基本不从0开始的情况,还有一个问题,就是 序号不连续 ,例如 1001 , 1002 , 1020,1021,1050
这种情况下,数组中间对应的序号就需要填充0,因此会使体积变大,微软并没有解决这个问题,因为这是写dll的人自己的问题
例如
- 不指定序号(这样序号连续)


可以看出大小是38kb
- 部分指定序号使序号不连续


可以看到此时dll大小变成了 346KB
名称导出

序号跟成名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来决定的(**但是它是导出函数是否转发的判断条件之一**)。



导出地址表


导出名称表



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

解析
正常情况

在通过cff去看

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


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

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



此时情况是


导出表序号不连续的情况





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


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

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





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








可以看出转发的话,导出地址表存的是一个字符串的地址,而系统需要解析这个字符串拿出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
WindowsPE文件格式入门08.导出表的更多相关文章
- JavaScript基础入门08
目录 JavaScript 基础入门08 DOM 介绍 绑定事件 给一组元素绑定事件 节点 节点树 节点类型 选取文档内容 通过id选取元素 通过指定的标签名选取元素 用指定的css类来选取元素 通过 ...
- YAML文件格式入门
YAML快速入门 https://www.jianshu.com/p/97222440cd08 https://yaml.org/spec/1.2/spec.pdf http://nodeca.git ...
- 【机器学习】机器学习入门08 - 聚类与聚类算法K-Means
时间过得很快,这篇文章已经是机器学习入门系列的最后一篇了.短短八周的时间里,虽然对机器学习并没有太多应用和熟悉的机会,但对于机器学习一些基本概念已经差不多有了一个提纲挈领的了解,如分类和回归,损失函数 ...
- WindowsPE 第五章 导出表编程-1(枚举导出表)
导出表编程-1-枚举导出表 开始前先回忆一下导出表: 1.枚举dll函数的导出函数名字: 思路: (1)加载dll到内存里. (2)获取PE头,逐步找到可选头部. (3)然后找到里面的第一个结构(导出 ...
- WindowsPE 第五章 导出表
导出表 PE中的导出表存在于动态链接库文件里.导出表的主要作用是将PE中存在的函数导出到外部,以便其他人可以使用这些函数,实现代码重用. 5.1导出表的作用 代码重用机制提供了重用代码的动态链接库,它 ...
- 机器学习入门08 - 表示法 (Representation)
原文链接:https://developers.google.com/machine-learning/crash-course/representation/ 机器学习模型不能直接看到.听到或感知输 ...
- Shell - 简明Shell入门08 - 函数(Function)
示例脚本及注释 #!/bin/bash function Check() # 使用function定义函数 { Say # 通过函数名直接调用函数 if test $1 then return 0 # ...
- C#零基础入门08:代码规范
一:前言 没有规矩,不成方圆.在代码的世界中,尤其这样.作为程序员,我们不想让我们的代码写出去之后被人耻笑:看,连个换行都换的这么不专业.作为开发主管,我们则不想我们的组员写出来的代码各类风格都有,五 ...
- springMVC入门-08
这一讲介绍用户登录实现以及两种异常处理controller控制器的方法,最后提一下在springMVC中对静态资源访问的实现方法. 用户登录需要一个登录页面login.jsp,对应代码如下所示: &l ...
- 【Asp.net入门08】第一个Asp.net应用程序-创建窗体并设置其样式
本节内容: 添加一个aspx窗体并设计窗体内容 为aspx窗体添加样式 前面我们为PartyInvites应用程序项目添加了两个c#文件:GuestResponse.cs和ResponseReposi ...
随机推荐
- FineReport - [01] 概述
Gartner报表平台全球市场唯一入选国产软件! 一.FineReport 是什么?有什么用途? FineReport 是一款企业级Web报表工具,由帆软自主研发,秉持零编码的理念,易学易用且功能强大 ...
- java.lang.IllegalStateException: File name has been re-used with different files. (flume报错)
报错日志: java.lang.IllegalStateException: File name has been re-used with different files. Spooling ass ...
- ABC393E题解
大概评级:绿. 拿到题目,寻找突破口,发现 \(A_i \le 10^6\),一般的数据都是 \(A_i \le 10^9\),所以必有蹊跷. 数学,权值,最大公约数,联想到了因子--懂了,原来是这么 ...
- Selenium WebDriver上创建 WebDriver测试脚本
本文实现一个WebDriver测试脚本,介绍WebDrive的常用命令.UI元素定位的策略以及在脚本中的使用,还有Get命令. 你将学到: 脚本创建 代码走查 测试执行 定位Web元素 ...
- 【CIM信息整合】关于三维建筑模型
还是无暇细细检索并总结列出有逻辑的明确表述,以下很多地方都是人云亦云的复制,自己也没太搞清 1.5 三维建筑模型 CIM中三维建筑模型主要表达建(构)筑物的空间位置.几何形态及外观效果等. 在建筑相关 ...
- Assignment to property of function parameter 'XXX' no-param-reassign 记录
在react项目中写了一个工具方法将两个数组数据进行整合,用了双重for循环,但是在提交代码时报了eslint的no-param-reassign 结果效果是有了,但是报lint错误,图片中已是解决后 ...
- 工作面试必备:SQL 中的各种连接 JOIN 的区别总结!
前言 尽管大多数开发者在日常工作中经常用到Join操作,如Inner Join.Left Join.Right Join等,但在面对特定查询需求时,选择哪种Join类型以及如何使用On和Where子句 ...
- DeepSeek 加持!IvorySQL 文档智能助手正式上线!
DeepSeek 加持!IvorySQL 文档智能助手正式上线! "那个配置参数到底在第几章?"--正在部署 IvorySQL 的运维工程师小 "I",第 5 ...
- 【Python】词频统计
需求:一篇文章,出现了哪些词?哪些词出现得最多? 英文文本词频统计 英文文本:Hamlet 分析词频 统计英文词频分为两步: 文本去噪及归一化 使用字典表达词频 代码: #CalHamletV1.py ...
- unigui显示websocket服务端向客户端发送信息【15】
用WebSocket从服务端直接发送消息给all客户端. 1.在ServerModule 放TUniThreadTimer 2.timer event: procedure TUniServerMod ...