相关知识点 导出表: 导出表结构分析 1、如何定位导出表: 数据目录项的第一个结构,就是导出表. typedef struct _IMAGE_DATA_DIRECTORY { DWORD VirtualAddress; DWORD Size; } IMAGE_DATA_DIRECTORY, *PIMAGE_DA
相关知识点
导出表:导出表结构分析
1、如何定位导出表:
数据目录项的第一个结构,就是导出表.
typedef struct _IMAGE_DATA_DIRECTORY {
DWORD VirtualAddress;
DWORD Size;
} IMAGE_DATA_DIRECTORY, *PIMAGE_DATA_DIRECTORY;
VirtualAddress 导出表的RVA
Size 导出表大小
2、导出表结构
上面的结构,只是说明导出表在哪里,有多大,并不是真正的导出表.
如何在FileBuffer中找到这个结构呢?在VirtualAddress中存储的是RVA,如果想在FileBuffer中定位
必须要先将该RVA转换成FOA.
真正的导出表结构如下:
typedef struct _IMAGE_EXPORT_DIRECTORY {
DWORD Characteristics; // 未使用
DWORD TimeDateStamp; // 时间戳
WORD MajorVersion; // 未使用
WORD MinorVersion; // 未使用
DWORD Name; // 指向该导出表文件名字符串
DWORD Base; // 导出函数起始序号
DWORD NumberOfFunctions; // 所有导出函数的个数
DWORD NumberOfNames; // 以函数名字导出的函数个数
DWORD AddressOfFunctions; // 导出函数地址表RVA
DWORD AddressOfNames; // 导出函数名称表RVA
DWORD AddressOfNameOrdinals; // 导出函数序号表RVA
} IMAGE_EXPORT_DIRECTORY, *PIMAGE_EXPORT_DIRECTORY;
导出函数两种方式:
(1)名字导出 (2)序号导出
下面是重要参数选项:
AddressOfFunctions AddressOfNameOrdinals AddressOfNames
0 Fn1地址 0 3 0 0x12345678
1 Fn2地址 1 1 1 0x12345678
2 Fn3地址 2 4 2 0x12345678
3 Fn4地址 3 7 3 0x12345678
4 Fn5地址 4 8 4 0x12345678
Fn6地址 6 0x12345678
Fn7地址 7 0x12345678
.. 0x12345678
.. 0x12345678
.. 0x12345678
.. 0x12345678
.. 0x12345678
Fnx地址 N 0x12345678
宽度4 宽度2 宽度4
数量:NumberOfFunctions 数量:NumberOfNames 数量:NumberOfNames
3、AddressOfFunctions说明:
该表中元素宽度为4个字节
该表中存储所有导出函数的地址
该表中个数由NumberOfFunctions决定
该表项中的值是RVA, 加上ImageBase才是函数真正的地址
定位:
IMAGE_EXPORT_DIRECTORY->AddressOfFunctions 中存储的是该表的RVA 需要先转换成FOA
4、AddressOfNames说明:
该表中元素宽度为4个字节
该表中存储所有以名字导出函数的名字的RVA
该表项中的值是RVA, 指向函数真正的名称
AddressOfNames 特别说明:
0x12345678 1、函数的真正的名字在文件中位置是不确定的
0x12345678 DXXXXXXXXX
0x12345678 2、但函数名称表中是按名字排序的
0x12345678
0x12345678 AXXXXXXXXXXX 也就是说,A开头的函数在AddressOfNames排在最前面.
0x12345678
0x12345678 CXXXXXX 但AXXXXXX这个真正的名字,可能排在BXXXXX后面
0x12345678
0x12345678 BXXXXXXXXX 3、如果想打印名字,要先将AddressOfNames转换为FOA
0x12345678
0x12345678
0x12345678
0x12345678
5、AddressOfNameOrdinals
该表中元素宽度为2个字节
该表中存储的内容 + Base = 函数的导出序号
总结:
为什么要分成3张表?
1.函数导出的个数与函数名的个数未必一样.所以要将函数地址表和函数名称表分开.
2.函数地址表是不是一定大于函数名称表?
未必,一个相同的函数地址,可能有多个不同的名字.
3.如何根据函数的名字获取一个函数的地址?
函数VA = ImageBase + 0x1234
函数名称表 函数序号表 函数地址表
4.如何根据函数的导出序号获取一个函数的地址?
假设导出序号是10
Base 的值是5
函数地址:10 - 5 = 5
找出下标为5的函数地址即可;
课后练习
1.编写程序打印所有的导出表信息;
typedef struct _IMAGE_EXPORT_DIRECTORY {
DWORD Characteristics; // 未使用
DWORD TimeDateStamp; // 时间戳
WORD MajorVersion; // 未使用
WORD MinorVersion; // 未使用
DWORD Name; // 指向该导出表文件名字符串
DWORD Base; // 导出函数起始序号
DWORD NumberOfFunctions; // 所有导出函数的个数
DWORD NumberOfNames; // 以函数名字导出的函数个数
DWORD AddressOfFunctions; // 导出函数地址表RVA
DWORD AddressOfNames; // 导出函数名称表RVA
DWORD AddressOfNameOrdinals; // 导出函数序号表RVA
} IMAGE_EXPORT_DIRECTORY, *PIMAGE_EXPORT_DIRECTORY;
地址空间:这个地址空间指的是PE文件被加载到内存的空间,是一个虚拟的地址空间,
之所以不是物理空间是因为数据在内存中的位置经常在变,这样既可以节约内存开支又可以避开错误的内存位置。
这个地址空间的大小为4G,但其中供程序装载的空间只有2G而且还是低2G空间,高2G空间则被用于装载内核DLL文件,
所以也被称作内核空间。
文件映射:PE文件在磁盘上的状态和在内存中的状态是不一样的,我们把PE文件在磁盘上的状态称作FileBuffer,
在内存中的状态称为ImageBuffer。当PE文件通过装载器装入内存是会经过“拉伸”的过程,
所以它在FileBuffer状态下和ImageBuffer状态下的大小是不一样的;
VA:英文全称是Virual Address,简称VA,中文意思是虚拟地址。指的是文件被载入虚拟空间后的地址。
ImageBase:中文意思是基址,指的是程序在虚拟空间中被装载的位置。
RVA:英文全称是Relative Virual Address,简称RVA,中文意思是相对虚拟地址。
可以理解为文件被装载到虚拟空间(拉伸)后先对于基址的偏移地址。
计算方式:RVA = VA(虚拟地址) - ImageBase(基址)。它的对齐方式一般是以1000h为单位在虚拟空间中对齐的(传说中的4K对齐),
具体对齐需要参照IMAGE_OPTIONAL_HEADER32中的SectionAlignment成员;
FOA:英文全称是File Offset Address,简称FOA,中文意思是文件偏移地址。
可以理解为文件在磁盘上存放时相对于文件开头的偏移地址。它的对齐方式一般是以200h为单位在硬盘中对齐的(512对齐),
具体对齐需要参照IMAGE_OPTIONAL_HEADER32中的FileAlignment成员;
下面是判断RVA和FOA的计算方法;
<1> 得到RVA的值,RVA=内存地址-ImageBase(ImageBase是IMAGE_OPTION_HEADER中的成员);
<2> 比较RVA与SizeofHeaders的大小,判断RVA是否位于PE头中,如果是的话,FOA=RVA,(SizeofHeaders是IMAGE_OPTION_HEADER中的成员);
<3> 判断RVA位于哪个节,
RVA >= 节.VirtualAddress
RVA <= 节.VirtualAddress + 当前节内存对齐后的大小
差值 = RVA - 节.VirtualAddress
<4> FOA = 节.PointerToRawData + 差值
总结:
总体来说,首先就是要判断某一个内存地址是否是在SizeOfHeaders里面,如果是,那么RVA=FOA,因为SizeOfHeaders在拉伸前后不变;
而如果不在SizeOfHeaders,里面那么就要判断这个内地址落在哪个节的范围,然后减去落在这个节VirtualAddress的地址,得到她们;
的差值,将得到的这个差值加上这个节中对应在文件中偏移的地址(PointerToRawData)结果就是FOA;
实例计算:
1.判断不带ImageBase地址0x00051EC0在哪个节里面;
2.通过计算查找,发现0x00051EC0是落在第二个节里面,因为:VirtualAddress + VirtualSize > 上面不带ImageBase的地址;
第二个节对应的VirtualAddress和VirtuallSIze 0x00046000+0x0000D74D=0x0005374D > 0x00051EC0;
3.确认在哪个节里面之后就可以根据上面的总结计算,下面是导出表给出的VirtualAddress地址加上ImageBase然后减去ImageBase;
对应的距离,刚好就是0x00051EC0
RVA = 0x00051EC0 + 0x00400000 - 0x00400000 = 0x00051EC0;
4.计算出她们之间的差值
差值 = 0x00051EC0 - 0x00046000 = 0x0000BEC0;
5.计算FOA的值
FOA = 对应节的PointerToRawData + 差值
FOA = 0x00046000 + 0x0000BEC0 = 0x00051EC0;
注意:这里的0x00046000是因为此程序中节里面的PointerToRawData=VirtualAddress=0x00046000;
2.GetFunctionAddrByName(FileBuffer指针,函数名指针)
3.GetFunctionAddrByOrdinals(FileBuffer指针,函数名导出序号)
上面计算FOA的过程验证
验证方式是使用winhex打开存储在硬盘位置的ipmsg.exe和意见打开的ipmsg.exe程序,找到其对应的导出表的VirtualAddress地址;
迷茫的人生,需要不断努力,才能看清远方模糊的志向!