自己的知识分享文章,整理到博客上 ---------hgy notes.

一.PE简介

PE全名Portable Executable File Format(可移植的执行体),是目前window主流可执行文件格式, 是一种通用于所有window平台和所有CPU上的文件格式

二.PE文件的定义

PE文件的类型定义集中在WinNT.h这个类中,打开WinNT.h,再搜索Image Format(搜到的在9560行),为什么叫Image Format,估计意思就是镜像文件,在这一节点后给出了DOS MZ格式和window3.1的NE格式文件,之后就是PE文件,那么在这个文件里我们几乎可以找到所有关于PE文件的每个数据结构定义.枚举类型.常量定义

EXE和DLL都是PE文件,唯一的区别是用了一个字段标识这个文件是EXE还是DLL,另外win64只对PE格式作了简单的变化,仅仅把32位字段扩展成了64位了,新格式叫PE32+,其实我们不需要关心这些,哥特别仔细看了下WinNT.h,搜下_WIN64,在9872行找到这样的定义:

#ifdef _WIN64
typedef IMAGE_OPTIONAL_HEADER64             IMAGE_OPTIONAL_HEADER;
typedef PIMAGE_OPTIONAL_HEADER64            PIMAGE_OPTIONAL_HEADER;
#define IMAGE_NT_OPTIONAL_HDR_MAGIC         IMAGE_NT_OPTIONAL_HDR64_MAGIC
#else
typedef IMAGE_OPTIONAL_HEADER32             IMAGE_OPTIONAL_HEADER;
typedef PIMAGE_OPTIONAL_HEADER32            PIMAGE_OPTIONAL_HEADER;
#define IMAGE_NT_OPTIONAL_HDR_MAGIC         IMAGE_NT_OPTIONAL_HDR32_MAGIC
#endif

很明显,加了宏控制,我们只需调用IMAGE_OPTIONAL_HEADER这样的宏,就能在win32和win64下横行了.

三.PE文件图

PE文件图是从论坛上找的,很强大的一张图,大家可以边参考图片边理解PE、(找得辛苦,别浪费了)

四.重要的概念

1.我们要分析的PE文件有两种,一种是已运行的,就是已装到虚拟地址的,另一种是不运行,直接文件解析,这样可以直接给文件加壳,PE文件不是作为单一的内存映射文件被装入内存的,PE加载器遍历PE文件来决定文件哪部分被映射,但是PE使用的是一个平面地址,这就是说,偏移位置高的总是会被映射到相对较高的位置,这么说吧,PE有很多块(section),每一块都有一个RVA和RAW,根椐这两个值来得到相应的区域

为了便于大家理解,哥又摆渡了张图:

再来回味下,偏移位置高的总是会被映射到相对较高的位置,有人问为什么,这个主要是内存分块和文件分块的大小不同引起的吧,具体看核心编程去。还要注意前面三块在文件和内存映像中都一样.

2.上面提到了两个重要概念,RVA,RAW,这两个概念相当重要

RVA:Relative Virtual Address 相对虚拟地址,在运行时,API拦截等操作需要用它来确定导入表的内存地址,这个值会被给出,那么有如下关系:

虚拟地址(VA) = 基地址(ImageBase) + 相对虚拟地址(RVA)

RAW:相对文件偏移地址或相对物理地址,文件偏移地址是从PE文件的第一个字节开始计数,起始值为0

那么用UE之类的十六进制工具打开文件所显示的地址就是RAW,好理解吧

3. 基地址(ImageBase),WINNT将Module的基地址作为Module的实例句柄(Hinstance),可以通过GetModuleHandle得到,在写PE分析器时,经常用内存映射文件,那么MapViewOfFile返回文件的基地址(ImageBase)

五.学习PE的工具

推荐Stud_PE这个工具, 由于大家可能对这个工具不太熟悉,这里使用UE打开,示例文件来自网络

六.PE表头

所有PE都是以一个DOS stub的小程序开始的,有了它,一旦程序运行在DOS下,DOS就会运行DOS stub。所以第一个是DOS头IMAGE_DOS_HEADER

定义如下J (从WinNt.h中拷来的,重要的加个中文注释吧!)

typedef struct _IMAGE_DOS_HEADER {      // DOS .EXE headerWORD   e_magic;    // DOS标记“MZ”(9568行有它的宏:IMAGE_DOS_SIGNATURE)WORD   e_cblp;                      // Bytes on last page of fileWORD   e_cp;                        // Pages in fileWORD   e_crlc;                      // RelocationsWORD   e_cparhdr;                   // Size of header in paragraphsWORD   e_minalloc;                  // Minimum extra paragraphs neededWORD   e_maxalloc;                  // Maximum extra paragraphs neededWORD   e_ss;                        // Initial (relative) SS valueWORD   e_sp;                        // Initial SP valueWORD   e_csum;                      // ChecksumWORD   e_ip;                        // DOS代码入口IPWORD   e_cs;                        // DOS代码入口CSWORD   e_lfarlc;                    // File address of relocation tableWORD   e_ovno;                      // Overlay numberWORD   e_res[4];                    // Reserved wordsWORD   e_oemid;                     // OEM identifier (for e_oeminfo)WORD   e_oeminfo;                   // OEM information; e_oemid specificWORD   e_res2[10];                  // Reserved wordsLONG   e_lfanew;                    // 指向PE文件头 “PE”,0,0} IMAGE_DOS_HEADER, *PIMAGE_DOS_HEADER;

这个结构体两个参数最重要,e_magic:需要被设置为ME

#define IMAGE_DOS_SIGNATURE                 0x5A4D      // MZ

e_lfanew定义了真正的PE文件头的相对偏移量RVA,其实是不是RVA都不重要,上面也提到了,( PE文件磁盘与内存映像结构图),前三块文件和内存都一样,IMAGE_DOS_HEADER大小为64,那么e_lfanew占四个字节,所以e_lfanew的起始RAW为64-4=60(3Ch),那么我们随便用UE打开一个文件,比如:

黑色标出的四位就是000000B0,也就是PE真正的头的RVA,同时,大家也应该注意到起始位的两个字节MZ,CS+IP确定了程序的入口点,大家可以用Ollydbg调试。

那么接下来当然是说PE真正的头IMAGE_NT_HEADERS:

定义如下(哥继续从WinNt.h中拷过来)

typedef struct _IMAGE_NT_HEADERS {DWORD Signature;//IMAGE_FILE_HEADER FileHeader;IMAGE_OPTIONAL_HEADER32 OptionalHeader;
} IMAGE_NT_HEADERS32, *PIMAGE_NT_HEADERS32;

第一个参数是PE00的定义:

#define IMAGE_NT_SIGNATURE                  0x00004550  // PE00

继续看下去000000B0:

那么我们可以随手写个判断是否为PE文件的函数了:

假家我们载入一个文件:

ImageBase=MapViewOfFile(hMapping,FILE_MAP_READ,0,0,0);//得到文件在内存中的基地址

BOOL IsPEFile(LPVOID ImageBase)
{PIMAGE_DOS_HEADER  pDH=NULL;PIMAGE_NT_HEADERS  pNtH=NULL;if(!ImageBase)return FALSE;pDH=(PIMAGE_DOS_HEADER)ImageBase;if(pDH->e_magic!=IMAGE_DOS_SIGNATURE)return FALSE;pNtH=(PIMAGE_NT_HEADERS32)((DWORD)pDH+pDH->e_lfanew);if (pNtH->Signature != IMAGE_NT_SIGNATURE )return FALSE;return TRUE;
}

第二个参数IMAGE_FILE_HEADER:

typedef struct _IMAGE_FILE_HEADER {WORD    Machine;                            //运行平台WORD    NumberOfSections;                   //文件的区块数目DWORD   TimeDateStamp;                      //文件创建的日期和时间DWORD   PointerToSymbolTable;               //指向符号表(用于调试)DWORD   NumberOfSymbols;                    //符号表中符号个数(用于调试)WORD    SizeOfOptionalHeader;               //IMAGE_OPTIONAL_HEADER32的大小WORD    Characteristics;                    //文件属性
} IMAGE_FILE_HEADER, *PIMAGE_FILE_HEADER;

SizeOfOptionalHeader指出了IMAGE_OPTIONAL_HEADER的大小,32位这个值为00E0h,64位为00F0h(32的二倍),但有可能出现更大的值,所以这个大小被做为一个变量传递.刚提到的文件000000B0开始为PE头,加上20个字节为000000C4:

Characteristics也是一个比较有用的字段,我们前面提到了,用了一个字段标识这个文件是EXE还是DLL,那这就是那个字段.我在9715-9729行找到了它们的信息:(同样,哥把它们换成了中文翻译)

#define IMAGE_FILE_RELOCS_STRIPPED           0x0001  // 文件中不存在重定位信息
#define IMAGE_FILE_EXECUTABLE_IMAGE          0x0002  // 文件可执行,如果为0,一般是链接时出了问题
#define IMAGE_FILE_LINE_NUMS_STRIPPED        0x0004  // 行号信息被移除
#define IMAGE_FILE_LOCAL_SYMS_STRIPPED       0x0008  // 符号信息被移除.
#define IMAGE_FILE_AGGRESIVE_WS_TRIM         0x0010  // Agressively trim working set
#define IMAGE_FILE_LARGE_ADDRESS_AWARE       0x0020  // 应用程序可以处理超过2G的地址,从NTSP3开始后,可以分配2-3G用户区
#define IMAGE_FILE_BYTES_REVERSED_LO         0x0080  // 处理器的低位字节是相反的
#define IMAGE_FILE_32BIT_MACHINE             0x0100  // 32位机器
#define IMAGE_FILE_DEBUG_STRIPPED            0x0200  // 在.DBG file 中的Debugging 信息被移除
#define IMAGE_FILE_REMOVABLE_RUN_FROM_SWAP   0x0400  // If Image is on removable media, copy and run from the swap file.
#define IMAGE_FILE_NET_RUN_FROM_SWAP         0x0800  // If Image is on Net, copy and run from the swap file.
#define IMAGE_FILE_SYSTEM                    0x1000  // 系统文件
#define IMAGE_FILE_DLL                       0x2000  // 文件是DLL(这就是EXE和DLL的区别)
#define IMAGE_FILE_UP_SYSTEM_ONLY            0x4000  // File should only be run on a UP machine
#define IMAGE_FILE_BYTES_REVERSED_HI         0x8000  //处理器的高位字节是相反的

第三个参数IMAGE_OPTIONAL_HEADER32:(同样从9776行拷来了:)

typedef struct _IMAGE_OPTIONAL_HEADER {WORD    Magic;// 标志字, ROM 映像(0107h),普通32可执行文件(010Bh),普通64可执行文件(020Bh)(DLL和EXE都是可执行文件)BYTE    MajorLinkerVersion;// 链接程序的主版本号BYTE    MinorLinkerVersion;// 链接程序的次版本号DWORD   SizeOfCode;// 所有含代码的节的总大小DWORD   SizeOfInitializedData;// 所有含已初始化数据的节的总大小DWORD   SizeOfUninitializedData;// 所有含未初始化数据的节的大小DWORD   AddressOfEntryPoint;//程序执行入口RVADWORD   BaseOfCode;//代码节的起始RVADWORD   BaseOfData;//数据结的起始RVADWORD   ImageBase;//程序默认装入基地址DWORD   SectionAlignment;// 内存中的区块的对齐大小DWORD   FileAlignment;// 文件中的区块的对齐大小WORD    MajorOperatingSystemVersion;//操作系统主版本号WORD    MinorOperatingSystemVersion; //操作系统次版本号WORD    MajorImageVersion;//用户自定义主版本号WORD    MinorImageVersion;//用户自定义次版本号WORD    MajorSubsystemVersion; //所需子系统主版本号WORD    MinorSubsystemVersion; //所需子系统次版本号DWORD   Win32VersionValue;// 莫须有字段,不被病毒利用的话一般为0DWORD   SizeOfImage;// 映像装入内存后的总尺寸DWORD   SizeOfHeaders;//DOS头,PE头,区表总大小DWORD   CheckSum;//映象校验和WORD    Subsystem;//文件子系统WORD    DllCharacteristics;// DllMain()函数何时被调用,默认为 0DWORD   SizeOfStackReserve;// 初始化时的栈大小DWORD   SizeOfStackCommit;// 初始化时实际提交的栈大小DWORD   SizeOfHeapReserve;// 初始化时保留的堆大小DWORD   SizeOfHeapCommit;// 初始化时实际提交的堆大小DWORD   LoaderFlags;// 与调试有关,默认为 0DWORD   NumberOfRvaAndSizes;// 下边数据目录的项数,这个字段自Windows NT 发布以来一直是16IMAGE_DATA_DIRECTORY DataDirectory[IMAGE_NUMBEROF_DIRECTORY_ENTRIES];// // 数据目录表
} IMAGE_OPTIONAL_HEADER32, *PIMAGE_OPTIONAL_HEADER32;

介绍几个重要的,不然太多了!

AddressOfEntryPoint:

程序执行入口RVA,对于DLL,这个入口点是在进程初始化和关闭时以及线程创建/销毁时调用,在大多数的PE文件中,这个地址并不直接指向Main/WinMain或DllMain,而是指向运行时库代码并由它来调用上述的函数,如果在DLL中把这个值设为0,则前面提到的通知消息都不能收到,地址,这是一个RVA地址,如果在一个可执行文件上附加了一段代码并想让这段代码首先被执行,那么只需要将这个入口地址指向附加的代码就可以了

ImageBase:

文件在内存中的首选装入地址,如果有可能(也就是说,目前如果没有其他占据这块地址,它是一个正确对齐且合法的地址),加载器试图在这个地址装入PE文件,如果可执行文件是在这个地址装入的,那么加载器会跳过应用基址重定位的步骤

对于EXE文件来说,由于每个文件总是使用独立的虚拟地址空间,优先装入地址不可能被**模块占据,所以EXE总是能够按照这个地址装入,这也意味着EXE 文件不再需要重定位信息。对于DLL文件来说,由于多个DLL文件全部使用宿主EXE文件的地址空间,不能保证优先装入地址没有被**的DLL使用,所以 DLL文件中必须包含重定位信息以防万一。因此,在前面介绍的 IMAGE_FILE_HEADER 结构的 Characteristics 字段中,DLL 文件对应的 IMAGE_FILE_RELOCS_STRIPPED 位总是为0,而EXE文件的这个标志位总是为1

链接的时候,可以通过对link.exe指定/base:address选项来自定义优先装入地址,如果不指定这个选项的话,一般EXE文件的默认优先装入地址被定为00400000h,而DLL文件的默认优先装入地址被定为10000000h。

SectionAlignment和FileAlignment:

SectionAlignment字段指定了节被装入内存后的对齐单位。也就是说,每个节被装入的地址必定是本字段指定数值的整数倍。而FileAlignment字段指定了节存储在磁盘文件中时的对齐单位

Subsystem

一个标明可执行文件所期望的子系统的枚举值,这个值只对EXE是重要的:

链接时的/subsystem:**选项指定的就是这个字段的值,如果将子系统指定为Windows CUI,那么系统会自动为程序建立一个控制台窗口,而指定为Windows GUI的话,窗口必须由程序自己建立

取值

Windows.inc中的预定义值

含义

0

IMAGE_SUBSYSTEM_UNKNOWN

未知的子系统

1

IMAGE_SUBSYSTEM_NATIVE

不需要子系统(如驱动程序)

2

IMAGE_SUBSYSTEM_WINDOWS_GUI

Windows图形界面

3

IMAGE_SUBSYSTEM_WINDOWS_CUI

Windows控制台界面

5

IMAGE_SUBSYSTEM_OS2_CUI

OS2控制台界面

7

IMAGE_SUBSYSTEM_POSIX_CUI

POSIX控制台界面

8

IMAGE_SUBSYSTEM_NATIVE_WINDOWS

不需要子系统

9

IMAGE_SUBSYSTEM_WINDOWS_CE_GUI

Windows CE图形界面

DataDirectory[16]

这个字段可以说是最重要的字段之一,它由16个相同的IMAGE_DATA_DIRECTORY结构组成,虽然PE文件中的数据是按照装入内存后的页属性归类而被放在不同的节中的,但是这些处于各个节中的数据按照用途可以被分为导出表、导入表、资源、重定位表等数据块,这16个 IMAGE_DATA_DIRECTORY结构就是用来定义多种不同用途的数据块的

typedef struct _IMAGE_DATA_DIRECTORY {DWORD   VirtualAddress;//数据块的起始RVADWORD   Size;//数据块的长度
} IMAGE_DATA_DIRECTORY, *PIMAGE_DATA_DIRECTORY;

索引

索引值在Windows.inc中的预定义值

对应的数据块

0

IMAGE_DIRECTORY_ENTRY_EXPORT

导出表

1

IMAGE_DIRECTORY_ENTRY_IMPORT

导入表

2

IMAGE_DIRECTORY_ENTRY_RESOURCE

资源

3

IMAGE_DIRECTORY_ENTRY_EXCEPTION

异常(具体资料不详)

4

IMAGE_DIRECTORY_ENTRY_SECURITY

安全(具体资料不详)

5

IMAGE_DIRECTORY_ENTRY_BASERELOC

重定位表

6

IMAGE_DIRECTORY_ENTRY_DEBUG

调试信息

7

IMAGE_DIRECTORY_ENTRY_ARCHITECTURE

版权信息

8

IMAGE_DIRECTORY_ENTRY_GLOBALPTR

具体资料不详

9

IMAGE_DIRECTORY_ENTRY_TLS

Thread Local Storage

10

IMAGE_DIRECTORY_ENTRY_LOAD_CONFIG

具体资料不详

11

IMAGE_DIRECTORY_ENTRY_BOUND_IMPORT

具体资料不详

12

IMAGE_DIRECTORY_ENTRY_IAT

导入函数地址表

13

IMAGE_DIRECTORY_ENTRY_DELAY_IMPORT

具体资料不详

14

IMAGE_DIRECTORY_ENTRY_COM_DESCRIPTOR

具体资料不详

15

未使用

在 PE文件中寻找特定的数据时就是从这些IMAGE_DATA_DIRECTORY结构开始的,比如要存取资源,那么必须从第3个 IMAGE_DATA_DIRECTORY结构(索引为2)中得到资源数据块的大小和位置;同理,如果要查看PE文件导入了哪些DLL文件的哪些API函数,那就必须首先从第2个IMAGE_DATA_DIRECTORY结构得到导入表的位置和大小

(这里要特别提示下,运行时是VA = RVA+基地址,不运行时是RVA转换成RAW,VA=RAW+基地址)

有个叫ImageRvaToVa的函数,哥特别测试过,无论PE有没有运行,这个函数得到的值再减去基地址都是PE文件偏移量(RAW)。

再介绍下ImageDirectoryEntryToData:,哥也特别测试过,无论PE有没有运行,为FALSE,得到的值减去基地址为RAW,设为TRUE,得到的值减去基地址为RVA

当然我们也可以计算节的偏差来得到,而不使用window的API,下面我会讲到这种方法.

七.区块表

紧接着IMAGE_NT_HEADERS后的是区块表,它是一个IMAGE_SECTION_HEADER结构数组

数目由表头的NumberOfSections指出(自己找

IMAGE_SECTION_HEADERS(在9623行找到):

typedef struct _IMAGE_SECTION_HEADER {BYTE    Name[IMAGE_SIZEOF_SHORT_NAME];//8个字节的块名union {DWORD   PhysicalAddress;//物理地址DWORD   VirtualSize; //真实长度,这两个值是一个联合结构,可以使用其中的任何一个,一般是取后一个} Misc;DWORD   VirtualAddress;//RVADWORD   SizeOfRawData;//在文件中对齐后的尺寸DWORD   PointerToRawData;//RawDWORD   PointerToRelocations;//重定位的偏移量DWORD   PointerToLinenumbers;//行号的偏移量WORD    NumberOfRelocations;//重定位项的数目WORD    NumberOfLinenumbers;//行号表的行号数目DWORD   Characteristics;//节属性,如可读可写可执行,
} IMAGE_SECTION_HEADER, *PIMAGE_SECTION_HEADER;

Name

区块名。这是一个由8位的ASCII 码名,用来定义区块的名称。多数区块名都习惯性以一个“.”作为开头(例如:.text),这个“.” 实际上是不是必须的。值得我们注意的是,如果区块名超过 8 个字节,则没有最后的终止标志“NULL” 字节。并且前边带有一个“$” 的区块名字会从连接器那里得到特殊的待遇,前边带有“$” 的相同名字的区块在载入时候将会被合并,在合并之后的区块中,他们是按照“$” 后边的字符的字母顺序进行合并的。
每个区块的名称都是唯一的,不能有同名的两个区块。但事实上节的名称不代表任何含义,他的存在仅仅是为了正规统一编程的时候方便程序员查看方便而设置的一个标记而已。所以将包含代码的区块命名为“.Data” 或者说将包含数据的区块命名为“.Code” 都是合法的,事实上你可以自定义节名,#pragam data_seg去查查干嘛用的(J)
 因此,当我们要从PE 文件中读取需要的区块时候,不能以区块的名称作为定位的标准和依据,正确的方法是按照 IMAGE_OPTIONAL_HEADER32 结构中的数据目录字段结合进行定位。

Virtual Size

对表对应的区块的大小,这是区块的数据在没有进行对齐处理前的实际大小,
Virtual Address

该区块装载到内存中的RVA 地址。这个地址是按照内存页来对齐的,因此它的数值总是 SectionAlignment 的值的整数倍。在Microsoft 工具中,第一个快的默认 RVA 总为1000h。在OBJ 中,该字段没有意义地,并被设为0。

SizeOfRawData

该区块在磁盘中所占的大小。在可执行文件中,该字段是已经被FileAlignment 潜规则处理过的长度。
PointerToRawData

该区块在磁盘中的偏移。这个数值是从文件头开始算起的偏移量哦。
PointerToRelocations

这哥们在EXE文件中没有意义,在OBJ 文件中,表示本区块重定位信息的偏移值。(在OBJ 文件中如果不是零,它会指向一个IMAGE_RELOCATION 结构的数组)
PointerToLinenumbers

行号表在文件中的偏移值,文件的调试信息,于我们没用,鸡肋。

NumberOfRelocations

这哥们在EXE文件中也没有意义,在OBJ 文件中,是本区块在重定位表中的重定位数目来着。
NumberOfLinenumbers

该区块在行号表中的行号数目,鸡肋。
Characteristics

该区块的属性。该字段是按位来指出区块的属性(如代码/数据/可读/可写等)的标志。
(9651行)

字段集

用途

IMAGE_SCN_CNT_CODE            0x00000020

包含代码,常与10000000h设置

IMAGE_SCN_CNT_INITIALIZED_DATA       0x00000040

包含已初始化数据

IMAGE_SCN_CNT_UNINITIALIZED_DATA     0x00000080

包含未初始化数据

IMAGE_SCN_MEM_DISCARDABLE            0x02000000

该区块可被丢弃,因为当它一旦被装入后,
进程就不在需要它了,典型的如重定位区块

IMAGE_SCN_MEM_EXECUTE                0x20000000

该区块可以执行。通常当0x00000020被设置
时候,该标志也被设置。

IMAGE_SCN_MEM_READ                   0x40000000

该区块可读,可执行文件中的区块总是设置该
标志。

IMAGE_SCN_MEM_WRITE                  0x80000000

该区块可写

J自己去看点击打开链接

八.换算RVA和文件偏移

当处理PE 文件时候,任何的 RVA 必须经过到文件偏移的换算,才能用来定位并访问文件中的数据,但换算却无法用一个简单的公式来完成,事实上,可用的方法就是最土最笨的方法:(前面提到了一种懒人方法,自己去翻)
步骤一:循环扫描区块表得出每个区块在内存中的起始 RVA(根据IMAGE_SECTION_HEADER 中的VirtualAddress 字段),并根据区块的大小(根据IMAGE_SECTION_HEADER 中的SizeOfRawData 字段)算出区块的结束 RVA(两者相加即可),最后判断目标 RVA 是否落在该区块内。

步骤二:通过步骤一定位了目标 RVA 处于具体的某个区块中后,那么用目标 RVA 减去该区块的起始 RVA ,这样就能得到目标 RVA 相对于起始地址的偏移量 RVA2.

步骤三:在区块表中获取该区块在文件中所处的偏移地址(根据IMAGE_SECTION_HEADER 中的PointerToRawData 字段), 将这个偏移值加上步骤二得到的 RVA2 值,就得到了真正的文件偏移地址。
为了懒人的方便,提供个路径:win95有代码(我也懒,不想找了):

九.导入表

首先,我们知道PE 文件中的数据被载入内存后根据不同页面属性被划分成很多区块(节),并有区块表(节表)的数据来描述这些区块。这里我们需要注意的问题是:一个区块中的数据仅仅只是由于属性相同而放在一起,并不一定是同一种用途的内容。例如接着要讲的输入表、输出表等就有可能和只读常量一起被放在同一个区块中,因为他们的属性都是可读不可写的。
其次,由于不同用途的数据有可能被放入同一个区块中,因此仅仅依靠区块表是无法确定和定位的。那要怎么办?对了,PE 文件头中 IMAGE_OPTIONAL_DEADER32 结构的数据目录表来指出他们的位置,我们可以由数据目录表来定位的数据包括输入表、输出表、资源、重定位表和TLS等15 种数据。(数据目录表)

当PE 文件被执行的时候,Windows 加载器将文件装入内存并将导入表(Export Table) 登记的动态链接库(一般是DLL 格式)文件一并装入地址空间,再根据DLL 文件中的函数导出信息对被执行文件的IAT 进行修正。
注意,对于磁盘中的PE文件,它是无法得知这些输入函数在内存中的地址,只有PE被装入内存后.

在PE文件中,有一组数据结构,它们分别对应着每个被输入的DLL,每个这样的结构都给出了被输入的DLL的名称并指向一组函数指针,这组函数指针被称为IAT(Import Address Table)

每个被引入的API在IAT中都有它自己保留的位置,在那里它将被加载器写入输入函数的地址,一旦模块被装入,IAT中包含所要调用输入函数的地址。

输入表以一个IMAGE_IMPORT_DESCRIPTOR(IID)数线开始,每个隐式链接的DLL都有一个IID,隐式链接和动态加载的区别,建议大家查下window的编译过程,

typedef struct _IMAGE_IMPORT_DESCRIPTOR {union {DWORD   Characteristics;            // 0 for terminating null import descriptorDWORD   OriginalFirstThunk;         // RVA to original unbound IAT (PIMAGE_THUNK_DATA)};DWORD   TimeDateStamp;                  // 0 if not bound,// -1 if bound, and real date\time stamp//     in IMAGE_DIRECTORY_ENTRY_BOUND_IMPORT (new BIND)// O.W. date/time stamp of DLL bound to (Old BIND)DWORD   ForwarderChain;                 // -1 if no forwardersDWORD   Name;DWORD   FirstThunk;                     // RVA to IAT (if bound this IAT has actual addresses)
} IMAGE_IMPORT_DESCRIPTOR;
typedef IMAGE_IMPORT_DESCRIPTOR UNALIGNED *PIMAGE_IMPORT_DESCRIPTOR;

这个结构是比较难理解的!

OriginalFirstThunk
包括指向输入名称表(INT)的RVA,INT是一个IMAGE_THUNK_DATA数组,数组中每个IMAGE_THUNK_DATA又指向IMAGE_IMPORT_BY_NAME结构,数组最后一个以内容为NULL的IMAGE_THUNK_DATA结束

TimeDateStamp
该字段可以忽略

ForwarderChain
一般情况下我们也可以忽略该字段。在老版的绑定中,它引用API的第一个forwarder chain(传递器链表)。它可被设置为0xFFFFFFFF以代表没有forwarder。

Name
DLL 名字的指针,以00结尾的ASCII字符的RVA地址,该字符包含输入的DLL名称

FirstThunk
指向IAT的RVA,它包含由IMAGE_THUNK_DATA定义的 first thunk数组的虚地址,通过loader用函数虚地址初始化thunk。在Orignal First Thunk缺席下,它指向first thunk:Hints和The Function names的thunks。

OriginalFirstThunk和FirstThunk非常像,都指向本质上相同的数组IMAGE_THUNK_DATA,

摆渡个表吧():

typedef struct _IMAGE_THUNK_DATA32 {union {DWORD ForwarderString;      // PBYTE 指向一个转向者字符串的RVADWORD Function;             // PDWORD被输入函数的内存地址DWORD Ordinal;              // 被输入的API序数号 DWORD AddressOfData;        // PIMAGE_IMPORT_BY_NAME} u1;
} IMAGE_THUNK_DATA32;
typedef IMAGE_THUNK_DATA32 * PIMAGE_THUNK_DATA32;

当 IMAGE_THUNK_DATAR的值最高位为1时,表示函数以序号方式输入,这时低31位或底63位被看作为一个函数诹号,当

最高位为0时,表示函数是以字符串在型的函数名输入,这时它的值为一个指向IMAGE_IMPORT_BY_NAME结构,

为方便懒人们,我在10413行找到了如下宏定义

#define IMAGE_ORDINAL64(Ordinal) (Ordinal & 0xffff)
#define IMAGE_ORDINAL32(Ordinal) (Ordinal & 0xffff)
随手写个函数:
if (HIWORD(*pdwThunk)==0x8000){sprintf("Ord:%08lX",IMAGE_ORDINAL32(*pdwThunk));}

IMAGE_IMPORT_BY_NAME:

typedef struct _IMAGE_IMPORT_BY_NAME {WORD    Hint;BYTE    Name[1];
} IMAGE_IMPORT_BY_NAME, *PIMAGE_IMPORT_BY_NAME;

Hint 字段也表示函数的序号,不过这个字段是可选的,有些编译器总是将它设置为 0

Name 字段定义了导入函数的名称字符串,这是一个以 NULL 为结尾的字符串

在分析PE时,我们先判断OriginalFirstThunk是否为NULL,不为NULL,就用它来分析导入表,为NULL,则用FirstThunk来分析(PE未加载到内存时),给一个宏定义:

#define GETTHUNK(pImportDesc) ((DWORD)                          \((PIMAGE_IMPORT_DESCRIPTOR)pImportDesc->OriginalFirstThunk ?                      \(PIMAGE_IMPORT_DESCRIPTOR)pImportDesc->OriginalFirstThunk:(PIMAGE_IMPORT_DESCRIPTOR)pImportDesc->FirstThunk \))

FirstThunk指向的IAT是由加载器填写的,在初始化时,系统根据FirstThunk的值找到指向函数名的地址串,由地址串找到函数名,再根据函数名得到入口地址,然后用入口地址取代FirstThunk指向的地址串中的原值.(那么加载后FirstThunk保存的是什么呢?给个window图形编程的代码测试下)

const unsigned * KPEFile::GetFunctionPtr(PIMAGE_IMPORT_DESCRIPTOR pImport, LPCSTR pProcName)
{PIMAGE_THUNK_DATA pThunk;pThunk = (PIMAGE_THUNK_DATA) RVA2Ptr(pImport->OriginalFirstThunk);for (int i=0; pThunk->u1.Function; i++){bool match;if ( pThunk->u1.Ordinal & 0x80000000 )  // by ordinalmatch = (pThunk->u1.Ordinal & 0xFFFF) == ((DWORD) pProcName);elsematch = stricmp(pProcName, RVA2Ptr((unsigned) pThunk->u1.AddressOfData)+2) == 0;if ( match )return (unsigned *) RVA2Ptr(pImport->FirstThunk)+i;pThunk ++;}return NULL;
}

.后记

好像够多了,不写了,导出表和资源表都比较复杂,有空继续hgy413

转载于:https://www.cnblogs.com/hgy413/archive/2012/05/17/3693522.html

API拦截方法一:PE简介相关推荐

  1. php调用win32 api,C#_c#使用win32api实现获取光标位置,方法一:需要调用win32api,winfo - phpStudy...

    c#使用win32api实现获取光标位置 方法一:需要调用win32api,winform.wpf通用 [DllImport("user32.dll")] public stati ...

  2. python创建数据库字数不限制_textarea字数限制方法一例

    function checkLen(obj) { var maxChars = 30;//最多字符数 if (obj.value.length > maxChars) obj.value = o ...

  3. 揭示win32 api拦截细节

    转自啊D 原文出处:http://www.codeproject.com/system/hooksys.asp     拦截win32 API 调用对于多数windows开发人员来说都一直是很有挑战性 ...

  4. 揭示Win32 API拦截细节/API hooking revealed (1)

    原文出处:http://www.codeproject.com/system/hooksys.asp 简要介绍 拦截win32 API 调用对于多数windows开发人员来说都一直是很有挑战性的课题, ...

  5. HTML 转 PDf 方法一 wkhtmltopdf.exe

    工作中涉及到制作PDF报表,找了很多办法,总是不尽人意,有诸多bug,如: 1:中文显示乱码 2:处理字符串分割及换行时要么是字符串被截断无法显示,要么就是无法换行超出显示范围 3:处理分页是内容被截 ...

  6. vue-cli3.0引入高德3d动画效果方法一:

    前言: 因为两个方法代码量都特别大,这里分2个页面详细说一下,这个页面是方法一,包括vue-cli3.0中使用和html中使用两种示例 ***注意:这里有想看另一种方法的童鞋请点入口: https:/ ...

  7. TF学习——TF之API:TensorFlow的高级机器学习API—tf.contrib.learn的简介、使用方法、案例应用之详细攻略

    TF学习--TF之API:TensorFlow的高级机器学习API-tf.contrib.learn的简介.使用方法.案例应用之详细攻略 目录 tf.contrib.learn的简介 tf.contr ...

  8. 导出oracle sequences,CSS_oracle导出序列方法分析,方法一:SELECT ' CREATE SEQUEN - phpStudy...

    oracle导出序列方法分析 方法一: SELECT ' CREATE SEQUENCE '||SEQUENCE_NAME|| ' INCREMENT BY '|| INCREMENT_BY ||' ...

  9. JBOSS通过Apache负载均衡方法一:使用mod_jk

    JBOSS通过Apache负载均衡方法一:使用mod_jk   本文第一.二节分别对Linux环境下前端使用Apache以及windows环境下前端使用IIS通过AJP协议和后端的JBOSS通信实现负 ...

  10. 分析:windows下cmd默认的编码是ASCII编码 ,windows的中文环境下编码是GBK 方法一:在保存输出流保存的时候做一个对文字GBK编码,在输出到文件 如下 [python] view

    分析:windows下cmd默认的编码是ASCII编码 ,windows的中文环境下编码是GBK 方法一:在保存输出流保存的时候做一个对文字GBK编码,在输出到文件 如下 [python] view ...

最新文章

  1. Android重绘ListView高度
  2. Educational Codeforces Round 108(Rated for Div. 2) E - Off by One(一种一般图的边最大匹配,好题)
  3. BFC与IFC概念理解+布局规则+形成方法+用处
  4. zImage与uImage的区别
  5. 第十七部分-Python文档和测试
  6. 牛客小白月赛12 H 华华和月月种树 (离线dfs序+线段树)
  7. datastage 函数_DataStage常用函数大全
  8. ftp可以传输什么类型文件_FTP文件传输工具-ForkLift for Mac
  9. 用Android打出马奔跑的动画,一款非常好用的动画库Lottie
  10. python 对比文件内容差异_使用Python来比较文件夹并提取差异部分
  11. svg 地图_找地图素材?有这个网站就够了!
  12. 95-190-440-源码-window-Trigger-Trigger简介
  13. Java修改文件MD5值-yellowcong
  14. 神来之笔,2021CTF内核漏洞精选解析
  15. 虚拟机VMware安装苹果系统macOS,超级详细教程,附文件下载,真教程!!
  16. [APIO2014]连珠线 题解
  17. WSN无线传感网络--网络连通率测试
  18. ubuntu从零到一跑通ORB_SLAM2及其ORBSLAM2_with_pointcloud稠密建图
  19. LeetCode 字符串(简单题)
  20. 【纯净版windows系统】U盘启动制作图文教程

热门文章

  1. 基本概念学习(7002)---网络流量控制
  2. qt水波进度控件设计
  3. python在电脑上怎样下载_怎样在电脑上下载哔哩哔哩的视频?
  4. Solidity IDE Remix中文版使用手册
  5. 【前端框架】Element UI Dialog 组件中执行 DOM 操作异常问题的分析与处理
  6. ----实现查看历史记录及清除功能的具体过程----
  7. Matplotlib 配色表
  8. 分享:蛋花儿主题WordPress瀑布流 V1.05(收费版)
  9. 用python告诉你,韦小宝跟他七个老婆哪个最亲?
  10. tomcat启动失败!‘Staring Tomcat v8.0 Server at localhost' has encountered a problem. failed to start