深入解析PE文件结构之导出表获取

本文详细探讨了PE文件结构,重点讲解了如何通过映射方式将PE文件加载到内存并获取基址,进而深入研究了可选镜像头部结构,特别是导出表的获取方法。通过实例代码展示了如何利用PE文件结构特性,简便地获取PE文件的导出表信息。
 新学期新气象,一般是小学作文的开头,在此引用一下。

最近有时间坐下来仔细研究一下PE文件结构了,以前遇到这种问题时总是拆东墙补西墙。学的不够透彻。几天来一番研究之后,和大家分享一下。

PE的文件结构从DOS头开始,其主要作用就两个一个是若是在DOS环境下输出一句话。另一个作用就是找到PE文件头的位置,这是我们要关心的,本文只注重所要关心的问题——导出表。和一些你再众多网上资料上很难找到的细节,至于整体的PE结构网上的资料中说的很详细。

首先,PE文件的加载原理这里就不多说了,整个复杂的过程都是由PE装载器完成的,过程不是一两句话能够说清楚的,这里我们只需要注意一个细节就可以了。PE装载器将PE文件已文件映射的方式从磁盘映射到内存,在映射时PE文件被加载到内存的开始位置叫做基址,我们用lPImageBase来表示。我们首先需要知道我们如何将一个PE文件加载到内存,并获得基址。

HANDLE hfile = CreateFile("c:\\example.exe", 
		GENERIC_READ|GENERIC_WRITE, 
		FILE_SHARE_READ,
		NULL,
		OPEN_EXISTING,
		FILE_ATTRIBUTE_NORMAL,
		NULL);
if(hfile==INVALID_HANDLE_VALUE)
{
	printf("Create File Failed.\n");
	return ;
}
HANDLE hFileMapping=CreateFileMapping(hfile,NULL,PAGE_READONLY,0,0,NULL);
if (hFileMapping==NULL||hFileMapping==INVALID_HANDLE_VALUE) 
{ 
printf("Could not create file mapping object (%d).\n", GetLastError());
	return ;
}
//内存映射文件的基址
LPBYTE lpBaseAddress=(LPBYTE)MapViewOfFile(hFileMapping,FILE_MAP_READ,0,0,0);
if (lpBaseAddress == NULL) 
{ 
	printf("Could not map view of file (%d).\n", GetLastError()); 
	return ;
}‘

这里我们需要关心的是一个叫IMAGE_OPTIONAL_HEADER的结构,中文名称叫“可选镜像头部”,说是可选的,其实非常重要。这个结构是在IMAGE_NT_HERDERS结构中,并且是第二个成员变量。

typedef struct IMAGE_NT_HERDERS
{
   Signature dd?;
   FileHeader IMAGE_FILE_HEADER<>;
   OptionalHeader IMAGE_OPTIONAL_HEADER;//这里是下面结构体IMAGE_OPTIONAL_HEADER
}

IMAGE_NT_HERDERS地址获取:

PIMAGE_DOS_HEADER pDosHeader=(PIMAGE_DOS_HEADER)lpBaseAddress;

PIMAGE_NT_HEADERS pNtHeaders=(PIMAGE_NT_HEADERS)(lpBaseAddress + pDosHeader->e_lfanew);

/可选镜像头部
typedef 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;
  DWORD                CheckSum;
  WORD                 Subsystem;
  WORD                 DllCharacteristics;
  DWORD                SizeOfStackReserve;
  DWORD                SizeOfStackCommit;
  DWORD                SizeOfHeapReserve;
  DWORD                SizeOfHeapCommit;
  DWORD                LoaderFlags;
  DWORD                NumberOfRvaAndSizes;     //这个成员的大小是下面数组的数目
  IMAGE_DATA_DIRECTORY  DataDirectory[IMAGE_NUMBEROF_DIRECTORY_ENTRIES]; //这里是下面的IMAGE_DATA_DIRECTORY
} IMAGE_OPTIONAL_HEADER, *PIMAGE_OPTIONAL_HEADER;

IMAGE_OPTIONAL_HEADER地址获取:

pNtHeaders->OptionalHeader;


然后我们需要注意IMAGE_DATA_DIRECTORY这个数组,这个数组一般有15个元素,每个元素都记录着这个PE文件的重要数据结构。我们来看看这个数组的每个元素的内容。

是不是很让人吃惊?有了这个数组,获取一些PE信息就非常简单了,一般我们或者是计算机病毒所关心的多数是导出表(EAT),导入表(IAT),表,分别是这个数组的第0个元素和第12个元素。这个方法和网上的一些EAT IAT地址获取方法有所不同。很是方便。

而pNTHeader->OptionalHeader.DataDirectory[0]又是一个结构体,这个结构体的定义如下:

typedef struct _IMAGE_DATA_DIRECTORY {
  DWORD VirtualAddress;//指向导出表的RVA地址(相对地址)
  DWORD Size;
} IMAGE_DATA_DIRECTORY, *PIMAGE_DATA_DIRECTORY;

说以这里的EAT表的地址我们就应该这样获取:

pNTHeader->OptionalHeader.DataDirectory[0].VirtualAddress

 

 

到这里我们已经找到了PE文件的导出表相对地址了(RVA),现在的问题是,我们如何将导出表中存放的导出函数信息给弄出来。

我们先看看导出表是个什么东西:

//导入地址表
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;

不难看出AddressOfNames;就是存放导出函数的一个数组RVA了。但是这里有一个小问题。导致我在这个问题上花了半天时间。AddressOfNames;中存放的确实是一个RVA(相对地址),但是不是直接指向函数数组的,而是指向了一个存放地址的数组也就是一个DWORD数组,这个数组的每一个元素存放一个导出函数的RVA,这样说来有点绕,我们已一张图说明:

 

这就给我这种菜鸟造成了一定的麻烦,诶基础语法还是很重要啊。。。

所以地址需要这样获取(这里已经加上了基址lPImageBase,后面说明)

  DWORD * k=(DWORD *)(pExport->AddressOfNames+lpBaseAddress);    //RVAÊý×éÊ×µØÖ·
  char * functionName=(char *)(*k+lpBaseAddress);

 

 


好了地址我们都有了,那么程序也就不难了。下面贴出核心代码,我觉得这种获取地址的方法比常见方法的获取方法简单不少(菜鸟本人是这样遐想的):

#include "windows.h"
#include "stdio.h"


void Doshow(char * place)
{
	int i=0;
	HANDLE hfile = CreateFile(place, 
		GENERIC_READ|GENERIC_WRITE, 
		FILE_SHARE_READ,
		NULL,
		OPEN_EXISTING,
		FILE_ATTRIBUTE_NORMAL,
		NULL);
	if(hfile==INVALID_HANDLE_VALUE)
	{

		printf("Create File Failed.\n");
		return ;
	}
	HANDLE hFileMapping=CreateFileMapping(hfile,NULL,PAGE_READONLY,0,0,NULL);
	if (hFileMapping==NULL||hFileMapping==INVALID_HANDLE_VALUE) 
	{ 
		printf("Could not create file mapping object (%d).\n", GetLastError());
		return ;
	}
	//ÄÚ´æÓ³ÉäÎļþµÄ»ùÖ·
	LPBYTE lpBaseAddress=(LPBYTE)MapViewOfFile(hFileMapping,FILE_MAP_READ,0,0,0);
	if (lpBaseAddress == NULL) 
	{ 
		printf("Could not map view of file (%d).\n", GetLastError()); 
		return ;
	}
	PIMAGE_DOS_HEADER pDosHeader=(PIMAGE_DOS_HEADER)lpBaseAddress;
	PIMAGE_NT_HEADERS pNtHeaders=(PIMAGE_NT_HEADERS)(lpBaseAddress + pDosHeader->e_lfanew);
	PIMAGE_EXPORT_DIRECTORY pExport=(PIMAGE_EXPORT_DIRECTORY)(pNtHeaders->OptionalHeader.DataDirectory[0].VirtualAddress+lpBaseAddress);
	int Num=pExport->NumberOfNames;
	printf("DLLÊÇ%s\n",pExport->Name+lpBaseAddress);
	printf("¹²ÓÐ%d¸öº¯Êý\n",pExport->NumberOfNames);
	printf("º¯ÊýÐòºÅ        º¯ÊýÃû\n");
	for (i;i<Num;i++)
	{
		DWORD * k=(DWORD *)(pExport->AddressOfNames+lpBaseAddress+4*i);    //RVAÊý×éÊ×µØÖ·
		char * functionName=(char *)(*k+lpBaseAddress);
		printf("%d             %s\n",i+1,functionName);
	}
	UnmapViewOfFile(lpBaseAddress);
	CloseHandle(hFileMapping);
	CloseHandle(hfile);


}

int main()
{
	char p[20];
	printf("ÇëÊäÈëÎļþλÖãº\n");
	scanf("%s",p);
	Doshow(p);
	return 1;
}


另外我还写了界面,给大家展示一下吧。

 

 

 

 

下面的任务是尝试更加困难的IAT表的改写,如插入DLL,实现PE病毒的基本功能等等,如果可能,一定会给大家分享我的“成果”。

 

最后菜鸟言论,仅供娱乐。

 

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值