您的位置:首页 > 其它

深入解析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病毒的基本功能等等,如果可能,一定会给大家分享我的“成果”。

 

最后菜鸟言论,仅供娱乐。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: