1.概述
某个DLL中的函数被EXE或者另外的DLL使用需要借助导出表(有的EXE也有导出表)。数据目录项的第一个结构,就是导出表.。
2.结构
数据目录项的结构
这个结构在OptionalHeader的最后一个结构
typedef struct _IMAGE_DATA_DIRECTORY {
DWORD VirtualAddress;
DWORD Size;
} IMAGE_DATA_DIRECTORY, *PIMAGE_DATA_DIRECTORY;
其中 VirtualAddress 导出表的RVA。Size 导出表的大小
上面这个结构指向导出表的位置和大小。
导出表的结构
typedef struct _IMAGE_EXPORT_DIRECTORY {
DWORD Characteristics; //未使用
DWORD TimeDateStamp; //时间戳
WORD MajorVersion; //未使用
WORD MinorVersion; //未使用
DWORD Name; //指向导出表文件名字符串
DWORD Base; //导出函数起始序号(导出序号是Base+序号)
DWORD NumberOfFunctions; //导出函数的个数,与AddressOfFunctions的个数对应
DWORD NumberOfNames; //以函数名字导出的函数的个数
DWORD AddressOfFunctions; // 导出函数地址表RVA
DWORD AddressOfNames; // 导出函数名称表RVA
DWORD AddressOfNameOrdinals; // 导出函数序号表RVA
} IMAGE_EXPORT_DIRECTORY, *PIMAGE_EXPORT_DIRECTORY;
重点的三个成员构成三个表
三个表的目的
函数导出的个数与函数名的个数未必一样.所以要将函数地址表和函数名称表分开.
一个相同的函数地址,可能有多个不同的名字.
AddressOfFunctions:导出函数地址RVA
该表中元素宽度为4个字节
该表中存储所有导出函数的地址
该表中个数由NumberOfFunctions决定
该表项中的值是RVA, 加上ImageBase才是函数真正的地址
AddressOfNames:导出函数名称表
该表中元素宽度为4个字节
该表中存储所有以名字导出函数的名字的RVA
该表项中的值是RVA, 指向函数真正的名称
该表中个数由NumberOfNames决定
AddressOfNameOrdinals:导出函数序号表
该表中元素宽度为2个字节
该表中存储的内容 + Base = 函数的导出序号
该表元素的个数与AddressOfNames表的个数一一对应。
3.导出过程
根据函数名字获取函数的地址
1.通过名称反找AddressOfNames表,然后遍历函数名称表得到匹配的函数的索引(与函数序号索引一直)。
2.函数序号表找到对应索引的元素取出值为序号数。
3.通过函数序号访问函数地址表AddressOfFunctions访问函数的地址。
代码演示
#include <stdio.h>
#include <stdlib.h>
#include <windows.h>
LPVOID ReadPEFile(LPSTR lpszFile)//读取文件
{
FILE *pFile = NULL;
DWORD fileSize = 0;
LPVOID pFileBuffer = NULL;
//打开文件
pFile = fopen(lpszFile,"rb");
if(!pFile)
{
printf("无法打开EXE文件!");
return NULL;
}
//读取文件大小
fseek(pFile,0,SEEK_END);
//int fseek(FILE *stream, long offset, int fromwhere);
//函数设置文件指针stream的位置。如果执行成功,stream将指向以fromwhere为基准偏移offset(指针偏移量)个字节的位置。
fileSize = ftell(pFile);
fseek(pFile,0,SEEK_SET);
//分配缓冲区
pFileBuffer = malloc(fileSize);
if(!pFileBuffer)
{
printf("分配空间失败!");
fclose(pFile);
return NULL;
}
//将文件读取到缓冲区中
size_t n = fread(pFileBuffer,fileSize,1,pFile);
//pFileBuffer流
//1 对象个数
//pFile 输入流
if(!n)
{
printf("读取数据失败!");
free(pFileBuffer);
fclose(pFile);
return NULL;
}
//关闭文件
fclose(pFile);
return pFileBuffer;
}
DWORD GetFunctionAddrByName(LPSTR FunctionName)
{
LPVOID pFileBuffer = NULL;
PIMAGE_DOS_HEADER pDosHeader = NULL;
PIMAGE_NT_HEADERS pNtHeader = NULL;
PIMAGE_FILE_HEADER pPEHeader = NULL;
PIMAGE_OPTIONAL_HEADER pOptionalHeader = NULL;
PIMAGE_EXPORT_DIRECTORY pExport;
LPSTR FilePath = "D://test//Dym.dll";
//将文件读入内存
pFileBuffer = ReadPEFile(FilePath);
pDosHeader = (PIMAGE_DOS_HEADER)pFileBuffer;
printf("*********************DOS头*********************\n");
printf("MZ标志:%x\n",pDosHeader->e_magic);
printf("PE偏移:%x\n",pDosHeader->e_lfanew);
pNtHeader = (PIMAGE_NT_HEADERS)((DWORD)pDosHeader->e_lfanew + pFileBuffer);
printf("*********************NT头*********************\n");
printf("NT: %x\n",pNtHeader->Signature);
pPEHeader = (PIMAGE_FILE_HEADER)(((DWORD)pNtHeader)+4);
printf("*********************文件头*********************\n");
printf("PE: %x\n",pPEHeader->Machine);
printf("节的数量: %x\n",pPEHeader->NumberOfSections);
printf("SizeOfOptionalHeader: %x\n",pPEHeader->SizeOfOptionalHeader);
pOptionalHeader = (PIMAGE_OPTIONAL_HEADER32)((DWORD)pPEHeader+IMAGE_SIZEOF_FILE_HEADER);
printf("*********************OptionalHeader*********************\n");
printf("OPTIONAL_PE: %x\n",pOptionalHeader->SizeOfImage);
pExport = (PIMAGE_EXPORT_DIRECTORY)(pOptionalHeader->DataDirectory[0].VirtualAddress + pFileBuffer);
//导出表的成员
DWORD ExAddressOfNames = (DWORD)(pExport->AddressOfNames);
printf("ExAddressOfNames: %x\n",ExAddressOfNames);
PDWORD pExAddressOfNames = (PDWORD)(pFileBuffer + ExAddressOfNames);
DWORD ExNumberOfNames = pExport->NumberOfNames;
printf("ExNumberOfNames: %x\n",ExNumberOfNames);
//函数名称表
int k;
char* s_name;
for(k = 0;k<ExNumberOfNames;k++)
{
//遍历查找函数名称
PDWORD NameAddress = (PDWORD)(*pExAddressOfNames);
char* s_name = (char*)((DWORD)pFileBuffer + (DWORD)NameAddress);
printf("Function: %s\n",s_name);
//比较名称相同为0
WORD num = strcmp(FunctionName,s_name);
if(num == 0)
{
break;
}
pExAddressOfNames++;//按照单位进行后移
}
//printf("%d",k);
WORD Location_Fum = k;//存储k作为与名称表相同的序号表的索引值
//函数序号表
DWORD ExAddressOfNameOrdinals = pExport->AddressOfNameOrdinals;
ExNumberOfNames = pExport->NumberOfNames;
PWORD pExAddressOfNameOrdinals = (PWORD)(pFileBuffer + ExAddressOfNameOrdinals);
WORD NUM_FUN = pExAddressOfNameOrdinals[Location_Fum];
printf("NUM_FUN: %d \n",NUM_FUN);
//函数地址表的信息
DWORD ExAddressOfFunctions = pExport->AddressOfFunctions;
DWORD ExNumberOfFunctions = pExport->NumberOfFunctions;
PDWORD pExAddressOfFunctions = NULL;
pExAddressOfFunctions = (PDWORD)(ExAddressOfFunctions + (DWORD)pFileBuffer);
//确定函数的地址
DWORD Fun_Addr = pExAddressOfFunctions[NUM_FUN];
return Fun_Addr;
}
int main()
{
LPSTR FunctionName = "Plus";
DWORD Fun_Addr = GetFunctionAddrByName(FunctionName);
printf("Address: %x\n",Fun_Addr);
return 0;
}
根据函数导出序号获取函数的地址
1.查找导出表的Base值,用导出序号 - Base值作为函数的序号值(函数地址表的索引值)。
2.根据索引找到函数地址。
代码演示
DWORD GetFunctionAddrByOrdinals(WORD FunctionOrdinals)//函数名导出序号
{
LPVOID pFileBuffer = NULL;
PIMAGE_DOS_HEADER pDosHeader = NULL;
PIMAGE_NT_HEADERS pNtHeader = NULL;
PIMAGE_FILE_HEADER pPEHeader = NULL;
PIMAGE_OPTIONAL_HEADER pOptionalHeader = NULL;
PIMAGE_EXPORT_DIRECTORY pExport;
LPSTR FilePath = "D://test//Dym.dll";
//将文件读入内存
pFileBuffer = ReadPEFile(FilePath);
pDosHeader = (PIMAGE_DOS_HEADER)pFileBuffer;
pNtHeader = (PIMAGE_NT_HEADERS)((DWORD)pDosHeader->e_lfanew + pFileBuffer);
pPEHeader = (PIMAGE_FILE_HEADER)(((DWORD)pNtHeader)+4);
pOptionalHeader = (PIMAGE_OPTIONAL_HEADER32)((DWORD)pPEHeader+IMAGE_SIZEOF_FILE_HEADER);
//定位导出表
pExport = (PIMAGE_EXPORT_DIRECTORY)(pOptionalHeader->DataDirectory[0].VirtualAddress + pFileBuffer);
//定位函数的位置
WORD Function_Index = FunctionOrdinals - pExport->Base;
//输出函数地址表信息
DWORD ExAddressOfFunctions = pExport->AddressOfFunctions;
DWORD ExNumberOfFunctions = pExport->NumberOfFunctions;
PDWORD pExAddressOfFunctions = NULL;
pExAddressOfFunctions = (PDWORD)(ExAddressOfFunctions + (DWORD)pFileBuffer);
//确定函数的地址
DWORD Fun_Addr = pExAddressOfFunctions[Function_Index];
return Fun_Addr;
}