PE文件学习

本文链接:http://blog.csdn.net/u012763794/article/details/51469477

1.介绍


什么是PE文件?


PE文件是windows操作系统下使用的可执行文件格式。32位就直接叫PE或PE32,64位的就PE+或PE32+,注意不是PE64哦!!!!


学习PE文件其实就是学习结构体,里面储存了如何加载到内存,从何处开始运行,运行需要那些dll,需要多大的栈和内存等


初识PE文件

看看大概包括那些结构体吧


2.PE头

下面实例是利用notepad.exe作为实例

DOS头

看看SDK winnt.h,就是一个结构体嘛


首先我们可以动手算算结构体的大小, 是64,即0x40个字节
跟程序的结果符合


我们用winhex(16进制编辑器就行),打开 windows记事本(notepad.exe)看看,选中的即DOS头,刚好0x40个字节



我们关注第一个成员和最后一个

e_magic:所有PE开头都有DOS签名  “MZ”,这是以一个名叫Mark Zbikowski的DOS可执行文件的设计者首字母命名的,看图上的前两个字节

e_lfanew:指向NT头的位置,long类型,占4个字节,看上图就是0x0000000E,注意是小端模式哦,不同程序有所不同的

下图箭头位置就是NT头了



DOS存根

是个可选项,大小不固定

根据dos头的最后一个成员,下面被框住的就是DOS存根


里面是16位汇编代码,用debug可以看(u:Unassemble),64位系统没有这个哦


我们以dos环境运行看看(G命令),可以看到就是输出不能以dos模式运行



NT头

看sdk,刚好64位的在上面,可以看到有3个成员,64位的就只是IMAGE_OPTIONAL_HEADER32扩展成IMAGE_OPTIONAL_HEADER64



大小为0xf8


签名结构体


第一个签名结构体值为0x50450000(“PE”00)


NT头之文件头---IMAGE_FILE_HEADER


可以看到有7个成员

0x14字节,即20字节

那么就是下面选中部分了


    WORD    Machine; 每个CPU都有唯一的机器码,兼容32位的Intel x86的机器码为14C,具体看下面

#define IMAGE_FILE_MACHINE_UNKNOWN           0
#define IMAGE_FILE_MACHINE_I386              0x014c  // Intel 386.
#define IMAGE_FILE_MACHINE_R3000             0x0162  // MIPS little-endian, 0x160 big-endian
#define IMAGE_FILE_MACHINE_R4000             0x0166  // MIPS little-endian
#define IMAGE_FILE_MACHINE_R10000            0x0168  // MIPS little-endian
#define IMAGE_FILE_MACHINE_WCEMIPSV2         0x0169  // MIPS little-endian WCE v2
#define IMAGE_FILE_MACHINE_ALPHA             0x0184  // Alpha_AXP
#define IMAGE_FILE_MACHINE_SH3               0x01a2  // SH3 little-endian
#define IMAGE_FILE_MACHINE_SH3DSP            0x01a3
#define IMAGE_FILE_MACHINE_SH3E              0x01a4  // SH3E little-endian
#define IMAGE_FILE_MACHINE_SH4               0x01a6  // SH4 little-endian
#define IMAGE_FILE_MACHINE_SH5               0x01a8  // SH5
#define IMAGE_FILE_MACHINE_ARM               0x01c0  // ARM Little-Endian
#define IMAGE_FILE_MACHINE_THUMB             0x01c2  // ARM Thumb/Thumb-2 Little-Endian
#define IMAGE_FILE_MACHINE_ARMNT             0x01c4  // ARM Thumb-2 Little-Endian
#define IMAGE_FILE_MACHINE_AM33              0x01d3
#define IMAGE_FILE_MACHINE_POWERPC           0x01F0  // IBM PowerPC Little-Endian
#define IMAGE_FILE_MACHINE_POWERPCFP         0x01f1
#define IMAGE_FILE_MACHINE_IA64              0x0200  // Intel 64
#define IMAGE_FILE_MACHINE_MIPS16            0x0266  // MIPS
#define IMAGE_FILE_MACHINE_ALPHA64           0x0284  // ALPHA64
#define IMAGE_FILE_MACHINE_MIPSFPU           0x0366  // MIPS
#define IMAGE_FILE_MACHINE_MIPSFPU16         0x0466  // MIPS
#define IMAGE_FILE_MACHINE_AXP64             IMAGE_FILE_MACHINE_ALPHA64
#define IMAGE_FILE_MACHINE_TRICORE           0x0520  // Infineon
#define IMAGE_FILE_MACHINE_CEF               0x0CEF
#define IMAGE_FILE_MACHINE_EBC               0x0EBC  // EFI Byte Code
#define IMAGE_FILE_MACHINE_AMD64             0x8664  // AMD64 (K8)
#define IMAGE_FILE_MACHINE_M32R              0x9041  // M32R little-endian
#define IMAGE_FILE_MACHINE_CEE               0xC0EE
    WORD    NumberOfSections;

有多少个节区,一定大于0 ,比如基本的,什么代码区(.text),数据(.data),资源(.rsrc)区,若跟实际的不符则出错
    DWORD   TimeDateStamp;

文件时何时创建的
    DWORD   PointerToSymbolTable;

这里指向一个表,很少使用,网上也没怎么介绍
    DWORD   NumberOfSymbols;

上面表的数量
    WORD    SizeOfOptionalHeader;

说明IMAGE_OPTIONAL_HEADER32(可选头)的大小
    WORD    Characteristics;

用于标识文件的属性(0x0002,2000h一定记住,一个是可执行文件,一个是DLL),具体看下

#define IMAGE_FILE_RELOCS_STRIPPED           0x0001  // Relocation info stripped from file.
#define IMAGE_FILE_EXECUTABLE_IMAGE          0x0002  // File is executable  (i.e. no unresolved externel references).
#define IMAGE_FILE_LINE_NUMS_STRIPPED        0x0004  // Line nunbers stripped from file.
#define IMAGE_FILE_LOCAL_SYMS_STRIPPED       0x0008  // Local symbols stripped from file.
#define IMAGE_FILE_AGGRESIVE_WS_TRIM         0x0010  // Agressively trim working set
#define IMAGE_FILE_LARGE_ADDRESS_AWARE       0x0020  // App can handle >2gb addresses
#define IMAGE_FILE_BYTES_REVERSED_LO         0x0080  // Bytes of machine word are reversed.
#define IMAGE_FILE_32BIT_MACHINE             0x0100  // 32 bit word machine.
#define IMAGE_FILE_DEBUG_STRIPPED            0x0200  // Debugging info stripped from file in .DBG file
#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  // System File.
#define IMAGE_FILE_DLL                       0x2000  // File is a DLL.
#define IMAGE_FILE_UP_SYSTEM_ONLY            0x4000  // File should only be run on a UP machine
#define IMAGE_FILE_BYTES_REVERSED_HI         0x8000  // Bytes of machine word are reversed.

NT头之可选头------- IMAGE_OPTIONAL_HEADER32

结构体挺大,先看前置的定义

typedef struct _IMAGE_DATA_DIRECTORY {
    DWORD   VirtualAddress;
    DWORD   Size;
} IMAGE_DATA_DIRECTORY, *PIMAGE_DATA_DIRECTORY;

#define IMAGE_NUMBEROF_DIRECTORY_ENTRIES    16


真正的定义
typedef struct _IMAGE_OPTIONAL_HEADER {
    //
    // Standard fields.
    //

    WORD    Magic;
    BYTE    MajorLinkerVersion;
    BYTE    MinorLinkerVersion;
    DWORD   SizeOfCode;
    DWORD   SizeOfInitializedData;
    DWORD   SizeOfUninitializedData;
    DWORD   AddressOfEntryPoint;
    DWORD   BaseOfCode;
    DWORD   BaseOfData;

    //
    // NT additional fields.
    //

    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_OPTIONAL_HEADER32, *PIMAGE_OPTIONAL_HEADER32;


//
    // Standard fields.
    //


    WORD    Magic; 32位为10B,64位20B
    BYTE    MajorLinkerVersion; 主链接版本
    BYTE    MinorLinkerVersion; 次链接版本
    DWORD   SizeOfCode; 代码节的大小
    DWORD   SizeOfInitializedData; 已初始化数据大小
    DWORD   SizeOfUninitializedData; 未初始化数据大小
    DWORD   AddressOfEntryPoint; 入口点的RVA,非常重要,指明哪里最先执行代码
    DWORD   BaseOfCode; 代码段的RVA(相对虚拟地址)
    DWORD   BaseOfData; 数据点的RVA


//
    // NT additional fields.
    //
    DWORD   ImageBase; 文件首选建议装入的地址,运行时,EIP就设置为ImageBase+AddressOfEntryPoint了
    DWORD   SectionAlignment; 节区对齐,就是节区的最小占用多少
    DWORD   FileAlignment; 文件对齐,文件最小是多少,不够应该补0 吧
    WORD    MajorOperatingSystemVersion; 操作系统最低主版本
    WORD    MinorOperatingSystemVersion; 最低次版本
    WORD    MajorImageVersion; 可执行文件主版本
    WORD    MinorImageVersion; 次版本
    WORD    MajorSubsystemVersion; 最低子操作系统主版本
    WORD    MinorSubsystemVersion; 次版本
    DWORD   Win32VersionValue; 保留字段
    DWORD   SizeOfImage; 装入内存后的总大小

    DWORD   SizeOfHeaders; PE头大小(dos头,一直到节表头)
    DWORD   CheckSum; 校验和
    WORD    Subsystem; 可执行文件的子系统类型(是系统驱动文件还是普通可执行文件)
    WORD    DllCharacteristics; dll文件类型
    DWORD   SizeOfStackReserve; 为线程保留的栈大小
    DWORD   SizeOfStackCommit; 已提交的栈大小
    DWORD   SizeOfHeapReserve; 保留的堆大小
    DWORD   SizeOfHeapCommit; 已提交的堆大小
    DWORD   LoaderFlags; 被废弃了
    DWORD   NumberOfRvaAndSizes; 数据目录项个数,即下面DataDirectory的个数
    IMAGE_DATA_DIRECTORY DataDirectory[IMAGE_NUMBEROF_DIRECTORY_ENTRIES];数据目录表,包括输入输出表,资源等


看看具体是那些,刚好是.text前面


0xe0字节,224字节,16*14字节,那就是14行咯

(下图的左边的地址为10进制)



重点是最后一个成员IMAGE_DATA_DIRECTORY DataDirectory[IMAGE_NUMBEROF_DIRECTORY_ENTRIES];

虽然常量定义其为16个,但最终还是由倒数第二个变量决定NumberOfRvaAndSizes

winnt.h中定义了15项,最后一项保留

#define IMAGE_DIRECTORY_ENTRY_EXPORT          0   // Export Directory
#define IMAGE_DIRECTORY_ENTRY_IMPORT          1   // Import Directory
#define IMAGE_DIRECTORY_ENTRY_RESOURCE        2   // Resource Directory
#define IMAGE_DIRECTORY_ENTRY_EXCEPTION       3   // Exception Directory
#define IMAGE_DIRECTORY_ENTRY_SECURITY        4   // Security Directory
#define IMAGE_DIRECTORY_ENTRY_BASERELOC       5   // Base Relocation Table
#define IMAGE_DIRECTORY_ENTRY_DEBUG           6   // Debug Directory
//      IMAGE_DIRECTORY_ENTRY_COPYRIGHT       7   // (X86 usage)
#define IMAGE_DIRECTORY_ENTRY_ARCHITECTURE    7   // Architecture Specific Data
#define IMAGE_DIRECTORY_ENTRY_GLOBALPTR       8   // RVA of GP
#define IMAGE_DIRECTORY_ENTRY_TLS             9   // 
#define IMAGE_DIRECTORY_ENTRY_LOAD_CONFIG    10   // Load Configuration Directory
#define IMAGE_DIRECTORY_ENTRY_BOUND_IMPORT   11   // Bound Import Directory in headers
#define IMAGE_DIRECTORY_ENTRY_IAT            12   // Import Address Table
#define IMAGE_DIRECTORY_ENTRY_DELAY_IMPORT   13   // Delay Load Import Descriptors
#define IMAGE_DIRECTORY_ENTRY_COM_DESCRIPTOR 14   // COM Runtime descriptor

索引为 7 那个不同cpu的架构就选的不同吧


因为DataDirectory数组里保存了导入表(用了哪些dll),导出表,TLS(Thread Local Storage) Directory等RVA和大小的信息


关于导出表:

下面是某个博客原文

英文:When the PE loader runs a program, it loads the associated DLLs into the process address space. It then extracts information about the import functions from the main program. It uses the information to search the DLLs for the addresses of the functions to be patched into the main program. The place in the DLLs where the PE loader looks for the addresses of the functions is the export table.

我可能不是很对的翻译

中文:当PE装载器运行一个程序,它装入导入表的dll到进程的对应的地址空间,跟着从main函数展开导入函数的信息,接着用这些信息搜索dll中导入函数的地址,跟着放在main函数的调用该函数的位置上。那么导出表就是dll中查找某个函数地址的地方

导出表,导出嘛,就是给别人用,你要告诉别人函数的地址在哪啊


那最后一个成员在哪呢,因为一个数组占8个字节,共16个,从后面数上来就可以了

(下图的左边的地址为10进制)


可以看到索引为0的导出表是全0,因为是记事本,索引没有导出表吧,一般dll才会有

索引1的就是导入表了,可以看到RVA为0x7604,但在文件中查看要转化为文件偏移,因为文件中存的是内存中的相对地址

这里就涉及节区头的概念,看下一小节的节区头的截图,(以四字节为单位,每个节区头第四个四字节,不懂的可以看看节区头结构体中VirtualAddress的位置)VirtualAddress就是节区的RVA

可看到.text的RVA为0x1000,.data的RVA为0x9000,.rsrc的RVA为0xB000

很明显0x7604在.text节区,那么它距离.text的起始位置就是0x7604-0x1000=0x6604,

因为无论文件还是内存中,距离.text的起始位置都是一样的,所以0x6604也是文件中距离.text的起始位置的距离

最后加上.text的起始位置的文件偏移(PointerToRawData)即可,PointerToRawData还是在节区头中,看看下面,VirtualAddress再数两个4字节,还是看图吧

(下图的左边的地址为10进制)


所以最终求出0x7604的文件偏移为0x0400 + 0x6604 = 0x6A04

我们看看0x6A04是不是有导入表的信息,即导入表结构体的信息


可以看到刚好5个双字,即20字节

typedef struct _IMAGE_IMPORT_DESCRIPTOR {
    union {
        DWORD   Characteristics;            // 0 for terminating null import descriptor
        DWORD   OriginalFirstThunk;         // RVA to original unbound IAT (PIMAGE_THUNK_DATA)
    } DUMMYUNIONNAME;
    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 forwarders
    DWORD   Name;
    DWORD   FirstThunk;                     // RVA to IAT (if bound this IAT has actual addresses)
} IMAGE_IMPORT_DESCRIPTOR;
typedef IMAGE_IMPORT_DESCRIPTOR UNALIGNED *PIMAGE_IMPORT_DESCRIPTOR;

我们来验证一下吧,第4个Name成员比较直观,RVA :0x7AAC,那么文件偏移(RAW):0x7AAC-0x1000+0x400=0x6EAC

可以看到没错的,其他的成员自己找吧


节区头

#define IMAGE_SIZEOF_SHORT_NAME              8

typedef struct _IMAGE_SECTION_HEADER {
    BYTE    Name[IMAGE_SIZEOF_SHORT_NAME];
    union {
            DWORD   PhysicalAddress;
            DWORD   VirtualSize;
    } Misc;
    DWORD   VirtualAddress;
    DWORD   SizeOfRawData;
    DWORD   PointerToRawData;
    DWORD   PointerToRelocations;
    DWORD   PointerToLinenumbers;
    WORD    NumberOfRelocations;
    WORD    NumberOfLinenumbers;
    DWORD   Characteristics;
} IMAGE_SECTION_HEADER, *PIMAGE_SECTION_HEADER;


    BYTE    Name[IMAGE_SIZEOF_SHORT_NAME]; 保存节的名称,通常以点开头
    union {
            DWORD   PhysicalAddress;
            DWORD   VirtualSize; 内存中节区所占大小,不一定是内存对齐后的值
    } Misc;
    DWORD   VirtualAddress; 内存中节区的起始RVA
    DWORD   SizeOfRawData; 磁盘中节区所占大小
    DWORD   PointerToRawData; 磁盘中节区的起始RVA
    DWORD   PointerToRelocations;
    DWORD   PointerToLinenumbers;
    WORD    NumberOfRelocations;
    WORD    NumberOfLinenumbers;
    DWORD   Characteristics; 节区属性,由下面的组合而成(按位或,bit  OR)


#define IMAGE_SCN_CNT_CODE                   0x00000020  // Section contains code.
#define IMAGE_SCN_CNT_INITIALIZED_DATA       0x00000040  // Section contains initialized data.
#define IMAGE_SCN_CNT_UNINITIALIZED_DATA     0x00000080  // Section contains uninitialized data.


#define IMAGE_SCN_MEM_SHARED                 0x10000000  // Section is shareable.
#define IMAGE_SCN_MEM_EXECUTE                0x20000000  // Section is executable.
#define IMAGE_SCN_MEM_READ                   0x40000000  // Section is readable.
#define IMAGE_SCN_MEM_WRITE                  0x80000000  // Section is writeable.

比如

代码区: 具有执行,读取权限

数据区: 非执行,读写权限

资源区: 非执行,读权限


具体看看在记事本的哪里,首先大小0x28=40字节


通过显示的名字我们看到有3个节区头,第一个字段是节区的名字也是符合的

.text节区头


.data节区头


.rsrc节区头



好了就先这样,有什么再补充

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值