免杀之重识PE文件(一)

前言

虽然已经会免杀,但是对这些基础掌握不牢。因此重新学习一下。

PE文件

PE的指纹识别

PE文件以MZ开头

image-20240620223511055

在偏移3C的位置,硬编码为78,ASCII码为x

image-20240620223645747

找到偏移78的位置,硬编码为50和45对应ASCII的PE

image-20240620223745278

PE结构

PE结构分为4部分,DOS部分、PE文件头、节表、节数据

image-20240620223941961

DOS部分

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
typedef struct _IMAGE_DOS_HEADER {      
WORD e_magic; //通常为"MZ"
WORD e_cblp;
WORD e_cp;
WORD e_crlc;
WORD e_cparhdr;
WORD e_minalloc;
WORD e_maxalloc;
WORD e_ss;
WORD e_sp;
WORD e_csum;
WORD e_ip;
WORD e_cs;
WORD e_lfarlc;
WORD e_ovno;
WORD e_res[4];
WORD e_oemid;
WORD e_oeminfo;
WORD e_res2[10];
LONG e_lfanew; // 指向PE头的指针
} IMAGE_DOS_HEADER, *PIMAGE_DOS_HEADER;

对照着看下

要记得的是e_lfanew,是指向PE头的指针

在PE头开始和DOS头中间的部分是DOS块没什么用,可以用来填充一小段shellcode

image-20240620230740962

PE头(NT头)

PE头分为标识PE头、标准PE头、扩展PE头

PE文件一些重要信息通常存储在标准PE头和扩展PE头中

PE头结构体代码

32位

1
2
3
4
5
typedef struct _IMAGE_NT_HEADERS {
DWORD Signature;
IMAGE_FILE_HEADER FileHeader;
IMAGE_OPTIONAL_HEADER32 OptionalHeader;
} IMAGE_NT_HEADERS32, *PIMAGE_NT_HEADERS32;

64位

1
2
3
4
5
typedef struct _IMAGE_NT_HEADERS64 {
DWORD Signature; //标识PE头,占2字节
IMAGE_FILE_HEADER FileHeader; //标准PE头,占20字节
IMAGE_OPTIONAL_HEADER64 OptionalHeader; //扩展PE头,占224字节
} IMAGE_NT_HEADERS64, *PIMAGE_NT_HEADERS64;

标识PE头

image-20240620231613413

标准PE头(文件头)

标准PE头通常占20个字节,包括了PE文件的基本属性和信息,例如文件类型、机器架构、入口点、标 志、段信息等等。其结构体代码如下所示

1
2
3
4
5
6
7
8
9
typedef struct _IMAGE_FILE_HEADER {
WORD Machine; //决定能运行在什么样的cpu上,若值为0则表示能任意cpu运行;值为14C表示能在intel386及后续版本;值为8664能运行在x64cpu上。因此可以用来判断程序是32位还是64位的
WORD NumberOfSections; //表示当前节的数量
DWORD TimeDateStamp; //编译器填写的时间戳,与文件创建或修改时间无关
DWORD PointerToSymbolTable; //与调试相关
DWORD NumberOfSymbols; //与调试相关
WORD SizeOfOptionalHeader; //扩展PE头的大小,一般32位为E0,64位为F0
WORD Characteristics; //文件属性
} IMAGE_FILE_HEADER, *PIMAGE_FILE_HEADER;

image-20240620232116037

拓展PE头(可选头)

扩展PE头通常占224个字节,以下结构体成员中有几个成员要重点注意 如果一个可执行文件它的dos部分+pe头+节表的共同大小为332,其 200, 那么 FileAlignment (文件对齐)的值为 SizeOfHeaders 的值应该为文件对齐的整数倍,所以 SizeOfHeaders 的值为400

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
#define IMAGE_NUMBEROF_DIRECTORY_ENTRIES 16
typedef struct _IMAGE_OPTIONAL_HEADER {
WORD Magic; // PE文件的类型, 32位为0x10b, 64位为0x20b
BYTE MajorLinkerVersion; //链接程序的主版本号
BYTE MinorLinkerVersion; //链接程序的次版本号
DWORD SizeOfCode; //所有代码节的总和
DWORD SizeOfInitializedData; //初始化数据节的大小
DWORD SizeOfUninitializedData; //未初始化节的大小
DWORD AddressOfEntryPoint; //程序入口点
DWORD BaseOfCode; //代码节的基地址
DWORD BaseOfData; //数据节的基地址,仅在32位PE文件中有效
DWORD ImageBase; //内存镜像基址
DWORD SectionAlignment; //内存对齐
DWORD FileAlignment; //文件对齐
WORD MajorOperatingSystemVersion; //操作系统主版本号
WORD MinorOperatingSystemVersion; //操作系统次版本号
WORD MajorImageVersion; // PE文件的主版本号
WORD MinorImageVersion; //PE文件的次版本号
WORD MajorSubsystemVersion; //子系统主版本号
WORD MinorSubsystemVersion; //子系统次版本号
DWORD Win32VersionValue; //Win32版本值,通常为0
DWORD SizeOfImage; //内存中整个PE文件映射的尺寸,可比实际值大,必须是内存对齐的整数倍
DWORD SizeOfHeaders; //所有头+节表按照文件对齐后的大小,否则加载会出错
DWORD CheckSum; //校验和,用来判断文件是否被修改
WORD Subsystem; //子系统 驱动程序(1) 图形界面(2) 控制台或dll(3)
WORD DllCharacteristics; // DLL文件特性,与Subsystem的值有关
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;

image-20240620233047860

节表

节表包含了PE文件中所有的节信息,每个节都包括了一个段的名称、大小、属性和数据等信息 节表是由一系列 IMAGE_SECTION_HEADER 结构的元素组成的结构体数组, 每个元素占40个字节

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#define IMAGE_SIZEOF_SHORT_NAME  8
typedef struct _IMAGE_SECTION_HEADER {
BYTE Name[IMAGE_SIZEOF_SHORT_NAME]; //ASCII字符串,当前节的名字
union {
DWORD PhysicalAddress;
DWORD VirtualSize;
} Misc; //当前节实际的大小(内存未对齐)
DWORD VirtualAddress; //节在内存中的偏移地址(真正的地址需加上Imagebase)
DWORD SizeOfRawData; //节在文件对齐后的大小
DWORD PointerToRawData; //节在文件中的偏移地址
DWORD PointerToRelocations;
DWORD PointerToLinenumbers;
WORD NumberOfRelocations;
WORD NumberOfLinenumbers;
DWORD Characteristics; //节的属性,涉及到此节是否可读可写可执行
} IMAGE_SECTION_HEADER, *PIMAGE_SECTION_HEADER;

节数据

节数据是PE文件中实际的二进制数据,它包含了PE文件中所有的代码、数据和资源等信息。每个节的数 据都位于PE文件中的一个连续的段中 PE文件中,不同的节用于存储不同类型的数据,通常包括以下几个节:

data节:用于存储PE文件中的静态数据,它是可读写的,通常包含全局变量、静态变量、已初始化 的数据等等。

rdata节:用于存储PE文件中的只读数据,它是只读的,通常包含字符串、常量、只读数据等等。

text节:用于存储PE文件中的可执行代码,它是可执行的,通常包含程序的主要代码,例如函数、 指令等等

.rsrc节:用于存储PE文件中的资源,包括图标、位图、字符串、菜单等等,通常是只读的。

.reloc节:reloc节用于存储PE文件中的重定位信息,它包含了需要被修改的地址和偏移量等信息, 通常是只读写的。

.tls节:用于存储PE文件中的线程本地存储(Thread Local Storage,TLS)相关信息,包括TLS索引、 初始化回调函数等等, 通常是可读写的。

__END__