PE导出表,C语言打印导出表信息【滴水逆向三期49笔记+作业】

我们前面在学习PE文件结构的时候,在可选PE头里跳过了最后一项,为一个有16个元素结构体数组,我们称它为数据目录
今天我们来学习数据目录的第一个结构:导出表

一.如何定位导出表

我们先来看看数据目录的结构:

typedef struct _IMAGE_DATA_DIRECTORY{
DWORD VirtualAddress;//数据的RVA
DWORD Size;//数据的大小
}

这里的VirtualAddress指向导出表
需要注意的是,这里的VirtulaAddress为RVA,也就是ImageBuffer中的偏移,如果我们要在ImageBuffer里找到导出表,我们需要将RVA转换为FOA之后在ImageBuffer中通过偏移找到导出表。
这里的Size表示数据的大小,也就是导出表的大小

二.导出表解析

我们来看看导出表的结构:

typedef struct _IMAGE_EXPORT_DIRECTORY{
DWORD Characteristics;//未使用
DWORD TimeDateStamp;//时间戳,表示输出表的创建时间
WORD MajorVersion;//输出表的主版本号,未使用,设置为0
WORD MinorVersion;//输出表的次版本号,未使用,设置为0
DWORD Name;//指向一个与输出函数关联的文件名的RVA
DWORD Base;//导出函数的其实序号
DWORD NumberOfFunctions;//导出函数的总数
DWORD NumberOfNames;//以函数名导出的函数的个数
DWORD AddressOfFunctions;//指向导出函数地址表的RVA
DWORD AddressOfNames;//指向导出函数名称表的RVA
DWORD AddressOfOrdinals;//指向函数序号表的RVA
}IMAGE_EXPORT_DIRECTORY,*PIMAGE_EXPORT_DIRECTORY

在导出表中我们要特别注意最后三个元素:它们分别指向导出函数地址表,导出函数名称表,导出函数序号表,我们需要将三个RVA转换为FOA来找到三张表。
我们来分别讲解一下这三张表:

1.函数地址表

如何找到函数地址表:
通过导出表的AddressOfFunction字段,若要在FIleBuffer中找到函数地址表,还需将其转换为FAO加上FileBuffer基址。
我们来看看函数地址表:
函数导出表
这里我们可以通过下标来寻找函数地址,下标标记方式为函数导出序号-Base
比如说,我们已知函数导出序号为5,Base为2,那么该函数的函数地址为:函数地址表的第5-2=3个。

2.函数导出序号表

如何找到函数导出序号表:
通过导出表的AddressOfOrdinals字段,同样,若要在FileBuffer中找到函数导出序号表,需要将其转换为FOA加上FileBuffer基址。
我们来看看函数导出序号表:
函数导出序号表
需要注意的是,函数导出序号表中存储的函数导出序号为:函数真正导出序号-Base
我们可以发现:函数导出序号表中的元素并不是按照顺序排列的,那么他是如何排列的呢?
其实这里的函数导出序号是和函数名称表对应的,我们假设有两个函数,其函数名称为Abcde(函数真正导出序号为3)和Bcdef(函数真正导出序号为2),那么Base就为2,在函数名称表中,以A开头的函数名称存储在最前面,以B开头的函数名称存储在后面,在我们举例的两个函数中,函数名称表中Abcde存储在Bcdef前面,所以Abcde的导出序号就存储在Bcdef后面,也就是在函数导出序号表中,存储1和0,而且1在0前面。
我们给出图来加深理解:
导出序号表中序号的排列

3.函数名称表

如何找到函数名称表:
通过导出表的AddressOfNames字段,同样如果要在FileBuffer中找到,需要将其转化为FOA。
我们来看看函数名称表:
函数导出表
我们可以看到:在函数名称表中存储的还是地址,那么这个地址又指向哪里呢?
函数地址表中的元素为该存储函数名称的地址。
需要注意的是,在函数名称表中,函数是按照函数名称排序的,也就是说以A开头的函数排列在最前面,以Z开头的函数排列在后面,但是在文件中,并不一定是以A开头的函数在以Z开头的函数前面,有可能在文件中,某个函数名称以B开头的函数存储在函数名称以A开头的函数前面
在函数导出序号表中,函数的排列方式与函数名称表中的一致:比如某个函数名称以A开头的函数存储在函数地址表中第一个,那么在函数导出序号表中,该函数的导出序号也存储在第一个。
我们给出图来加深对导出表的理解:
PE导出表

四.如何通过函数名称找到函数地址

这里我们给出思路,代码放在后面:
1.通过遍历函数名称表,找到该函数名称下标
2.通过该下标在函数导出序号表中找到该函数到处序号
3.通过该函数导出序号在函数地址表中找到该函数地址

五.如何通过函数导出序号找到函数地址

这个就比较简单了,通过函数导出序号,减去Base,通过这个值在函数地址表中找到该函数地址

六.课程作业

1.编写程序打印导出表信息
2.通过函数名称找到函数地址 GetAddressOfFunctionByName
3.通过函数导出序号找到函数地址 GetAddressOfFunctionByOrdinal

课程作业源码

在这里我给出我自己写的程序源码,大家自行理解:
函数文件:Function.h

/**************************************************************/ 
//*********************自编PE函数库****************************/



/********1.打开文件函数(将文件二进制读取到FileBuffer) *******/
/*函数说明:
该函数需要一个char类型的指针,指向想要打开的文件路径
该函数返回一个char类型的指针,指向FileBuffer
该函数完成文件的打开,并且将文件的二进制读取到FIleBuffer
该函数读取完成后会将文件关闭,避免出现误操作
该函数会通过移动文件指针获取文件大小
获取的文件大小用于动态申请内存 
该函数会动态申请内存,用于存放FileBuffer
*/ 
char* ReadToFileBuffer(char* filename){
	FILE* fp;
	char* FileBuffer=NULL;
	int length;
	if((fp=fopen(filename,"rb"))==NULL){
		printf("%s打开失败!",filename);
		exit(0);
	}else{
		printf("文件打开成功,正在计算文件大小...\n");
	}
	fseek(fp,0,SEEK_END);
	length=ftell(fp);
	fseek(fp,0,SEEK_SET);
	printf("文件大小:%d字节\n正在申请FileBuffer内存...\n",length);
	FileBuffer=(char*)malloc(length);
	if(FileBuffer!=NULL){
		printf("FileBuffer内存申请成功。准备向FileBuffer写入数据...\n");
	}
	if((fread(FileBuffer,length,1,fp))==0){
		printf("写入数据失败!\n");
		exit(0);
	}else{
		printf("写入数据成功。\n");
	}
	if(fclose(fp)){
		printf("文件关闭失败!\n");
		exit(0);
	}else{
		printf("文件关闭成功。\n");
	}
	return FileBuffer;
}
/**********************2.获取PE导出表数据函数********************
该函数需要一个指针,指向FileBuffer
该函数在找到导出表之前,需要定义DOS结构指针,标准PE头结构指针和可选PE头指针,以方便找到导出表
该函数会在控制台输出函数地址表,函数名称表和函数导出序号表 */
void ShowExportDirectory(char* FileBuffer){
	IMAGE_DOS_HEADER* pDosHeader=NULL;
	IMAGE_FILE_HEADER* pFileHeader=NULL;
	IMAGE_OPTIONAL_HEADER32* pOptionalHeader=NULL;
	IMAGE_EXPORT_DIRECTORY* pExportDirectory=NULL;
	pDosHeader=(IMAGE_DOS_HEADER*)FileBuffer;
	if(*((PDWORD)((DWORD)FileBuffer + pDosHeader->e_lfanew))!= IMAGE_NT_SIGNATURE){
		printf("不是有效的PE标志!\n");
	}else{
		printf("检测到有效的PE标志。\n");
	}
	pFileHeader=(IMAGE_FILE_HEADER*)(FileBuffer+pDosHeader->e_lfanew+4);
	pOptionalHeader=(IMAGE_OPTIONAL_HEADER32*)((DWORD)pFileHeader+20);
	pExportDirectory = (IMAGE_EXPORT_DIRECTORY*)(FileBuffer+RVAToFOA(FileBuffer,pOptionalHeader->DataDirectory[0].VirtualAddress));
	printf("导出表RVA:%x\n",pOptionalHeader->DataDirectory[0].VirtualAddress);
	printf("导出表大小:%x字节\n",pOptionalHeader->DataDirectory[0].Size);
	printf("导出表FOA:%d\n",RVAToFOA(FileBuffer,pOptionalHeader->DataDirectory[0].VirtualAddress));
	printf("*******************************导出表*********************************\n");
	printf("TimeDataStamp(经加密):%d\n",pExportDirectory->TimeDateStamp);
	printf("Name(导出表文件名字符串):%s\n",FileBuffer+RVAToFOA(FileBuffer,pExportDirectory->Name));
	printf("Base(函数起始序号):%d\n",pExportDirectory->Base);
	printf("NumberOfFunction(导出函数总数):%d\n",pExportDirectory->NumberOfFunctions);
	printf("NumberOfNames(以名称导出函数的总数):%d\n",pExportDirectory->NumberOfNames);
	printf("*****************************函数地址表********************************\n");
	int i=0;
	char* AddressOfFunction;
	AddressOfFunction=(char*)(FileBuffer+RVAToFOA(FileBuffer,pExportDirectory->AddressOfFunctions));
	for(i=0;i<pExportDirectory->NumberOfFunctions;i++){
		printf("下标:%d     函数地址:0x%x\n",i,*(PWORD)((DWORD)AddressOfFunction+i*4));
	}
	printf("*****************************函数名称表********************************\n");
	int* AddressOfNames=NULL;
	char* name=NULL;
	AddressOfNames=(int*)(FileBuffer+RVAToFOA(FileBuffer,pExportDirectory->AddressOfNames));
	for(i=0;i<pExportDirectory->NumberOfNames;i++){
		name=(char*)(FileBuffer+RVAToFOA(FileBuffer,*AddressOfNames));
		printf("下标:%d     函数名称:%s\n",i,name);
		AddressOfNames++;
	}
	printf("*******************************函数序号表*******************************\n");
	char *AddressOfNameOrdinals=NULL;
	char *Ordinal=NULL;
	AddressOfNameOrdinals=(int*)(FileBuffer+RVAToFOA(FileBuffer,pExportDirectory->AddressOfNameOrdinals));
	for(i=0;i<pExportDirectory->NumberOfNames;i++){
		Ordinal=(int*)(FileBuffer+RVAToFOA(FileBuffer,*AddressOfNameOrdinals));
		printf("下标:%d      函数序号(加Base):%d\n",i,*(AddressOfNameOrdinals+i*2)+pExportDirectory->Base);
	} 
}
/***********************3.RVA转换为FOA函数*************************
该函数需要一个指针,指向FileBuffer
该函数需要一个整数,为要转换的RVA
该函数返回一个整数,为转换后的FOA地址*/ 
int RVAToFOA(IN LPVOID pFileBuffer, IN DWORD dwRva)
{
	PIMAGE_DOS_HEADER pDosHeader = NULL;
	PIMAGE_FILE_HEADER pFileHeader = NULL;
	PIMAGE_OPTIONAL_HEADER32 pOptionalHeader = NULL;
	PIMAGE_SECTION_HEADER pSectionHeader = NULL;
	PIMAGE_SECTION_HEADER pNextSectionHeader = NULL;
	//DOS头
	pDosHeader = (PIMAGE_DOS_HEADER)pFileBuffer;	// 强转 DOS_HEADER 结构体指针
	//PE头
	pFileHeader = (PIMAGE_FILE_HEADER)((DWORD)pFileBuffer + pDosHeader->e_lfanew + 4);	//NT头地址 + 4 为 FileHeader 首址
	//可选PE头	
	pOptionalHeader = (PIMAGE_OPTIONAL_HEADER32)((DWORD)pFileHeader + IMAGE_SIZEOF_FILE_HEADER);//SIZEOF_FILE_HEADER为固定值且不存在于PE文件字段中
	if (dwRva < pOptionalHeader->SizeOfHeaders)	//偏移小于头的大小,内存偏移则为文件偏移
	{
		return dwRva;
	}
    //首个节表
	pSectionHeader = (PIMAGE_SECTION_HEADER)((DWORD)pOptionalHeader + pFileHeader->SizeOfOptionalHeader);
	//下一个节表
	pNextSectionHeader = pSectionHeader + 1;
	//循环遍历节表
	int i;
	for (i = 1; i < pFileHeader->NumberOfSections; i++, pSectionHeader++, pNextSectionHeader++)//注意这里i从1开始 i < NumberOfSections
	{	//注意这里的pSectionHeader已经是加了基址的,不是偏移, 是绝对地址。而dwRva是偏移地址
		if (dwRva >= pSectionHeader->VirtualAddress && dwRva < pNextSectionHeader->VirtualAddress)//大于当前节的内存偏移而小于下一节的内存偏移
		{	//则dwRva属于当前节,则dwRva - VirtualAddress为dwRva基于当前节的偏移。此偏移加上当前节的文件起始偏移地址 则为dwRva在文件中的偏移
			DWORD PointerToRawData = pSectionHeader->PointerToRawData;
			DWORD VirtualAddress = pSectionHeader->VirtualAddress;
			DWORD aa = pSectionHeader->PointerToRawData + dwRva - pSectionHeader->VirtualAddress;
			return pSectionHeader->PointerToRawData + dwRva - pSectionHeader->VirtualAddress ;
		}
	}	//出循环后pSectionHeader指向最后一个节表
	//大于当前节(最后一节)的内存偏移且小于内存映射大小
	if (dwRva >= pSectionHeader->VirtualAddress && dwRva < pOptionalHeader->SizeOfImage)
	{	//同上return
		return pSectionHeader->PointerToRawData + dwRva - pSectionHeader->VirtualAddress;
	}
	else //大于内存映射大小
	{
		printf("dwRva大于内存映射大小\n");
		return -1;
	}
}
/***********************4.根据函数名称查找函数地址函数***********************
该函数需要一个指针,指向要打开的文件路径 
该函数返回一个整数,指向得到的函数地址
*/
void GetAddressOfFunctionByName(char* filename){
	char* FileBuffer=NULL;
	char inname[5];
	int i;
	FileBuffer=ReadToFileBuffer(filename);
	IMAGE_DOS_HEADER* pDosHeader=NULL;
	IMAGE_FILE_HEADER* pFileHeader=NULL;
	IMAGE_OPTIONAL_HEADER32* pOptionalHeader=NULL;
	IMAGE_EXPORT_DIRECTORY* pExportDirectory=NULL;
	pDosHeader=(IMAGE_DOS_HEADER*)FileBuffer;
	if(*(PWORD)((DWORD)FileBuffer+pDosHeader->e_lfanew)==IMAGE_NT_SIGNATURE){
		printf("检测到有效的PE标志。\n");
	}else{
		printf("未检测到有效的PE标志!\n");
		exit(0);
	}
	pFileHeader=(IMAGE_FILE_HEADER*)((DWORD)FileBuffer+pDosHeader->e_lfanew+4);
	pOptionalHeader=(IMAGE_OPTIONAL_HEADER32*)((DWORD)pFileHeader+20);
	pExportDirectory = (IMAGE_EXPORT_DIRECTORY*)(FileBuffer+RVAToFOA(FileBuffer,pOptionalHeader->DataDirectory[0].VirtualAddress));
	int* AddressOfNames=NULL;
	char* name=NULL;
	AddressOfNames=(int*)(FileBuffer+RVAToFOA(FileBuffer,pExportDirectory->AddressOfNames));
	
	printf("请输入您需要查找的函数名:");
	scanf("%s",inname);
	printf("正在查找中...\n");
	for(i=0;i<pExportDirectory->NumberOfNames;i++){
	name=(int*)(FileBuffer+RVAToFOA(FileBuffer,RVAToFOA(FileBuffer,*AddressOfNames)));
		if(!(strcmp("Plus",name))){
			printf("下标为%d的函数名符合。\n",i);
			break;
		}
		AddressOfNames++;
	}
	char* Ordinal=NULL;
	Ordinal=(char*)(FileBuffer+RVAToFOA(FileBuffer,pExportDirectory->AddressOfNameOrdinals)+i*2);
	char* AddressOfFunction=NULL;
	AddressOfFunction=(char*)(FileBuffer+RVAToFOA(FileBuffer,pExportDirectory->AddressOfFunctions)+*Ordinal*4);
	printf("Ordinal(函数导出序号(未加Base)):%d    AddressOfFunction:0x%x",*Ordinal,*(PWORD)((DWORD)AddressOfFunction));
}
/***********************5.根据函数序号查找函数地址函数***********************
该函数需要一个指针,指向要打开的文件路径 
该函数返回一个整数,指向得到的函数地址
*/
void GetAddressOfFunctionByOrdinal(char* filename){
	char* FileBuffer=NULL;
	int InOrdinal=0;
	IMAGE_DOS_HEADER* pDosHeader=NULL;
	IMAGE_FILE_HEADER* pFileHeader=NULL;
	IMAGE_OPTIONAL_HEADER32* pOptionalHeader=NULL;
	IMAGE_EXPORT_DIRECTORY* pExportDirectory=NULL;
	FileBuffer=ReadToFileBuffer(filename);
	char* AddressOfFunction;
	pDosHeader=(IMAGE_DOS_HEADER*)FileBuffer;
	if(*(PWORD)((DWORD)FileBuffer+pDosHeader->e_lfanew)==IMAGE_NT_SIGNATURE){
		printf("检测到PE标志。");
	}else{
		printf("未检测到PE标志!\n");
		exit(0);
	}
	pFileHeader=(IMAGE_FILE_HEADER*)(FileBuffer+pDosHeader->e_lfanew+4);
	pOptionalHeader=(IMAGE_OPTIONAL_HEADER32*)((DWORD)pFileHeader+20);
	
	pExportDirectory = (IMAGE_EXPORT_DIRECTORY*)(FileBuffer+RVAToFOA(FileBuffer,pOptionalHeader->DataDirectory[0].VirtualAddress));
	printf("请输入您需要查找的函数导出序号:");
	scanf("%d",&InOrdinal);
	InOrdinal=InOrdinal-pExportDirectory->Base;
	AddressOfFunction=(char*)FileBuffer+RVAToFOA(FileBuffer,pExportDirectory->AddressOfFunctions+InOrdinal*4);
	printf("函数导出序号(未加Base):%d       函数地址:0x%x",InOrdinal,*(PWORD)((DWORD)AddressOfFunction));
}

函数实现:main.c

#include <stdio.h>
#include <windows.h>
#include <string.h>
#include "PEFunction.h"
int main(int argc, char** argv){
	char filename[50];
	printf("请输入文件路径:");
	scanf("%s",filename);
	char* FileBuffer;
	int choose=0;
	printf("*******************************欢迎使用PE导出表自编工具***************************************\n");
	printf("************                                                                      ************\n");
	printf("************                                                                      ************\n");
	printf("************                        请选择您需要的项目:                           ************\n");
	printf("************                        1.导出问价导出表.                             ************\n");
	printf("************                        2.根据函数名称查找函数地址.                   ************\n");
	printf("************                        3.根据函数导出序号查找函数地址.               ************\n");
	printf("************                                                                      ************\n");
	printf("************                                                                      ************\n");
	printf("**********************************************************************************************\n");
	printf("     ----------------------版权所有:https://blog.csdn.net/qq_73985089?spm=1000.2115.3001.5343\n");
	printf("     -----------------------------------------------------------------如需引用,请告知原作者!\n"); 
	printf("请输入您需要的项目:");
	scanf("%d",&choose); 
	switch(choose){
		case 1:{
			FileBuffer=ReadToFileBuffer(filename);
			ShowExportDirectory(FileBuffer);
			break;
		}
		case 2:{
			GetAddressOfFunctionByName(filename);
			break;
		}
		case 3:{
			GetAddressOfFunctionByOrdinal(filename);
			break;
		}
	}
	
	free(FileBuffer);
	return 0;
}

今天的课程就分享到这里,如果发现文章中有理解错误,还请大家指出来,我会非常虚心地学习,希望我们能一起进步!

  • 5
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Shad0w-2023

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值