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

来源:互联网 发布:网络情缘一线牵什么梗 编辑:程序博客网 时间:2024/05/22 05:22

http://blog.csdn.net/yiyefangzhou24/article/details/7268319

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

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

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

[cpp] view plaincopy
  1. HANDLE hfile = CreateFile("c:\\example.exe",   
  2.         GENERIC_READ|GENERIC_WRITE,   
  3.         FILE_SHARE_READ,  
  4.         NULL,  
  5.         OPEN_EXISTING,  
  6.         FILE_ATTRIBUTE_NORMAL,  
  7.         NULL);  
  8. if(hfile==INVALID_HANDLE_VALUE)  
  9. {  
  10.     printf("Create File Failed.\n");  
  11.     return ;  
  12. }  
  13. HANDLE hFileMapping=CreateFileMapping(hfile,NULL,PAGE_READONLY,0,0,NULL);  
  14. if (hFileMapping==NULL||hFileMapping==INVALID_HANDLE_VALUE)   
  15. {   
  16. printf("Could not create file mapping object (%d).\n", GetLastError());  
  17.     return ;  
  18. }  
  19. //内存映射文件的基址  
  20. LPBYTE lpBaseAddress=(LPBYTE)MapViewOfFile(hFileMapping,FILE_MAP_READ,0,0,0);  
  21. if (lpBaseAddress == NULL)   
  22. {   
  23.     printf("Could not map view of file (%d).\n", GetLastError());   
  24.     return ;  
  25. }‘  

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

[cpp] view plaincopy
  1. typedef struct IMAGE_NT_HERDERS  
  2. {  
  3.    Signature dd?;  
  4.    FileHeader IMAGE_FILE_HEADER<>;  
  5.    OptionalHeader IMAGE_OPTIONAL_HEADER;//这里是下面结构体IMAGE_OPTIONAL_HEADER  
  6. }  

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
  1. /可选镜像头部  
  2. typedef struct _IMAGE_OPTIONAL_HEADER   
  3. {  
  4.   WORD                 Magic;  
  5.   BYTE                 MajorLinkerVersion;  
  6.   BYTE                 MinorLinkerVersion;  
  7.   DWORD                SizeOfCode;  
  8.   DWORD                SizeOfInitializedData;  
  9.   DWORD                SizeOfUninitializedData;  
  10.   DWORD                AddressOfEntryPoint;  
  11.   DWORD                BaseOfCode;  
  12.   DWORD                BaseOfData;  
  13.   DWORD                ImageBase;  
  14.   DWORD                SectionAlignment;  
  15.   DWORD                FileAlignment;  
  16.   WORD                 MajorOperatingSystemVersion;  
  17.   WORD                 MinorOperatingSystemVersion;  
  18.   WORD                 MajorImageVersion;  
  19.   WORD                 MinorImageVersion;  
  20.   WORD                 MajorSubsystemVersion;  
  21.   WORD                 MinorSubsystemVersion;  
  22.   DWORD                Win32VersionValue;  
  23.   DWORD                SizeOfImage;  
  24.   DWORD                SizeOfHeaders;  
  25.   DWORD                CheckSum;  
  26.   WORD                 Subsystem;  
  27.   WORD                 DllCharacteristics;  
  28.   DWORD                SizeOfStackReserve;  
  29.   DWORD                SizeOfStackCommit;  
  30.   DWORD                SizeOfHeapReserve;  
  31.   DWORD                SizeOfHeapCommit;  
  32.   DWORD                LoaderFlags;  
  33.   DWORD                NumberOfRvaAndSizes;     //这个成员的大小是下面数组的数目  
  34.   IMAGE_DATA_DIRECTORY  DataDirectory[IMAGE_NUMBEROF_DIRECTORY_ENTRIES]; //这里是下面的IMAGE_DATA_DIRECTORY  
  35. } 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
  1. typedef struct _IMAGE_DATA_DIRECTORY {  
  2.   DWORD VirtualAddress;//指向导出表的RVA地址(相对地址)  
  3.   DWORD Size;  
  4. } IMAGE_DATA_DIRECTORY, *PIMAGE_DATA_DIRECTORY;  

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

pNTHeader->OptionalHeader.DataDirectory[0].VirtualAddress

 

 

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

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

[cpp] view plaincopy
  1. //导入地址表  
  2. typedef struct _IMAGE_EXPORT_DIRECTORY  
  3. {  
  4.         DWORD Characteristics;  
  5.         DWORD TimeDateStamp;  
  6.         WORD MajorVersion;  
  7.         WORD MinorVersion;  
  8.         DWORD Name;  
  9.         DWORD Base;  
  10.         DWORD NumberOfFunctions;  
  11.         DWORD NumberOfNames;  
  12.         DWORD AddressOfFunctions;     // 函数RVA  
  13.         DWORD AddressOfNames;     //函数名RVA  
  14.         DWORD AddressOfNameOrdinals;  // 函数索引号RVA  
  15. }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
  1. #include "windows.h"  
  2. #include "stdio.h"  
  3.   
  4.   
  5. void Doshow(char * place)  
  6. {  
  7.     int i=0;  
  8.     HANDLE hfile = CreateFile(place,   
  9.         GENERIC_READ|GENERIC_WRITE,   
  10.         FILE_SHARE_READ,  
  11.         NULL,  
  12.         OPEN_EXISTING,  
  13.         FILE_ATTRIBUTE_NORMAL,  
  14.         NULL);  
  15.     if(hfile==INVALID_HANDLE_VALUE)  
  16.     {  
  17.   
  18.         printf("Create File Failed.\n");  
  19.         return ;  
  20.     }  
  21.     HANDLE hFileMapping=CreateFileMapping(hfile,NULL,PAGE_READONLY,0,0,NULL);  
  22.     if (hFileMapping==NULL||hFileMapping==INVALID_HANDLE_VALUE)   
  23.     {   
  24.         printf("Could not create file mapping object (%d).\n", GetLastError());  
  25.         return ;  
  26.     }  
  27.     //ÄÚ´æÓ³ÉäÎļþµÄ»ùÖ·  
  28.     LPBYTE lpBaseAddress=(LPBYTE)MapViewOfFile(hFileMapping,FILE_MAP_READ,0,0,0);  
  29.     if (lpBaseAddress == NULL)   
  30.     {   
  31.         printf("Could not map view of file (%d).\n", GetLastError());   
  32.         return ;  
  33.     }  
  34.     PIMAGE_DOS_HEADER pDosHeader=(PIMAGE_DOS_HEADER)lpBaseAddress;  
  35.     PIMAGE_NT_HEADERS pNtHeaders=(PIMAGE_NT_HEADERS)(lpBaseAddress + pDosHeader->e_lfanew);  
  36.     PIMAGE_EXPORT_DIRECTORY pExport=(PIMAGE_EXPORT_DIRECTORY)(pNtHeaders->OptionalHeader.DataDirectory[0].VirtualAddress+lpBaseAddress);  
  37.     int Num=pExport->NumberOfNames;  
  38.     printf("DLLÊÇ%s\n",pExport->Name+lpBaseAddress);  
  39.     printf("¹²ÓÐ%d¸öº¯Êý\n",pExport->NumberOfNames);  
  40.     printf("º¯ÊýÐòºÅ        º¯ÊýÃû\n");  
  41.     for (i;i<Num;i++)  
  42.     {  
  43.         DWORD * k=(DWORD *)(pExport->AddressOfNames+lpBaseAddress+4*i);    //RVAÊý×éÊ×µØÖ·  
  44.         char * functionName=(char *)(*k+lpBaseAddress);  
  45.         printf("%d             %s\n",i+1,functionName);  
  46.     }  
  47.     UnmapViewOfFile(lpBaseAddress);  
  48.     CloseHandle(hFileMapping);  
  49.     CloseHandle(hfile);  
  50.   
  51.   
  52. }  
  53.   
  54. int main()  
  55. {  
  56.     char p[20];  
  57.     printf("ÇëÊäÈëÎļþλÖãº\n");  
  58.     scanf("%s",p);  
  59.     Doshow(p);  
  60.     return 1;  
  61. }  


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

 

 

 

 

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

 

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

原创粉丝点击