自学PE详解(1)导入表
2020-02-13 21:29:21 Author: bbs.pediy.com(查看原文) 阅读量:171 收藏

这段时间学了PE文件,将我学习的一点心得和理解发给大家。

要学习PE文件首先就要了解PE文件的结构。

PE文件的结构 分为PE头和PE体以下是PE头的详细结构

typedef IMAGE_DOS_HEADER STRUCT 

+0000h WORD  e_magic   //EXE标志,“MZ”

+0002h WORD  e_cblp  // 最后(部分)页中的字节数

+0004h WORD  e_cp   //文件中全部和部分页数

+0006h WORD  e_crlc   //重定位表中的指针数

+0008h WORD  e_cparhdr   //头部尺寸,以段落为单位

+000ah WORD  e_minalloc  //所需最小附加段

+000ch WORD  e_maxalloc  //所需最大附加段

+000eh WORD  e_ss    //DOS代码的初始化堆栈SS 

+0010h WORD  e_sp    //DOS代码的初始化堆栈指针SP 

+0012h WORD  e_csum    // 补码校验值

+0014h WORD  e_ip    //DOS代码的初始化指令入口[指针IP] 

+0016h WORD  e_cs    //DOS代码的初始堆栈入口 [寄存器CS值]

+0018h WORD  e_lfarlc    // 重定位表的字节偏移量

+001ah WORD  e_ovno        //覆盖号

+001ch WORD  e_res[4]    //保留字

+0024h WORD  e_oemid    //OEM标识符

+0026h WORD  e_oeminfo   //OEM信息

+0029h WORD  e_res2[10]   //保留字

+003ch DWORD   e_lfanew   //PE头相对于文件的偏移地址 指向PE头 

} IMAGE_DOS_HEADER ENDS,IMAGE_DOS-HEADER, *PIMAGE_DOS_HEADER;  


typedefstruct_IMAGE_NT_HEADERS

{

+00h DWORD Signature                                                         //PE头文件标识,“PE\0\0”

+04h IMAGE_FILE_HEADER FileHeader                                   //PE标准头

+18h IMAGE_OPTIONAL_HEADER32 OptionalHeader           //PE扩展头

} IMAGE_NT_HEADERS,*PIMAGE_NT_HEADERS32;

typedefstruct_IMAGE_FILE_HEADER
{
+04h WORD   Machine;//运行平台
+06h WORD   NumberOfSections;//文件的区块数目(PE中节的数量)
+08h DWORD TimeDateStamp;//文件创建日期和时间
+0Ch DWORD PointerToSymbolTable;//指向符号表(主要用于调试)
+10h DWORD NumberOfSymbols;//符号表中符号个数(同上)
+14h WORD   SizeOfOptionalHeader;//IMAGE_OPTIONAL_HEADER32 结构大小(长度)
+16h WORD   Characteristics;//文件属性
} IMAGE_FILE_HEADER, *PIMAGE_FILE_HEADER;

typedef struct _IMAGE_OPTIONAL_HEADER 
{
    //
    // Standard fields.  
    //
+18h    WORD    Magic;         // ROM 映像(0107h),普通可执行文件(010Bh)
// x32: 10B, x64: 20B
+1Ah    BYTE      MajorLinkerVersion;     // 链接程序的主版本号
+1Bh    BYTE      MinorLinkerVersion;     // 链接程序的次版本号
+1Ch    DWORD   SizeOfCode;     // 所有含代码的节的总大小
+20h    DWORD   SizeOfInitializedData;    // 所有含已初始化数据的节的总大小
+24h    DWORD   SizeOfUninitializedData; // 所有含未初始化数据的节的大小
+28h    DWORD   AddressOfEntryPoint;    // 程序执行入口RVA
+2Ch    DWORD   BaseOfCode;      // 代码的区块的起始RVA
+30h    DWORD   BaseOfData;      // 数据的区块的起始RVA
//-------------------------------------------------------------
// NT additional fields.    以下是属于NT结构增加的领域。
//-------------------------------------------------------------
+34h    DWORD   ImageBase;      // 程序的首选装载地址(程序建议装载地址)
+38h    DWORD   SectionAlignment;      // 内存中的区块的对齐大小
+3Ch    DWORD   FileAlignment;      // 文件中的区块的对齐大小
+40h    WORD    MajorOperatingSystemVersion;  // 要求操作系统最低版本号的主版本号
+42h    WORD    MinorOperatingSystemVersion;  // 要求操作系统最低版本号的副版本号
+44h    WORD    MajorImageVersion;       // 可运行于操作系统的主版本号
+46h    WORD    MinorImageVersion;       // 可运行于操作系统的次版本号
+48h    WORD    MajorSubsystemVersion;  // 要求最低子系统版本的主版本号
+4Ah    WORD    MinorSubsystemVersion;  // 要求最低子系统版本的次版本号
+4Ch    DWORD   Win32VersionValue;       // 莫须有字段,不被病毒利用的话一般为0
+50h    DWORD   SizeOfImage;       // 映像装入内存后的总尺寸(内存中的整个PE映像尺寸)

+54h    DWORD   SizeOfHeaders;       // 所有头 + 区块表的尺寸大小
+58h    DWORD   CheckSum;       // 映像的校检和
+5Ch    WORD    Subsystem;       // 可执行文件期望的子系统
+5Eh    WORD    DllCharacteristics;       // DllMain()函数何时被调用,默认为 0(DLL文件特性)
+60h    DWORD   SizeOfStackReserve;       // 初始化时的栈大小
+64h    DWORD   SizeOfStackCommit;       // 初始化时实际提交的栈大小
+68h    DWORD   SizeOfHeapReserve;        // 初始化时保留的堆大小
+6Ch    DWORD   SizeOfHeapCommit;        // 初始化时实际提交的堆大小
+70h    DWORD   LoaderFlags;        // 与调试有关,默认为 0 

+74h    DWORD   NumberOfRvaAndSizes;  // 下边数据目录的项数,这个字段自Windows NT 发布以来        // 一直是16

+78h    IMAGE_DATA_DIRECTORY DataDirectory[IMAGE_NUMBEROF_DIRECTORY_ENTRIES];// 数据目录表

} IMAGE_OPTIONAL_HEADER32, *PIMAGE_OPTIONAL_HEADER32;

typedef struct _IMAGE_DATA_DIRECTORY

{

DWORD VirtualAddress;      //0000h - 数据得起始RVA

DWORD isize;                      //0004h - 数据块的长度 

}IMAGE_DATA_DIRECTORY,*PIMAGE_DATA_DIRECTORY;  


PE体主要就是包含各个节表中的信息。

在这里我就不做过多的介绍了 下面开始不如今天的正题。

导入表

什么导入表,导入表就是程序引用动态链接库中的函数的各项信息。

在windows中 每个程序不可能调用一个API函数,就把API函数的代码给放到 PE文件中,那样会损失大量的空间所以动态链接库孕育而生。

当然如果你静态编译的话情况可能就会不同

下面我们继续,导入表在PE文件中的什么位置

在 _IMAGE_OPTIONAL_HEADER 结构中有一个成员 叫做  DataDirectory[ IMAGE_NUMBEROF_DIRECTORY_ENTRIES ]

这个成员是一个数组 里面的成员的位置已经被windows设置好了 如下所示

#define IMAGE_DIRECTORY_ENTRY_EXPORT       0 导出表地址和大小

#define IMAGE_DIRECTORY_ENTRY_IMPORT        1 导入表地址和大小 

#define IMAGE_DIRECTORY_ENTRY_RESOURCE  2 资源目录资源表地址和大小

#define IMAGE_DIRECTORY_ENTRY_EXCEPTION  3 异常目录异常表地址和大小

#define IMAGE_DIRECTORY_ENTRY_SECURITY      4 安全目录属性证书数据地址和大小

#define IMAGE_DIRECTORY_ENTRY_BASERELOC          5 基地址重定位表地址和大小

#define IMAGE_DIRECTORY_ENTRY_DEBUG                   6 调试信息地址和大小

#define IMAGE_DIRECTORY_ENTRY_COPYRIGHT              7 预留为0

#define IMAGE_DIRECTORY_ENTRY_GLOBALPTR             8 指向全局指针寄存器的值

#define IMAGE_DIRECTORY_ENTRY_TLS                          9 线程句柄存储地址和大小

#define IMAGE_DIRECTORY_ENTRY_LOAD_CONFIG         10 加载配置表地址和大小

#define IMAGE_DIRECTORY_ENTRY_BOUND_IMPORT             11 绑定导入表地址和大小

#define IMAGE_DIRECTORY_ENTRY_IAT                                  12 导入函数地址表地址和大小

#define IMAGE_DIRECTORY_ENTRY_DELAY_IMPORT             13 延迟导入表地址和大小

#define IMAGE_DIRECTORY_ENTRY_COM_DESCRIPTOR           14 COM信息

#define Reserved                                                                               15系统保留


那么我们现在开始 在PE文件中找到导入表

以一个简单的helloworld.exe程序为例


我所选中的就是PE头所有结构的信息。

让我们根据上面所提供的结构 来找到导入表的RVA。

这里RVA指的是 虚拟内存相对偏移

假设虚拟地址是0x400000 那么从 这个位置开始 偏移 4000 等于 0x404000

这4000就是相对于0x400000的RVA

首先我们找到 _IMAGE_OPTIONAL_HEADER结构中 一个已经预先设置好的值 0x10 十进制就是16

在前面的结构描述中 这个值一直就是被设置好的。

下面我们在PE文件中定位


这里我们成功找到了这个预先被设置好的一个值,0x00000010在这个值后面就是刚才我说的那个数组

我所选中的就是这个PE文件的数据目录


根据之前贴出来的数组结构信息我们可以发现 数组每个成员都是8个字节 所以我们略过第一个成员

直接访问第二个成员也就是导入表

箭头所指就是导入表的起始RVA还有 数据的大小


现在我们拿到导入表的RVA 00002010 记住这里是RVA 是加载在内存中之后的偏移 所以我们需要进行转换才能直接访问到正确的信息

接着在紧跟着PE标准头结构之后 就是节区信息

在这里面我们找到导入表的节区信息


.rdata或者.idata 

.rdata可能会合并 .idata

下面是节区的结构信息

typedefstruct_IMAGE_SECTION_HEADER {

+0h BYTE Name[IMAGE_SIZEOF_SHORT_NAME];//节表名称,如“.text” 8个字节节名 //IMAGE_SIZEOF_SHORT_NAME=8

union

+8h

{

DWORD PhysicalAddress;//物理地址 节区的尺寸

DWORD VirtualSize;//真实长度,这两个值是一个联合结构,可以使用其中的任何一个,一般是取后一个

} Misc;

+ch DWORD VirtualAddress;//节区的 RVA 地址

+10h DWORD SizeOfRawData;//在文件中对齐后的尺寸

+14h DWORD PointerToRawData;//在文件中的偏移量

+18h DWORD PointerToRelocations;//在OBJ文件中使用,重定位的偏移

+1ch DWORD PointerToLinenumbers;//行号表的偏移(供调试使用)

+1eh WORD NumberOfRelocations;//在OBJ文件中使用,重定位项数目

+20h WORD NumberOfLinenumbers;//行号表中行号的数目

+24h DWORD Characteristics;//节属性如可读,可写,可执行等
} IMAGE_SECTION_HEADER, *PIMAGE_SECTION_HEADER; 


下面我们来找到节区中对我们有用的几个关键成员

VirtualSize  和   VirtualAddress  和  SizeOfRawData  和  PointerToRawData

正好这四个成员是接近的

00000092是 内存中节区所占大小

00002000是 内存中节区的起始RVA  (注意这里的起始代表着这个节区最开始的位置)

00000200是 文件中节区的大小

00000600是 文件中的节区起始偏移 

得到了以上的信息下面我们就可以查看导入表的信息了

但是在这之前我们要进行一下 转换将RVA转换成 FOA(文件偏移)

首先用导入表的起始RVA减去 节区的起始RVA

00002010  -  00002000  = 00000010 得到实际的偏移

00000010 + 00000600 = 00000610 然后再加上文件节区的起始偏移就得到了 FOA


这里我说选中的就是 导入表结构体的各种信息

struct_IMAGE_IMPORT_DESCRIPTOR
{

union
       {
                  DWORD Characteristics; 
                  DWORD OriginalFirstThunk; //INT表
        } DUMMYUNIONNAME;
        DWORD      TimeDateStamp; 
        DWORD     ForwarderChain;
        DWORD     Name;//库名
        DWORD     FirstThunk; //IAT表
} IMAGE_IMPORT_DESCRIPTOR;

同时导入表的结构体同样是一个数组

下面我们来获得导入表的信息

00002054 是INT表的RVA        FOA  654

0000206a 是库名                     FOA 66A

00002008 是IAT表的RVA         FOA 608

0000204c 是INT表的RVA        FOA 64c

00002084 是库名                     FOA 684

00002000 是IAT表的RVA         FOA 600

首先我们来看导入表结构体的第一个成员包含的是什么信息

来看第一个偏移 654


我们发现INT表保存的是一个 RVA下面我们将 RVA 转换成 FOA 65c


可以看见INT表指向的 数据包含了两个结构

(注:这里INT表指向的RVA地址实际上是一个数组)

前两个字节包含的时函数编号

后面的字符串就是函数名

导入表结构的NAME成员 偏移是 66A


这里我们可以见看 NAME所指向的DLL名

接着我们来查看 IAT表

相信表哥们可以发现一个细节

IAT表指向的地址 和 INT 表所指向的值是一样的。

到这里我们打开OD


我们发现在PE文件装载进内存之后 值就发生了改变

让我们看看这个地址又是什么


在这里我们可以发现 IAT表中的信息已经被改成了 DLL链接库中函数的地址


从上图中可以发现 750b0380是函数的入口地址


可以发现JMP的位置 就是函数的入口地址

由此我们可以发现IAT表在文件中和INT表是一样的,但是被加载进内存之后就会被替换掉变成DLL导入函数中的实际地址

IAT表在文件中是处于一种站位置的状态。

新手第一次写,各位大佬见笑了

2020安全开发者峰会(2020 SDC)议题征集 中国.北京 7月!


文章来源: https://bbs.pediy.com/thread-257513.htm
如有侵权请联系:admin#unsafe.sh