首页
社区
课程
招聘
[原创]windows逆向自第二步自学PE
2021-10-31 16:57 12050

[原创]windows逆向自第二步自学PE

2021-10-31 16:57
12050

3.PE文件

PE(Portable Executable可移植的执行体),它是Win32自身所带的执行体文件格式。在Windows操作系统下,所有的可执行文件都是PE文件格式。包括:EXE文件、DLL文件、SYS文件、OCX文件等。

 

所有的PE文件都按照PE文件结构的方式组织存在的。PE文件结构不是一个单纯的结构。一个PE文件由若干个结构集合而成,不同的结构有不同的用处。

 

PE文件格式是对Windows下可执行文件的一种管理方式。

 

即使windows运行在非Intel的CPU上,任何win32平台的PE装载器都能识别和使用该文件格式。

 

windows下每个进程都有独立的4G地址空间,低两G(0x00000000~0x7fffffff)给应用程序使用,高两G(0x80000000~0xFFFFFFFF)给系统内核使用。

 

PE文件中经常用到三种地址:

  • 相对虚拟地址(Relative Virual Address,RVA,也可以说成偏移量):是PE文件中的数据、模块等运行在内存中的实际地址相对PE文件装载到内存的基址之间的距离。
  • 虚拟地址(Virtual Address,VA)
  • 文件偏移地址(File Offset Address,FOA)img

 

 


 

DOS头部在winnt.h头文件中的定义如下:(该文件头的大小为64个字节)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
typedef struct _IMAGE_DOS_HEADER{
    WORD e_magic;                           //0000h EXE标志 MZ              下图中的4D 5A
    WORD e_cblp;                            //0002h 最后(部分)页中的字节数    下图中的90 00
    WORD e_cp;                              //0004h 文件中的全部和部分页数      03 00
    WORD e_crlc;                            //0006h 重定位表中的指针数         00 00
    WORD e_cparhdr;                         //0008h 头部尺寸,以段位为单位      04 00
    WORD e_minalloc;                        //000Ah 所需的最小附加段           00 00
    WORD e_maxalloc;                        //000Ch 所需的最大附加段           FF FF
    WORD e_ss;                              //000Eh 初始的SS值(相对偏移量)    00 00
    WORD e_sp;                              //0010h 初始的SP值                B8 00
    WORD e_csum;                            //0012h 校验和                   00 00
    WORD e_ip;                              //0014h 初始的IP值                00 00
    WORD e_cs;                              //0016h 初始的CS值                00 00
    WORD e_lfarlc;                          //0018h 重定位表的字节偏移量        40 00
    WORD e_ovno;                            //001Ah 覆盖号                    00 00
    WORD e_res[4];                          //001Ch 保留字                     00 00 00 00 00 00 00 00
    WORD e_oemid;                           //0024h OEM标识符(相对e_oeminfo)  00 00
    WORD e_oeminfo;                         //0026h OEM信息                   00 00
    WORD e_res2[10];                        //0028h 保留字                    00 00 00 00 ... 00
    LONG e_lfanew;                          //003Ch PE头相对于文件的偏移地址,也就是结构体IMAGE_NT_HEADERS的开头  08 01 00 00
}
#define IMAGE_DOS_SIGNATURE       0X4D5A    //MZ

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
typedef struct _IMAGE_NT_HEADERS{
    DWORD Signature; //PE标识符 50 45 00 00
    IMAGE_FILE_HEADER FileHeader; //文件头
    IMAGE_OPTIONAL_HEADER32 OptionalHeader; //可选头
}IMAGE_NT_HEADERS32,*PIMAGE_NT_HEADERS32;
 
typedef struct _IMAGE_FILE_HEADER{ //该结构体可判断文件是EXE文件还是DLL文件。占用20个字节
    WORD Machine;                  //0004h 运行平台  0x014c指的是Intel 386 ,而0x0200指的是Intel 64
    WORD NumberOfSections;         //0006h PE中节的数量 最大96个节
    DWORD TimeDateStamp;           //0008h 文件创建日期和时间,编译器创建此文件时的时间戳
    DWORD PointerToSymbolTable;    //000Ch 指向符号表(用于调试)
    DWORD NumberOfSymbols;         //0010h 符号表中的符号数量(用于调试)
    WORD SizeOfOptionalHeader;     //0014h 可选头结构体的长度 32位版本是E0 64位版本的是F0
    WORD Characteristics;          //0016h 文件的属性 exe文件是010f dll文件是210e
}IMAGE_FILE_HEADER,*PIMAGE_FILE_HEADER;
#define IMAGE_SIZEOF_FILE_HEADER 20
#define IMAGE_FILE_MACHINE_l386   0x014c // Intel 386 指的是32位系统
#define IMAGE_FILE_MACHINE_lA64   0x0200 // Intel 64
1
2
3
4
5
6
7
8
IMAGE_FILE_HEADER.Characteristics的常用属性
#define IMAGE_FILE_RELOCS_STRIPPED  0x0001 //没有重定位
#define IMAGE_FILE_EXECUTABLE_IMAGE 0x0002 //文件是可执行的
#define IMAGE_FILE_LINE_NUMS_STRIPPED 0x0004 //没有行号
#define IMAGE_FILE_LOCAL_SYMS_STRIPPED 0X0008 //没有符号
#define IMAGE_FILE_32BIT_MACHINE 0x0100 //32位机器
#define IMAGE_FILE_SYSTEM   0x1000     //系统文件
#define IMAGE_FILE_DLL      0x2000     //DLL文件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
typedef struct _IMAGE_OPTIONAL_HEADER{ //占用224个字节
    WORD Magic;                        //0018h  取值为10B20B
    BYTE MajorLinkerVersion;           //001Ah  链接器版本号(对执行没有任何影响)
    BYTE MinorLinkerVersion;           //001Bh
    DWORD SizeOfCode;                  //001Ch  所有含代码的节的大小(按照文件对齐,判断某节是否含代码,使用节属性IMAGE_SCN_CNT_CODE属性判断,而不是通过IMAGE_SCN_CNT_EXECUTE)
    DWORD SizeOfInitializedData;       //0020h  所有含初始化数据的节的大小
    DWORD SizeOfUninitializedData;     //0024h  所有含未初始化数据的节的大小(被定义为未初始化,不占用文件空间,加载入内存后为其分配空间)
    DWORD AddressOfEntryPoint;         //0028h  程序执行入口RVA(距离PE加载后地址的距离,对于病毒和加密程序,都会修改该值,从而获得程序的控制权,对于DLL,如果没有入口函数,那么是0,对于驱动,该值是初始化的函数的地址)
    DWORD BaseOfCode;                  //002Ch  代码的节的起始RVA(一般情况跟在PE头部的后面)
    DWORD BaseOfData;                  //0030h  数据的节的起始RVA
    //以上是standard,以下是additional
    DWORD ImageBase;                   //0034h  程序的建议装载地址
    DWORD SectionAlignment;            //0038h  内存中的节的对齐值  320x1000  640x2000
    DWORD FileAlignment;               //003Ch  文件中的节的对齐值  现在都是0x1000 早期都是0x200
    WORD  MajorOperatingSystemVersion; //0040h  操作系统版本号
    WORD  MinorOperatingSystemVersion; //0042h
    WORD  MajorImageVersion;           //0044h  该PE的版本号
    WORD  MinorImageVersion;           //0046h
    WORD  MajorSubsystemVersion;       //0048h  所需子系统的版本号
    WORD  MinorSubsystemVersion;       //004Ah
    DWORD Win32VersionValue;           //004Ch  未使用,必须为0
    DWORD SizeOfImage;                 //0050h  内存中的整个PE文件映像大小(按照内存对齐)
    DWORD SizeOfHeaders;               //0054h  所有头+节表的大小
    DWORD CheckSum;                    //0058h  校验和(一般EXE文件为0,而DLL和SYS文件则必须是正确的值)
    WORD  Subsystem;                   //005Ch  文件子系统
    WORD  DllCharacteristics;          //005Eh  DLL文件特性
    DWORD SizeOfStackReserve;          //0060h  初始化时保留的栈的大小(默认1M
    DWORD SizeOfStackCommit;           //0064h  初始化时实际提交的栈的大小(默认4K
    DWORD SizeOfHeapReserve;           //0068h  初始化时保留的堆的大小(默认1M
    DWORD SizeOfHeapCommit;            //006Ch  初始化时实际提交的堆大小(默认4K
    DWORD LoaderFlags;                 //0070h  加载标志一般为0
    DWORD NumberOfRvaAndSizes;         //0074h  数据目录的数量
    IMAGE_DATA_DIRECTORY DataDirectory{IMAGE_NUMBEROF_DIRECTORY_ENTRIES};  //0078h 数据目录数组  导入表的定位由这个值给出
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#define IMAGE_SIZEOF_SHORT_NAME 8
typedef IMAGE_SIZEOF_SECTION_HEADER{//节表,占用40个字节
    BYTE Name[IMAGE_SIZEOF_SHORT_NAME];   //0000h 节名称
    union{
        DWORD PhysicalAddress;
        DWORD VirtualSize;                //0008h 节区的尺寸
    }Misc;
    DWORD VirtualAddress;                 //000Ch 节区的RVA地址
    DWORD SizeOfRawData;                  //0010h 在文件中对齐后的尺寸
    DWORD PointerToRawData;               //0014h 该节在文件中的偏移
    DWORD PointerToRelocations;           //0018h 在OBJ文件中使用
    DWORD PointerToLinenumbers;           //001Ch 行号表的位置(供调试用)
    WORD NumberOfRelocations;             //0020h 在OBJ文件中使用
    WORD NumberOfLinenumbers;             //0024h 行号表中行号的数量
    DWORD Characteristics;                //0028h 节的属性
}IMAGE_SECTION_HEADER,*PIMAGE_SECTION_HEADER;

  1. 所有可执行文件(EXE,DLL文件等)都以4D 5A开头
  2. MZ为可执行(EXE,DLL文件等)文件标识
  3. Intel的CPU都采用小尾方式,网络上传输数据或者其他CPU架构都采用大尾方式
  4. 不是MZ或者4D 5A开头的都不是可执行文件
  5. 有效的PE文件能从DOS头部找到PE头地址,且PE头开始标志为50 45 或者字符串中的PE
  6. DOS头占用64个字节

从内存中的字符串的位置找到文件中的字符串的位置

 

1.通过VA转换为RVA

 

403006-400000=3006

 

2.找RVA所在的节

 

3006在.data节

 

3.计算.data节起始RVA和起始FOA的差值

 

3000-800=2800(十六进制相减)

 

4.通过RVA减去差值

 

3006-2800=806

 

添加节的步骤:

  1. 增加节表项
  2. 修正文件的映像长度
  3. 修正一个节的数量
  4. 增加文件的节数据

需要调用外部函数库,需要用到导入表(导入表在PE文件体中)

1
2
3
4
5
6
7
8
9
10
typedef struct _IMAGE_IMPORT_DESCRIPTOR{
    union {
        DWORD Characteristics;
        DWORD OriginalFirstThunk;  //保存一个RVA,这个RVA指向一个INT(Import Name Table)表,这个表中保存的是所有导入函数名称的RVA
    }
    DWORD TimeDateStamp;
    DWORD ForwarderChain;
    DWORD Name;                    //保存一个RVA,这个RVA指向的内容是DLL的文件名
    DWORD FirstThunk;              //保存一个RVA,这个RVA指向一个IAT(Import Address Table)表,这个表中保存的是所有导入函数的地址(VA)
}IMAGE_IMPORT_DESCRIPTOR;

构造PE头部,包括:

  1. IMAGE_DOS_HEADER
  2. IMAGE_PE_SIGNATURE
  3. IAMGE_FILE_HEADER
  4. IMAGE_OPTION_HEADER
  5. IMAGE_SECTION_HEADER

构造PE体,包括:

  1. 构造代码.text
  2. 构造数据.data
  3. 构造导入表.rdata
  4. 修正代码
  5. 修正PE头

1.加壳:压缩壳、加密壳(隐藏程序的真正入口点)、虚拟机壳(VM关键代码,让破解者毫无头绪)

 

加壳的过程:

  1. 压缩节 1000->200
  2. 压缩引擎 体积减小
  3. 加密代码 采用加密算法
  4. VM关键代码
  5. 隐藏导入表
  6. 写入shell

2.查壳:识别程序的签名(特征码)

 

3.脱壳:专门的脱壳工作、通用的脱壳工具、手动脱壳(找OEP、DUMP、修复导入表)

 

特征码通过提取指令的操作码(opcode)集合

PE文件主要运行步骤

  1. PE文件被执行,PE装载器检查DOS MZ Header里面的PE Header偏移量。如果找到则跳转到PE Header。
  2. PE装载器检查PE Header的有效性。如果有效,就跳转到PE Header 的尾部
  3. 紧跟PE Header的是节表。PE装载器读取其中的节信息,并采用文件映射方式将这些节映射到内存中,同时附上节表里指定的节属性。
  4. PE文件映射入内存后,PE装载器将处理PE文件中类似import table(引入表)的逻辑部分。程序加载的时候需要加载很多函数和DLL文件,这时程序需要判断目标函数的地址并将该函数插补到该执行文件的映像中,所需要的信息都是放在PE文件的Import表中,PE文件中的每一个输入函数都明确的存于该表中。

执行文件遇到的节

1.text节。它的内容是指令代码,必须加密。

 

2.data节。data包括初始化的数据,比如编译时被初始化的global和static变量,和字符串。也可加密。
3.idata节。这个节是输入数据,包括输入目录和输入地址名字表(IAT{Import Address Table}和INT{Import Name Table})。idata包含其他外来DLL的函数及数据信息。

 

4.rsrc节。包括所有资源文件。

 

5.reloc节。reloc保存基地址重定位表(当文件刚开始装载入内存,若指定的内存装载地址已被其他进程占用,则需要此重定位表,来重定位重新装载进内存的地址,这个重定位表包含了调整所需的数据)。

 

6.edata节。该PE文件输出的函数和数据的表。

 

7.tls节。线程本地存储器。

 

8.rdata节。存放调试目录和说明字符串。


[CTF入门培训]顶尖高校博士及硕士团队亲授《30小时教你玩转CTF》,视频+靶场+题目!助力进入CTF世界

收藏
点赞4
打赏
分享
最新回复 (3)
雪    币: 310
活跃值: (1912)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
niuzuoquan 2021-11-2 14:33
2
0
mark
雪    币:
活跃值: (149)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
抚琴 2022-4-26 13:00
3
0
6
雪    币: 220
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
等你的... 2022-8-17 21:30
4
0
要记的东西太多了
游客
登录 | 注册 方可回帖
返回