• 利用PE数据目录的导入表获取函数名及其地址


    PE文件是以64字节的DOS文件头开始的(IMAGE_DOS_HEADER),接着是一段小DOS程序,然后是248字节的

    NT文件头(IMAGE_NT_HEADERS),NT的文件头位置由IMAGE_DOS_HEADER的e_lfanew给出!


    NT文件头的前4个字节是文件签名(“PE00"字符串),紧接着是20字节的IMAGE_FILE_HEADER结构,它的

    后面是224字节的IMAGE_OPTIONAL_HEADER结构,而就在这个结构里,里面有模块基地址,代码和数据大

    小和基地址、线程堆栈和进程堆的配置,程序入口点的地址,还有数据目录表指针,PE文件还保留着16

    个数据目录,常见的有导入表,导出表,资源和重定位表,而我们这里就是用的到了导入表

    IMAGE_IMPORT_DESCRIPTOR,代码如下

    1. int _tmain(int argc, TCHAR* argv[], TCHAR* envp[])  
    2. {  
    3.  int nRetCode = 0;  
    4.   
    5.  // initialize MFC and print and error on failure  
    6.  if (!AfxWinInit(::GetModuleHandle(NULL), NULL, ::GetCommandLine(), 0))  
    7.  {  
    8.   // TODO: change error code to suit your needs  
    9.   cerr << _T("Fatal Error: MFC initialization failed") << endl;  
    10.   nRetCode = 1;  
    11.  }  
    12.  else  
    13.  {  
    14.   // TODO: code your application's behavior here.  
    15.   CString strHello;  
    16.   strHello.LoadString(IDS_HELLO);  
    17.   cout << (LPCTSTR)strHello << endl;  
    18.  }  
    19.  //这里开始  
    20.  HMODULE hMod = ::GetModuleHandle(NULL);  
    21.  IMAGE_DOS_HEADER *pDosHeader = (IMAGE_DOS_HEADER *)hMod;  
    22.  IMAGE_OPTIONAL_HEADER *pOptHeader = (IMAGE_OPTIONAL_HEADER *)((BYTE *)hMod +   
    23.   
    24. pDosHeader->e_lfanew + 24);  
    25.  IMAGE_IMPORT_DESCRIPTOR *pImportDesc = (IMAGE_IMPORT_DESCRIPTOR *) ((BYTE *)hMod +   
    26.   
    27. pOptHeader->DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress);  
    28.  while(pImportDesc->FirstThunk)  
    29.  {  
    30.   char *pszDllName = (char *)((BYTE *)hMod + pImportDesc->Name);  
    31.   printf("/n模块名称:%s/n", pszDllName);  
    32.   IMAGE_THUNK_DATA *pThunk = (IMAGE_THUNK_DATA *)((BYTE *)hMod + pImportDesc  
    33.   
    34. ->OriginalFirstThunk);  
    35.   int n = 0;  
    36.   //MessageBox(NULL, "Test", "MESS", MB_OK);  
    37.   char *pszFunName = NULL;  
    38.   while(pThunk->u1.Function)  
    39.   {  
    40.    pszFunName = (char *)((BYTE *)hMod + (DWORD)pThunk-  
    41.   
    42. >u1.AddressOfData + 2);  
    43.    PDWORD lpAddr = (DWORD *)((BYTE *)hMod + pImportDesc->FirstThunk) +   
    44.   
    45. n;  
    46.    try  
    47.    {  
    48.     printf("function name : %-25s", (char *)pszFunName);  
    49.    }  
    50.    catch(...)  
    51.    {  
    52.     printf("function name :unknown!");  
    53.    }  
    54.    printf("addr :%0X/n", lpAddr);  
    55.    n++;  
    56.    pThunk++;  
    57.      
    58.     
    59.   }  
    60.   pImportDesc++;  
    61.  }  
    62.  return nRetCode;  
    63. }  

    顺便带上一些检查是否为PE的代码

    1. CFileDialog dlg(true);  
    2.     if(dlg.DoModal() != IDOK)  
    3.     {  
    4.         return ;  
    5.     }  
    6.     HANDLE hFile = ::CreateFile(dlg.GetFileName(), GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);  
    7.     if(hFile == INVALID_HANDLE_VALUE)  
    8.     {  
    9.         ::MessageBox(NULL, "INVALID FILE", "VALID PE", MB_OK);   
    10.     }  
    11.     IMAGE_DOS_HEADER dosHeader;  
    12.     IMAGE_NT_HEADERS ntHeader;  
    13.     bool bValid = false;  
    14.     DWORD dwRead;  
    15.     ::ReadFile(hFile, &dosHeader, sizeof(dosHeader), &dwRead, NULL);  
    16.     if(dwRead == sizeof(dosHeader))  
    17.     {  
    18.         if(dosHeader.e_magic == IMAGE_DOS_SIGNATURE)  
    19.         {  
    20.             if(::SetFilePointer(hFile, dosHeader.e_lfanew, NULL, FILE_BEGIN) != -1)  
    21.             {  
    22.                 ::ReadFile(hFile, &ntHeader, sizeof(ntHeader), &dwRead, NULL);  
    23.                 if( dwRead == sizeof(ntHeader) )  
    24.                 {  
    25.                     if( ntHeader.Signature == IMAGE_NT_SIGNATURE )  
    26.                     {  
    27.                         bValid = true;  
    28.                     }  
    29.                 }  
    30.             }  
    31.         }  
    32.     }  
    33.     if( bValid)  
    34.         ::MessageBox(NULL, "It's a PE", "PE FILE", MB_OK);  
    35.     else  
    36.         ::MessageBox(NULL, "It's not a PE", "PE FILE", MB_OK);  

    1、PE文件格式的背景和由来:

    在开始介绍PE 文件结构之前,有必要向读者提一提常用的PE件结构分析工具:Win32 SDK

    提供的 DUMPBIN 可以转储PE文件和COFF OBJ/LIB 件;Borland 的使用者可用TDUMP 观

    察PE 文件,但TDUMP 不支持COFF OBJ。

    2、PE 文件的顺序结构:

    我们可以把PE 的内存映象结构用下面的图示简要的表示出来:

    DOS   MZ   HEADER

    DOS   STUB

    PE

    Header Signature( “PE/0/0”)

    FileHeader

    OptionalHeader

    Section Table(array of IMAGE_SECTION_HEADER)

    .text

    .data

    .edata

    .idata

    .reloc

    COFF   Line Number

    COFF   Symbols

    Code   View Debug nformation

    2.1、DOS header 和 DOS Stub:

    所有的 PE 文件 (或32位的DLLS)都必须以一个简单的DOS MZ header 为起始

    (IMAGE_DOS_HEADER 结构体)。在实际中,除了e_lfanew (PE header 的 件偏移量)我

    们可以不必太关心其余成 数据。DOS Stub只是提供了PE 文件在DOS 下执行时 ,DOS

    会把它当作有效的执行文件而顺利执行。通常会在屏幕上输出 " This program cannot

    run in DOS mode " 之类的提示语。程序员也可以改变DOS Stub,根据自己的意图实现

    完整的 DOS代码。

    2.2、PE Header:

    PE 表头内含程序代码和各种资料的大小位置、适用的操作系统、堆栈(stack)最初大小

    等等重要信息。犹如执行文件的纲目。整个PEHeader是一个IMAGE_NT_HEADERS 结构体,

    在Win32 SDK 中定义如下:

    1. typedef struct _IMAGE_NT_HEADERS   
    2.   
    3. {   
    4.   
    5.     DWORD  Signature;//PE 标记,值为50h,45h,00h,00h (ASCII:”PE/0/0”)   
    6.   
    7.     IMAGE_FILE_HEADER   FileHeader;   
    8.   
    9.     IMAGE_OPTIONAL_HEADER32   OptionalHeader;   
    10.   
    11. }IMAGE_NT_HEADERS32,*PIMAGE_NT_HEADERS32;   

    PE Signature 为校验标记,由连接器产生。通常装载器通过指向此位的指针e_lfanew (DOS

    header 中)来检验此文件是否为PE 格式。FileHeader域包含了关于PE 文件物理分布的

    一般信息, opionalHeader域包含了关于PE 文件逻辑分布的信息。显然可以pNTHeader=

    dosHeader + dosHeader->e_lfanew 获得pe 头的地址。

    2.2.1、File Header 结构域:

    在Win32 SDK 中File Header 定义如下:

    1. typedef struct _IMAGE_FILE_HEADER   
    2.   
    3. {   
    4.   
    5.   
    6.   
    7.     WORD        Machine;   
    8.   
    9.     WORD        NumberOfSections;   
    10.   
    11.     DWORD       TimeDateStamp;   
    12.   
    13.     DWORD       PointerToSymbolTable;   
    14.   
    15.     DWORD       NumberOfSymbols;   
    16.   
    17.     WORD        SizeOfOptionalHeader;   
    18.   
    19.     WORD        Characteristics;   
    20.   
    21. }IMAGE_FILE_HEADER,*PIMAGE_FILE_HEADER;   

    各个成 的说明列表如下:

    成员说

    Machine 该文件运行所要求的CPU。对于Intel平台,该值是

    IMAGE_FILE_MACHINE_I386(14Ch)。

    NumberOfSections 文件的节数目。如果我们要在文件中增加或删除一个节,就需要修改

    这个值。

    TimeDateStamp 连接器创建文件时刻。从1969/12/31 4:00 P.M. 之后的总秒数。

    PointerToSymbolTable COFF 符号表格的偏移位置,用于调试。

    NumberOfSymbols COFF 符号表格中符号的个数,用于调试。

    SizeOfOptionalHeader指示紧随本结构之后的OptionalHeader结构大小,必须为有效值。

    Characteristics 关于文件信息的标记,比如文件是exe(0x0002) 还是dll(0x2000)在

    实际应用中,有三个域对我们有用:NumberOfSections,SizeOfOptionalHeader和

    Characteristics。我们通常不会改变SizeOfOptionalHeader和Characteristics的值,

    如果要遍历节表就得使用 NumberOfSections。

    2. 2。2、Option Header 结构域:

    这是PE 表头的第三个成分。对于PE 文件而言,这一部分其实并不是可有可无。因为COFF

    格式允许不同的设计者在标准的File header之后定义一个结构。Optional header 正是

    PE 设计者认为在基本的File header 信息之外还需要的一些重要的信息。完整的结构定

    义可以参考Win32 SDK 中的WINNT.H 头文件,这里列举了其中的重要成员说明 :

    成员说明

    AddressOfEntryPoint PE 装载器准备运行的PE文件的第一条指令的RVA。若要改变整个

    执行的流程,可以将该值指定到新的RVA,这样新RVA 处的指令首先被执行。

    ImageBase PE文件的优先装载地址。如果该值是400000h,PE装载器将尝试把文件装到

    虚拟地址空间的400000h 处。若该地址区域已被其他模块占用,那PE 装载器会选用其他

    空闲地址,这个过程我们把它叫做基址重定位。

    SectionAlignment 内存中节对齐的粒度(granularity)。一旦装载到内存中,每个节(s

    ection)保证从一个此值的倍数的虚拟地址开始。如果该值是4096Byte(1000hByte),那么每节

    的起始地址必须是4096 的倍数。

    FileAlignment 文件中节对齐的粒度。文件中 成每个section 的原始数据(raw   data)

    保证是从一个此值的倍数的虚拟地址开始。如果该值是512Byte(200hByte),,那么每节的起始地址必

    须是512 的倍数。

    MajorSubsystemVersion

    MinorSubsystemVersion win32子系统版本。若PE 文件是专门为Win32 设计的,该子系

    统版本必定是4.0 否则不会有3维立体感对话框等。

    SizeOfImage 内存中整个PE 映像体(map)的尺寸。它是所有头和节经过节对齐处理后的

    大小。

    SizeOfHeaders 所有头+节表的大小,也就等于文件尺寸减去文件中所有节的尺寸。可以

    以此值作为PE 文件第一节的文件偏移量。

    Subsystem NT 用来识别PE 文件属于哪个子系统。对于大多数Win32程序,只有两类值:

    Windows GUI 和 Windows    CUI  (控制台)。

    DataDirectory IMAGE_DATA_DIRECTORY 结构数 。每个结构给出一个重要数据结构的

    RVA,比如引入地址表等。在PE header 中,有很多地址指针是用RVA 来表示的。 RVA 代

    表相对虚拟地址 (Relative Virtual Address)。简言之,RVA 是虚拟空间中到参考点的

    一段距离,类似文件偏移量。当然它是相对虚拟空间里的一个地址,而不是文件头部。举

    例来说,如果PE 文件装入虚拟地址(VA)空间的400000h 处,且进程从虚址401000h 开始

    执行,我们可以说进程执行起始地址在RVA 1000h。每个RVA 都是相对于模块的起始VA

     (Virtual Address)而言的。

    2.2.3、Option  Header 的中的重要数据成员 Data Directory:

    Data Directory   定义为IMAGE_DATA_DIRECTORY 结构体数 ,每个数据元素给出一个重

    要数据结构的RVA (Relative Visual Address相对虚地址),比如引入表地址、重定位

    表地址。通常共16个成员 。(详细的宏定义请参阅WIN32 SDK 的WINNT.H 头文件)

    这个数据起到让装载器迅速的在内存中找到特定的节(section)的作用,省去了遍历节表

    的麻烦。

    2. 3、Section  Header:

    紧接在PE header 的是section table (IMAGE_SECTION_HEADER)。每个表项包含有该节

    的属性、偏移量等。如果PE 文件里有3个节,那么此数据结构就有3个元素。为了更好的

    理解PE header和Section header在PE 文件中的组织关系,我们可以把PE 文件看作一

    逻辑磁盘,PE header 是boot 扇区而sections是各种文件,节表视为逻辑磁盘中的根目

    录。节表定义如下:

    1. typedef truct _IMAGE_SECTION_HEADER   
    2.   
    3. {   
    4.   
    5.         BYTE       Name[IMAGE_SIZEOF_SHORT_NAME];   
    6.   
    7.         union   
    8.   
    9.         {   
    10.   
    11.             DWORD       PhysicalAddress;   
    12.   
    13.             DWORD       VirtualSize;   
    14.   
    15.         }Misc;   
    16.   
    17.         DWORD       VirtualAddress;   
    18.   
    19.         DWORD       SizeOfRawData;   
    20.   
    21.         DWORD       PointerToRawData;   
    22.   
    23.         DWORD       PointerToRelocations;   
    24.   
    25.         DWORD       PointerToLinenumbers;   
    26.   
    27.         WORD        NumberOfRelocations;   
    28.   
    29.         WORD        NumberOfLinenumbers;   
    30.   
    31.         DWORD       Characteristics;   
    32.   
    33. }IMAGE_SECTION_HEADER,*PIMAGE_SECTION_HEADER;   

    重要数据成员的说明列表如下:

    成员说明

    Name 节名 长度不超过8字节。仅仅是个标记而已,注意这里不用null结束。

    VirtualAddres 本节的RVA (相对虚拟地址)。PE装载器将节映射至内存时会读取本值。

    如果域值是1000h,而PE 文件装在地址400000h 处,那么本节就被载到401000h。

    SizeOfRawData 经过文件对齐处理后节尺寸,PE 装载器提取本域值了解需映射入内存的

    节字节数。

    PointerToRawData 这是节基于文件的偏移量,PE装载器通过本域值找到节数据在文件中

    的位置。


    ----------------------- Page 4-----------------------

    Characteristics 包含标记以指示节属性,比如节是否含有可执行代码、初始化数据、未

    初始数据,是否可写、可读等。

    2.4、Section域

    节(section)是PE 文件真正内容的划分。每一节是是拥有共同属性的数据的集合。每节

    都有相应的命名,不过这个命名不是太重要,只要是相同属性的内容都可以放进一节,命

    名只是便于识别。下面重点介绍一下一些重要的段。

    2.4.1、text section

    此节一般包含有连接器连接的所有obj 目标文件的执行代码。这个执行代码块是一个大

    的.text。不同于在DOS下面的执行文件可以分成几部分。如果是使用的Borland C++,

    其编译器将产生的代码存于名为CODE 的区域,连接器连接到名为CODE 而不是.text 的

    节中。

    2.4.2、data section

    .data 是初始化的数据块。这些数据块包括编译时被初始化的字符串常量、全局(globle)

    和静 (static)变量。

    2.4.3、bss section

    任何没有初始化的全局和局部变量都会存放到.bss节中。这个节并不占用文件的储藏空

    间,所以 RawDataOffset         总是为0。

    2.4.4、rsrc section

    该节包含模块的全部资源。如图标、菜单、位图等等。

    2.4.5、idata section

    .idata 包含其他外来的如DLL 中的函数及数据信息。PE 文件的每一个输入函数都明确的

    列于该节中。

    2.4.6、edata section

    与.idata对应,.edata 是该PE 文件输出函数和数据的列表,以供其他模块引用。有的

    PE 文件没有引出函数或数据,也就没有该节。

    3、其余部分:

    在节 (Sections)的后面是COFF 符号表格、COFF调试信息、COFF 行号信息。这些域对

    我们的作用不大,有兴趣的读者可参阅

  • 相关阅读:
    HTML 5 音频
    HTML 5 视频
    HTMl链接- target/ name
    HTML 链接
    OGNL_一点
    struts_表单得到数据
    MySql_十六进制值
    HTML 事件属性(下)
    作业3-2 输入一个正整数 n,再输入 n 个学生的成绩,计算平均成绩,并统计所有及格学生的人数
    作业3-1 .输入一个整数 x,计算并输出下列分段函数 sign(x) 的值
  • 原文地址:https://www.cnblogs.com/milantgh/p/3956213.html
Copyright © 2020-2023  润新知