一、导出表概述
1.引入导出表
-
前面学过一个Win32下的.exe文件,是由多个PE文件组成。比如通过OD打开一个Ipmsg.exe,查看模块
M
,会发现一个ipmsg.exe和多个.dll(即模块)构成。 -
这种以动态链接库.dll的方式导出函数时,如果此.exe需要用到某个.dll中的函数,那么.exe怎么知道:
- 引入的.dll中有哪些函数可以供.exe使用
- .dll中的各种函数都存在哪里
-
所以.dll中应当有一个目录或者结构提供给.exe文件,来记录.dll中的函数有哪些,函数的起始地址等信息。所以如果.dll或者.exe文件想要给别的程序提供函数,就必须同时给别的程序一个“清单”,这个“清单”就是导出表
-
故当我们分析上面例子中的ole32.dll,或者LPK.dll等.dll文件结构时,就可以看到有导出表
2.常见误区
- 不是只有.dll才提供函数给别的程序使用,有些.exe也可以提供函数
- 有些.exe的导出表数据目录不是全0,表示有此.exe程序有导出表,即可对外提供函数;有些没有导出表,就不对外提供函数
- 所以一个PE文件(如.dll/.exe/.sys)如果对外提供函数,那么就需要有导出表,且需要遵守一定的格式
- 一个安全的PE文件,是不会显示导出表的,但可以对外提供函数:正常情况下如果一个PE文件不提供导出表,别人是无法使用其函数的;但是逆向一个游戏时,如果知道了别人的导出表,就不用找call了(不现实)。所以游戏会做安全加固,我们不知道哪些函数是导出的,就算给我函数地址,我也没办法拿这个地址去调用,只能分析反汇编代码,分析程序入口是什么,分析函数的参数是什么
3.导出表在哪里
-
先定位到可选PE头 --> 再找到可选PE头的最后一个成员:即一个结构体数组,第一个结构体就是导出表数据目录,第一个成员是导出表的内存偏移地址RVA,第二个成员是导出表的大小
-
通过导出表数据目录的RVA转FOA就可以找到导出表
-
导出表其实就在这个PE文件的某个节中(后面要学的各种表也是在某个节中)
-
注意事项:学到现在解析PE结构时就不用拉伸了,用FileBuffer分析即可。因为拉伸只是有助于理解,如果直接在FileBuffer中做,只是需要多使用一个==RVA–>FOA==的地址转换函数,每次遇到RVA地址时转换一下即可,就省去了将文件整个拉伸的过程,会减少很多代码量和多余操作,而且市面上关于PE的书籍几乎都是直接用FileBuffer讲解操作的,所以现在开始,我们就要学会不拉伸的情况下去分析
4.导出表的结构
struct _IMAGE_EXPORT_DIRECTORY{ //40字节
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 *
};
1.Name
-
指向该导出表文件名字符串的RVA,比如一个DBGHELP.dll的PE文件提供函数,那么这个PE文件的Name指向的字符串为为dbghelp.dll
可以用LordPE软件,选择
PE Editor
,选择你要打开的PE文件,查看其导出表
2.Base
-
导出函数起始序号(最小的序号)
比如有序号为14、6、10、8的导出函数,那么Base的值为6
3.NumberOfFunctions
-
所有导出函数的个数
-
注意:day35中讲过,这个值是通过导出函数的最大序号 - 最小序号 + 1算出来的;正常来说这个值是多少,那么此PE文件中导出函数的个数就是多少。但是如果使用自定义序号,序号定义时不是连续的,而是中间有空缺的序号,那么此时NumberOfFunctions的值会比实际的定义的导出函数个数多
- 比如在.def中定义为
Plus @12
、Sub @15 NONAME
、Mul @13
、Div @16
- 那么NumberOfFunctions值 = 16 - 12 + 1 = 5;而不是4!
- 比如在.def中定义为
4.NumberOfNames
- 以函数名字导出的函数个数:比如以动态链接库的方式导出(注意和只以序号导出函数区分)
- 如果导出时加了
NONAME
关键字,那么就不计数
5.AddressOfFunctions
-
导出函数地址表RVA,即这个地址指向一个表!这个表中记录了此PE文件的所有导出函数的地址
-
该表中元素宽度:4个字节
-
元素个数:由NumberOfFunctions决定
-
注意:day35中讲过导出函数是有序号的,上图中的下标等于导出函数的相对序号
(参考上图)比如使用自定义序号导出函数,即.def的方式导出,定义了序号13、14、16、17、19的导出函数,那么Base的值应为13,那么序号为13的函数相对相对下标就是0,序号14的导出函数相对下标就是1,序号为15的导出函数虽然没有,但是会把位置空出来,只是地址值为NULL,即0x00000000,序号16的导出函数相对下标就是3…以此类推
6.AddressOfNames
-
导出函数名称表RVA(拉伸后的内存地址偏移,所以要先转成FOA),即这个地址指向一个表,这个表中记录的是导出函数的名称字符串地址!!不是直接存储名称(且此字符串地址也是RVA)
就像C语言中的字符串
char* name = "abc";
,name变量值为abc这个字符串的首地址。所以导出函数名称表中存储的是name值(即名称的地址),而不是直接存储的abc这个名称 -
该表中元素宽度:4个字节
-
表中元素的数量:由NumberOfNames决定
-
注意:如果函数导出时添加了
NONAME
,即函数没有名称,那么这个表中就不会出现这个函数名地址- 所以AddressOfNames表中元素个数可能比AddressOfFunctions表中元素个数少!(AddressOfFunctions表中不管导出函数有没有名字,都会有地址)
- 也有可能AddressOfNames表中元素个数比AddressOfFunctions表中元素个数多!因为导出函数时可以让多个不同名字的函数指向同一个函数地址
-
函数名称表中是按名字排序的(根据ASCII码排序)
7.AddressOfNameOrdinals
-
导出函数序号表RVA,即这个地址指向一个表,表中存的是导出函数的相对序号
-
即==该表中存储的内容 + Base = 函数的导出序号==
-
表中元素个数:由NumberOfNames决定,且和AddressOfName表一一对应的
比如:现在要找名字叫Sub的导出函数,经查找在AddressOfNames表的下标为1的位置,那么这个函数所对应的相对序号则在AddressOfNameOrdinals表中下标为1的位置,即由上图可知Sub函数对应的序号为0x0001 + Base
-
该表中元素宽度:2个字节
5.NONAME导出函数注意事项
- 如果导出时,定义的无名字函数,即
Div @13 NONAME
,那么函数名称表中就不会有指向Div函数名的元素,同样函数序号表中也不会有Div的相对序号,但是在函数地址表中会留出来一个元素位置存储Div函数地址
二、由导出表获取函数地址
1.按名字找函数地址
- 找到导出表后,先根据AddressOfNames,将RVA转成FOA,即可定位到函数名称表,遍历函数名称表,用名字依次与函数名称表每个元素指向的字符串做比较,直到名字匹配,记录下此时元素在函数名称表的下标
i
- 再根据AddressOfNameOrdinals,将RVA转成FOA,即可定位到函数序号表,得到此表下标为
i
的元素值n
- 最后根据AddressOfFunctions,将RVA转成FOA,即可定位到函数地址表,则此表下标为
n
的元素值就是该名字对应函数的RVA地址,将RVA转成FOA就得到了该导出函数在FileBuffer中的地址
比如要根据名字查找导出函数
mul()
,假设在函数名称表中下标为2指向的位置找了"mul"字符串与函数名称匹配,所以再去查找函数序号表下标为2的元素为0x0004,最后再函数地址表中找下标为4的元素值,即为mul函数的RVA,转成FOA即可
2.按序号找函数地址
-
找到导出表后,根据AddressOfFunctions,将RVA转成FOA,定位到函数地址表
-
再用给定的序号 - Base = 相对序号
i
,得到相对序号i
-
最后找函数地址表中下标为
i
的元素值即为该序号对应函数RVA地址,将RVA转成FOA就得到了该函数在FileBuffer中的地址
比如要根据序号找导出函数
mul()
,因为mul的序号为17,Base值为13,那么17 - 13 = 4,所以直接再函数地址表中找到下标为4的元素值,即为mul函数的RVA,最后转成FOA即可所以根据导出序号找,跟函数序号表没有关系!函数序号表的存在就是为了用于按名字查找
上图可以发现,t_method函数用NONAME导出,所以函数名称表和函数序号表都没有t_method的信息,但是由于此函数序号为19,相对序号为19 - 13 = 6,所以会在函数地址表中下标为6的位置存储t_method函数的RVA
三、作业
1.编写程序打印导出表的信息
#include "stdafx.h"
#include <stdlib.h>
typedef unsigned short WORD;
typedef unsigned int DWORD;
typedef unsigned char BYTE;
#define MZ 0x5A4D
#define PE 0x4550
#define IMAGE_SIZEOF_SHORT_NAME 8
//DOS头
struct _IMAGE_DOS_HEADER {
WORD e_magic; //MZ标记
WORD e_cblp;
WORD e_cp;
WORD e_crlc;
WORD e_cparhdr;
WORD e_minalloc;
WORD e_maxalloc;
WORD e_ss;
WORD e_sp;
WORD e_csum;
WORD e_ip;
WORD e_cs;
WORD e_lfarlc;
WORD e_ovno;
WORD e_res[4];
WORD e_oemid;
WORD e_oeminfo;
WORD e_res2[10];
DWORD e_lfanew; //PE文件真正开始的偏移地址
};
//标准PE头
struct _IMAGE_FILE_HEADER {
WORD Machine; //文件运行平台
WORD NumberOfSections; //节数量
DWORD TimeDateStamp; //时间戳
DWORD PointerToSymbolTable;
DWORD NumberOfSymbols;
WORD SizeOfOptionalHeader; //可选PE头大小
WORD Characteristics; //特征值
};
//数据目录
struct _IMAGE_DATA_DIRECTORY{
DWORD VirtualAddress;
DWORD Size;
};
//可选PE头
struct _IMAGE_OPTIONAL_HEADER {
WORD Magic; //文件类型
BYTE MajorLinkerVersion;
BYTE MinorLinkerVersion;
DWORD SizeOfCode; //代码节文件对齐后的大小
DWORD SizeOfInitializedData; //初始化数据文件对齐后的大小
DWORD SizeOfUninitializedData; //未初始化数据文件对齐后大小
DWORD AddressOfEntryPoint; //程序入口点(偏移量)
DWORD BaseOfCode; //代码基址
DWORD BaseOfData; //数据基址
DWORD ImageBase; //内存镜像基址
DWORD SectionAlignment; //内存对齐粒度
DWORD FileAlignment; //文件对齐粒度
WORD MajorOperatingSystemVersion;
WORD MinorOperatingSystemVersion;
WORD MajorImageVersion;
WORD MinorImageVersion;
WORD MajorSubsystemVersion;
WORD MinorSubsystemVersion;
DWORD Win32VersionValue;
DWORD SizeOfImage; //文件装入虚拟内存后大小
DWORD SizeOfHeaders; //DOS、NT头和节表大小
DWORD CheckSum; //校验和
WORD Subsystem;
WORD DllCharacteristics;
DWORD SizeOfStackReserve; //预留堆栈大小
DWORD SizeOfStackCommit; //实际分配堆栈大小
DWORD SizeOfHeapReserve; //预留堆大小
DWORD SizeOfHeapCommit; //实际分配堆大小
DWORD LoaderFlags;
DWORD NumberOfRvaAndSizes; //目录项数目
_IMAGE_DATA_DIRECTORY DataDirectory[16]; //数据目录
};
//NT头
struct _IMAGE_NT_HEADERS {
DWORD Signature; //PE签名
_IMAGE_FILE_HEADER FileHeader;
_IMAGE_OPTIONAL_HEADER OptionalHeader;
};
//节表
struct _IMAGE_SECTION_HEADER{
BYTE Name[IMAGE_SIZEOF_SHORT_NAME]; //节表名
union{
DWORD PhysicalAddress;
DWORD VirtualSize; //内存中未对齐大小
}Misc;
DWORD VirtualAddress; //该节在内存中偏移地址
DWORD SizeOfRawData; //该节在硬盘上文件对齐后大小
DWORD PointerToRawData; //该节在硬盘上文件对齐后偏移地址
DWORD PointerToRelocations;
DWORD PointerToLinenumbers;
WORD NumberOfRelocations;
WORD NumberOfLinenumbers;
DWORD Characteristics; //该节特征属性
};
//导出表
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 *
};
/*计算文件大小函数
参数:文件绝对路径
返回值:返回文件大小(单位字节)
*/
int compute_file_size(char* filePath){
FILE* fp = fopen(filePath,"rb");
if(!fp){
printf("打开文件失败");
exit(0);
}
fseek(fp,0,2);
int size = ftell(fp);
//fseek(fp,0,0); 单纯计算文件大小,就不需要还原指针了
fclose(fp);
return size;
}
/*将文件读入FileBuffer函数
参数:文件绝对路径
返回值:FileBuffer起始地址
*/
char* to_FileBuffer(char* filePath){
FILE* fp = fopen(filePath,"rb");
if(!fp){
printf("打开文件失败");
exit(0);
}
int size = compute_file_size(filePath);
char* mp = (char*)malloc(sizeof(char) * size); //分配内存空间
if(!mp){
printf("分配空间失败");
fclose(fp);
exit(0);
}
int isSucceed = fread(mp,size,1,fp);
if(!isSucceed){
printf("读取数据失败");
free(mp);
fclose(fp);
exit(0);
}
fclose(fp);
return mp;
}
/*虚拟内存偏移地址->文件偏移地址函数
参数:FileBuffer起始地址,RVA地址值
返回值:RVA对应的FOA,返回0则表示非法RVA
*/
DWORD RVA_to_FOA(char* fileBufferp,DWORD RVA){
_IMAGE_DOS_HEADER* _image_dos_header = NULL;
_IMAGE_FILE_HEADER* _image_file_header = NULL;
_IMAGE_OPTIONAL_HEADER* _image_optional_header = NULL;
_IMAGE_SECTION_HEADER* _image_section_header = NULL;
_image_dos_header = (_IMAGE_DOS_HEADER*)fileBufferp;
_image_file_header = (_IMAGE_FILE_HEADER*)(fileBufferp + _image_dos_header->e_lfanew + 4);
_image_optional_header = (_IMAGE_OPTIONAL_HEADER*)((char*)_image_file_header + 20);
_image_section_header = (_IMAGE_SECTION_HEADER*)((char*)_image_optional_header +
_image_file_header->SizeOfOptionalHeader);
bool flag = 0; //用来判断最终RVA值是否在节中的非空白区
if(RVA < _image_section_header->VirtualAddress)
return RVA;
for(int i = 0;i < _image_file_header->NumberOfSections;i++){
if(RVA >= _image_section_header->VirtualAddress
&& RVA < _image_section_header->VirtualAddress + _image_section_header->Misc.VirtualSize){
flag = 1;
break;
}else{
_image_section_header++;
}
}
if(!flag)
return 0;
DWORD mem_offset_from_section = RVA - _image_section_header->VirtualAddress;
return _image_section_header->PointerToRawData + mem_offset_from_section;
}
/*打印导出表函数
参数:指定可执行文件绝对路径
返回值:无
*/
void exportTable_print(char* filePath){
char* fileBufferp = to_FileBuffer(filePath);
_IMAGE_DOS_HEADER* _image_dos_header = NULL;
_IMAGE_FILE_HEADER* _image_file_header = NULL;
_IMAGE_OPTIONAL_HEADER* _image_optional_header = NULL;
_IMAGE_DATA_DIRECTORY* _image_data_directory = NULL;
_IMAGE_EXPORT_DIRECTORY* _image_export_directory = NULL;
_image_dos_header = (_IMAGE_DOS_HEADER*)fileBufferp;
_image_file_header = (_IMAGE_FILE_HEADER*)(fileBufferp + _image_dos_header->e_lfanew + 4);
_image_optional_header = (_IMAGE_OPTIONAL_HEADER*)((char*)_image_file_header + 20);
_image_data_directory = (_IMAGE_DATA_DIRECTORY*)_image_optional_header->DataDirectory;
if(_image_data_directory->VirtualAddress == 0){
printf("此PE文件没有导出表");
getchar();
exit(0);
}
//打印导出表
_image_export_directory = (_IMAGE_EXPORT_DIRECTORY*)(RVA_to_FOA(fileBufferp,_image_data_directory->VirtualAddress) + (DWORD)fileBufferp);
char* name = (char*)(RVA_to_FOA(fileBufferp,_image_export_directory->Name) + (DWORD)fileBufferp);
printf("***************ExportTable****************\n");
printf("Name:%08X --> %s\n",_image_export_directory->Name,name);
printf("Base:%08X\n",_image_export_directory->Base);
printf("NumberOfFunctions:%08X\n",_image_export_directory->NumberOfFunctions);
printf("NumberOfNames:%08X\n",_image_export_directory->NumberOfNames);
printf("AddressOfFunctions:%08X\n",_image_export_directory->AddressOfFunctions);
printf("AddressOfNames:%08X\n",_image_export_directory->AddressOfNames);
printf("AddressOfNameOrdinals:%08X\n\n",_image_export_directory->AddressOfNameOrdinals);
//打印函数地址表
DWORD* functionAddressTable = (DWORD*)(RVA_to_FOA(fileBufferp,_image_export_directory->AddressOfFunctions) + (DWORD)fileBufferp);
printf("***************FunctionAddressTable****************\n");
for(DWORD i = 0;i < _image_export_directory->NumberOfFunctions;i++){
printf("%08X\n",*(functionAddressTable + i));
}
//打印函数序号表(别忘了加Base)
WORD* functionOrdinalsTable = (WORD*)(RVA_to_FOA(fileBufferp,_image_export_directory->AddressOfNameOrdinals) + (DWORD)fileBufferp);
printf("\n***************FunctionOrdinalsTable****************\n");
for(i = 0;i < _image_export_directory->NumberOfNames;i++){
printf("%04X\n",*(functionOrdinalsTable + i) + _image_export_directory->Base);
}
//打印函数名称表(别忘了转FOA)
DWORD* functionNameTable = (DWORD*)(RVA_to_FOA(fileBufferp,_image_export_directory->AddressOfNames) + (DWORD)fileBufferp);
printf("\n***************FunctionNameTable****************\n");
for(i = 0;i < _image_export_directory->NumberOfNames;i++){
printf("%08X --> %s\n",*(functionNameTable + i),(char*)(RVA_to_FOA(fileBufferp,*(functionNameTable + i)) + (DWORD)fileBufferp));
}
}
int main(int argc, char* argv[])
{
char* filePath = "D:/LordPE/16Edit.DLL"; //选一个有导出表的PE文件
exportTable_print(filePath);
return 0;
}
验证一下:
使用LordPE打开
16Edit.DLL
(因为这个PE文件有导出表)和VC6打印数据对比
2.编写按名字、序号找导出函数地址信息
-
说明:昨天用到的
GetProcAddress()
系统函数,也可以按照这两种方式获取DLL中导出函数的地址,今天要编写的两个函数和GetProcAddress()
有相同的功能,只是没有系统函数写的那么好那么复杂。区别是GetProcAddress()
第一个参数HMODULE是DLL的ImageBase,即PE文件拉伸后的起始地址;自定义函数的第一个参数FileBuffer指针是PE文件未拉伸时的起始地址,即FileBuffer首地址 -
按名字查找函数:
GetFunctionAddrByName(FileBuffer指针,函数名指针)
#include "stdafx.h" #include <stdlib.h> #include <string.h> typedef unsigned short WORD; typedef unsigned int DWORD; typedef unsigned char BYTE; #define MZ 0x5A4D #define PE 0x4550 #define IMAGE_SIZEOF_SHORT_NAME 8 //DOS头 struct _IMAGE_DOS_HEADER { WORD e_magic; //MZ标记 WORD e_cblp; WORD e_cp; WORD e_crlc; WORD e_cparhdr; WORD e_minalloc; WORD e_maxalloc; WORD e_ss; WORD e_sp; WORD e_csum; WORD e_ip; WORD e_cs; WORD e_lfarlc; WORD e_ovno; WORD e_res[4]; WORD e_oemid; WORD e_oeminfo; WORD e_res2[10]; DWORD e_lfanew; //PE文件真正开始的偏移地址 }; //标准PE头 struct _IMAGE_FILE_HEADER { WORD Machine; //文件运行平台 WORD NumberOfSections; //节数量 DWORD TimeDateStamp; //时间戳 DWORD PointerToSymbolTable; DWORD NumberOfSymbols; WORD SizeOfOptionalHeader; //可选PE头大小 WORD Characteristics; //特征值 }; //数据目录 struct _IMAGE_DATA_DIRECTORY{ DWORD VirtualAddress; DWORD Size; }; //可选PE头 struct _IMAGE_OPTIONAL_HEADER { WORD Magic; //文件类型 BYTE MajorLinkerVersion; BYTE MinorLinkerVersion; DWORD SizeOfCode; //代码节文件对齐后的大小 DWORD SizeOfInitializedData; //初始化数据文件对齐后的大小 DWORD SizeOfUninitializedData; //未初始化数据文件对齐后大小 DWORD AddressOfEntryPoint; //程序入口点(偏移量) DWORD BaseOfCode; //代码基址 DWORD BaseOfData; //数据基址 DWORD ImageBase; //内存镜像基址 DWORD SectionAlignment; //内存对齐粒度 DWORD FileAlignment; //文件对齐粒度 WORD MajorOperatingSystemVersion; WORD MinorOperatingSystemVersion; WORD MajorImageVersion; WORD MinorImageVersion; WORD MajorSubsystemVersion; WORD MinorSubsystemVersion; DWORD Win32VersionValue; DWORD SizeOfImage; //文件装入虚拟内存后大小 DWORD SizeOfHeaders; //DOS、NT头和节表大小 DWORD CheckSum; //校验和 WORD Subsystem; WORD DllCharacteristics; DWORD SizeOfStackReserve; //预留堆栈大小 DWORD SizeOfStackCommit; //实际分配堆栈大小 DWORD SizeOfHeapReserve; //预留堆大小 DWORD SizeOfHeapCommit; //实际分配堆大小 DWORD LoaderFlags; DWORD NumberOfRvaAndSizes; //目录项数目 _IMAGE_DATA_DIRECTORY DataDirectory[16]; //数据目录 }; //NT头 struct _IMAGE_NT_HEADERS { DWORD Signature; //PE签名 _IMAGE_FILE_HEADER FileHeader; _IMAGE_OPTIONAL_HEADER OptionalHeader; }; //节表 struct _IMAGE_SECTION_HEADER{ BYTE Name[IMAGE_SIZEOF_SHORT_NAME]; //节表名 union{ DWORD PhysicalAddress; DWORD VirtualSize; //内存中未对齐大小 }Misc; DWORD VirtualAddress; //该节在内存中偏移地址 DWORD SizeOfRawData; //该节在硬盘上文件对齐后大小 DWORD PointerToRawData; //该节在硬盘上文件对齐后偏移地址 DWORD PointerToRelocations; DWORD PointerToLinenumbers; WORD NumberOfRelocations; WORD NumberOfLinenumbers; DWORD Characteristics; //该节特征属性 }; //导出表 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 * }; /*计算文件大小函数 参数:文件绝对路径 返回值:返回文件大小(单位字节) */ int compute_file_size(char* filePath){ FILE* fp = fopen(filePath,"rb"); if(!fp){ printf("打开文件失败"); exit(0); } fseek(fp,0,2); int size = ftell(fp); //fseek(fp,0,0); 单纯计算文件大小,就不需要还原指针了 fclose(fp); return size; } /*将文件读入FileBuffer函数 参数:文件绝对路径 返回值:FileBuffer起始地址 */ char* to_FileBuffer(char* filePath){ FILE* fp = fopen(filePath,"rb"); if(!fp){ printf("打开文件失败"); exit(0); } int size = compute_file_size(filePath); char* mp = (char*)malloc(sizeof(char) * size); //分配内存空间 if(!mp){ printf("分配空间失败"); fclose(fp); exit(0); } int isSucceed = fread(mp,size,1,fp); if(!isSucceed){ printf("读取数据失败"); free(mp); fclose(fp); exit(0); } fclose(fp); return mp; } /*虚拟内存偏移地址->文件偏移地址函数 参数:FileBuffer起始地址,RVA地址值 返回值:RVA对应的FOA,返回0则表示非法RVA */ DWORD RVA_to_FOA(char* fileBufferp,DWORD RVA){ _IMAGE_DOS_HEADER* _image_dos_header = NULL; _IMAGE_FILE_HEADER* _image_file_header = NULL; _IMAGE_OPTIONAL_HEADER* _image_optional_header = NULL; _IMAGE_SECTION_HEADER* _image_section_header = NULL; _image_dos_header = (_IMAGE_DOS_HEADER*)fileBufferp; _image_file_header = (_IMAGE_FILE_HEADER*)(fileBufferp + _image_dos_header->e_lfanew + 4); _image_optional_header = (_IMAGE_OPTIONAL_HEADER*)((char*)_image_file_header + 20); _image_section_header = (_IMAGE_SECTION_HEADER*)((char*)_image_optional_header + _image_file_header->SizeOfOptionalHeader); bool flag = 0; //用来判断最终RVA值是否在节中的非空白区 if(RVA < _image_section_header->VirtualAddress) return RVA; for(int i = 0;i < _image_file_header->NumberOfSections;i++){ if(RVA >= _image_section_header->VirtualAddress && RVA < _image_section_header->VirtualAddress + _image_section_header->Misc.VirtualSize){ flag = 1; break; }else{ _image_section_header++; } } if(!flag) return 0; DWORD mem_offset_from_section = RVA - _image_section_header->VirtualAddress; return _image_section_header->PointerToRawData + mem_offset_from_section; } /*按名字查找导出函数地址 参数:PE文件的FileBuffer起始地址,函数名字符串指针 返回值:该导出函数的RVA(如果想返回FOA也可以,代码都在下面) */ DWORD GetFunctionAddrByName(char* fileBufferp,char* funcName){ _IMAGE_DOS_HEADER* _image_dos_header = NULL; _IMAGE_FILE_HEADER* _image_file_header = NULL; _IMAGE_OPTIONAL_HEADER* _image_optional_header = NULL; _IMAGE_DATA_DIRECTORY* _image_data_directory = NULL; _IMAGE_EXPORT_DIRECTORY* _image_export_directory = NULL; _image_dos_header = (_IMAGE_DOS_HEADER*)fileBufferp; _image_file_header = (_IMAGE_FILE_HEADER*)(fileBufferp + _image_dos_header->e_lfanew + 4); _image_optional_header = (_IMAGE_OPTIONAL_HEADER*)((char*)_image_file_header + 20); _image_data_directory = (_IMAGE_DATA_DIRECTORY*)_image_optional_header->DataDirectory; if(_image_data_directory->VirtualAddress == 0){ printf("此PE文件没有导出表,没有函数导出"); getchar(); exit(0); } _image_export_directory = (_IMAGE_EXPORT_DIRECTORY*)(RVA_to_FOA(fileBufferp,_image_data_directory->VirtualAddress) + (DWORD)fileBufferp); //先遍历函数名称表 DWORD* functionNameTable = (DWORD*)(RVA_to_FOA(fileBufferp,_image_export_directory->AddressOfNames) + (DWORD)fileBufferp); bool flag = 0; for(DWORD index = 0;index < _image_export_directory->NumberOfNames;index++){ char* nameTemp = (char*)(RVA_to_FOA(fileBufferp,*functionNameTable) + (DWORD)fileBufferp); if(strcmp(nameTemp,funcName) == 0){ //小贴士:strcmp执行完,参数值并不改变 flag = 1; break; } functionNameTable++; } if(!flag){ printf("没有此导出函数"); getchar(); exit(0); } //再找函数序号表对应下标index中的值funcIndex WORD* functionOrdinalsTable = (WORD*)(RVA_to_FOA(fileBufferp,_image_export_directory->AddressOfNameOrdinals) + (DWORD)fileBufferp); WORD funcIndex = *(functionOrdinalsTable + index); //最后找函数地址表下标为funcIndex中的值 DWORD* functionAddressTable = (DWORD*)(RVA_to_FOA(fileBufferp,_image_export_directory->AddressOfFunctions) + (DWORD)fileBufferp); //返回该导出函数RVA return *(functionAddressTable + funcIndex); //return RVA_to_FOA(fileBufferp,*(functionAddressTable + funcIndex)); } int main(int argc, char* argv[]) { char* filePath = "D:/LordPE/16Edit.DLL"; //选一个有导出表的PE文件 char* funcName = "HEEnterWindowLoopInNewThread"; //使用LordPE看有哪些导出函数 char* fileBufferp = to_FileBuffer(filePath); printf("%08X",GetFunctionAddrByName(fileBufferp,funcName)); return 0; }
验证一下:
-
先用LordPE打开LordPE文件夹下的一个dll:
16Edit.DLL
(因为这个文件有导出表),接着查看此dll文件中的导出函数有哪些,我们选择其中之一HEEnterWindowLoopInNewThread
进行验证 -
运行C程序,发现得到的RVA = 0x000019A0,与答案一致
-
-
按序号查找函数:
GetFunctionAddrByOrdinals(FileBuffer指针,函数名导出序号)
#include "stdafx.h" #include <stdlib.h> #include <string.h> typedef unsigned short WORD; typedef unsigned int DWORD; typedef unsigned char BYTE; #define MZ 0x5A4D #define PE 0x4550 #define IMAGE_SIZEOF_SHORT_NAME 8 //DOS头 struct _IMAGE_DOS_HEADER { WORD e_magic; //MZ标记 WORD e_cblp; WORD e_cp; WORD e_crlc; WORD e_cparhdr; WORD e_minalloc; WORD e_maxalloc; WORD e_ss; WORD e_sp; WORD e_csum; WORD e_ip; WORD e_cs; WORD e_lfarlc; WORD e_ovno; WORD e_res[4]; WORD e_oemid; WORD e_oeminfo; WORD e_res2[10]; DWORD e_lfanew; //PE文件真正开始的偏移地址 }; //标准PE头 struct _IMAGE_FILE_HEADER { WORD Machine; //文件运行平台 WORD NumberOfSections; //节数量 DWORD TimeDateStamp; //时间戳 DWORD PointerToSymbolTable; DWORD NumberOfSymbols; WORD SizeOfOptionalHeader; //可选PE头大小 WORD Characteristics; //特征值 }; //数据目录 struct _IMAGE_DATA_DIRECTORY{ DWORD VirtualAddress; DWORD Size; }; //可选PE头 struct _IMAGE_OPTIONAL_HEADER { WORD Magic; //文件类型 BYTE MajorLinkerVersion; BYTE MinorLinkerVersion; DWORD SizeOfCode; //代码节文件对齐后的大小 DWORD SizeOfInitializedData; //初始化数据文件对齐后的大小 DWORD SizeOfUninitializedData; //未初始化数据文件对齐后大小 DWORD AddressOfEntryPoint; //程序入口点(偏移量) DWORD BaseOfCode; //代码基址 DWORD BaseOfData; //数据基址 DWORD ImageBase; //内存镜像基址 DWORD SectionAlignment; //内存对齐粒度 DWORD FileAlignment; //文件对齐粒度 WORD MajorOperatingSystemVersion; WORD MinorOperatingSystemVersion; WORD MajorImageVersion; WORD MinorImageVersion; WORD MajorSubsystemVersion; WORD MinorSubsystemVersion; DWORD Win32VersionValue; DWORD SizeOfImage; //文件装入虚拟内存后大小 DWORD SizeOfHeaders; //DOS、NT头和节表大小 DWORD CheckSum; //校验和 WORD Subsystem; WORD DllCharacteristics; DWORD SizeOfStackReserve; //预留堆栈大小 DWORD SizeOfStackCommit; //实际分配堆栈大小 DWORD SizeOfHeapReserve; //预留堆大小 DWORD SizeOfHeapCommit; //实际分配堆大小 DWORD LoaderFlags; DWORD NumberOfRvaAndSizes; //目录项数目 _IMAGE_DATA_DIRECTORY DataDirectory[16]; //数据目录 }; //NT头 struct _IMAGE_NT_HEADERS { DWORD Signature; //PE签名 _IMAGE_FILE_HEADER FileHeader; _IMAGE_OPTIONAL_HEADER OptionalHeader; }; //节表 struct _IMAGE_SECTION_HEADER{ BYTE Name[IMAGE_SIZEOF_SHORT_NAME]; //节表名 union{ DWORD PhysicalAddress; DWORD VirtualSize; //内存中未对齐大小 }Misc; DWORD VirtualAddress; //该节在内存中偏移地址 DWORD SizeOfRawData; //该节在硬盘上文件对齐后大小 DWORD PointerToRawData; //该节在硬盘上文件对齐后偏移地址 DWORD PointerToRelocations; DWORD PointerToLinenumbers; WORD NumberOfRelocations; WORD NumberOfLinenumbers; DWORD Characteristics; //该节特征属性 }; //导出表 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 * }; /*计算文件大小函数 参数:文件绝对路径 返回值:返回文件大小(单位字节) */ int compute_file_size(char* filePath){ FILE* fp = fopen(filePath,"rb"); if(!fp){ printf("打开文件失败"); exit(0); } fseek(fp,0,2); int size = ftell(fp); //fseek(fp,0,0); 单纯计算文件大小,就不需要还原指针了 fclose(fp); return size; } /*将文件读入FileBuffer函数 参数:文件绝对路径 返回值:FileBuffer起始地址 */ char* to_FileBuffer(char* filePath){ FILE* fp = fopen(filePath,"rb"); if(!fp){ printf("打开文件失败"); exit(0); } int size = compute_file_size(filePath); char* mp = (char*)malloc(sizeof(char) * size); //分配内存空间 if(!mp){ printf("分配空间失败"); fclose(fp); exit(0); } int isSucceed = fread(mp,size,1,fp); if(!isSucceed){ printf("读取数据失败"); free(mp); fclose(fp); exit(0); } fclose(fp); return mp; } /*虚拟内存偏移地址->文件偏移地址函数 参数:FileBuffer起始地址,RVA地址值 返回值:RVA对应的FOA,返回0则表示非法RVA */ DWORD RVA_to_FOA(char* fileBufferp,DWORD RVA){ _IMAGE_DOS_HEADER* _image_dos_header = NULL; _IMAGE_FILE_HEADER* _image_file_header = NULL; _IMAGE_OPTIONAL_HEADER* _image_optional_header = NULL; _IMAGE_SECTION_HEADER* _image_section_header = NULL; _image_dos_header = (_IMAGE_DOS_HEADER*)fileBufferp; _image_file_header = (_IMAGE_FILE_HEADER*)(fileBufferp + _image_dos_header->e_lfanew + 4); _image_optional_header = (_IMAGE_OPTIONAL_HEADER*)((char*)_image_file_header + 20); _image_section_header = (_IMAGE_SECTION_HEADER*)((char*)_image_optional_header + _image_file_header->SizeOfOptionalHeader); bool flag = 0; //用来判断最终RVA值是否在节中的非空白区 if(RVA < _image_section_header->VirtualAddress) return 0; for(int i = 0;i < _image_file_header->NumberOfSections;i++){ if(RVA >= _image_section_header->VirtualAddress && RVA < _image_section_header->VirtualAddress + _image_section_header->Misc.VirtualSize){ flag = 1; break; }else{ _image_section_header++; } } if(!flag) return 0; DWORD mem_offset_from_section = RVA - _image_section_header->VirtualAddress; return _image_section_header->PointerToRawData + mem_offset_from_section; } /*按名字查找导出函数地址信息函数 参数:PE文件的FileBuffer起始地址,函数名字符串指针 返回值:该导出函数的RVA(如果想返回FOA也可以,代码都在下面) */ /*DWORD GetFunctionAddrByName(char* fileBufferp,char* funcName){ _IMAGE_DOS_HEADER* _image_dos_header = NULL; _IMAGE_FILE_HEADER* _image_file_header = NULL; _IMAGE_OPTIONAL_HEADER* _image_optional_header = NULL; _IMAGE_DATA_DIRECTORY* _image_data_directory = NULL; _IMAGE_EXPORT_DIRECTORY* _image_export_directory = NULL; _image_dos_header = (_IMAGE_DOS_HEADER*)fileBufferp; _image_file_header = (_IMAGE_FILE_HEADER*)(fileBufferp + _image_dos_header->e_lfanew + 4); _image_optional_header = (_IMAGE_OPTIONAL_HEADER*)((char*)_image_file_header + 20); _image_data_directory = (_IMAGE_DATA_DIRECTORY*)_image_optional_header->DataDirectory; if(_image_data_directory->VirtualAddress == 0){ printf("此PE文件没有导出表,没有函数导出"); getchar(); exit(0); } _image_export_directory = (_IMAGE_EXPORT_DIRECTORY*)(RVA_to_FOA(fileBufferp,_image_data_directory->VirtualAddress) + (DWORD)fileBufferp); //先遍历函数名称表 DWORD* functionNameTable = (DWORD*)(RVA_to_FOA(fileBufferp,_image_export_directory->AddressOfNames) + (DWORD)fileBufferp); bool flag = 0; for(DWORD index = 0;index < _image_export_directory->NumberOfNames;index++){ char* nameTemp = (char*)(RVA_to_FOA(fileBufferp,*functionNameTable) + (DWORD)fileBufferp); if(strcmp(nameTemp,funcName) == 0){ //小贴士:strcmp执行完,参数值并不改变 flag = 1; break; } functionNameTable++; } if(!flag){ printf("没有此导出函数"); getchar(); exit(0); } //再找函数序号表对应下标index中的值funcIndex WORD* functionOrdinalsTable = (WORD*)(RVA_to_FOA(fileBufferp,_image_export_directory->AddressOfNameOrdinals) + (DWORD)fileBufferp); WORD funcIndex = *(functionOrdinalsTable + index); //最后找函数地址表下标为funcIndex中的值 DWORD* functionAddressTable = (DWORD*)(RVA_to_FOA(fileBufferp,_image_export_directory->AddressOfFunctions) + (DWORD)fileBufferp); //返回该导出函数RVA return *(functionAddressTable + funcIndex); //return RVA_to_FOA(fileBufferp,*(functionAddressTable + funcIndex)); }*/ /*按序号查找导出函数地址信息函数 参数:PE文件的FileBuffer起始地址,函数名字符串指针 返回值:该导出函数的RVA(如果想返回FOA也可以,代码都在下面) */ DWORD GetFunctionAddrByOrdinals(char* fileBufferp,DWORD ordinal){ _IMAGE_DOS_HEADER* _image_dos_header = NULL; _IMAGE_FILE_HEADER* _image_file_header = NULL; _IMAGE_OPTIONAL_HEADER* _image_optional_header = NULL; _IMAGE_DATA_DIRECTORY* _image_data_directory = NULL; _IMAGE_EXPORT_DIRECTORY* _image_export_directory = NULL; _image_dos_header = (_IMAGE_DOS_HEADER*)fileBufferp; _image_file_header = (_IMAGE_FILE_HEADER*)(fileBufferp + _image_dos_header->e_lfanew + 4); _image_optional_header = (_IMAGE_OPTIONAL_HEADER*)((char*)_image_file_header + 20); _image_data_directory = (_IMAGE_DATA_DIRECTORY*)_image_optional_header->DataDirectory; if(_image_data_directory->VirtualAddress == 0){ printf("此PE文件没有导出表,没有函数导出"); getchar(); exit(0); } _image_export_directory = (_IMAGE_EXPORT_DIRECTORY*)(RVA_to_FOA(fileBufferp,_image_data_directory->VirtualAddress) + (DWORD)fileBufferp); //判断导出序号是否在范围内 if(ordinal - _image_export_directory->Base > _image_export_directory->NumberOfFunctions - 1){ //因为ordinal用的WORD类型,所以<0的情况不用考虑 printf("导出序号不合法"); getchar(); exit(0); } //定位到函数地址表起始地址 DWORD* functionAddressTable = (DWORD*)(RVA_to_FOA(fileBufferp,_image_export_directory->AddressOfFunctions) + (DWORD)fileBufferp); //返回该序号导出函数的RVA return *(functionAddressTable + (ordinal - _image_export_directory->Base)); //return RVA_to_FOA(fileBufferp,*(functionAddressTable + (ordinal - _image_export_directory->Base))); } int main(int argc, char* argv[]) { char* filePath = "D:/OllyICE_1.10/OllyDBG.EXE"; //选一个有导出表的PE文件 char* fileBufferp = to_FileBuffer(filePath); //char* funcName = "_Go"; //使用LordPE看有哪些导出函数 //printf("%08X",GetFunctionAddrByName(fileBufferp,funcName)); printf("%08X",GetFunctionAddrByOrdinals(fileBufferp,0xA5)); //使用LordPE看有哪些导出序号 return 0; }
验证一下:
-
使用LordPE打开
OllyDBG.EXE
(因为这个PE文件有导出表)查看有哪些导出函数以及序号是什么,我选择序号为0xA5的导出函数进行验证 -
运行C程序,RVA = 0x0008DD10,与答案一致
-