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 ...
随机推荐
- Java进阶 - [1-4] 反射
一.类加载区别 当我们刚接触java语言的时候,我们最常见的代码应该就是初始化某个对象,然后调用该对象的方法. 1.使用new创建对象,返回对象的引用.Student student = new St ...
- Codeforces Round 1006 (Div. 3) 比赛记录
Codeforces Round 1006 (Div. 3) 比赛记录 比赛链接 这场的题目名称都很长啊~. 很简单的一场(毕竟是div3,能不简单嘛)赛时切掉了A - F,C题花的时间有点多,G题偶 ...
- sap 管理--企业解决方案 -设备管理
1.什么是sap 管理 2.设备管理管的是什么 3.设备的几种状态 4.设备bom(物料清单) 5.测量点计数器 1.什么是sap 管理 System Applications and Product ...
- 大模型基础补全计划(二)---词嵌入(word embedding)
PS:要转载请注明出处,本人版权所有. PS: 这个只是基于<我自己>的理解, 如果和你的原则及想法相冲突,请谅解,勿喷. 环境说明 无 前言 本文是这个系列第二篇,它们是: &l ...
- HIVE将长整数转字符串的错误
有一个超长字符串,比如:441066000000001005712973,原来存放在HIVE里表A 是用DECIMAL(24)类型.现在要与另外一个用string类型保存这个字段的表B关联,老是失败. ...
- deepseek内网离线部署手册
前言 在当下 AI 浪潮汹涌的时代,DeepSeek 以其卓越的性能和出色的表现,迅速成为了众多专业人士和科技爱好者热议的焦点工具.在众多AI大模型的比拼中,DeepSeek 展现出了优越的实力.然而 ...
- IvorySQL 4.4 发布
IvorySQL 4.4 已于 2025 年 3 月 10 日正式发布.新版本全面支持 PostgreSQL 17.4,新增多项新功能,并修复了已知问题. 增强功能 PostgreSQL 17.3 增 ...
- 使用Win32控制台实现socket通信
本示例使用Win32控制台实现socket通信 ,可多个客户端同时连接服务器. 服务端代码: #include <iostream> #include <winsock2.h> ...
- VirtualBox 新建虚拟电脑时没有64-bit选项?
好久没用VirtualBox了,没事下载了个准备看下新版的Ubuntu 16.04 & umake命令. 下载&安装完成,准备新建的时候,发现个问题:没有64-bit的选项? 目测了下 ...
- Nginx 301永久性转移
我有个域名www.taadis.com, 想永久性转移到taadis.com. 前言 看到很多网友的做法是把taadis.com & www.taadis.com等多个域名放到一个server ...