PE文件(九)导出表

引入导出表

Win32下的一个PE文件,是由多个PE文件组成。比如通过OD打开一个Ipmsg.exe,查看模块M,会发现模块有一个ipmsg.exe文件和多个动态链接库.dll文件。

当一个exe文件通过使用动态链接库.dll的方式导出某.dll文件某函数进行使用时,就需要通过导入表获取该函数的各种信息。导入表记录了当前PE文件使用的所有其他PE文件的相关信息,比如使用到的.dll中的函数信息,使用到的其他PE文件的名字等等

与导入表相对的导出表,则记录了当前的PE文件的提供了哪些函数给其他PE文件使用

注意:一个安全的.exe文件通常不提供导出表,这就意味着.exe不提供函数给其他PE文件使用,但这并不意味着.exe文件不能提供函数给其他PE文件使用

而.dll文件通常提供导出表和导入表,这就意味着.dll文件既可以提供函数给其他PE文件使用,也可以使用其他PE文件的函数

导出表的位置

先定位到可选PE头,再找到可选PE头的最后一个成员:即一个结构体数组,结构体中每个成员大小都是四字节。第一个结构体就是导出表数据目录。结构体的第一个成员是导出表的内存偏移地址RVA,第二个成员是导出表的大小。

 

通过导出表数据目录的RVA转FOA就可以找到导出表

导出表其实就在这个PE文件的某个节中(后续的各种表也是在某个节中)

导出表的结构

struct _IMAGE_EXPORT_DIRECTORY{   //40字节      

    DWORD Characteristics;  //未使用     

    DWORD TimeDateStamp;  //时间戳,用于获取该PE文件编译时的时间

    WORD MajorVersion;  //未使用      

    WORD MinorVersion;   //未使用     

    DWORD Name;  //指向该导出表文件名字符串  *

    DWORD Base;  //导出函数起始序号  *

    DWORD NumberOfFunctions;  //所有导出函数的个数  *      

    DWORD NumberOfNames;  //以函数名字导出的函数个数  *

    DWORD AddressOfFunctions;  //导出函数地址表RVA  *             DWORD AddressOfNames;  //导出函数名称表RVA  *              

    DWORD AddressOfNameOrdinals;  //导出函数序号表RVA  *          

};

注意:我们在可选PE头中观察到的导出表的大小可能并不是40字节,这是因为在导出表中有几个指向其他表的指针,而这其他表便是导出表的子表。子表的大小加上导出表的大小才是我们在可选PE头中实际观察到的大小

1.Name

指向该导出表文件名字符串的RVA,比如一个DBGHELP.dll的PE文件具有导出表,其表中Name指向的字符串为为dbghelp.dll,注意字符串在内存中以0结尾

2.Base

导出函数起始序号(最小的序号) 

比如有序号为14、6、10、8的导出函数,那么Base的值为6

3.NumberOfFunctions

所有导出函数的个数

注意:这个值是通过导出函数的最大序号 - 最小序号 + 1算出来的。正常来说这个值是多少,那么此PE文件中导出函数的个数就是多少。但是如果序号定义是不是连续的,中间有空缺的序号,那么此时NumberOfFunctions的值会比实际的定义的导出函数个数多

比如说一个PE文件在.def中定义的函数为Plus @12 、Sub @15 NONAME、 Mul @13 、Div @16

那么NumberOfFunctions值 = 16 - 12 + 1 = 5,而不是4

4.NumberOfNames

以函数名字导出的函数个数:比如以动态链接库的方式导出,导出时函数加了NONAME关键字,那么该函数就不计数(注意和只以序号导出函数区分)

5.AddressOfFunctions

导出函数地址表RVA,即该地址指向一个表。这个表中记录了此PE文件的所有导出函数的地址。我们举一个例子画出下表进行形象的表示

该表中每个元素宽度为4个字节

元素个数:由NumberOfFunctions决定

注意:导出函数是有序号的,上图中的下标0 1 2 3都是与序号最小的函数相比的相对序号,

解析此图:该PE文件以.def的方式自定义序号导出函数,定义了序号13、14、16、17、19的导出函数,那么Base的值应为13,那么序号为13的函数相对相对下标就是0,序号14的导出函数相对下标就是1,序号为15的导出函数虽然没有,但是会把位置空出来,定义地址值为NULL,即0x00000000,序号16的导出函数相对下标就是3…以此类推

当我们获取了函数地址以后,找到这个函数,复制该函数二进制数据在OD等软件打开即可获取该函数的反汇编,我们也就可以进一步推理该函数的作用

6.AddressOfNames

导出函数名称表RVA我们在硬盘数据找该表地址时要先转成FOA,这个地址指向的表记录了导出函数的名称字符串RVA首地址,而不是导出函数名称。从首地址开始到00结束,便是整个函数的名称

该表中元素宽度:4个字节

函数名称表是按名字ASCII码排序排序的

表中元素的数量:由NumberOfNames决定

如果函数导出时添加了NONAME,即函数没有名称,那么这个表中就不会出现这个函数名地址

注意:一般来说AddressOfNames表中元素个数比AddressOfFunctions表中元素个数少。这是因为AddressOfFunctions表中不管导出函数有没有名字,都会有地址。但是特殊情况下AddressOfNames表中元素个数比AddressOfFunctions表中元素个数多,这是因为导出函数时可以让多个不同名字的函数指向同一个函数地址

NONAME导出函数注意事项:

如果导出时,定义的无名字函数,即Div @13 NONAME,那么函数名称表中就不会有指向Div函数名的元素,同样函数序号表中也不会有Div的相对序号,但是在函数地址表中会留出来一个元素位置存储Div函数地址

7.addressOfNameOrdinals

导出函数序号表RVA,该地址指向一个存储导出函数的相对序号的表

该表中元素宽度:2个字节

表中存储内容与AddressOfName表一一对应的,先确定AddressOfName表的地址对应的函数,之后按照函数对应AddressOfNameOrdinals表中的相对序号

该表中存储的内容 + Base = 函数的导出序号,base指的是序号最小的函数的序号

表中元素个数:由NumberOfNames决定

如下图,很形象的表现了两表之间的关系:

比如:现在要找名字叫Sub的导出函数,经查找在AddressOfNames指向的表的下标为1的位置,那么这个函数所对应的相对序号则在AddressOfNameOrdinals指向的表中下标为1的位置,之后我们取相对序号就可以在AddressOfFunctions指向的表找到我们想要查找的表了

导出表获取函数地址

Windows中有一个API:

FARPROC GetProcAddress

(

HMODULE hModule, // DLL模块句柄

LPCSTR lpProcName // 函数名

);

通过使用这个函数,我们可以获取导出表中的函数的地址。

该函数使用有以下两种方法:1.按名字找函数地址2. 按序号找函数地址

按名字找函数地址

找到导出表后,先根据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即可

按序号找函数地址

找到导出表后,根据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<stdio.h>
#include<Windows.h>
#include<string.h>
//本程序均建立在SecHeader->SizeOfRawData >= SecHeader->Misc.VirtualSize的前提下
DWORD ReadPEFileSize(const char* lpszFile) //将一个文件的硬盘内存数据读取到自定义的文件内存缓冲区
{
	FILE* pFile = NULL;
	pFile = fopen(lpszFile, "rb");
	DWORD FileSize = 0;
	if (!pFile)
	{
		printf("无法打开EXE文件");
		return 0;
	}
	fseek(pFile, 0, SEEK_END);
	FileSize = ftell(pFile);
	return FileSize;
}
char* ReadPEFile(const char* lpszFile)
{
	FILE* pFile = NULL;
	pFile = fopen(lpszFile, "rb");
	DWORD FileSize = ReadPEFileSize(lpszFile);
	fseek(pFile, 0, SEEK_SET);
	char* pFileBuffer = NULL;
	pFileBuffer = (char*)malloc(sizeof(char) * FileSize);
	if (!pFileBuffer)
	{
		printf("分配空间失败");
		fclose(pFile);
		return 0;
	}
	size_t i = fread(pFileBuffer, FileSize, 1, pFile);
	if (!i)
	{
		printf("读取数据失败!");
		free(pFileBuffer);
		fclose(pFile);
		return 0;
	}
	IMAGE_DOS_HEADER* pDosHeader = (IMAGE_DOS_HEADER*)pFileBuffer;
	IMAGE_NT_HEADERS* pNTHeader = (IMAGE_NT_HEADERS*)((char*)pFileBuffer + pDosHeader->e_lfanew); //(char*)pFileBuffer只有此处转换为了char*类型,之后加减是为char的大小为一个单位进行加减
	if (pDosHeader->e_magic != IMAGE_DOS_SIGNATURE)
	{
		printf("不是有效MZ标志,结束\n");
		free(pFileBuffer);
		fclose(pFile);
		return 0;
	}
	if (pNTHeader->Signature != IMAGE_NT_SIGNATURE)
	{
		printf("不是有效的PE标志,打印结束\n");
		free(pFileBuffer);
		pFileBuffer = NULL;
		return 0;
	}
	fclose(pFile);
	return pFileBuffer;
}
int Alige(int mes, int aligement)
{
	return mes <= aligement ? aligement : mes + aligement - mes % aligement;
}
char* FileBufferToImageBuffer(const char* lpszFile) //文件转内存并返回内存地址
{
	char* pFileBuffer = ReadPEFile(lpszFile); //获取自定义文件内存缓冲区指针
	if (pFileBuffer == NULL)
	{
		printf("缓冲区指针无效\n");
		return FALSE;
	}
	PIMAGE_DOS_HEADER pDosHeader = (PIMAGE_DOS_HEADER)pFileBuffer; //获取自定义文件内存缓冲区DOS头指针
	PIMAGE_FILE_HEADER pFileHeader = (PIMAGE_FILE_HEADER)((char*)pDosHeader + pDosHeader->e_lfanew + 4);//获取标准PE头指针
	PIMAGE_OPTIONAL_HEADER pOptionHeader = (PIMAGE_OPTIONAL_HEADER)((char*)pFileHeader + 20);//获取可选PE头指针
	PIMAGE_SECTION_HEADER pSecHeader = (PIMAGE_SECTION_HEADER)((char*)pOptionHeader + pFileHeader->SizeOfOptionalHeader); // 获取文件节表指针
	char* pSecBuffer = (char*)pDosHeader + pSecHeader->PointerToRawData; //获取第一个节的指针
	char* ImageBuffer = (char*)malloc(pOptionHeader->SizeOfImage); //开辟自定义的文件的虚拟内存大小
	if (ImageBuffer == NULL)
	{
		printf("申请虚拟内存失败\n");
		return FALSE;
	}
	memset(ImageBuffer, 0, pOptionHeader->SizeOfImage); //初始化虚拟内存
	memcpy(ImageBuffer, pDosHeader, pOptionHeader->SizeOfHeaders); //将文件内存头字段数据复制到虚拟内存
	PIMAGE_DOS_HEADER iDosHeader = (PIMAGE_DOS_HEADER)ImageBuffer; //获取自定义虚拟内存内存缓冲区DOS头指针
	PIMAGE_FILE_HEADER iFileHeader = (PIMAGE_FILE_HEADER)((char*)iDosHeader + iDosHeader->e_lfanew + 4);//获取标准PE头指针
	PIMAGE_OPTIONAL_HEADER iOptionHeader = (PIMAGE_OPTIONAL_HEADER)((char*)iFileHeader + 20);//获取可选PE头指针
	PIMAGE_SECTION_HEADER iSecHeader = (PIMAGE_SECTION_HEADER)((char*)iOptionHeader + iFileHeader->SizeOfOptionalHeader); // 获取虚拟内存节表指针
	char* iSecBuffer = (char*)iDosHeader + iSecHeader->VirtualAddress; //保存第一个节的指针
	for (int i = 0; i < pFileHeader->NumberOfSections; i++)
	{
		memcpy(iSecBuffer, pSecBuffer, pSecHeader->SizeOfRawData);
		iSecHeader++;
		iSecBuffer = (char*)iDosHeader + iSecHeader->VirtualAddress;
		pSecHeader++;
		pSecBuffer = (char*)pDosHeader + pSecHeader->PointerToRawData;
	}
	char* ExportTable = ImageBuffer + 0x00013B70;
	return ImageBuffer;
}
DWORD RVAtoFOA(char* FileBuffer, char* ImageBuffer, DWORD RVA) //该函数用于转换为相对于dos头的地址
{
	PIMAGE_DOS_HEADER pDosHeader = (PIMAGE_DOS_HEADER)FileBuffer; //获取自定义文件内存缓冲区DOS头指针
	PIMAGE_FILE_HEADER pFileHeader = (PIMAGE_FILE_HEADER)((char*)pDosHeader + pDosHeader->e_lfanew + 4);//获取标准PE头指针
	PIMAGE_OPTIONAL_HEADER pOptionHeader = (PIMAGE_OPTIONAL_HEADER)((char*)pFileHeader + 20);//获取可选PE头指针
	PIMAGE_SECTION_HEADER pSecHeader = (PIMAGE_SECTION_HEADER)((char*)pOptionHeader + pFileHeader->SizeOfOptionalHeader); // 获取文件节表指针
	char* pSecBuffer = (char*)pDosHeader + pSecHeader->PointerToRawData; //获取第一个节的指针
	PIMAGE_DOS_HEADER iDosHeader = (PIMAGE_DOS_HEADER)ImageBuffer; //获取虚拟内存中缓冲区DOS头指针
	PIMAGE_NT_HEADERS iNTHeader = (PIMAGE_NT_HEADERS)((char*)iDosHeader + iDosHeader->e_lfanew); //获取NT头指针
	PIMAGE_FILE_HEADER iFileHeader = (PIMAGE_FILE_HEADER)((char*)iDosHeader + iDosHeader->e_lfanew + 4);//获取标准PE头指针
	PIMAGE_OPTIONAL_HEADER iOptionHeader = (PIMAGE_OPTIONAL_HEADER)((char*)iFileHeader + 20);//获取可选PE头指针
	PIMAGE_SECTION_HEADER iSecHeader1 = (PIMAGE_SECTION_HEADER)((char*)iOptionHeader + pFileHeader->SizeOfOptionalHeader); // 获取虚拟内存节表指针
	PIMAGE_SECTION_HEADER  iSecHeaderEnd = iSecHeader1 + pFileHeader->NumberOfSections - 1;// 使节表指针指向最后一个节表
	char* iSecBuffer1 = (char*)iDosHeader + iSecHeader1->VirtualAddress; //保存第一个节的指针
	//以下开始虚拟内存节地址转换硬盘内存节地址
	DWORD SecEndAlige = Alige(iSecHeaderEnd->SizeOfRawData, iOptionHeader->SectionAlignment);
	DWORD SecMin = (DWORD)iDosHeader + iSecHeader1->VirtualAddress; //节起始位置
	DWORD LastSecPoint = (DWORD)iDosHeader + iSecHeaderEnd->VirtualAddress + iSecHeaderEnd->SizeOfRawData + SecEndAlige;//最后一个节末尾
	RVA = RVA + (DWORD)iDosHeader;
	while (1)
	{
		if (RVA < (DWORD)iDosHeader)
		{
			printf("输入地址过小,请重新输入\n");
			scanf("%d", &RVA);
		}
		else if (RVA > LastSecPoint)
		{
			printf("输入地址过大,请重新输入\n");
			scanf("%d", &RVA);
		}
		else if (RVA <= SecMin && RVA >= (DWORD)iDosHeader)
		{
			printf("RVA to FOA: %d\n",RVA - (DWORD)iDosHeader);
			return RVA;
		}
		else
		{
			printf("输入地址正确,开始转换地址\n");
			DWORD offset1 = RVA - (DWORD)iDosHeader; //获取转换地址在虚拟存中相对于DOS头的偏移量
	        DWORD nNumber = 0; //用于获取转换地址所在第几个节
			for (int i = 0; i < iFileHeader->NumberOfSections - 1; i++)
			{
				if (offset1 >= (iSecHeader1 + i)->VirtualAddress && offset1 <= (iSecHeader1 + i + 1)->VirtualAddress)
				{
					break;
				}		
				else if (offset1 >= iSecHeaderEnd->VirtualAddress && offset1 <= LastSecPoint)
				{
					nNumber = iFileHeader->NumberOfSections - 1;
					break;
				}
		        else
		        {
					nNumber++;
		        }
	        }
	        pSecHeader += nNumber;
	        DWORD offset2 = offset1 - pSecHeader->VirtualAddress; //获取转换地址在内存中相对于目标节的偏移量
	        DWORD pSecBufferPoint = pSecHeader->PointerToRawData + offset2;//获取转换地址在硬盘中相对于dos头的偏移量
	        printf("RVA to FOA: %d\n", pSecBufferPoint);
        	return pSecBufferPoint + (DWORD)pDosHeader;			
		}
	}
}
BOOL PrintExportTable(const char* lpszFile)
{
	char* pFileBuffer = ReadPEFile(lpszFile); //获取自定义文件内存缓冲区指针
	if (pFileBuffer == NULL)
	{
		printf("硬盘缓冲区指针无效\n");
		return FALSE;
	}
	char* ImageBuffer = FileBufferToImageBuffer(lpszFile);
	if (pFileBuffer == NULL)
	{
		printf("内存缓冲区指针无效\n");
		return FALSE;
	}
	PIMAGE_DOS_HEADER pDosHeader = (PIMAGE_DOS_HEADER)pFileBuffer; //获取自定义文件内存缓冲区DOS头指针
	PIMAGE_FILE_HEADER pFileHeader = (PIMAGE_FILE_HEADER)((char*)pDosHeader + pDosHeader->e_lfanew + 4);//获取标准PE头指针
	PIMAGE_OPTIONAL_HEADER pOptionHeader = (PIMAGE_OPTIONAL_HEADER)((char*)pFileHeader + 20);//获取可选PE头指针
	PIMAGE_SECTION_HEADER pSecHeader = (PIMAGE_SECTION_HEADER)((char*)pOptionHeader + pFileHeader->SizeOfOptionalHeader); // 获取文件节表指针
	IMAGE_DATA_DIRECTORY  pExportTable = pOptionHeader->DataDirectory[0];
	if (pExportTable.VirtualAddress == NULL && pExportTable.Size == NULL)
	{
		printf("该PE文件没有导出表");
		return FALSE;
	}
	PIMAGE_EXPORT_DIRECTORY RealExportTable = (PIMAGE_EXPORT_DIRECTORY)(RVAtoFOA(pFileBuffer, ImageBuffer, pExportTable.VirtualAddress));
	printf("导入表打印开始\n");
	printf("Characteristics:%08X\n", RealExportTable->Characteristics);
	printf("TimeDateStamp:%08X\n", RealExportTable->TimeDateStamp);
	printf("MajorVersion:%04X\n", RealExportTable->MajorVersion);
	printf("MinorVersion:%04X\n", RealExportTable->MinorVersion);
	printf("Name:%08X\n", RealExportTable->Name);
	printf("Base:%08X\n", RealExportTable->Base);
	printf("NumberOfFunctions:%08X\n", RealExportTable->NumberOfFunctions);
	printf("NumberOfNames:%08X\n", RealExportTable->NumberOfNames);
	printf("AddressOfFunctions:%08X\n", RealExportTable->AddressOfFunctions);
	printf("AddressOfNames:%08X\n", RealExportTable->AddressOfNames);
	printf("AddressOfNameOrdinals:%08X\n\n", RealExportTable->AddressOfNameOrdinals);
	printf("导出函数地址表打印开始\n");
	char* FunctionsTable = (char*)RVAtoFOA(pFileBuffer, ImageBuffer, RealExportTable->AddressOfFunctions);
	for (int i = 0; i < RealExportTable->NumberOfFunctions; i++)
	{
		printf("导出函数地址表第%d个元素:%08X\n", i, *(FunctionsTable));
		FunctionsTable += 4;
	}
	printf("导出函数名称表打印开始\n");
	char* NamesTable = (char*)RVAtoFOA(pFileBuffer, ImageBuffer, RealExportTable->AddressOfNames);
	for (int i = 0; i < RealExportTable->NumberOfNames; i++)
	{
		printf("导出函数名称表第%d个元素:%08X\n", i, *(NamesTable));
		NamesTable += 4;
	}
	char* NameOrdinalsTable = (char*)RVAtoFOA(pFileBuffer, ImageBuffer, RealExportTable->AddressOfNameOrdinals);
	for (int i = 0; i < RealExportTable->NumberOfNames; i++)
	{
		printf("导出函数序号表序号为%d的元素:%08X\n", (RealExportTable->Base + i), *(NameOrdinalsTable));
		NameOrdinalsTable += 4;
	}
	return TRUE;
}
int main(int argc, char* argv[])
{
	const char* lpszFile = "C:\\appverifUI.dll";
	PrintExportTable(lpszFile);
	return 0;
}

2.编写按名字、序号找导出函数地址信息

按名字找:

#include<stdio.h>
#include<Windows.h>
#include<string.h>
//本程序均建立在SecHeader->SizeOfRawData >= SecHeader->Misc.VirtualSize的前提下
DWORD ReadPEFileSize(const char* lpszFile) //将一个文件的硬盘内存数据读取到自定义的文件内存缓冲区
{
	FILE* pFile = NULL;
	pFile = fopen(lpszFile, "rb");
	DWORD FileSize = 0;
	if (!pFile)
	{
		printf("无法打开EXE文件");
		return 0;
	}
	fseek(pFile, 0, SEEK_END);
	FileSize = ftell(pFile);
	return FileSize;
}
char* ReadPEFile(const char* lpszFile)
{
	FILE* pFile = NULL;
	pFile = fopen(lpszFile, "rb");
	DWORD FileSize = ReadPEFileSize(lpszFile);
	fseek(pFile, 0, SEEK_SET);
	char* pFileBuffer = NULL;
	pFileBuffer = (char*)malloc(sizeof(char) * FileSize);
	if (!pFileBuffer)
	{
		printf("分配空间失败");
		fclose(pFile);
		return 0;
	}
	size_t i = fread(pFileBuffer, FileSize, 1, pFile);
	if (!i)
	{
		printf("读取数据失败!");
		free(pFileBuffer);
		fclose(pFile);
		return 0;
	}
	IMAGE_DOS_HEADER* pDosHeader = (IMAGE_DOS_HEADER*)pFileBuffer;
	IMAGE_NT_HEADERS* pNTHeader = (IMAGE_NT_HEADERS*)((char*)pFileBuffer + pDosHeader->e_lfanew); //(char*)pFileBuffer只有此处转换为了char*类型,之后加减是为char的大小为一个单位进行加减
	if (pDosHeader->e_magic != IMAGE_DOS_SIGNATURE)
	{
		printf("不是有效MZ标志,结束\n");
		free(pFileBuffer);
		fclose(pFile);
		return 0;
	}
	if (pNTHeader->Signature != IMAGE_NT_SIGNATURE)
	{
		printf("不是有效的PE标志,打印结束\n");
		free(pFileBuffer);
		pFileBuffer = NULL;
		return 0;
	}
	fclose(pFile);
	return pFileBuffer;
}
int Alige(int mes, int aligement)
{
	return mes <= aligement ? aligement : mes + aligement - mes % aligement;
}
char* FileBufferToImageBuffer(const char* lpszFile) //文件转内存并返回内存地址
{
	char* pFileBuffer = ReadPEFile(lpszFile); //获取自定义文件内存缓冲区指针
	if (pFileBuffer == NULL)
	{
		printf("缓冲区指针无效\n");
		return FALSE;
	}
	PIMAGE_DOS_HEADER pDosHeader = (PIMAGE_DOS_HEADER)pFileBuffer; //获取自定义文件内存缓冲区DOS头指针
	PIMAGE_FILE_HEADER pFileHeader = (PIMAGE_FILE_HEADER)((char*)pDosHeader + pDosHeader->e_lfanew + 4);//获取标准PE头指针
	PIMAGE_OPTIONAL_HEADER pOptionHeader = (PIMAGE_OPTIONAL_HEADER)((char*)pFileHeader + 20);//获取可选PE头指针
	PIMAGE_SECTION_HEADER pSecHeader = (PIMAGE_SECTION_HEADER)((char*)pOptionHeader + pFileHeader->SizeOfOptionalHeader); // 获取文件节表指针
	char* pSecBuffer = (char*)pDosHeader + pSecHeader->PointerToRawData; //获取第一个节的指针
	char* ImageBuffer = (char*)malloc(pOptionHeader->SizeOfImage); //开辟自定义的文件的虚拟内存大小
	if (ImageBuffer == NULL)
	{
		printf("申请虚拟内存失败\n");
		return FALSE;
	}
	memset(ImageBuffer, 0, pOptionHeader->SizeOfImage); //初始化虚拟内存
	memcpy(ImageBuffer, pDosHeader, pOptionHeader->SizeOfHeaders); //将文件内存头字段数据复制到虚拟内存
	PIMAGE_DOS_HEADER iDosHeader = (PIMAGE_DOS_HEADER)ImageBuffer; //获取自定义虚拟内存内存缓冲区DOS头指针
	PIMAGE_FILE_HEADER iFileHeader = (PIMAGE_FILE_HEADER)((char*)iDosHeader + iDosHeader->e_lfanew + 4);//获取标准PE头指针
	PIMAGE_OPTIONAL_HEADER iOptionHeader = (PIMAGE_OPTIONAL_HEADER)((char*)iFileHeader + 20);//获取可选PE头指针
	PIMAGE_SECTION_HEADER iSecHeader = (PIMAGE_SECTION_HEADER)((char*)iOptionHeader + iFileHeader->SizeOfOptionalHeader); // 获取虚拟内存节表指针
	char* iSecBuffer = (char*)iDosHeader + iSecHeader->VirtualAddress; //保存第一个节的指针
	for (int i = 0; i < pFileHeader->NumberOfSections; i++)
	{
		memcpy(iSecBuffer, pSecBuffer, pSecHeader->SizeOfRawData);
		iSecHeader++;
		iSecBuffer = (char*)iDosHeader + iSecHeader->VirtualAddress;
		pSecHeader++;
		pSecBuffer = (char*)pDosHeader + pSecHeader->PointerToRawData;
	}
	char* ExportTable = ImageBuffer + 0x00013B70;
	return ImageBuffer;
}
DWORD RVAtoFOA(char* FileBuffer, char* ImageBuffer, DWORD RVA) //该函数用于转换为相对于dos头的地址
{
	PIMAGE_DOS_HEADER pDosHeader = (PIMAGE_DOS_HEADER)FileBuffer; //获取自定义文件内存缓冲区DOS头指针
	PIMAGE_FILE_HEADER pFileHeader = (PIMAGE_FILE_HEADER)((char*)pDosHeader + pDosHeader->e_lfanew + 4);//获取标准PE头指针
	PIMAGE_OPTIONAL_HEADER pOptionHeader = (PIMAGE_OPTIONAL_HEADER)((char*)pFileHeader + 20);//获取可选PE头指针
	PIMAGE_SECTION_HEADER pSecHeader = (PIMAGE_SECTION_HEADER)((char*)pOptionHeader + pFileHeader->SizeOfOptionalHeader); // 获取文件节表指针
	char* pSecBuffer = (char*)pDosHeader + pSecHeader->PointerToRawData; //获取第一个节的指针
	PIMAGE_DOS_HEADER iDosHeader = (PIMAGE_DOS_HEADER)ImageBuffer; //获取虚拟内存中缓冲区DOS头指针
	PIMAGE_NT_HEADERS iNTHeader = (PIMAGE_NT_HEADERS)((char*)iDosHeader + iDosHeader->e_lfanew); //获取NT头指针
	PIMAGE_FILE_HEADER iFileHeader = (PIMAGE_FILE_HEADER)((char*)iDosHeader + iDosHeader->e_lfanew + 4);//获取标准PE头指针
	PIMAGE_OPTIONAL_HEADER iOptionHeader = (PIMAGE_OPTIONAL_HEADER)((char*)iFileHeader + 20);//获取可选PE头指针
	PIMAGE_SECTION_HEADER iSecHeader1 = (PIMAGE_SECTION_HEADER)((char*)iOptionHeader + pFileHeader->SizeOfOptionalHeader); // 获取虚拟内存节表指针
	PIMAGE_SECTION_HEADER  iSecHeaderEnd = iSecHeader1 + pFileHeader->NumberOfSections - 1;// 使节表指针指向最后一个节表
	char* iSecBuffer1 = (char*)iDosHeader + iSecHeader1->VirtualAddress; //保存第一个节的指针
	//以下开始虚拟内存节地址转换硬盘内存节地址
	DWORD SecEndAlige = Alige(iSecHeaderEnd->SizeOfRawData, iOptionHeader->SectionAlignment);
	DWORD SecMin = (DWORD)iDosHeader + iSecHeader1->VirtualAddress; //节起始位置
	DWORD LastSecPoint = (DWORD)iDosHeader + iSecHeaderEnd->VirtualAddress + iSecHeaderEnd->SizeOfRawData + SecEndAlige;//最后一个节末尾
	RVA = RVA + (DWORD)iDosHeader;
	while (1)
	{
		if (RVA < (DWORD)iDosHeader)
		{
			printf("输入地址过小,请重新输入\n");
			scanf("%d", &RVA);
		}
		else if (RVA > LastSecPoint)
		{
			printf("输入地址过大,请重新输入\n");
			scanf("%d", &RVA);
		}
		else if (RVA <= SecMin && RVA >= (DWORD)iDosHeader)
		{
			printf("RVA to FOA: %d\n",RVA - (DWORD)iDosHeader);
			return RVA;
		}
		else
		{
			printf("输入地址正确,开始转换地址\n");
			DWORD offset1 = RVA - (DWORD)iDosHeader; //获取转换地址在虚拟存中相对于DOS头的偏移量
	        DWORD nNumber = 0; //用于获取转换地址所在第几个节
			for (int i = 0; i < iFileHeader->NumberOfSections - 1; i++)
			{
				if (offset1 >= (iSecHeader1 + i)->VirtualAddress && offset1 <= (iSecHeader1 + i + 1)->VirtualAddress)
				{
					break;
				}		
				else if (offset1 >= iSecHeaderEnd->VirtualAddress && offset1 <= LastSecPoint)
				{
					nNumber = iFileHeader->NumberOfSections - 1;
					break;
				}
		        else
		        {
					nNumber++;
		        }
	        }
	        pSecHeader += nNumber;
	        DWORD offset2 = offset1 - pSecHeader->VirtualAddress; //获取转换地址在内存中相对于目标节的偏移量
	        DWORD pSecBufferPoint = pSecHeader->PointerToRawData + offset2;//获取转换地址在硬盘中相对于dos头的偏移量
	        printf("RVA to FOA: %d\n", pSecBufferPoint);
        	return pSecBufferPoint + (DWORD)pDosHeader;			
		}
	}
}
DWORD GetProcAddressByName(const char* lpszFile)
{
	char* pFileBuffer = ReadPEFile(lpszFile); //获取自定义文件内存缓冲区指针
	if (pFileBuffer == NULL)
	{
		printf("硬盘缓冲区指针无效\n");
		return FALSE;
	}
	char* ImageBuffer = FileBufferToImageBuffer(lpszFile);
	if (pFileBuffer == NULL)
	{
		printf("内存缓冲区指针无效\n");
		return FALSE;
	}
	PIMAGE_DOS_HEADER pDosHeader = (PIMAGE_DOS_HEADER)pFileBuffer; //获取自定义文件内存缓冲区DOS头指针
	PIMAGE_FILE_HEADER pFileHeader = (PIMAGE_FILE_HEADER)((char*)pDosHeader + pDosHeader->e_lfanew + 4);//获取标准PE头指针
	PIMAGE_OPTIONAL_HEADER pOptionHeader = (PIMAGE_OPTIONAL_HEADER)((char*)pFileHeader + 20);//获取可选PE头指针
	PIMAGE_SECTION_HEADER pSecHeader = (PIMAGE_SECTION_HEADER)((char*)pOptionHeader + pFileHeader->SizeOfOptionalHeader); // 获取文件节表指针
	IMAGE_DATA_DIRECTORY  pExportTable = pOptionHeader->DataDirectory[0];
	if (pExportTable.VirtualAddress == NULL && pExportTable.Size == NULL)
	{
		printf("该PE文件没有导出表");
		return FALSE;
	}
	PIMAGE_EXPORT_DIRECTORY RealExportTable = (PIMAGE_EXPORT_DIRECTORY)(RVAtoFOA(pFileBuffer, ImageBuffer, pExportTable.VirtualAddress));
	char* FunctionsTable = (char*)RVAtoFOA(pFileBuffer, ImageBuffer, RealExportTable->AddressOfFunctions);
	char* NamesTable = (char*)RVAtoFOA(pFileBuffer, ImageBuffer, RealExportTable->AddressOfNames);
	char* NameOrdinalsTable = (char*)RVAtoFOA(pFileBuffer, ImageBuffer, RealExportTable->AddressOfNameOrdinals);
	DWORD FunctionsTable1 = DWORD(FunctionsTable - (char*)pDosHeader);
	DWORD NamesTable1 = DWORD(NamesTable - (char*)pDosHeader);
	DWORD NameOrdinalsTable1 = DWORD(NameOrdinalsTable - (char*)pDosHeader);
	DWORD Name;
	printf("以十六进制数的形式输入要查找的导出函数名");
	scanf("%08X", &Name);
	DWORD NameBase = 0;
	for (int i = 0; i < RealExportTable->NumberOfNames; i++)
	{
		if (Name != *(DWORD*)NamesTable)
		{
			NamesTable += 4;
			NameBase++;
		}
		else
		{
			break;
		}
	}
	NameOrdinalsTable += 4 * NameBase;
	DWORD FunctionsTableBase = RVAtoFOA(pFileBuffer, ImageBuffer, *(DWORD*)NameOrdinalsTable); 

	printf("要查找的导出函数的地址是:%d", FunctionsTableBase);
	return TRUE;
}
int main(int argc, char* argv[])
{
	const char* lpszFile = "C:\\appverifUI.dll";
	GetProcAddressByName(lpszFile);
	return 0;
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/798961.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Qt+ESP32+SQLite 智能大棚

环境简介 硬件环境 ESP32、光照传感器、温湿度传感器、继电器、蜂鸣器 基本工作流程 上位机先运行&#xff0c;下位机启动后尝试连接上位机连接成功后定时上报传感器数据到上位机&#xff0c;上位机将信息进行处理展示判断下位机传感器数据&#xff0c;如果超过设置的阈值&a…

Puppeteer动态代理实战:提升数据抓取效率

引言 Puppeteer是由Google Chrome团队开发的一个Node.js库&#xff0c;用于控制Chrome或Chromium浏览器。它提供了高级API&#xff0c;可以进行网页自动化操作&#xff0c;包括导航、屏幕截图、生成PDF、捕获网络活动等。在本文中&#xff0c;我们将重点介绍如何使用Puppeteer…

项目部署笔记

1、安全组需开放&#xff08;如果不开放配置nginx也访问不到&#xff09; 2、域名解析配置IP(子域名也需配置IP&#xff0c;IP地址可以不同) 3、如果出现图片获其他的文件找不到的情况请仔细检查一下路径是否正确 4、服务器nginx配置SSL证书后启动报错&#xff1a; nginx: […

嘉立创EDA隐藏地线或者

https://prodocs.lceda.cn/cn/pcb/side-panel-left-net/#%E9%A3%9E%E7%BA%BF

Ceph集群部署(基于ceph-deploy)

目录 部署Ceph集群的方法 Ceph生产环境推荐 部署Ceph实验&#xff08;基于ceph-deploy&#xff09; 一、准备工作 二、环境准备 1.关闭selinux与防火墙 2.修改主机名并且配置hosts解析映射 3.admin管理节点配置ssh免密登录node节点 4.安装常用软件和依赖包 5.配置时间…

807.力扣每日一题7/14 Java(执行用时分布击败100%)

博客主页&#xff1a;音符犹如代码系列专栏&#xff1a;算法练习关注博主&#xff0c;后期持续更新系列文章如果有错误感谢请大家批评指出&#xff0c;及时修改感谢大家点赞&#x1f44d;收藏⭐评论✍ 目录 解题思路 解题过程 时间复杂度 空间复杂度 Code 解题思路 首先…

C语言--递归

曾经有一个段子&#xff1a;上大学时&#xff0c;我们的c语言老师说&#xff1a;学c时&#xff0c;如果有50%的同学死在了循环上面&#xff0c;那么就有90%的同学死在了递归上面。接下来&#xff0c;就来看看递归是怎么个事&#xff1f; 一.递归的介绍 递归是指一个函数直接或…

护佑未来!引领儿童安全新时代的AI大模型

引领儿童安全新时代的AI大模型 一. 前言1.1 AI在儿童安全方面的潜在作用1.2 实时监控与预警1.3 个性化安全教育与引导1.4 家长监护与安全意识提升 二. AI大模型的优势2.1. 保护儿童隐私和安全的重要性2.2. AI大模型如何应用于儿童安全领域2.1 儿童内容过滤2.2.1 儿童行为监测 2…

算法力扣刷题记录 四十四【222.完全二叉树的节点个数】

前言 二叉树篇继续。 记录 四十四【222.完全二叉树的节点个数】 一、题目阅读 给你一棵 完全二叉树 的根节点 root &#xff0c;求出该树的节点个数。 完全二叉树 的定义如下&#xff1a;在完全二叉树中&#xff0c;除了最底层节点可能没填满外&#xff0c;其余每层节点数都…

Java时间复杂度介绍以及枚举

时间复杂度 从小到大&#xff1a; O(1) 常数阶。复杂度为O(1)与问题规模无关 线性阶 O&#xff08;n&#xff09;比如一个for循环中代码执行n遍 n阶 对数阶 int n9; int i1; while(i<n) { i*2; } 2^x>n时候退出。次数xlog2^n 时间复杂度为O(logN) 根号阶 int…

09 函数基础

目录 一、定义一个函数 二、调用函数 三、函数的参数 1.形参和实参 2. 参数的分类 3.参数默认值 4.参数类型说明 5.不定长参数 四、函数的返回值 1.定义 2.关键字return 五、变量的作用域 六、匿名函数 七、实参高阶函数 1.定义 2.常见实参高阶函数 max、min、so…

数据结构回顾(Java)

1.数组 线性表 定义的方式 int[] anew int[10] 为什么查询快&#xff1f; 1.可以借助O(1)时间复杂度访问某一元素&#xff0c; 2.地址连续&#xff0c;逻辑连续 3.数组长度一旦确定就不可以被修改 当需要扩容的时候需要将老数组的内容复制过来 在Java中数组是一个对象 Ar…

SpringBoot开发的AI导航站技术架构剖析 —— 技术如何选型 - 第520篇

历史文章&#xff08;文章累计520&#xff09; 《国内最全的Spring Boot系列之一》 《国内最全的Spring Boot系列之二》 《国内最全的Spring Boot系列之三》 《国内最全的Spring Boot系列之四》 《国内最全的Spring Boot系列之五》 《国内最全的Spring Boot系列之六》 《…

C#与PLC通信——如何设置电脑IP地址

前言&#xff1a; 我们与PLC通过以太网通信时&#xff0c;首先要做的就是先设置好电脑的IP&#xff0c;这样才能实现上位机电脑与PLC之间的通信&#xff0c;并且电脑的ip地址和PLC的Ip地址要同处于一个网段&#xff0c;比如电脑的Ip地址为192.168.1.1&#xff0c;那么PLC的Ip地…

【Android面试八股文】请描述一下 android 的系统架构?

Android 是一个基于 Linux 的开源软件堆栈,针对多种不同设备类型打造。下图显示了 Android 平台的主要组件。 早期的Android架构如下图所示 官方网站最新的Android平台架构图,如下所示: Linux 内核 Android 平台的基础是 Linux 内核。例如,Android 运行时 (ART) 依赖…

css-grid布局(栅格布局)

css新世界-auto-fit grid 一个比flex更强大的布局,适合做整体布局 grid-template-columns: repeat(auto-fill, minmax(100px, 1fr)); auto-fit的话有strech效果gap 不仅可以用于grid 也可用flex. 在grid-template-areas表示这个位置空着grid area 的 [a b]命名可重复命名 表示的…

RHCA II之路---EX442-6

RHCA II之路---EX442-6 1. 题目2. 解题3. 确认 1. 题目 2. 解题 sysctl -a|grep shmall echo kernel.shmall 367001 >> /etc/sysctl.conf sysctl -p3. 确认 去人这里max total shared memory的值使我们之前设定的即可.这里的值单位是kb所以只需要2个1024就可以了 ipc…

如何快速区分电子原件极性

表贴式电阻电容无极性 1表贴式.二极管 如图所示:有横杠的表示负极&#xff08;竖杠标示&#xff09;&#xff0c;注意一定要查阅数据手册在引脚信息栏一般会有 铝电解电容 手册一般会对正负极有说明 钽电容有极性 发光二极管 芯片 一般规律&#xff1a;1.看丝印朝向正对丝印的…

监控易V7.6.6.15升级详解7,日志分析更高效

随着企业IT系统的日益复杂&#xff0c;日志管理成为了保障系统稳定运行、快速定位问题的重要工具。为了满足广大用户对日志管理功能的更高需求&#xff0c;监控易系统近日完成了重要版本升级&#xff0c;对日志管理功能进行了全面优化和新增。 一、Syslog日志与SnmpTrap日志统…

uniapp踩坑之项目:uni-table垂直居中和水平居中

uni-table 中的水平居中uni-td align"center" //html 水平居中<uni-table ref"table" :loading"loading" border stripe emptyText"暂无更多数据"><uni-tr><uni-th :width"tdWidth/6" align"center&quo…