深入解析PE文件结构之导出表获取
2013-05-15 12:49
465 查看
http://blog.csdn.net/yiyefangzhou24/article/details/7268319
最近有时间坐下来仔细研究一下PE文件结构了,以前遇到这种问题时总是拆东墙补西墙。学的不够透彻。几天来一番研究之后,和大家分享一下。
PE的文件结构从DOS头开始,其主要作用就两个一个是若是在DOS环境下输出一句话。另一个作用就是找到PE文件头的位置,这是我们要关心的,本文只注重所要关心的问题——导出表。和一些你再众多网上资料上很难找到的细节,至于整体的PE结构网上的资料中说的很详细。
首先,PE文件的加载原理这里就不多说了,整个复杂的过程都是由PE装载器完成的,过程不是一两句话能够说清楚的,这里我们只需要注意一个细节就可以了。PE装载器将PE文件已文件映射的方式从磁盘映射到内存,在映射时PE文件被加载到内存的开始位置叫做基址,我们用lPImageBase来表示。我们首先需要知道我们如何将一个PE文件加载到内存,并获得基址。
[cpp] view
plaincopy
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结构中,并且是第二个成员变量。
[cpp] view
plaincopy
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);
[cpp] view
plaincopy
/可选镜像头部
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]又是一个结构体,这个结构体的定义如下:
[cpp] view
plaincopy
typedef struct _IMAGE_DATA_DIRECTORY {
DWORD VirtualAddress;//指向导出表的RVA地址(相对地址)
DWORD Size;
} IMAGE_DATA_DIRECTORY, *PIMAGE_DATA_DIRECTORY;
说以这里的EAT表的地址我们就应该这样获取:
pNTHeader->OptionalHeader.DataDirectory[0].VirtualAddress
到这里我们已经找到了PE文件的导出表相对地址了(RVA),现在的问题是,我们如何将导出表中存放的导出函数信息给弄出来。
我们先看看导出表是个什么东西:
[cpp] view
plaincopy
//导入地址表
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);
好了地址我们都有了,那么程序也就不难了。下面贴出核心代码,我觉得这种获取地址的方法比常见方法的获取方法简单不少(菜鸟本人是这样遐想的):
[cpp] view
plaincopy
#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病毒的基本功能等等,如果可能,一定会给大家分享我的“成果”。
最后菜鸟言论,仅供娱乐。
最近有时间坐下来仔细研究一下PE文件结构了,以前遇到这种问题时总是拆东墙补西墙。学的不够透彻。几天来一番研究之后,和大家分享一下。
PE的文件结构从DOS头开始,其主要作用就两个一个是若是在DOS环境下输出一句话。另一个作用就是找到PE文件头的位置,这是我们要关心的,本文只注重所要关心的问题——导出表。和一些你再众多网上资料上很难找到的细节,至于整体的PE结构网上的资料中说的很详细。
首先,PE文件的加载原理这里就不多说了,整个复杂的过程都是由PE装载器完成的,过程不是一两句话能够说清楚的,这里我们只需要注意一个细节就可以了。PE装载器将PE文件已文件映射的方式从磁盘映射到内存,在映射时PE文件被加载到内存的开始位置叫做基址,我们用lPImageBase来表示。我们首先需要知道我们如何将一个PE文件加载到内存,并获得基址。
[cpp] view
plaincopy
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结构中,并且是第二个成员变量。
[cpp] view
plaincopy
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);
[cpp] view
plaincopy
/可选镜像头部
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]又是一个结构体,这个结构体的定义如下:
[cpp] view
plaincopy
typedef struct _IMAGE_DATA_DIRECTORY {
DWORD VirtualAddress;//指向导出表的RVA地址(相对地址)
DWORD Size;
} IMAGE_DATA_DIRECTORY, *PIMAGE_DATA_DIRECTORY;
说以这里的EAT表的地址我们就应该这样获取:
pNTHeader->OptionalHeader.DataDirectory[0].VirtualAddress
到这里我们已经找到了PE文件的导出表相对地址了(RVA),现在的问题是,我们如何将导出表中存放的导出函数信息给弄出来。
我们先看看导出表是个什么东西:
[cpp] view
plaincopy
//导入地址表
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);
好了地址我们都有了,那么程序也就不难了。下面贴出核心代码,我觉得这种获取地址的方法比常见方法的获取方法简单不少(菜鸟本人是这样遐想的):
[cpp] view
plaincopy
#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病毒的基本功能等等,如果可能,一定会给大家分享我的“成果”。
最后菜鸟言论,仅供娱乐。
相关文章推荐
- 深入解析PE文件结构之导出表获取
- PE总结9 --PE文件结构之 解析导出表
- 【C++源码】PE文件结构中导出表的解析
- PE文件结构详解(三)PE导出表(转…
- PE总结8---PE文件结构之导出表 (IMAGE_EXPORT_DIRECTORY)
- PE文件结构解析
- pe文件解析:读取pe信息获取文件资源
- PE总结11--PE文件结构之 解析导入表
- PE文件结构详解(三)PE导出表
- PE文件结构详解(三)PE导出表
- PE文件学习笔记(三):导出表(Export Table)解析
- MemoryModule阅读与PE文件解析(四)---深入理解TLS
- PE文件结构详解--PE导出表
- PE总结13 --PE文件结构之 解析资源表
- PE文件结构详解(三)PE导出表
- PE文件结构详解(三)PE导出表
- PE文件结构详解(三)PE导出表
- PE文件结构部分解析以及输入的定位
- 解析PE结构之-----导出表
- 【PE结构】由浅入深PE基础学习-菜鸟手动查询导出表、相对虚拟地址(RVA)与文件偏移地址转换(FOA)