PE 导出表
Posted 不会写代码的丝丽
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了PE 导出表相关的知识,希望对你有一定的参考价值。
名称寻址
我们下window
经常导出函数,因此在动态库我们往往有一个特殊结构叫做导出表
。
我们首先看一个导出模块信息
#export.def
EXPORTS
MsgBox
Foo
TestFunc
ABCDFunc
上面我们导出四个函数且是名称导出
我们的导出表地址位于NT头中数据目录的第一项。
这个导出目录的位于虚拟地址的2060h
处 换算成文件地址为660h
相关的数据结构
对应上面的数值我们得出以下数据
Field | Value | 备注 |
---|---|---|
Export Flags | 0 | |
Time/Date Stamp | 0x62510738 | 换算为10进制得到对应时间戳2022-04-09 12:10:32 |
Major Version | 0 | |
Minor Version | 0 | |
Name RVA | 20B0h | 名称字符串所在虚拟地址 |
Ordinal Base | 1 | 导出函数起始序列号 |
Address Table Entries | 4 | 证明有四个地址数组 |
Number of Name Pointers | 4 | 证明有四个名称导出 |
Export Address Table RVA | 0x2088h | 导出地址表 |
Name Pointer RVA | 0x2098h | 导出名称表地址 |
Ordinal Table RVA | 20A8h | 导出名称对应序列号表地址 |
要明白这些字段的含义我们首先要理解动态连接器是如何完成寻址的。
我们首先讲本例中的程序三个以名称导出示例:
- 连接器在导出名称地址表搜索某个函数
x
所在下标index
- 将
index
带入到导出名称序号地址表中得到某个数值y
,y
就是是导出地址表的下标
- 得到导出地址表下标为
y
的地址
假设我们想得到TetFunc
的函数地址:
1 遍历名称地址表得到下标为3的位置
2 去导出序号表中得到下标为3的数值,非常凑巧也为3
3 得到导出地址表下标3的地址0x00001022
上面是名称导出的寻址过程。我们看看序号导出的情况
序号寻址
假设我们有如下模块定义
EXPORTS
MsgBox @3
Foo @4 NONAME
TestFunc @7
ABCDFunc @8 NONAME
我们通过CFF Explorer给出如下导出表信息
上面需要特别注意我们如下几个字段
NumberOfFunctions
是6而我们导出的函数只有4四个,证明编译器帮我们多填充了。
Base
基础下标值3,这个会用于名称查找
再次举例名称查找TestFunc
:
- 名称表得到下标1,
- 名称序号表的下标1得到导出地址表下标4
- 导出地址表下标4的地址0x00001022
序号查找导出函数地址:
导出函数序号-Base = 导出地址表下标
举例查找导出序号4 Foo函数的地址:
4 - 3 = 1
在从导出地址表下标为1得出地址0x00001011
转发函数
我们知道模块定义中的某个函数可能存在函数转发行为
如下代码的MyMsBox
EXPORTS
MsgBox @3
Foo @4
TestFunc @7
ABCDFunc @8
MyMsBox = user32.MessageBoxA
我们注意下MyMsBox 在20E8这个位置中。
同时我们的导出表的范围为2060-2104
这个范围中,所以对于转发函数来说它的函数位置在导出表范围,而非转发在别的区域(官方文档有写)
模拟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
.data
g_szDllName db "DllExport",0
g_szFuncName db "MsgBox",0
.code
MyGetProcAddress proc hDll:HINSTANCE,pName:LPCTSTR
LOCAL @dwAddressOfNames:DWORD
LOCAL @dwCnt:DWORD
mov esi,hDll
assume esi:ptr IMAGE_DOS_HEADER
mov esi,[esi].e_lfanew
add esi,hDll
assume esi:ptr IMAGE_NT_HEADERS
;得到导出表位置
mov esi,[esi].OptionalHeader.DataDirectory[0].VirtualAddress
add esi,hDll
assume esi:ptr IMAGE_EXPORT_DIRECTORY
.if pName > 0ffffh ;名称
;导出名称表地址
mov eax,[esi].AddressOfNames
add eax,hDll
mov @dwAddressOfNames,eax
;遍历导出名称表,查找对应函数
mov eax,[esi].NumberOfNames
mov @dwCnt,eax
dec @dwCnt
.while @dwCnt> 0
;数组首地址
mov ebx,@dwAddressOfNames
mov eax,@dwCnt
mov eax,[ebx+ eax * sizeof DWORD]
add eax,hDll
;对比字符串
invoke crt_strcmp,pName,eax
.if eax == 0
;int 3;
mov eax,@dwCnt
mov ebx,[esi].AddressOfNameOrdinals
add ebx,hDll
movzx eax,word ptr [ebx+eax*sizeof WORD]
;获取地址
mov ebx,[esi].AddressOfFunctions
add ebx,hDll
mov eax,[ebx+eax*sizeof DWORD]
add eax,hDll
ret
.endif
dec @dwCnt
.endw
.else
;获取导出地址表中的下标索引
ret
.endif
xor eax,eax
ret
MyGetProcAddress endp
; ---------------------------------------------------------------------------
start:
invoke LoadLibrary,offset g_szDllName
invoke MyGetProcAddress,eax,offset g_szFuncName
call eax
xor eax,eax
invoke ExitProcess,eax
end start
以上是关于PE 导出表的主要内容,如果未能解决你的问题,请参考以下文章