PE

Contents

1. PE头字段解析

PE(Portable Executable)文件,是windows上的可移植的可执行文件,常见的 .exe .dll .sys 等都是PE文件。那么PE文件和计算机网络的各种包头一样,都有自己独特的格式。

首先PE文件开头就是DOS头 DOS_HEADER,DOS头只需要看第一个字段magic和最后一个字段lfanewmagic字段为一个WORD即两字节,若为MZ则属于PE文件;lfanew是指向NT头NT_HEADER的相对偏移,即文件起始位置(magic字段所在位置)+ lfanew 即为NT头的位置。

可以用十六进制查看器查看会有更直观的印象。lfanew字段到NT头之间会有一段说明字符串或者垃圾数据称为DOS stub

接下来NT头分为一个字段和两大部分,Signature字段,一大部分是FILE_HEADER(即COFF头或称文件头/标准PE头),另一大部分是OPTIONAL_HEADER 可选头。注意FILE_HEADER一共有0x14的长度,可以看成一个大的结构体,结构体内容就是图中_IMAGE_FILE_HEADER的部分。OPTIONAL_HEADER 可选头。

同理过了NT头,就是节表SECTION_HEADER 了。节表类似于结构体数组的形式,图所示会有多个同样结构的SECTION_HEADER重复。

1.1 DOS头(64个字节)

宽度 字段名称 重要 说明
WORD e_magic * “MZ标记” 用于判断是否为可执行文件
DWORD e_lfanew * PE头相对于文件的偏移,用于定位PE文件
 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 {      // DOS .EXE header
    WORD   e_magic;                     // Magic number  DOS头的标识,为4Dh和5Ah。分别为字母MZ
    WORD   e_cblp;                      // Bytes on last page of file
    WORD   e_cp;                        // Pages in file
    WORD   e_crlc;                      // Relocations
    WORD   e_cparhdr;                   // Size of header in paragraphs
    WORD   e_minalloc;                  // Minimum extra paragraphs needed
    WORD   e_maxalloc;                  // Maximum extra paragraphs needed
    WORD   e_ss;                        // Initial (relative) SS value
    WORD   e_sp;                        // Initial SP value
    WORD   e_csum;                      // Checksum
    WORD   e_ip;                        // Initial IP value
    WORD   e_cs;                        // Initial (relative) CS value
    WORD   e_lfarlc;                    // File address of relocation table
    WORD   e_ovno;                      // Overlay number
    WORD   e_res[4];                    // Reserved words
    WORD   e_oemid;                     // OEM identifier (for e_oeminfo)
    WORD   e_oeminfo;                   // OEM information; e_oemid specific
    WORD   e_res2[10];                  // Reserved words
    LONG   e_lfanew;                    // File address of new exe header   指向IMAGE_NT_HEADERS的所在(PE头相对于文件的偏移,用于定位PE文件)
  } IMAGE_DOS_HEADER, *PIMAGE_DOS_HEADER;
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
0x00 WORD e_magic;      //5A4D * MZ标记用于判断是否为可执行文件
0x02 WORD e_cblp;       //0090
0x04 WORD e_cp;         //0003
0x06 WORD e_crlc;       //0000
0x08 WORD e_cparhdr;    //0004
0x0a WORD e_minalloc;   //0000
0x0c WORD e_maxalloc;   //FFFF
0x0e WORD e_ss;         //0000
0x10 WORD e_sp;         //00B8
0x12 WORD e_csum;       //0000
0x14 WORD e_ip;         //0000
0x16 WORD e_cs;         //0000
0x18 WORD e_lfarlc;     //0040
0x1a WORD e_ovno;       //0000
0x1c WORD e_res[4];     //0000000000000000
0x24 WORD e_oemid;      //0000
0x26 WORD e_oeminfo;    //0000
0x28 WORD e_res2[10];   //20
0x3c DWORD e_lfanew;    //00000080 * PE头相对于文件的偏移,用于定位PE文件

1.2 PE文件头(NT头)

1
2
3
4
5
typedef struct IMAGE_NT_HEADERS{
0x00      DWORD Signature;                         //PE标识
0x04      IMAGE_FILE_HEADER FileHeader;            //标准PE头(20字节)
0x18      IMAGE_OPTIONAL_HEADER32 OptionalHeader;  //扩展PE头(大小不确定)
}IMAGE_NT_HEADERS,*PIMAGE_NT_HEADERS;

1.3 标准PE头(20字节)

宽度 字段名称 重要 说明
WORD Machine * 程序运行的CPU型号:0x0 任何处理器/0x14C 386及后续处理器
WORD NumberOfSections * 文件中存在的节的总数,如果要新增节或者合并节 就要修改这个值
DWORD TimeDateStamp * 时间戳:文件的创建时间(和操作系统的创建时间无关),编译器填写的
DWORD PointerToSymbolTable
DWORD NumberOfSymbols
WORD SizeOfOptionalHeader * 可选PE头的大小,32位PE文件默认E0h,64位PE文件默认为F0h,大小可以自定义
WORD Characteristics * 每个位有不同的含义,可执行文件值为10F 即0 1 2 3 8位置1
1
2
3
4
5
6
7
8
9
typedef struct _IMAGE_FILE_HEADER {
0x00 WORD Machine;              //014C * 程序运行的CPU型号,0x0任何处理器/0x14C Intel 386及后续处理器
0x02 WORD NumberOfSections;     //0008 * 文件中存在的节的总数,除了头,还有几节数据,如果要新增节或者合并节就要修改这个值.
0x04 DWORD TimeDateStamp;       //3E22F0DF * 时间戳:文件的创建时间(和操作系统的创建时间无关),编译器填写的
0x08 DWORD PointerToSymbolTable;//00000000
0x0c DWORD NumberOfSymbols;     //00000000
0x10 WORD SizeOfOptionalHeader; //00E0 * 可选PE头的大小,32位PE文件默认E0h=16*14,64位PE文件默认为F0h,大小可以自定义
0x12 WORD Characteristics;      //010E * 每个位有不同的含义,可执行文件值为10F 即0 1 2 3 8位置1
} IMAGE_FILE_HEADER, *PIMAGE_FILE_HEADER;

1.4 可选PE头(大小是不确定的)

程序入口 + 内存镜像基址 才是真正的地址

宽度 字段名称 重要 说明
WORD Magic ** 说明文件类型:10B 32位下的PE文件,20B 64位下的PE文件
BYTE MajorLinkerVersion
BYTE MinorLinkerVersion
DWORD SizeOfCode * 所有代码节的和,必须是FileAlignment的整数倍,编译器填的。 没用
DWORD SizeOfInitializedData * 已初始化数据大小的和,必须是FileAlignment的整数倍 编译器填的 没用
DWORD SizeOfUninitializedData * 未初始化数据大小的和,必须是FileAlignment的整数倍 编译器填的 没用
DWORD AddressOfEntryPoint ** 程序入口
DWORD BaseOfCode * 代码开始的基址,编译器填的 没用
DWORD BaseOfData * 数据开始的基址,编译器填的 没用
DWORD ImageBase ** 内存镜像基址
DWORD SectionAlignment ** 内存对齐
DWORD FileAlignment ** 文件对齐
WORD MajorOperatingSystemVersion
WORD MinorOperatingSystemVersion
WORD MajorImageVersion
WORD MinorImageVersion
WORD MajorSubsystemVersion
WORD MinorSubsystemVersion
DWORD Win32VersionValue
DWORD SizeOfImage ** 内存中整个PE文件的映射的尺寸,可以比实际的值大,但必须是SectionAlignment的整数倍
DWORD SizeOfHeaders ** 所有头+节表按照文件对齐后的大小,否则加载会出错
DWORD CheckSum * 校验和,一些系统文件有要求.用来判断文件是否被修改
WORD Subsystem
WORD DllCharacteristics
DWORD SizeOfStackReserve * 初始化时保留的堆栈大小
DWORD SizeOfStackCommit * 初始化时实际提交的大小
DWORD SizeOfHeapReserve * 初始化时保留的堆大小
DWORD SizeOfHeapCommit * 初始化时实践提交的大小
DWORD LoaderFlags
DWORD NumberOfRvaAndSizes * 目录项数目
 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
0x00 WORD Magic; // 说明文件类型:10B->32位下的PE文件 20B->64位下的PE文件
0x02 BYTE MajorLinkerVersion;
0x03 BYTE MinorLinkerVersion;
0x04 DWORD SizeOfCode; // 代码大小/所有代码节的和,必须是FileAlignment的整数倍 编译器填的 没用
0x08 DWORD SizeOfInitializedData; // 已初始化数据大小的和,必须是 FileAlignment的整数倍 编译器填的 没用
0x0c DWORD SizeOfUninitializedData; // 未初始化数据大小的和,必须是 FileAlignment的整数倍 编译器填的 没用
0x10 DWORD AddressOfEntryPoint; // 程序入口
0x14 DWORD BaseOfCode; // 代码开始的基址,编译器填的 没用
0x18 DWORD BaseOfData; // 数据开始的基址,编译器填的 没用
0x1c DWORD ImageBase; // 内存镜像基址
0x20 DWORD SectionAlignment; // 内存对齐,区段对齐
0x24 DWORD FileAlignment; // 文件对齐
0x28 WORD MajorOperatingSystemVersion;
0x2a WORD MinorOperatingSystemVersion;
0x2c WORD MajorImageVersion;
0x2e WORD MinorImageVersion;
0x30 WORD MajorSubsystemVersion;
0x32 WORD MinorSubsystemVersion;
0x34 DWORD Win32VersionValue;
0x38 DWORD SizeOfImage; //镜像大小/ 内存中整个PE文件的映射的尺寸,可以比实际的值大,但必须是SectionAlignment的整数倍,是拉伸之后的大小,,,
0x3c DWORD SizeOfHeaders; // 所有头+节表,技照‘文件对齐 FileAlignment’后的大小,否则加载会出错
0x40 DWORD CheckSum; // 校验和,一些系统文件有要求,用来判断文件是否被修改
0x44 WORD Subsystem;
0x46 WORD DllCharacteristics;
0x48 DWORD SizeOfStackReserve; // 初始化时保留的堆栈大小
0x4c DWORD SizeOfStackCommit; // 初始化时实际提交的大小
0x50 DWORD SizeOfHeapReserve; // 初始化时保留的堆大小
0x54 DWORD SizeOfHeapCommit; // 初始化时实践提交的大小
0x58 DWORD LoaderFlags;
0x5c DWORD NumberOfRvaAndSizes; // 目录项数目,RVA数目和大小
0x60 _IMAGE_DATA_DIRECTORY DataDirectory[16]; //16个结构体,每个结构体是8个字节

1.5 节表(40字节)

1.5.1 联合体

1
2
3
4
union TestUnion {	
    char x;
    int y;
};

TestUnion联合体的占用的内存空间为:max(sizeof(int), sizeof(char)) = sizeof(int)

特点:

  1. 联合体的成员是共享内存空间的
  2. 联合体的内存空间大小是联合体成员中对内存空间大小要求最大的空间大小
  3. 联合体最多只有一个成员有效

1.5.2 节表

一个PE文件有多少个节,就有多少个节表!每一个节表的大小是确定的,40字节。

如何确定有多少个节:可以通过标准PE头中的NumberOfsections字段的值来确定;确定了有多少个节,就确定了有多少一个节表,一个节表记录管理一个节的信息

一个PE文件从哪里开始是节表(硬盘上的地址):DOS头大小 + 垃圾空位 + PE签名大小 + 标准PE头大小 + 可选PE头大小(需要查);我们知道DOS头大小固定为64字节;PE签名大小为4字节;标准PE头大小固定为20字节;可选PE头大小可以通过标准PE头中的SizeOfOptionalHeader字段的值来确定:e_lfanew + 4(Signature) + 20(标准PE头) + SizeOfOptionalHeader = 节表开始地址

如果文件运行装载到内存中节表在4GB内存中的地址要加上imagebase的值,才是节表真正在内存中的起始地址。

每一个节表的结构是一个结构体类型的,长度固定为0x28,即40字节。如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
#define IMAGE_SIZEOF_SHORT_NAME              8						

typedef struct _IMAGE_SECTION_HEADER {						
    BYTE    Name[IMAGE_SIZEOF_SHORT_NAME];						//8个字节 一般情况下是以"\0"结尾的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;						
  1. Name 8个字节 一般情况下是以"\0"结尾的ASCII吗字符串来标识的名称,内容可以自定义.

注意:该名称并不遵守必须以"\0"结尾的规律,如果不是以"\0"结尾,系统会截取8个字节的长度进行处理.

  1. Misc 双字 是该节在没有对齐前的真实尺寸,该值可以不准确。
  2. VirtualAddress 节区在内存中的偏移地址。加上ImageBase才是在内存中的真正地址.
  3. SizeOfRawData 节在文件中对齐后的尺寸.
  4. PointerToRawData 节区在文件中的偏移.
  5. PointerToRelocations 在obj文件中使用 对exe无意义
  6. PointerToLinenumbers 行号表的位置 调试的时候使用
  7. NumberOfRelocations 在obj文件中使用 对exe无意义
  8. NumberOfLinenumbers 行号表中行号的数量 调试的时候使用
  9. Characteristics 节的属性

  1. Name 该节的名字 可以随便改 只取前8个字节
  2. Misc 下图中从A 到 B一共有多少个字节
  3. VirtualAddress 在内存中的偏移 相对于ImageBase偏移
  4. SizeOfRawData 下图中绿色块的大小
  5. PointerToRawData 在文件中的偏移 下图中绿色块的开始地址
  6. Characteristics 节的属性(可读、可写、可执行)

1.6 打印PE头信息

使用window.h 都有相匹配的结构体,直接.或者->都能找到需要的字段,在VS中会识别出比这里更多的高亮,比如DWORDPIMAGE_DOS_HEADER 之类的windows.h 才有的宏定义。

说明:一位16进制数占一个字节大小;在32位系统中,指针占用4字节空间,即指向的地址大小占用4字节,所以对地址的操作都要转换为DWORD

方式1(推荐)

  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
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
#pragma warning(disable:4996)
#include <iostream>
#include <windows.h>
#include <fstream>
#include <vector>

#define FILE_PATH "CRACKME.EXE"

void analysis_PE_head(char* File_buffer)
{
	// 实例化PE文件头几个结构体
	PIMAGE_DOS_HEADER pDosHeader = NULL;			// DOS头
	PIMAGE_NT_HEADERS pNTHeader = NULL;				// NT头
	PIMAGE_FILE_HEADER pPEHeader = NULL;			// 标准PE头
	PIMAGE_OPTIONAL_HEADER32 pOptionHeader = NULL;	// 可选PE头
	PIMAGE_SECTION_HEADER pSectionHeader = NULL;	// SECTION(节)头

	// PIMAGE_DOS_HEADER结构体类型转换
	pDosHeader = (PIMAGE_DOS_HEADER)File_buffer;

	// 判断是不是有效的MZ标志
	if (*((PWORD)pDosHeader) != IMAGE_DOS_SIGNATURE) {
		printf("不是有效的MZ标志!\r\n");
		return;
	}

	// 打印DOS头
	printf("=============================DOS头信息如下=============================\r\n");
	printf("MZ标志:\t\t\t0x%04X\r\n", pDosHeader->e_magic);
	printf("PE偏移:\t\t\t0x%08X\r\n", pDosHeader->e_lfanew);

	// 判断是不是有效的PE标志  =》 说明:一位16进制数占一个字节大小;在32位系统中,指针占用4字节空间,即指向的地址大小占用4字节。
	if (*((PDWORD)((DWORD)File_buffer + pDosHeader->e_lfanew)) != IMAGE_NT_SIGNATURE) {
		printf("不是有效的PE标志!\r\n");
		return;
	}

	unsigned int x = (DWORD)File_buffer;
	unsigned int x2 = (DWORD)File_buffer + pDosHeader->e_lfanew;


	// 类型转换 PIMAGE_NT_HEADERS结构体
	pNTHeader = (PIMAGE_NT_HEADERS)((DWORD)File_buffer + pDosHeader->e_lfanew);
	// 打印NT头
	printf("=============================NT头信息如下===============================\r\n");
	printf("NT:\t\t\t\t0x%08X\r\n", pNTHeader->Signature);

	// 类型转换 PIMAGE_FILE_HEADER结构体
	pPEHeader = (PIMAGE_FILE_HEADER)((DWORD)pNTHeader + 4);
	// 打印标准PE文件头
	printf("=============================标准PE头信息如下============================\r\n");
	printf("Machine:\t\t\t0x%04X\r\n", pPEHeader->Machine);
	printf("NumberOfSections:\t\t0x%04X\n", pPEHeader->NumberOfSections);
	printf("TimeDateStamp:\t\t\t0x%08X\n", pPEHeader->TimeDateStamp);
	printf("SizeOfOptionalHeader:\t\t0x%04X\r\n", pPEHeader->SizeOfOptionalHeader);
	printf("Characteristics:\t\t0x%04X\r\n", pPEHeader->Characteristics);

	// 可选PE头的位置 = 标准PE的位置 + 标准PE头的大小(IMAGE_SIZEOF_FILE_HEADER)
	pOptionHeader = (PIMAGE_OPTIONAL_HEADER32)((DWORD)pPEHeader + IMAGE_SIZEOF_FILE_HEADER);//
	// 打印可选PE头
	printf("==============================可选PE头信息如下==============================\r\n");
	printf("Magic:\t\t\t\t0x%04X\r\n", pOptionHeader->Magic);
	printf("AddressOfEntryPoint:\t\t0x%08X\r\n", pOptionHeader->AddressOfEntryPoint);
	printf("ImageBase:\t\t\t0x%08X\r\n", pOptionHeader->ImageBase);
	printf("SectionAlignment:\t\t0x%08X\r\n", pOptionHeader->SectionAlignment);
	printf("FileAlignment:\t\t\t0x%08X\r\n", pOptionHeader->FileAlignment);
	printf("SizeOfImage:\t\t\t0x%08X\r\n", pOptionHeader->SizeOfImage);
	printf("SizeOfHeaders:\t\t\t0x%08X\r\n", pOptionHeader->SizeOfHeaders);

	// 节表的位置 = 可选PE头的位置 + 可选PE头的大小
	pSectionHeader = (PIMAGE_SECTION_HEADER)((DWORD)pOptionHeader + pPEHeader->SizeOfOptionalHeader);
	printf("==============================节表信息如下===============================\n");
	//printf("name:0x%s\n",pSectionHeader->Misc);
	DWORD dwNumberOfSection = pPEHeader->NumberOfSections;
	printf("节表的数量:%d\n", dwNumberOfSection);
	/*
	printf("0x%x\n",pPEHeader->NumberOfSections);
	printf("IMAGE_SIZEOF_SHORT_NAME:0x%x\n",IMAGE_SIZEOF_SHORT_NAME);
	printf("option_add:0x%x\n",pOptionHeader);
	printf("psection_add:0x%x\n",pSectionHeader);
	printf("==============================================================\n");*/
	for (DWORD i = 0; i < dwNumberOfSection; i++, pSectionHeader++) {
		printf("========================第%d个节信息:===============================\n", i + 1);
		printf("section_name: ");

		for (DWORD j = 0; j < IMAGE_SIZEOF_SHORT_NAME; j++) {
			printf("%c", pSectionHeader->Name[j]);
		}

		printf("\r\n");
		printf("Misc:\t\t\t\t0x%08X\r\n", pSectionHeader->Misc);
		printf("VirtualAddress:\t\t\t0x%08X\r\n", pSectionHeader->VirtualAddress);
		printf("SizeOfRawData:\t\t\t0x%08X\r\n", pSectionHeader->SizeOfRawData);
		printf("PointerToRawData:\t\t0x%08X\r\n", pSectionHeader->PointerToRawData);
		printf("Characteristics:\t\t0x%08X\r\n", pSectionHeader->Characteristics);
	}
}

void printNTHeaders()
{
	std::ifstream input(FILE_PATH, std::ios::binary);

	if (!input.is_open()) {
		std::cout << "文件打开失败" << std::endl;
	}

	// 计算文件长度
	input.seekg(0, std::ios::end);	// 设置到文件的末尾
	const int file_size = input.tellg();

	input.seekg(0, std::ios::beg);// 重新设置到文件的开始

	// 分配内存,并置零
	std::vector<char> file_buffer(file_size);
	//memset(file_buffer.data(), 0, file_size);

	// 将文件写入file_buffer中
	input.read(file_buffer.data(), file_size);

	// 打印PE头部信息
	analysis_PE_head(file_buffer.data());
}

int main(int argc, char* argv[])
{
	printNTHeaders();

	system("pause");

	return 0;
}

方式2

  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
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
#pragma warning(disable:4996)
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <windows.h>
#include <malloc.h>

#define F_PATH "CRACKME.EXE"

FILE* open_file(char* file_path, char* open_mode);
int compute_file_size(FILE* file_address);
char* allocate_buffer(int file_size);
char* readfile2memory(char* file_buffer, int file_size, FILE* file_address);
void analysis_PE_head(char* File_buffer);

VOID PrintNTHeaders()
{
	// 初始化
	//char file_path[] = "C:\\Windows\\System32\\notepad.exe";
	char file_path[] = F_PATH;
	char open_mode[] = "rb";

	// 打开文件,返回文件指针
	FILE* file_address = open_file(file_path, open_mode);

	// 计算文件长度
	int file_size = compute_file_size(file_address);

	// 分配内存,并置零
	char* File_buffer = allocate_buffer(file_size);

	// 将文件写入内存,并返回内存首地址
	File_buffer = readfile2memory(File_buffer, file_size, file_address);

	// 打印PE头部信息
	analysis_PE_head(File_buffer);

	// 释放内存、关闭文件流
	free(File_buffer);
	fclose(file_address);
}

FILE* open_file(char* file_path, char* open_mode)
{
	FILE* file_address = fopen(file_path, open_mode);  // fopen() 参数是字符串也就是常量指针类型
	if (!file_address)
	{
		printf("打开文件失败!\r\n");
		return 0;
	}
	return file_address;
}

// 计算文件的大小
int compute_file_size(FILE* file_address) {
	int size = 0;

	fseek(file_address, 0, SEEK_END);
	size = ftell(file_address);
	fseek(file_address, 0, SEEK_SET);

	return size;
}

char* allocate_buffer(int file_size) {
	char* file_buffer = (char*)malloc(file_size);

	if (!file_buffer) {
		printf("申请内存失败!\r\n");
		return 0;
	}

	memset(file_buffer, 0, file_size);

	return file_buffer;
}

char* readfile2memory(char* file_buffer, int file_size, FILE* file_address)
{
	if (!(fread(file_buffer, file_size, 1, file_address)))
	{
		printf("从文件向内存中读取数据失败!\r\n");
		return 0;
	}
	return file_buffer; // 如果写入内存成功,则返回内地址
}

void analysis_PE_head(char* File_buffer)
{
	// 实例化PE文件头几个结构体
	PIMAGE_DOS_HEADER pDosHeader = NULL;			// DOS头
	PIMAGE_NT_HEADERS pNTHeader = NULL;				// NT头
	PIMAGE_FILE_HEADER pPEHeader = NULL;			// 标准PE头
	PIMAGE_OPTIONAL_HEADER32 pOptionHeader = NULL;	// 可选PE头
	PIMAGE_SECTION_HEADER pSectionHeader = NULL;	// SECTION(节)头

	// 强制类型转换
	pDosHeader = (PIMAGE_DOS_HEADER)File_buffer;

	// 判断是不是有效的MZ标志
	if (*((PWORD)pDosHeader) != IMAGE_DOS_SIGNATURE)
	{
		printf("不是有效的MZ标志!\r\n");
		free(File_buffer);
		return;
	}

	// 强制类型转换 PIMAGE_DOS_HEADER结构体
	pDosHeader = (PIMAGE_DOS_HEADER)File_buffer;
	// 打印DOS头
	printf("=============================DOS头信息如下=============================\r\n");
	printf("MZ标志:\t\t\t0x%04X\r\n", pDosHeader->e_magic);
	printf("PE偏移:\t\t\t0x%08X\r\n", pDosHeader->e_lfanew);

	// 判断是不是有效的PE标志  =》 说明:一位16进制数占一个字节大小
	if (*((PDWORD)((DWORD)File_buffer + pDosHeader->e_lfanew)) != IMAGE_NT_SIGNATURE) {
		printf("不是有效的PE标志!\r\n");
		free(File_buffer);
		return;
	}

	// 强制类型转换 PIMAGE_NT_HEADERS结构体
	pNTHeader = PIMAGE_NT_HEADERS((DWORD)File_buffer + pDosHeader->e_lfanew);
	// 打印NT头
	printf("=============================NT头信息如下===============================\r\n");
	printf("NT:\t\t\t\t0x%04X\r\n", pNTHeader->Signature);
	// 强制类型转换 PIMAGE_FILE_HEADER结构体
	pPEHeader = (PIMAGE_FILE_HEADER)((DWORD)pNTHeader + 4);


	// 打印标准PE文件头
	printf("=============================标准PE头信息如下============================\r\n");
	printf("Machine:\t\t\t0x%04X\r\n", pPEHeader->Machine);
	printf("NumberOfSections:\t\t0x%04X\n", pPEHeader->NumberOfSections);
	printf("TimeDateStamp:\t\t0x%08X\n", pPEHeader->TimeDateStamp);
	printf("SizeOfOptionalHeader:\t\t0x%04X\r\n", pPEHeader->SizeOfOptionalHeader);
	printf("Characteristics:\t\t0x%04X\r\n", pPEHeader->Characteristics);

	// 可选PE头的位置 = 标准PE的位置 + 标准PE头的大小(IMAGE_SIZEOF_FILE_HEADER)
	pOptionHeader = (PIMAGE_OPTIONAL_HEADER32)((DWORD)pPEHeader + IMAGE_SIZEOF_FILE_HEADER);//
	// 打印可选PE头
	printf("==============================可选PE头信息如下==============================\r\n");
	printf("Magic:\t\t\t\t0x%04X\r\n", pOptionHeader->Magic);
	printf("AddressOfEntryPoint:\t\t0x%08X\r\n", pOptionHeader->AddressOfEntryPoint);
	printf("ImageBase:\t\t\t0x%08X\r\n", pOptionHeader->ImageBase);
	printf("SizeOfImage:\t\t\t0x%08X\r\n", pOptionHeader->SizeOfImage);
	printf("SizeOfHeaders:\t\t\t0x%08X\r\n", pOptionHeader->SizeOfHeaders);
	printf("SectionAlignment:\t\t0x%08X\r\n", pOptionHeader->SectionAlignment);
	printf("FileAlignment:\t\t\t0x%08X\r\n", pOptionHeader->FileAlignment);

	// 节表的位置 = 可选PE头的位置 + 可选PE头的大小
	pSectionHeader = (PIMAGE_SECTION_HEADER)((DWORD)pOptionHeader + pPEHeader->SizeOfOptionalHeader);
	printf("==============================节表信息如下===============================\n");
	//printf("name:0x%s\n",pSectionHeader->Misc);
	DWORD dwNumberOfSection = pPEHeader->NumberOfSections;
	printf("节表的数量:%d\n", dwNumberOfSection);
	/*
	printf("0x%x\n",pPEHeader->NumberOfSections);
	printf("IMAGE_SIZEOF_SHORT_NAME:0x%x\n",IMAGE_SIZEOF_SHORT_NAME);
	printf("option_add:0x%x\n",pOptionHeader);
	printf("psection_add:0x%x\n",pSectionHeader);
	printf("==============================================================\n");*/
	for (DWORD i = 0; i < dwNumberOfSection; i++, pSectionHeader++) {
		printf("========================第%d个节信息:===============================\n", i + 1);
		printf("section_name: ");

		for (DWORD j = 0; j < IMAGE_SIZEOF_SHORT_NAME; j++) {
			printf("%c", pSectionHeader->Name[j]);
		}

		printf("\r\n");
		printf("Misc:\t\t\t\t0x%08X\r\n", pSectionHeader->Misc);
		printf("VirtualAddress:\t\t\t0x%08X\r\n", pSectionHeader->VirtualAddress);
		printf("SizeOfRawData:\t\t\t0x%08X\r\n", pSectionHeader->SizeOfRawData);
		printf("PointerToRawData:\t\t0x%08X\r\n", pSectionHeader->PointerToRawData);
		printf("Characteristics:\t\t0x%08X\r\n", pSectionHeader->Characteristics);
	}
}

int main(int argc, char* argv[])
{
	PrintNTHeaders();

	system("pause");

	return 0;
}

2. PE加载过程

2.1 FileBuffer到ImageBuffer常见的误区

2.1.1 文件执行的总过程

  • 我们知道一个硬盘上的文件读入到内存中(FileBuffer),是原封不动的将硬盘上的文件数据复制一份放到内存中
  • 接着如果文件要运行,需要先将FileBuffer中的文件数据"拉伸",重载到每一个可执行文件的4GB虚拟内存中!此时称文件印象或者内存印象,即ImageBuffer
  • 但是ImageBuffer就是文件运行时真正在内存中状态吗?或者说文件在ImageBuffer中就是表示文件被执行了吗?不!!!!!!
  • 在ImageBuffer中的文件数据由于按照一定的规则被"拉伸",只是已经无线接近于可被windows执行的文件格式了!但是此时还不代表文件已经被执行了,因为此时文件也只是处在4GB的虚拟内存中,如果文件被执行操作系统还需要做一些事情,将文件真正的装入内存中,等待CPU的分配执行
  • 所以不要理解为ImageBuffer中的状态就是文件正在被执行,后面操作系统还要做很多事情才能让ImageBuffer中的文件真正执行起来的

2.1.2 SizeOfRawData一定大于Misc.VirtualSize?

  • SizeOfRawData表示此节在硬盘上经过文件对齐后的大小;Misc.VirtualSize表示此节没有经过对齐的在内存中的大小。那么是不是说SizeOfRawData一定大于Misc.VirtualSize呢?不一定!!!!!!!
  • 我们写C语言的时候知道如果你定义一个数组已经初始化,比如int arr[1000] = {0};,此时编译成.exe文件存放在硬盘上时,这1000个int类型的0肯定会存放在某一个节中,并且分配1000个0的空间,这个空间大小是多少,最后重载到ImageBuffer时还是多少,即Misc.VirtualSize不管文件在硬盘上还是内存中的值都是一致的。所以,SizeOfRawData一般都是大于Misc.VirtualSize的
  • 但是如果我们定义成int arr[1000];,表示数据还未初始化,并且如果程序中没有使用过或初始化过这块内存空间,那么我们平时看汇编会发现其实编译器还没有做任何事情,这就只是告诉编译器需要留出1000个int宽度大小的内存空间。所以如果某一个节中存在已经被定义过但还未初始化的数据,那么文件在硬盘上不会显式的留出空间,即SizeOfRawData中不会算上未初始化数据的空间;但是此节的Misc.VirtualSize为加载到内存中时节的未对齐的大小,那么这个值就需要算上给未初始化留出来空间后的整个节的大小,故在内存中的节本身的总大小可能会大于硬盘中的此节文件对齐后的大小。

2.2 模拟PE加载过程

  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
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
#pragma warning(disable : 4996)
#include <malloc.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <windows.h>

#define test 1

DWORD ToLoaderPE(LPSTR file_path, PVOID* pFileBuffer);

DWORD CopyFileBufferToImageBuffer(PVOID pFileBuffer, PVOID* pImageBuffer);
DWORD CopyImageBufferToNewFileBuffer(PVOID pImageBuffer, PVOID* pNewFileBuffer);
BOOL MemoryToFile(PVOID pMemBuffer, DWORD size, LPSTR lpszFile);

//char file_path[] = "C:\\Windows\\System32\\notepad.exe";
char file_path[] = "notepad.exe";
char imageBuffer_file_path[] = "imageBuffer.exe";
char fileBuffer_file_path[] = "fileBuffer.exe";

//返回PE文件大小
DWORD ToLoaderPE(LPSTR file_path, PVOID* pFileBuffer) {
	FILE* pFile = NULL;
	DWORD FileSize = 0;
	PVOID pFileBufferTemp = NULL;

	pFile = fopen(file_path, "rb");

	if (!pFile) {
		printf("(ToLoaderPE)Can't open file!\n");
		return 0;
	}

	fseek(pFile, 0, SEEK_END);
	FileSize = ftell(pFile);
	printf("FileBuffer: %#x\n", FileSize);
	fseek(pFile, 0, SEEK_SET);
	pFileBufferTemp = malloc(FileSize);

	if (!pFileBufferTemp) {
		printf("(ToLoaderPE)Allocate dynamic memory failed!\n");
		fclose(pFile);
		return 0;
	}

	DWORD n = fread(pFileBufferTemp, FileSize, 1, pFile);

	if (!n) {
		printf("(ToLoaderPE)Read file failed!\n");
		free(pFileBufferTemp);
		fclose(pFile);
		return 0;
	}

	*pFileBuffer = pFileBufferTemp;
	pFileBufferTemp = NULL;
	fclose(pFile);

	return FileSize;
}

DWORD CopyFileBufferToImageBuffer(PVOID pFileBuffer, PVOID* pImageBuffer) {
	PIMAGE_DOS_HEADER pDosHeader = NULL;
	PIMAGE_NT_HEADERS pNTHeader = NULL;
	PIMAGE_FILE_HEADER pPEHeader = NULL;
	PIMAGE_OPTIONAL_HEADER32 pOptionHeader = NULL;
	PIMAGE_SECTION_HEADER pSectionHeader = NULL;

	PVOID pImageTemp = NULL;

	if (!pFileBuffer) {
		printf("(CopyFileBufferToImageBuffer)Can't open file!\n");
		return 0;
	}

	if (*((PWORD)pFileBuffer) != IMAGE_DOS_SIGNATURE) {
		printf("(CopyFileBufferToImageBuffer)No MZ flag, not exe file!\n");
		return 0;
	}

	pDosHeader = (PIMAGE_DOS_HEADER)pFileBuffer;

	if (*((LPDWORD)((DWORD)pFileBuffer + pDosHeader->e_lfanew)) !=
		IMAGE_NT_SIGNATURE) {
		printf("(CopyFileBufferToImageBuffer)Not a valid PE flag!\n");
		return 0;
	}

	pNTHeader = (PIMAGE_NT_HEADERS)((DWORD)pFileBuffer + pDosHeader->e_lfanew);
	pPEHeader = (PIMAGE_FILE_HEADER)(((DWORD)pNTHeader) + 4);
	pOptionHeader =
		(PIMAGE_OPTIONAL_HEADER32)((DWORD)pPEHeader + IMAGE_SIZEOF_FILE_HEADER);
	pSectionHeader = (PIMAGE_SECTION_HEADER)((DWORD)pOptionHeader +
		pPEHeader->SizeOfOptionalHeader);

	// 分配内存中整个PE文件的映射的尺寸
	pImageTemp = malloc(pOptionHeader->SizeOfImage);

	if (!pImageTemp) {
		printf("(CopyFileBufferToImageBuffer)Allocate dynamic memory failed!\n");
		free(pImageTemp);
		return 0;
	}

	memset(pImageTemp, 0, pOptionHeader->SizeOfImage);
	// pOptionHeader->SizeOfHeaders:所有头+节表按照文件对齐后的大小
	// 填充所有头+节表(按照文件对齐的方式)
	memcpy(pImageTemp, pDosHeader, pOptionHeader->SizeOfHeaders);

	PIMAGE_SECTION_HEADER pSectionHeaderTemp = pSectionHeader;

	for (int n = 0; n < pPEHeader->NumberOfSections; n++, pSectionHeaderTemp++) {
		/**
		 * VirtualAddress:节区在内存中的偏移地址。加上ImageBase才是在内存中的真正地址.
		 * PointerToRawData:节区在文件中的偏移
		 * SizeOfRawData:节在文件中对齐后的尺寸
		 */
		memcpy((PVOID)((DWORD)pImageTemp + pSectionHeaderTemp->VirtualAddress),
			(PVOID)((DWORD)pFileBuffer + pSectionHeaderTemp->PointerToRawData),
			pSectionHeaderTemp->SizeOfRawData);
		printf("VirtualAddress%d: %#10x         PointerToRawData%d: %#10x\n",
			n, (DWORD)pImageTemp + pSectionHeader->VirtualAddress,
			n, (DWORD)pFileBuffer + pSectionHeader->PointerToRawData);
	}
	*pImageBuffer = pImageTemp;
	pImageTemp = NULL;

	//内存中整个PE文件的映射的尺寸,可以比实际的值大,但必须是SectionAlignment的整数倍
	return pOptionHeader->SizeOfImage;
}

DWORD CopyImageBufferToNewFileBuffer(PVOID pImageBuffer, PVOID* pNewFileBuffer) {
	PIMAGE_DOS_HEADER pDosHeader = NULL;
	PIMAGE_NT_HEADERS pNTHeader = NULL;
	PIMAGE_FILE_HEADER pPEHeader = NULL;
	PIMAGE_OPTIONAL_HEADER32 pOptionHeader = NULL;
	PIMAGE_SECTION_HEADER pSectionHeader = NULL;

	LPVOID pTempNewbuffer = NULL;

	if (!pImageBuffer) {
		printf("(CopyImageBufferToNewBuffer)Can't open file!\n");
		return 0;
	}

	if (*((PWORD)pImageBuffer) != IMAGE_DOS_SIGNATURE) {
		printf("(CopyImageBufferToNewBuffer)No MZ flag, not exe file!\n");
		return 0;
	}

	pDosHeader = (PIMAGE_DOS_HEADER)pImageBuffer;
	if (*((PDWORD)((DWORD)pImageBuffer + pDosHeader->e_lfanew)) !=
		IMAGE_NT_SIGNATURE) {
		printf("(CopyImageBufferToNewBuffer)Not a valid PE flag!\n");
		return 0;
	}

	pNTHeader = (PIMAGE_NT_HEADERS)((DWORD)pImageBuffer + pDosHeader->e_lfanew);
	pPEHeader =
		(PIMAGE_FILE_HEADER)((DWORD)pNTHeader + 4); // 这里必须强制类型转换
	pOptionHeader =
		(PIMAGE_OPTIONAL_HEADER32)((DWORD)pPEHeader + IMAGE_SIZEOF_FILE_HEADER);
	pSectionHeader = (PIMAGE_SECTION_HEADER)((DWORD)pOptionHeader +
		pPEHeader->SizeOfOptionalHeader);

	/**
	 * 计算ImageBuffer => FileBuffer的大小
	 *		SizeOfHeaders:所有头+节表按照文件对齐后的大小
	 *		SizeOfRawData:节在文件中对齐后的尺寸
	 */
	int new_buffer_size = pOptionHeader->SizeOfHeaders;
	for (DWORD i = 0; i < pPEHeader->NumberOfSections; i++) {
			new_buffer_size += pSectionHeader[i].SizeOfRawData; 
	}

	// 分配内存(newbuffer)
	pTempNewbuffer = malloc(new_buffer_size);
	if (!pTempNewbuffer) {
		printf("(CopyImageBufferToNewBuffer)Allocate dynamic memory failed!\n");
		return 0;
	}

	// 拷贝所有头+节表
	memset(pTempNewbuffer, 0, new_buffer_size);
	memcpy(pTempNewbuffer, pDosHeader, pOptionHeader->SizeOfHeaders);

	// 循环拷贝节区
	/**
	 * PointerToRawData:节区在文件中的偏移.
	 * VirtualAddress:节区在内存中的偏移地址
	 * SizeOfRawData:节在文件中对齐后的尺寸
	 */
	PIMAGE_SECTION_HEADER pTempSectionHeader = pSectionHeader;
	for (DWORD j = 0; j < pPEHeader->NumberOfSections; j++, pTempSectionHeader++) {
		memcpy(
			(PDWORD)((DWORD)pTempNewbuffer + pTempSectionHeader->PointerToRawData),
			(PDWORD)((DWORD)pImageBuffer + pTempSectionHeader->VirtualAddress),
			pTempSectionHeader->SizeOfRawData);
	}

	//返回数据
	*pNewFileBuffer = pTempNewbuffer; //暂存的数据传给参数后释放
	pTempNewbuffer = NULL;
	return new_buffer_size; // 返回计算得到的分配内存的大小
}

BOOL MemoryToFile(PVOID pMemBuffer, DWORD size, LPSTR lpszFile) {
	FILE* fp;
	fp = fopen(lpszFile, "wb");
	if (fp != NULL) {
		fwrite(pMemBuffer, size, 1, fp);
	}
	fclose(fp);

	return 1;
}

VOID operate() {
	LPVOID pFileBuffer = NULL;
	LPVOID pNewFileBuffer = NULL;
	LPVOID pImageBuffer = NULL;

	DWORD ret1 = ToLoaderPE(
		file_path,
		&pFileBuffer); // &pFileBuffer(void**类型) 传递地址对其值可以进行修改
	printf("exe->filebuffer  返回值为计算所得文件大小:%#x\n", ret1);

	DWORD ret2 = CopyFileBufferToImageBuffer(pFileBuffer, &pImageBuffer);
	printf("filebuffer -> imagebuffer返回值为计算所得文件大小:%#x\n", ret2);

	MemoryToFile(pImageBuffer, ret2, imageBuffer_file_path);

	DWORD ret3 = CopyImageBufferToNewFileBuffer(pImageBuffer, &pNewFileBuffer);
	printf("imagebuffer -> newfilebuffer返回值为计算所得文件大小:%#x\n", ret3);

	MemoryToFile(pNewFileBuffer, ret3, fileBuffer_file_path);

	free(pFileBuffer);
	free(pNewFileBuffer);
	free(pImageBuffer);
}

int main() {
	operate();
	system("pause");

	return 0;
}

3. 在代码空白区添加代码(手动)

3.1 MessageBox函数

API函数–-MessageBoxA。包含在头文件windows.h;如果一个程序中包含user32.dll,则此程序就有MessageBoxA API函数

1
int MessageBox( HWND hWnd,LPCTSTR lpText, LPCTSTR lpCaption = NULL, UINT nType = MB_OK);
  • hWnd:表示窗口句柄,指定该对话框的所有者窗口;如果该参数为空(0/NULL),则该对话框不属于任何窗口。
  • lpText:字符串,指显示在对话框中的内容。
  • lpCaption:字符串,指对话框的标题;如果此参数为空,则默认使用“错误”作为标题。
  • nType:指定显示按钮的数目及形式,表名使用的图标样式、缺省按钮是什么、以及消息框的强制回应等。

3.2 call与jmp指令的硬编码

  • call指令的硬编码:E8 后面跟了4个字节(转换后的地址)
  • jmp指令的硬编码:E9 后面跟了4个字节(转换后的地址)
  • push指令的硬编码:6A 参数的值(传入函数参数的值是什么对应的参数硬编码就是什么)

E8 和 E9 后面4字节地址X的转换公式:X = 真正要跳转的地址 - E8这条指令的下一行地址,因为call 地址的硬编码固定一共占5字节,所以E8指令的下一行地址 = E8当前地址 + 5;

所以公式最终为:X = 要跳转的地址 - (E8的地址 + 该指令长度)

注意:这些公式中所用到的地址值全部是可执行文件在虚拟内存中的地址值,不是文件地址!

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
#include <iostream>

void Function(int a, int b, int c, int d) {
}

int main(int argc, char* argv[]) {
    Function(1, 2, 3, 4);

    return 0;
}
 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
vs2019_project_test.exe!main(int, char * *):
00E817A0 55                   push        ebp  
00E817A1 8B EC                mov         ebp,esp  
00E817A3 81 EC C0 00 00 00    sub         esp,0C0h  
00E817A9 53                   push        ebx  
00E817AA 56                   push        esi  
00E817AB 57                   push        edi  
00E817AC 8B FD                mov         edi,ebp  
00E817AE 33 C9                xor         ecx,ecx  
00E817B0 B8 CC CC CC CC       mov         eax,0CCCCCCCCh  
00E817B5 F3 AB                rep stos    dword ptr es:[edi]  
00E817B7 B9 00 C0 E8 00       mov         ecx,offset _E4C34A80_main@cpp (0E8C000h)  
00E817BC E8 4B FB FF FF       call        @__CheckForDebuggerJustMyCode@4 (0E8130Ch)  
00E817C1 6A 04                push        4  
00E817C3 6A 03                push        3  
00E817C5 6A 02                push        2  
00E817C7 6A 01                push        1  
00E817C9 E8 95 F9 FF FF       call        Function (0E81163h)  
00E817CE 83 C4 10             add         esp,10h  
00E817D1 33 C0                xor         eax,eax  
00E817D3 5F                   pop         edi  
00E817D4 5E                   pop         esi  
00E817D5 5B                   pop         ebx  
00E817D6 81 C4 C0 00 00 00    add         esp,0C0h  
00E817DC 3B EC                cmp         ebp,esp  
00E817DE E8 52 FA FF FF       call        __RTC_CheckEsp (0E81235h)  
00E817E3 8B E5                mov         esp,ebp  
00E817E5 5D                   pop         ebp  
00E817E6 C3                   ret  

计算E8后面跟的4字节地址:

因为E8的当前地址为0x00E817C9,真正要跳转的Function函数的地址为0x0E81163,所有根据公式:X = 要跳转的地址 - (E8的地址 + 该指令长度) = 0x0E81163 - (0x00E817C9 + 5)= 0xFFFFF995,由于内存是低地址向高地址存储,所以为95 F9 FF FF,所以最后call指令的硬编码为E8 95 F9 FF FF

3.3 效果说明

我们将一段MessageBox函数添加到一个可执行文件中,当我们双击此文件时先弹出我们写的弹框窗口,点击确定按钮后再重新正常执行文件。所以我们的目的就是将此可执行文件的入口改成我们添加的代码位置,执行完代码后再jmp到此文件的原来的入口地址。

注意在代码节空白区添加代码相当于给在硬盘上的文件中添加数据,添加完后再运行文件,所以这个过程可以理解为文件注入;但是我们平时说的注入,则是文件在运行时我把代码添加进去,这个过程可以理解为内存注入。

3.4 思路

程序其实就是一堆二进制数据组成的,所以我们不可能直接将MessageBox函数加到一个可执行文件中,而是要想办法得到这个函数对应的二进制数据,将这些数据添加到文件中。

但是我们没必要去自己再去写一个MessageBox函数的二进制,因为只要是user32的可执行文件中都会调用系统API函数–-MessageBoxA,所以我们只要找到此文件中的MessageBox函数的地址,使用call 这个地址,在call之前先push需要的4个参数就等于使用了弹框的功能。因为最终我们要添加的是二进制数据,所以我们要知道传入4个参数的指令硬编码和call MessageBox地址指令对应的硬编码,最后再加上一个jmp 程序原来的入口地址对应的硬编码即可。

但是这个思路有一个小问题,换一个机器就用不了了,后面学习会知道原因。

获取运行时MessageBoxAPI函数的地址:

  • 使用OD打开可执行文件,在左下角的命令栏输入:bp MessageBoxA 回车,表示在此函数起始位置设置断点,接着我们点击上方栏中的B按钮,表示查看断点,接着双击我们刚设置的断点会跳转到断点所在位置,这个地址就是MessageBoxA函数的起始地址。

记下来此程序本来的入口地址:通过可选PE头中的AddressOfEntryPoint字段值可以知道程序入口地址的偏移量,再通过可选PE头中的ImageBase字段知道文件装载到内存中的起始地址,根据ImageBase + AddressOfEntryPoint可以得出来程序在虚拟内存中的真正的入口地址。

按照要求,我们只需要构造这样一个功能的代码:

  1. push MessageBox函数需要的4个参数;
  2. call MessageBox函数地址;
  3. jmp 程序原来的入口地址。

先初步判断空白区的长度是否能存放得下添加的硬编码(使用节对应的节表中的 SizeOfRawData - Misc.VirtualSize 计算空白区的大小)。因为push4个参数,一个push硬编码长度为2字节;call和jmp硬编码长度为5字节;加起来一共是18字节。

接着将这个功能的代码转换成对应的硬编码:其中需要计算E8 和 E9后面的地址,所以要用公式X = 要跳转的地址 - (E8的地址 + 5)进行计算,如果我们使用UE编辑器打开可执行文件进行编辑修改,由于UE编辑器打开的文件是在硬盘上的状态,即我们如果在UE或者winhex中改数据是改的FileBuffer中的数据,不是ImageBuffer中的,则公式的地址都是需要从文件地址转换成内存地址后再计算的!

如果是ipmsg.exe,由于在文件对齐和内存对齐是一样的,所以文件在硬盘上的数据格式和加载到内存中的格式是一样的,那使用UE打开上面显式的地址是什么就用就行,因为文件地址此时等于内存地址。但是如果是notepad.exe等,文件对齐和内存对齐不一样,从硬盘加载到内存是需要"拉伸"的,所以如果此时用UE添加硬编码计算E8 E9后面的地址值时需要使用内存地址,所以多了一个FOA转化成RVA的过程。

定位到任意一个节后面的空白区,将这段硬编码加到任意节的空白区中,但是最好是代码节中(节的名字一般默认为.text),因为代码区的属性一般是可执行的,所以加到代码节中不用修改节的characteristic属性。记下来这段添加的硬编码的起始偏移地址。

我们如果写程序来模拟这个过程,添加硬编码在"拉伸"后的文件中添加。

为什么要在拉伸后的文件中添加:因为我们添加的硬编码中有call和jmp指令后面的地址,这个地址是要转化能得到的—X = 要跳转的地址 - (E8的地址 + 5),转换完才得到了添加的代码的完整硬编码,而地址转化公式中使用的E8当前的地址 + 5,这个E8当前的地址值为文件运行时装入4GB内存(拉伸后)后的值;而且公式中要跳转的地址也是文件装入内存后的MessageBoxA函数的地址。综上我们应该将要添加的硬编码添加到拉伸后的文件任意节的空白区中,这样更方便计算E8 和 E9后面跟的地址值。如果我们想在文件在硬盘上的状态中添加也可以,但是计算E8 和 E9 后面跟的地址值使用公式前,还要自己先把文件地址转换成内存地址,就比较麻烦。

最后找到程序的AddressOfEntryPoint字段所在地址,将此字段的值改为添加的硬编码的偏移地址。

3.5 实现

开始在FileBuffer中添加代码:

 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
35
36
37
38
39
40
bp MessageBoxA:0x77D507EA  

AddressOfEntryPoint程序入口:0x0000739D

ImageBase装载地址:0x01000000

.txt 
	VirtualAddress内存中的偏移地址: 0x1000
	VirtualSize该节在没有对齐前的真实尺寸: 0x7748
	PointerToRawData节区在文件中的偏移:0x400

加入代码的地方 0x7B48 = PointerToRawData + VirtualSize = 0x7748 + 0x400

通过下一个节表的PointerToRawData字段值为0x7c00,综合0x7172可以判断出.text节的空白区大小为0x7c00 - 0x7B48 = 0xB8。空间还很大,所以我们可以将代码添加在这个空白区的任意大小合适的位置。

接着就是构造ShellCode:

从0x7B48处填充:6A 00 6A 00 6A 00 6A 00 E8 00 00 00 00 E9 00 00 00 00

由于现在加的位置是文件在硬盘上的地址,不是加载到ImageBuffer中的地址,所以要做地址转换。

.text节在内存中的起始地址 VirtualAddress + ImageBase:0x1000 + 0x01000000 = 0x1001000

E8的文件地址: 0x7B50   相对于.txt的偏移位置:0x7750 = 0x7B50 - PointerToRawData = 0x7B50 - 0x400
E8的内存地址:0x7750 + 0x1001000 = 0x1008750
E8后面的 MessageBoxA - (E8的内存地址+5): 0x77D507EA - 0x1008750 - 5 = 0x76D48095

E9的文件地址: 0x7B55   相对于.txt的偏移位置:0x7755 = 0x7B55 - PointerToRawData = 0x7B55 - 0x400
E9的内存地址:0x7755 + 0x1001000 = 0x1008755

实际程序加载到内存的入口:运行入口AddressOfEntryPoint + ImageBase:0x0000739D + 0x01000000 = 0x100739D
E9后面的 运行入口 - (E9的内存地址+5): 0x100739D - 0x1008755- 5 = 0x76D48090 = 0xFFFFEC43

E9相当于最终调转到实际程序加载到内存的入口的地方。

最终0x7B48处填充:6A 00 6A 00 6A 00 6A 00 E8 95 80 D4 76 E9 43 EC FF FF

加入代码的地方的内存入口 VirtualSize + VirtualAddress:0x7748 + 0x1000 = 0x8748

重新设置AddressOfEntryPoint程序入口 = 0x8748

4. 在代码空白区添加代码(编码)

我们用OD打开一个文件,模拟的是文件真正运行时的状态,所以此时左侧栏显示的所有地址都是内存地址,文件的起始地址就是ImageBase

我们使用UE和winhex打开一个文件,起始就是一个将文件在硬盘上时的数据复制一份存到FileBuffer内存中,所以此时左侧栏显示的地址是文件地址,文件的起始地址是逻辑地址地址为0x0

我们使用winhex–tools–open ram打开一个正在运行时的文件,此时左侧栏显示的地址就是文件真正运行时的内存地址,文件的起始地址就是ImageBase

我们如果使用C语言来模拟FileBuffer->ImageBuffer->NewBuffer的过程:

  • 由于我们使用malloc开辟一块内存空间模拟FileBuffer,这块内存的起始地址就不在是我们说的文件在硬盘上的起始逻辑地址0x0,而是随机动态分配的一个起始地址,所以我们在计算FileBuffer中的地址时,就要注意偏移量加的不是0x0了,而是要用偏移量加上动态分配的起始地址才是在FileBuffer中地址。
  • 接着如果我们再模拟拉伸的过程,即FileBuffer到ImageBuffer的过程时,由于也使用malloc动态给ImageBuffer分配内存,所以此时ImageBuffer中文件起始地址不再是ImageBase,而也是一个随机分配的地址,所以我们使用动态分配的起始地址加偏移量的方式计算ImageBuffer中的地址时,就不是加ImageBase!而且我们计算call和jmp等硬编码E8、E9后面的4字节值时,需要用到的E8、E9当前的内存地址不是现在在ImageBuffer中的值,因为这也只是我们模拟出来的文件运行状态的ImageBuffer,而不是最后程序在操作系统上真正装入内存时的状态,所以我们此时计算E8当前的内存地址还要想象一下文件真正被执行的状态,那么起始地址就要用ImageBase了,用这个值来计算E8、E9后面要跟的4字节值。
  • 包括最后ImageBuffer到NewBuffer的过程也是同样如此

文件双击真正运行时的起始地址就为ImageBase的值了

  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
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
#pragma warning(disable : 4996)
#include <iostream>
#include <string.h>

typedef unsigned short WORD;
typedef unsigned int DWORD;
typedef unsigned char BYTE;

#define MZ 0x5A4D
#define PE 0x4550
#define IMAGE_SIZEOF_SHORT_NAME 8

#define MessageBox_Address 0x754034B0 //宏定义的MessageBox内存地址

const char* filePath = "CRACKME.EXE";  //你要打开的PE文件路径
const char* storagePath = "new_CRACKME.exe"; //保存路径

//shellcode:定义要添加的硬编码全局变量,确定的写好,不确定的先用0x00填充,后面计算完再改
BYTE shellcode[] = {
	0x6A,0x00,0x6A,0x00,0x6A,0x00,0x6A,0x00,
	0xE8,0x00,0x00,0x00,0x00,
	0xE9,0x00,0x00,0x00,0x00
};

//DOS头
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];
	DWORD e_lfanew;  //PE文件真正开始的偏移地址
};

//标准PE头
struct _IMAGE_FILE_HEADER {
	WORD Machine;  //文件运行平台
	WORD NumberOfSections;  //节数量
	DWORD TimeDateStamp;  //时间戳
	DWORD PointerToSymbolTable;
	DWORD NumberOfSymbols;
	WORD SizeOfOptionalHeader;  //可选PE头大小
	WORD Characteristics;  //特征值
};

//可选PE头
struct _IMAGE_OPTIONAL_HEADER {
	WORD Magic;  //文件类型
	BYTE MajorLinkerVersion;
	BYTE MinorLinkerVersion;
	DWORD SizeOfCode;   //代码节文件对齐后的大小
	DWORD SizeOfInitializedData;  //初始化数据文件对齐后的大小
	DWORD SizeOfUninitializedData;  //未初始化数据文件对齐后大小
	DWORD AddressOfEntryPoint;  //程序入口点(偏移量)
	DWORD BaseOfCode;  //代码基址
	DWORD BaseOfData;  //数据基址
	DWORD ImageBase;   //内存镜像基址
	DWORD SectionAlignment;  //内存对齐粒度
	DWORD FileAlignment;  //文件对齐粒度
	WORD MajorOperatingSystemVersion;
	WORD MinorOperatingSystemVersion;
	WORD MajorImageVersion;
	WORD MinorImageVersion;
	WORD MajorSubsystemVersion;
	WORD MinorSubsystemVersion;
	DWORD Win32VersionValue;
	DWORD SizeOfImage;  //文件装入虚拟内存后大小
	DWORD SizeOfHeaders;  //DOS、NT头和节表大小
	DWORD CheckSum;  //校验和
	WORD Subsystem;
	WORD DllCharacteristics;
	DWORD SizeOfStackReserve;  //预留堆栈大小
	DWORD SizeOfStackCommit;  //实际分配堆栈大小
	DWORD SizeOfHeapReserve;  //预留堆大小
	DWORD SizeOfHeapCommit;  //实际分配堆大小
	DWORD LoaderFlags;
	DWORD NumberOfRvaAndSizes;  //目录项数目
	//_IMAGE_DATA_DIRECTORY DataDirectory[16];  //这个先不管
};

//NT头
struct _IMAGE_NT_HEADERS {
	DWORD Signature;  //PE签名
	_IMAGE_FILE_HEADER FileHeader;
	_IMAGE_OPTIONAL_HEADER OptionalHeader;
};

//节表
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;  //该节特征属性
};

/*计算文件大小函数
参数:文件绝对路径
返回值:返回文件大小(单位字节)
*/
int compute_file_size(const char* filePath) {
	FILE* fp = fopen(filePath, "rb");
	if (!fp) {
		printf("打开文件失败");
		exit(0);
	}
	fseek(fp, 0, 2);
	int size = ftell(fp);
	//fseek(fp,0,0); 单纯计算文件大小,就不需要还原指针了
	fclose(fp);
	return size;
}

/*将文件读入FileBuffer函数
参数:文件绝对路径
返回值:FileBuffer起始地址
*/
char* to_FileBuffer(const char* filePath) {
	FILE* fp = fopen(filePath, "rb");
	if (!fp) {
		printf("打开文件失败");
		exit(0);
	}
	int size = compute_file_size(filePath);

	char* mp = (char*)malloc(sizeof(char) * size);  //分配内存空间
	if (!mp) {
		printf("分配空间失败");
		fclose(fp);
		exit(0);
	}

	int isSucceed = fread(mp, size, 1, fp);
	if (!isSucceed) {
		printf("读取数据失败");
		free(mp);
		fclose(fp);
		exit(0);
	}

	fclose(fp);
	return mp;
}

/*FileBuffer到ImageBuffer函数
参数:FileBuffer起始地址
返回值:ImageBuffer起始地址
*/
char* fileBuffer_to_ImageBuffer(char* fileBufferp) {

	_IMAGE_DOS_HEADER* _image_dos_header = NULL;
	_IMAGE_FILE_HEADER* _image_file_header = NULL;
	_IMAGE_OPTIONAL_HEADER* _image_optional_header = NULL;
	_IMAGE_SECTION_HEADER* _image_section_header = NULL;

	_image_dos_header = (_IMAGE_DOS_HEADER*)fileBufferp;
	_image_file_header = (_IMAGE_FILE_HEADER*)(fileBufferp + _image_dos_header->e_lfanew + 4);
	_image_optional_header = (_IMAGE_OPTIONAL_HEADER*)((char*)_image_file_header + 20);
	_image_section_header = (_IMAGE_SECTION_HEADER*)((char*)_image_optional_header + _image_file_header->SizeOfOptionalHeader);

	DWORD sizeofImage = _image_optional_header->SizeOfImage;
	char* imageBufferp = (char*)malloc(sizeofImage);
	if (NULL == imageBufferp) {
		printf("动态申请ImageBuffer内存失败\n");
		exit(0);
	}
	memset(imageBufferp, 0, sizeofImage);

	// 复制所有头
	memcpy(imageBufferp, fileBufferp, _image_optional_header->SizeOfHeaders);


	// 将文件中的节表复制到内存上
	for (int i = 0; i < _image_file_header->NumberOfSections; ++i) {
		memcpy(imageBufferp + _image_section_header[i].VirtualAddress, fileBufferp + _image_section_header[i].PointerToRawData, _image_section_header[i].SizeOfRawData);
	}

	return imageBufferp;
}

/*计算NewBuffer大小函数
参数:NewBuffer起始地址
返回值:unsigned int类型(单位:字节)
*/
DWORD compute_NewBuffer_size(char* newBufferp) {
	_IMAGE_DOS_HEADER* _image_dos_header = NULL;
	_IMAGE_FILE_HEADER* _image_file_header = NULL;
	_IMAGE_OPTIONAL_HEADER* _image_optional_header = NULL;
	_IMAGE_SECTION_HEADER* _image_section_header = NULL;

	_image_dos_header = (_IMAGE_DOS_HEADER*)newBufferp;
	_image_file_header = (_IMAGE_FILE_HEADER*)(newBufferp + _image_dos_header->e_lfanew + 4);
	_image_optional_header = (_IMAGE_OPTIONAL_HEADER*)((char*)_image_file_header + 20);
	_image_section_header = (_IMAGE_SECTION_HEADER*)((char*)_image_optional_header + _image_file_header->SizeOfOptionalHeader);

	_IMAGE_SECTION_HEADER* last_image_section_header = _image_section_header + _image_file_header->NumberOfSections - 1;
	DWORD sizeofNewBuffer = last_image_section_header->PointerToRawData + last_image_section_header->SizeOfRawData;
	return sizeofNewBuffer;
}

/*ImageBuffer到NewBuffer函数
参数:ImageBuffer起始地址
返回值:NewBuffer起始地址
*/
char* imageBuffer_to_NewBuffer(char* imageBufferp) {

	_IMAGE_DOS_HEADER* _image_dos_header = NULL;
	_IMAGE_FILE_HEADER* _image_file_header = NULL;
	_IMAGE_OPTIONAL_HEADER* _image_optional_header = NULL;
	_IMAGE_SECTION_HEADER* _image_section_header = NULL;

	_image_dos_header = (_IMAGE_DOS_HEADER*)imageBufferp;
	_image_file_header = (_IMAGE_FILE_HEADER*)(imageBufferp + _image_dos_header->e_lfanew + 4);
	_image_optional_header = (_IMAGE_OPTIONAL_HEADER*)((char*)_image_file_header + 20);
	_image_section_header = (_IMAGE_SECTION_HEADER*)((char*)_image_optional_header + _image_file_header->SizeOfOptionalHeader);

	//重新计算newBuffer需要大小
	/*方法一:SizeOfHEADER + 所有节的SizeOfRawData之和
	int sizeofSections = 0;
	_IMAGE_SECTION_HEADER* _image_section_header_temp = _image_section_header;
	for(DWORD int i = 0;i < _image_file_header->NumberOfSections;i++){
		sizeofSections += _image_section_header_temp->SizeOfRawData;
		_image_section_header_temp++;
	}
	char* newBufferp = (char*)malloc(_image_optional_header->SizeOfHeaders + sizeofSections);
	*/

	//方法二:使用最后一个节的文件偏移地址 + 最后一个节对齐后的大小
	DWORD size = compute_NewBuffer_size(imageBufferp);
	char* newBufferp = (char*)malloc(size);
	if (NULL == newBufferp) {
		printf("NewBuffer内存分配失败\n");
		exit(0);
	}

	memset(newBufferp, 0, size);
	memcpy(newBufferp, imageBufferp, _image_optional_header->SizeOfHeaders);

	for (int i = 0; i < _image_file_header->NumberOfSections; i++) {
		memcpy(newBufferp + _image_section_header[i].PointerToRawData, imageBufferp + _image_section_header[i].VirtualAddress, _image_section_header[i].SizeOfRawData);
	}

	return newBufferp;
}

/*NewBuffer存盘函数
参数:需要写出数据的内存首地址,保存绝对路径,写出的数据大小(单位:字节)
返回值:成功返回1,失败返回0
*/
int save_to_disk(char* newBufferp, const char* storagePath, DWORD size) {
	FILE* fp = fopen(storagePath, "wb");
	if (!fp) {
		printf("打开文件失败");
		return 0;
	}
	int isSucceed = fwrite(newBufferp, size, 1, fp);
	if (!isSucceed) {
		free(newBufferp);
		fclose(fp);
		return 0;
	}
	fclose(fp);
	return 1;
}

/*向第一个节.text中注入shellcode函数
参数:要注入的可执行文件的ImageBuffer起始地址
返回值:0则注入失败,1则注入成功
*/
int add_shellcode(char* imageBufferp) {
	_IMAGE_DOS_HEADER* _image_dos_header = NULL;
	_IMAGE_FILE_HEADER* _image_file_header = NULL;
	_IMAGE_OPTIONAL_HEADER* _image_optional_header = NULL;
	_IMAGE_SECTION_HEADER* _image_section_header = NULL;

	_image_dos_header = (_IMAGE_DOS_HEADER*)imageBufferp;
	_image_file_header = (_IMAGE_FILE_HEADER*)(imageBufferp + _image_dos_header->e_lfanew + 4);
	_image_optional_header = (_IMAGE_OPTIONAL_HEADER*)((char*)_image_file_header + 20);
	_image_section_header = (_IMAGE_SECTION_HEADER*)((char*)_image_optional_header + _image_file_header->SizeOfOptionalHeader);

	/** 
	*  用SizeofRawData - VirtualSize判断空白区是否存的下shellcode:这里要考虑到一个问题:我们使用的是secp->VirtualAddress + secp->Misc.VirtualSize来确定内存中此节的空白区的偏移起始地址,
	*  这个算法是没错的,但是还记得前面将FileBuffer装载到ImageBuffer时我们复制每个节的长度是按照SizeOfRawData复制的,而且ImageBuffer到NewBuffer也是每个节按照SizeOfRawData复制的。
	*  那么如果某一个节的VirtualSize>SizeOfRawData,就会出现我们添加代码的地方确实在节装入内存时的空白区,
	*  但是最后ImageBuffer到NewBuffer时,复制的时候只复制了这个节开始的SizeOfRawData个字节,我们加的代码并没有复制进去,就可能出现问题,所以干脆直接要求SizeOfRawData > VirtualSize才能注入
	*/
	if ((int)(_image_section_header->SizeOfRawData - _image_section_header->Misc.VirtualSize) < sizeof(shellcode) / sizeof(shellcode[0])) {
		printf("此节空白区大小不够,添加失败");
		return 0;
	}

	//将shellcode注入到节空白区
	char* shellcode_ptr = imageBufferp + _image_section_header->VirtualAddress + _image_section_header->Misc.VirtualSize;	//计算插入代码的位置
	char* newOEP = shellcode_ptr;  //此shellcode注入起始地址即为新的OEP地址
	memcpy(shellcode_ptr, shellcode, sizeof(shellcode) / sizeof(shellcode[0]));

	//修改call MessageBox的硬编码E8后面的4字节
	DWORD E8_fill_value = MessageBox_Address - ((shellcode_ptr - imageBufferp + _image_optional_header->ImageBase) + 8 + 5);
	*(DWORD*)(shellcode_ptr + 9) = E8_fill_value;

	//修改jmp 原OEP的硬编码E9后面的4字节
	DWORD E9_fill_value = _image_optional_header->AddressOfEntryPoint - (shellcode_ptr - imageBufferp + 8 + 5 + 5);
	*(DWORD*)(shellcode_ptr + 14) = E9_fill_value;

	//修改OEP指向shellcode
	_image_optional_header->AddressOfEntryPoint = (DWORD)(newOEP - imageBufferp);

	return 1;
}

int main(int argc, char* argv[]) {
	char* fileBufferp = to_FileBuffer(filePath);
	char* imageBufferp = fileBuffer_to_ImageBuffer(fileBufferp);

	// 注入提示框
	if (!add_shellcode(imageBufferp)) {
		printf("shellcode注入失败");
		return 0;
	}

	char* newBufferp = imageBuffer_to_NewBuffer(imageBufferp);
	int isSucceed = save_to_disk(newBufferp, storagePath, compute_NewBuffer_size(newBufferp));
	if (!isSucceed)
		printf("存盘失败");
	else
		printf("存盘成功");

	free(fileBufferp);
	free(imageBufferp);
	free(newBufferp);
	return 0;
}

5. 新增节、扩大节-添加代码

5.1 新增节思路

5.1.1 满足新增节条件

为什么新增节来添加代码呢?因为有时所有节的空白区可能都不够存放我们要添加的代码,所以我们自己新增足够大的节来添加代码。

可以添加一个节表(新增节就要新增一个节表来记录此节信息),首先判断是否有足够的空间;判断方法:SizeOfHeaders - (DOS + 垃圾数据 + PE标记 + 标准PE头 + 可选PE头 + 已存在节表) >= 2个节表的大小

为什么需要2个节表大小:有时windows会根据一个结构后面是否有多个0x00来判断这个结构是否结束,所以算是windows规定的格式吧。故最后一个节表后面还要补跟节表宽度一样的0;但是其实最后一个节表后面不是40个0或者跟了一些其他数据也是可行的,但由于没有满足规定的格式要求,不知道什么时候就不好使了,所以我们新增节表时还是按照标准规定来。

如果满足添加节表的条件,此时我们可以将已有的一个节表的40字节信息复制一份紧挨着最后一个节表的末尾(节表之间必须要挨着!这是规定),然后再往后的40字节全部补0,接下来就是根据新增节的信息,去修改对应节表的字段值即可。

需要修改的数据:

  • 修改标准PE头中NumberOfSections字段(节的数量)。

  • 修改SizeOfImage的大小(原有值 + 新增节的大小,最后还要内存对齐)。

  • 在文件的最后,新增节(添加时,节的大小如果设置为内存对齐的整数倍,后面就不用再担心对齐的事)。

  • 修正新增节表的属性:

    • Name,这个按照ASCII码随便取,最好长度限制在8字节内。
    • Misc.VirtualSize,如果此时我们还不知道添加的数据长度(没对齐前),那么直接就按照对齐后的大小写即可,比如我们要新增节大小为0x1000(考虑了内存对齐),那么不管里面要用多少字节数据,VirtualSize的值就按照0x1000写。
    • VirtualAddress(内存起始偏移地址),我们通过上一个节表的VirtualAddress + VirtualSize内存对齐后,得到上一个节内存对齐后的结尾位置,所以新增节表的VirtualAddress就是这个值。

    千万不能用SizeOfRawData来判断,因为这个节的VirtualSize可能大于SizeOfRawData,因为包含未初始化数据,那么此时在内存中应该按照VirtualAddress + VirtualSize内存对齐后的得到的值来存储。

    我们发现有时候文件内存中未对齐的大小确实比文件中对齐的大小大(由于节中包含初始化数据的缘故),比如notepad.exe文件的第二个节.data中,内存中的大小为0x1BA8,但是文件中对齐后的大小为0x600。此时我们可以发现,第三个节在文件中就按照上一个节在文件中对齐后的大小往后接着存放,即0x7200 + 0x600 = 0x7800,所以第三个节的PointerToRawData为0x7800,而第三个节在内存中就按照上一个节在内存中对齐后的大小往后接着存放(就不是再按照上一个节的文件中对齐大小挨着存放了,因为此时VirtualSize比SizeOfRawData大),即0x8000 + 0x1BA8 = 0x9BA8,对齐后为0xA000,所以第三个节内存偏移为0xA000

  • SizeOfRawData,根据文件对齐粒度来修正这个值,比如上面如果设定了VirtualSize的值为0x1000,说明这个节的这0x1000字节都是有用数据,那么此节在硬盘上时也应该有这0x1000字节的数据,所以此时根据文件对齐粒度来修正这个值,如果为0x200或者0x1000,就不用修改0x1000,因为已经满足是文件对齐的整数倍了

  • PointerToRawData,文件对齐后的文件地址,我们可以通过上一个节表的PointerToRawData + SizeOfRawData计算得到此值

  • Characteristics,按照我们想要的属性来修改即可(可读、可写、可执行等)

  • SizeOfHeaders的值不要随便改变,因为如果这个值变了,后面的节都要跟着往后或者往前跟着变,但是这些节当中凡是涉及到地址的计算相关的就会改变,比如说我们昨天加的E8和E9后面的值,是通过其他的地址计算出来的,如果SizeOfHeaders变了,节地址跟着变,那么这些值也要改变,所以代价很高,不建议修改

5.1.2 如果节表后面有编译器添加的数据

有些程序会在节表到节的空白区之间添加一些信息,由于添加的新节表一定要与原来的节表紧挨着,但是我们又不知道程序的这些信息是否有用、是否影响程序的正常运行,所以此时我们不能轻易修改覆盖这些内容,那么我们如果要添加新节表就需要想办法:我们知道程序的DOS头到PE签名之间有一处区域DOS Stub,不影响程序的运行,数据也是程序的一些说明信息,对我们来说就是垃圾数据,所以我们可以将NT头到节表末尾这一部分整体上移,把Dos Stub这块数据覆盖了,接着修改DOS头中的e_lfanew字段的值为上移后PE签名的地址即可,那么此时下面就会空出来一部分,我们就可以先将这部分全部修改成0x00,再往这片区域新增节表即可。

5.1.3 整体上移后空间还不够大

如果向上覆盖DOS Stub之后,还不够新增节表的大小,那么我们就采用扩大最后一个节的方式,将添加的代码加到最后一个节扩大的空间中,这样可以保证不影响上面的所有地址。

5.1.4 注意事项

文件加载到内存中,最后一个节的VirtualAddress + VirtualSize对齐后的结尾一定就是一个文件在内存中的结束位置,即SizeOfImage;文件在硬盘上时,最后一个节的PointerToRawData + SizeOfRawData的值一定就是整个文件的结尾!

不管是往节的空白区中添加代码还是新增节、扩大节,如果用编程来实现,应该在ImageBuffer中操作更方便,即拉伸后来操作,不管是一些值的计算还是位置的选择上都要更方便一些。

加载到内存中,要看节之间怎么排的,应该根据VirtualAddress和VirtualSize以及内存对齐来判断;如果在硬盘上,节之间怎么排的,应该根据PointerToRawData和SizeOfRawData以及文件对齐来判断。

5.2 手动新增节添加代码

5.3 代码实现

定义函数,能够返回对齐后的大小:

定义一个函数Align(int x,int y),第一个参数为我们手动输入的字节大小,第二个参数为文件对齐大小或者内存对齐大小,现在功能是如果我们传入0x222字节,文件对齐大小为0x200,那么计算0x222文件对齐后的大小为多少;如果传入0x1222字节,内存对齐大小为0x1000,那么计算0x1222内存对齐后的大小是多少。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#include <iostream>

typedef unsigned int DWORD;

// 计算最小公倍数函数
DWORD alignment(DWORD num, DWORD align_num) {  //用int也行
	if ((int)num <= 0 || (int)align_num <= 0) {
		printf("输入的数据不合法\n");
		return 0;
	}

	if (num % align_num == 0) {
		return num;
	}
	else {
		return align_num * (num / align_num + 1);
	}
}

int main(int argc, char* argv[]) {
	printf("%x", alignment(0x1222, 0x1000)); //0x2000

	return 0;
}

5.3.1 编程实现:新增一个节,并添加代码

下面是最简单的新增节并添加代码,满足新增节的条件;注意问题:如果我们增加节或者扩大节,在ImageBuffer中操作更方便,而且一定要新申请一块内存NewImageBuffer来存储新增或扩大后的PE文件的数据,不能直接在原有的ImageBuffer的内存中操作,因为这块内存是动态分配的,大小是指定过的malloc(size),不能修改或者新增。

  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
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
#pragma warning(disable : 4996)
#include <iostream>
#include <string.h>

typedef unsigned short WORD;
typedef unsigned int DWORD;
typedef unsigned char BYTE;

#define MZ 0x5A4D
#define PE 0x4550
#define IMAGE_SIZEOF_SHORT_NAME 8
#define MessageBox_Address 0x754034B0 //宏定义的MessageBox内存地址

const char* filePath = "fg.exe";  //你要打开的PE文件路径
const char* storagePath = "fg_new.exe"; //保存路径

//shellcode:定义要添加的硬编码全局变量,确定的写好,不确定的先用0x00填充,后面计算完再改
BYTE shellcode[] = {
	0x6A,0x00,0x6A,0x00,0x6A,0x00,0x6A,0x00,
	0xE8,0x00,0x00,0x00,0x00,
	0xE9,0x00,0x00,0x00,0x00
};

//DOS头
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];
	DWORD e_lfanew;  //PE文件真正开始的偏移地址
};

//标准PE头
struct _IMAGE_FILE_HEADER {
	WORD Machine;  //文件运行平台
	WORD NumberOfSections;  //节数量
	DWORD TimeDateStamp;  //时间戳
	DWORD PointerToSymbolTable;
	DWORD NumberOfSymbols;
	WORD SizeOfOptionalHeader;  //可选PE头大小
	WORD Characteristics;  //特征值
};

//可选PE头
struct _IMAGE_OPTIONAL_HEADER {
	WORD Magic;  //文件类型
	BYTE MajorLinkerVersion;
	BYTE MinorLinkerVersion;
	DWORD SizeOfCode;   //代码节文件对齐后的大小
	DWORD SizeOfInitializedData;  //初始化数据文件对齐后的大小
	DWORD SizeOfUninitializedData;  //未初始化数据文件对齐后大小
	DWORD AddressOfEntryPoint;  //程序入口点(偏移量)
	DWORD BaseOfCode;  //代码基址
	DWORD BaseOfData;  //数据基址
	DWORD ImageBase;   //内存镜像基址
	DWORD SectionAlignment;  //内存对齐粒度
	DWORD FileAlignment;  //文件对齐粒度
	WORD MajorOperatingSystemVersion;
	WORD MinorOperatingSystemVersion;
	WORD MajorImageVersion;
	WORD MinorImageVersion;
	WORD MajorSubsystemVersion;
	WORD MinorSubsystemVersion;
	DWORD Win32VersionValue;
	DWORD SizeOfImage;  //文件装入虚拟内存后大小
	DWORD SizeOfHeaders;  //DOS、NT头和节表大小
	DWORD CheckSum;  //校验和
	WORD Subsystem;
	WORD DllCharacteristics;
	DWORD SizeOfStackReserve;  //预留堆栈大小
	DWORD SizeOfStackCommit;  //实际分配堆栈大小
	DWORD SizeOfHeapReserve;  //预留堆大小
	DWORD SizeOfHeapCommit;  //实际分配堆大小
	DWORD LoaderFlags;
	DWORD NumberOfRvaAndSizes;  //目录项数目
	//_IMAGE_DATA_DIRECTORY DataDirectory[16];  //这个先不管
};

//NT头
struct _IMAGE_NT_HEADERS {
	DWORD Signature;  //PE签名
	_IMAGE_FILE_HEADER FileHeader;
	_IMAGE_OPTIONAL_HEADER OptionalHeader;
};

//节表
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;  //该节特征属性
};

/*计算文件大小函数
参数:文件绝对路径
返回值:返回文件大小(单位字节)
*/
int compute_file_size(const char* filePath) {
	FILE* fp = fopen(filePath, "rb");
	if (!fp) {
		printf("打开文件失败");
		exit(0);
	}
	fseek(fp, 0, 2);
	int size = ftell(fp);
	//fseek(fp,0,0); 单纯计算文件大小,就不需要还原指针了
	fclose(fp);
	return size;
}

/*将文件读入FileBuffer函数
参数:文件绝对路径
返回值:FileBuffer起始地址
*/
char* to_FileBuffer(const char* filePath) {
	FILE* fp = fopen(filePath, "rb");
	if (!fp) {
		printf("打开文件失败");
		exit(0);
	}
	int size = compute_file_size(filePath);

	char* mp = (char*)malloc(sizeof(char) * size);  //分配内存空间
	if (!mp) {
		printf("分配空间失败");
		fclose(fp);
		exit(0);
	}

	int isSucceed = fread(mp, size, 1, fp);
	if (!isSucceed) {
		printf("读取数据失败");
		free(mp);
		fclose(fp);
		exit(0);
	}

	fclose(fp);
	return mp;
}

/*FileBuffer到ImageBuffer函数
参数:FileBuffer起始地址
返回值:ImageBuffer起始地址
*/
char* fileBuffer_to_ImageBuffer(char* fileBufferp) {

	_IMAGE_DOS_HEADER* _image_dos_header = NULL;
	_IMAGE_FILE_HEADER* _image_file_header = NULL;
	_IMAGE_OPTIONAL_HEADER* _image_optional_header = NULL;
	_IMAGE_SECTION_HEADER* _image_section_header = NULL;

	_image_dos_header = (_IMAGE_DOS_HEADER*)fileBufferp;
	_image_file_header = (_IMAGE_FILE_HEADER*)(fileBufferp + _image_dos_header->e_lfanew + 4);
	_image_optional_header = (_IMAGE_OPTIONAL_HEADER*)((char*)_image_file_header + 20);
	_image_section_header = (_IMAGE_SECTION_HEADER*)((char*)_image_optional_header + _image_file_header->SizeOfOptionalHeader);

	DWORD sizeofImage = _image_optional_header->SizeOfImage;
	char* imageBufferp = (char*)malloc(sizeofImage);
	if (NULL == imageBufferp) {
		printf("动态申请ImageBuffer内存失败\n");
		exit(0);
	}
	memset(imageBufferp, 0, sizeofImage);

	// 复制所有头
	memcpy(imageBufferp, fileBufferp, _image_optional_header->SizeOfHeaders);


	// 将文件中的节表复制到内存上
	for (int i = 0; i < _image_file_header->NumberOfSections; ++i) {
		memcpy(imageBufferp + _image_section_header[i].VirtualAddress, fileBufferp + _image_section_header[i].PointerToRawData, _image_section_header[i].SizeOfRawData);
	}

	return imageBufferp;
}

/*计算NewBuffer大小函数
参数:NewBuffer起始地址
返回值:unsigned int类型(单位:字节)
*/
DWORD compute_NewBuffer_size(char* newBufferp) {
	_IMAGE_DOS_HEADER* _image_dos_header = NULL;
	_IMAGE_FILE_HEADER* _image_file_header = NULL;
	_IMAGE_OPTIONAL_HEADER* _image_optional_header = NULL;
	_IMAGE_SECTION_HEADER* _image_section_header = NULL;

	_image_dos_header = (_IMAGE_DOS_HEADER*)newBufferp;
	_image_file_header = (_IMAGE_FILE_HEADER*)(newBufferp + _image_dos_header->e_lfanew + 4);
	_image_optional_header = (_IMAGE_OPTIONAL_HEADER*)((char*)_image_file_header + 20);
	_image_section_header = (_IMAGE_SECTION_HEADER*)((char*)_image_optional_header + _image_file_header->SizeOfOptionalHeader);

	_IMAGE_SECTION_HEADER* last_image_section_header = _image_section_header + _image_file_header->NumberOfSections - 1;
	DWORD sizeofNewBuffer = last_image_section_header->PointerToRawData + last_image_section_header->SizeOfRawData;
	return sizeofNewBuffer;
}

/*ImageBuffer到NewBuffer函数
参数:ImageBuffer起始地址
返回值:NewBuffer起始地址
*/
char* imageBuffer_to_NewBuffer(char* imageBufferp) {

	_IMAGE_DOS_HEADER* _image_dos_header = NULL;
	_IMAGE_FILE_HEADER* _image_file_header = NULL;
	_IMAGE_OPTIONAL_HEADER* _image_optional_header = NULL;
	_IMAGE_SECTION_HEADER* _image_section_header = NULL;

	_image_dos_header = (_IMAGE_DOS_HEADER*)imageBufferp;
	_image_file_header = (_IMAGE_FILE_HEADER*)(imageBufferp + _image_dos_header->e_lfanew + 4);
	_image_optional_header = (_IMAGE_OPTIONAL_HEADER*)((char*)_image_file_header + 20);
	_image_section_header = (_IMAGE_SECTION_HEADER*)((char*)_image_optional_header + _image_file_header->SizeOfOptionalHeader);

	//重新计算newBuffer需要大小
	/*方法一:SizeOfHEADER + 所有节的SizeOfRawData之和
	int sizeofSections = 0;
	_IMAGE_SECTION_HEADER* _image_section_header_temp = _image_section_header;
	for(DWORD int i = 0;i < _image_file_header->NumberOfSections;i++){
		sizeofSections += _image_section_header_temp->SizeOfRawData;
		_image_section_header_temp++;
	}
	char* newBufferp = (char*)malloc(_image_optional_header->SizeOfHeaders + sizeofSections);
	*/

	//方法二:使用最后一个节的文件偏移地址 + 最后一个节对齐后的大小
	DWORD size = compute_NewBuffer_size(imageBufferp);
	char* newBufferp = (char*)malloc(size);
	if (NULL == newBufferp) {
		printf("NewBuffer内存分配失败\n");
		exit(0);
	}

	memset(newBufferp, 0, size);
	memcpy(newBufferp, imageBufferp, _image_optional_header->SizeOfHeaders);

	for (int i = 0; i < _image_file_header->NumberOfSections; i++) {
		memcpy(newBufferp + _image_section_header[i].PointerToRawData, imageBufferp + _image_section_header[i].VirtualAddress, _image_section_header[i].SizeOfRawData);
	}

	return newBufferp;
}

/*NewBuffer存盘函数
参数:需要写出数据的内存首地址,保存绝对路径,写出的数据大小(单位:字节)
返回值:成功返回1,失败返回0
*/
int save_to_disk(char* newBufferp, const char* storagePath, DWORD size) {
	FILE* fp = fopen(storagePath, "wb");
	if (!fp) {
		printf("打开文件失败");
		return 0;
	}
	int isSucceed = fwrite(newBufferp, size, 1, fp);
	if (!isSucceed) {
		free(newBufferp);
		fclose(fp);
		return 0;
	}
	fclose(fp);
	return 1;
}

/*向新增节中注入shellcode函数
参数:要注入的可执行文件的ImageBuffer起始地址
返回值:0为注入失败、1为注入成功
*/
int add_shellcode_to_new_section(char* imageBufferp) {
	_IMAGE_DOS_HEADER* _image_dos_header = NULL;
	_IMAGE_FILE_HEADER* _image_file_header = NULL;
	_IMAGE_OPTIONAL_HEADER* _image_optional_header = NULL;
	_IMAGE_SECTION_HEADER* _image_section_header = NULL;

	_image_dos_header = (_IMAGE_DOS_HEADER*)imageBufferp;
	_image_file_header = (_IMAGE_FILE_HEADER*)(imageBufferp + _image_dos_header->e_lfanew + 4);
	_image_optional_header = (_IMAGE_OPTIONAL_HEADER*)((char*)_image_file_header + 20);
	_image_section_header = (_IMAGE_SECTION_HEADER*)((char*)_image_optional_header + _image_file_header->SizeOfOptionalHeader);

	//定位到新节表起始地址
	_IMAGE_SECTION_HEADER* target_image_section_header = _image_section_header + _image_file_header->NumberOfSections - 1;

	//判断新节是否存的下shellcode
	if ((int)(target_image_section_header->Misc.VirtualSize) < (int)(sizeof(shellcode) / sizeof(shellcode[0]))) {
		printf("新节大小不够,添加失败\n");
		return 0;
	}

	//将shellcode注入到新节
	char* shellcodep = imageBufferp + target_image_section_header->VirtualAddress;
	char* newOEP = shellcodep;  //此shellcode注入起始地址即为新的OEP地址
	memcpy(shellcodep, shellcode, sizeof(shellcode) / sizeof(shellcode[0]));

	//修改call MessageBox的硬编码E8后面的4字节
	DWORD E8_fill_value = MessageBox_Address - ((shellcodep - imageBufferp + _image_optional_header->ImageBase) + 0xD);
	*(DWORD*)(shellcodep + 9) = E8_fill_value;

	//修改jmp 原OEP的硬编码E9后面的4字节
	DWORD E9_fill_value = _image_optional_header->AddressOfEntryPoint + _image_optional_header->ImageBase - ((shellcodep - imageBufferp + _image_optional_header->ImageBase) + 0x12);
	*(DWORD*)(shellcodep + 14) = E9_fill_value;

	//修改OEP指向shellcode
	_image_optional_header->AddressOfEntryPoint = (DWORD)(newOEP - imageBufferp);

	return 1;
}

/*计算最小公倍数函数
参数:传入要计算的两个数
返回值:两数的最小公倍数
*/
int least_common_multiple(int a, int b) {
	if (a <= 0 || b <= 0)
		return -1;
	
	int max = (a > b ? a : b);
	while (1) {
		if (max % a == 0 && max % b == 0) {
			break;
		}
		max++;
	}
	return max;
}

/*在文件最后新增节函数
参数:要新增节的可执行文件的ImageBuffer起始地址
返回值:新增节后的NewImageBuffer内存的起始地址
*/
char* add_one_section(char* imageBufferp) {
	_IMAGE_DOS_HEADER* _image_dos_header = NULL;
	_IMAGE_FILE_HEADER* _image_file_header = NULL;
	_IMAGE_OPTIONAL_HEADER* _image_optional_header = NULL;
	_IMAGE_SECTION_HEADER* _image_section_header = NULL;

	// 定义原来的ImageBuffer几个指针(因为要用到几个值用于创建新的NewImageBuffer内存)
	_image_dos_header = (_IMAGE_DOS_HEADER*)imageBufferp;
	_image_file_header = (_IMAGE_FILE_HEADER*)(imageBufferp + _image_dos_header->e_lfanew + 4);
	_image_optional_header = (_IMAGE_OPTIONAL_HEADER*)((char*)_image_file_header + 20);

	// 先计算添加新节后的NewImageBuffer的大小,并初始化内存
	int expand_value = least_common_multiple(_image_optional_header->FileAlignment, _image_optional_header->SectionAlignment); // 这里为了方便直接把新节大小设为文件对齐粒度和内存对齐粒度的最小公倍数即可
	char* newImageBufferp = (char*)malloc(_image_optional_header->SizeOfImage + expand_value);
	memset(newImageBufferp, 0, _image_optional_header->SizeOfImage + expand_value);
	memcpy(newImageBufferp, imageBufferp, _image_optional_header->SizeOfImage);

	_image_dos_header = (_IMAGE_DOS_HEADER*)newImageBufferp;
	_image_file_header = (_IMAGE_FILE_HEADER*)(newImageBufferp + _image_dos_header->e_lfanew + 4);
	_image_optional_header = (_IMAGE_OPTIONAL_HEADER*)((char*)_image_file_header + 20);
	_image_section_header = (_IMAGE_SECTION_HEADER*)((char*)_image_optional_header + _image_file_header->SizeOfOptionalHeader);

	//判断节表末尾是否有空间新增节表
	if (_image_optional_header->SizeOfHeaders - _image_dos_header->e_lfanew - 4 - 20 - _image_file_header->SizeOfOptionalHeader -
		_image_file_header->NumberOfSections * 40 < 80) {
		printf("空间不足,无法新增节表\n");
		exit(0);
	}
	for (int i = 0; i < 80; i++) {
		if (*((char*)(_image_section_header + _image_file_header->NumberOfSections) + i) != 0) {
			printf("节表后80字节数据不全为0x00,有文件数据,无法新增节表\n");
			exit(0);
		}
	}

	//新增节
	DWORD original_SizeOfImage = _image_optional_header->SizeOfImage; //记录下原来的SizeOfImage
	_image_optional_header->SizeOfImage += expand_value;

	//新增节表
	_IMAGE_SECTION_HEADER* _image_section_header_new = (_IMAGE_SECTION_HEADER*)_image_section_header + _image_file_header->NumberOfSections;
	for (int i = 0; i < 40; i++) {
		*((char*)_image_section_header_new + i) = *((char*)_image_section_header + i);
	}
	_image_file_header->NumberOfSections++;

	//修改新增节表信息
	char* name = (char*)_image_section_header_new->Name;
	const char* newName = ".newsec";
	strncpy(name, newName, IMAGE_SIZEOF_SHORT_NAME);
	_image_section_header_new->Misc.VirtualSize = expand_value;
	_image_section_header_new->VirtualAddress = original_SizeOfImage;
	_image_section_header_new->SizeOfRawData = expand_value;
	_image_section_header_new->PointerToRawData = (_image_section_header_new - 1)->PointerToRawData + (_image_section_header_new - 1)->SizeOfRawData;
	_image_section_header_new->Characteristics = 0x60000020;

	return newImageBufferp;
}

int main(int argc, char* argv[]) {
	char* fileBufferp = to_FileBuffer(filePath);
	char* imageBufferp = fileBuffer_to_ImageBuffer(fileBufferp);

	char* newImageBufferp = add_one_section(imageBufferp);

	if (!add_shellcode_to_new_section(newImageBufferp)) {
		printf("在新节中注入shellcode失败");
		return 0;
	}

	char* newBufferp = imageBuffer_to_NewBuffer(newImageBufferp);
	DWORD size = compute_NewBuffer_size(newBufferp);
	int isSucceed = save_to_disk(newBufferp, storagePath, size);
	if (!isSucceed) {
		printf("存盘失败");
	}
	else
		printf("存盘成功");

	free(fileBufferp);
	free(imageBufferp);
	free(newImageBufferp);
	free(newBufferp);

	return 0;
}

5.3.2 编程实现:扩大最后一个节,并添加代码

  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
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
#pragma warning(disable : 4996)
#include <iostream>
#include <string.h>

typedef unsigned short WORD;
typedef unsigned int DWORD;
typedef unsigned char BYTE;

#define MZ 0x5A4D
#define PE 0x4550
#define IMAGE_SIZEOF_SHORT_NAME 8
#define MessageBox_Address 0x754034B0 //宏定义的MessageBox内存地址

const char* filePath = "fg.exe";  //你要打开的PE文件路径
const char* storagePath = "fg_new.exe"; //保存路径

//shellcode:定义要添加的硬编码全局变量,确定的写好,不确定的先用0x00填充,后面计算完再改
BYTE shellcode[] = {
	0x6A,0x00,0x6A,0x00,0x6A,0x00,0x6A,0x00,
	0xE8,0x00,0x00,0x00,0x00,
	0xE9,0x00,0x00,0x00,0x00
};

//DOS头
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];
	DWORD e_lfanew;  //PE文件真正开始的偏移地址
};

//标准PE头
struct _IMAGE_FILE_HEADER {
	WORD Machine;  //文件运行平台
	WORD NumberOfSections;  //节数量
	DWORD TimeDateStamp;  //时间戳
	DWORD PointerToSymbolTable;
	DWORD NumberOfSymbols;
	WORD SizeOfOptionalHeader;  //可选PE头大小
	WORD Characteristics;  //特征值
};

//可选PE头
struct _IMAGE_OPTIONAL_HEADER {
	WORD Magic;  //文件类型
	BYTE MajorLinkerVersion;
	BYTE MinorLinkerVersion;
	DWORD SizeOfCode;   //代码节文件对齐后的大小
	DWORD SizeOfInitializedData;  //初始化数据文件对齐后的大小
	DWORD SizeOfUninitializedData;  //未初始化数据文件对齐后大小
	DWORD AddressOfEntryPoint;  //程序入口点(偏移量)
	DWORD BaseOfCode;  //代码基址
	DWORD BaseOfData;  //数据基址
	DWORD ImageBase;   //内存镜像基址
	DWORD SectionAlignment;  //内存对齐粒度
	DWORD FileAlignment;  //文件对齐粒度
	WORD MajorOperatingSystemVersion;
	WORD MinorOperatingSystemVersion;
	WORD MajorImageVersion;
	WORD MinorImageVersion;
	WORD MajorSubsystemVersion;
	WORD MinorSubsystemVersion;
	DWORD Win32VersionValue;
	DWORD SizeOfImage;  //文件装入虚拟内存后大小
	DWORD SizeOfHeaders;  //DOS、NT头和节表大小
	DWORD CheckSum;  //校验和
	WORD Subsystem;
	WORD DllCharacteristics;
	DWORD SizeOfStackReserve;  //预留堆栈大小
	DWORD SizeOfStackCommit;  //实际分配堆栈大小
	DWORD SizeOfHeapReserve;  //预留堆大小
	DWORD SizeOfHeapCommit;  //实际分配堆大小
	DWORD LoaderFlags;
	DWORD NumberOfRvaAndSizes;  //目录项数目
	//_IMAGE_DATA_DIRECTORY DataDirectory[16];  //这个先不管
};

//NT头
struct _IMAGE_NT_HEADERS {
	DWORD Signature;  //PE签名
	_IMAGE_FILE_HEADER FileHeader;
	_IMAGE_OPTIONAL_HEADER OptionalHeader;
};

//节表
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;  //该节特征属性
};

/*计算文件大小函数
参数:文件绝对路径
返回值:返回文件大小(单位字节)
*/
int compute_file_size(const char* filePath) {
	FILE* fp = fopen(filePath, "rb");
	if (!fp) {
		printf("打开文件失败");
		exit(0);
	}
	fseek(fp, 0, 2);
	int size = ftell(fp);
	//fseek(fp,0,0); 单纯计算文件大小,就不需要还原指针了
	fclose(fp);
	return size;
}

/*将文件读入FileBuffer函数
参数:文件绝对路径
返回值:FileBuffer起始地址
*/
char* to_FileBuffer(const char* filePath) {
	FILE* fp = fopen(filePath, "rb");
	if (!fp) {
		printf("打开文件失败");
		exit(0);
	}
	int size = compute_file_size(filePath);

	char* mp = (char*)malloc(sizeof(char) * size);  //分配内存空间
	if (!mp) {
		printf("分配空间失败");
		fclose(fp);
		exit(0);
	}

	int isSucceed = fread(mp, size, 1, fp);
	if (!isSucceed) {
		printf("读取数据失败");
		free(mp);
		fclose(fp);
		exit(0);
	}

	fclose(fp);
	return mp;
}

/*FileBuffer到ImageBuffer函数
参数:FileBuffer起始地址
返回值:ImageBuffer起始地址
*/
char* fileBuffer_to_ImageBuffer(char* fileBufferp) {

	_IMAGE_DOS_HEADER* _image_dos_header = NULL;
	_IMAGE_FILE_HEADER* _image_file_header = NULL;
	_IMAGE_OPTIONAL_HEADER* _image_optional_header = NULL;
	_IMAGE_SECTION_HEADER* _image_section_header = NULL;

	_image_dos_header = (_IMAGE_DOS_HEADER*)fileBufferp;
	_image_file_header = (_IMAGE_FILE_HEADER*)(fileBufferp + _image_dos_header->e_lfanew + 4);
	_image_optional_header = (_IMAGE_OPTIONAL_HEADER*)((char*)_image_file_header + 20);
	_image_section_header = (_IMAGE_SECTION_HEADER*)((char*)_image_optional_header + _image_file_header->SizeOfOptionalHeader);

	DWORD sizeofImage = _image_optional_header->SizeOfImage;
	char* imageBufferp = (char*)malloc(sizeofImage);
	if (NULL == imageBufferp) {
		printf("动态申请ImageBuffer内存失败\n");
		exit(0);
	}
	memset(imageBufferp, 0, sizeofImage);

	// 复制所有头
	memcpy(imageBufferp, fileBufferp, _image_optional_header->SizeOfHeaders);


	// 将文件中的节表复制到内存上
	for (int i = 0; i < _image_file_header->NumberOfSections; ++i) {
		memcpy(imageBufferp + _image_section_header[i].VirtualAddress, fileBufferp + _image_section_header[i].PointerToRawData, _image_section_header[i].SizeOfRawData);
	}

	return imageBufferp;
}

/*计算NewBuffer大小函数
参数:NewBuffer起始地址
返回值:unsigned int类型(单位:字节)
*/
DWORD compute_NewBuffer_size(char* newBufferp) {
	_IMAGE_DOS_HEADER* _image_dos_header = NULL;
	_IMAGE_FILE_HEADER* _image_file_header = NULL;
	_IMAGE_OPTIONAL_HEADER* _image_optional_header = NULL;
	_IMAGE_SECTION_HEADER* _image_section_header = NULL;

	_image_dos_header = (_IMAGE_DOS_HEADER*)newBufferp;
	_image_file_header = (_IMAGE_FILE_HEADER*)(newBufferp + _image_dos_header->e_lfanew + 4);
	_image_optional_header = (_IMAGE_OPTIONAL_HEADER*)((char*)_image_file_header + 20);
	_image_section_header = (_IMAGE_SECTION_HEADER*)((char*)_image_optional_header + _image_file_header->SizeOfOptionalHeader);

	_IMAGE_SECTION_HEADER* last_image_section_header = _image_section_header + _image_file_header->NumberOfSections - 1;
	DWORD sizeofNewBuffer = last_image_section_header->PointerToRawData + last_image_section_header->SizeOfRawData;
	return sizeofNewBuffer;
}

/*ImageBuffer到NewBuffer函数
参数:ImageBuffer起始地址
返回值:NewBuffer起始地址
*/
char* imageBuffer_to_NewBuffer(char* imageBufferp) {

	_IMAGE_DOS_HEADER* _image_dos_header = NULL;
	_IMAGE_FILE_HEADER* _image_file_header = NULL;
	_IMAGE_OPTIONAL_HEADER* _image_optional_header = NULL;
	_IMAGE_SECTION_HEADER* _image_section_header = NULL;

	_image_dos_header = (_IMAGE_DOS_HEADER*)imageBufferp;
	_image_file_header = (_IMAGE_FILE_HEADER*)(imageBufferp + _image_dos_header->e_lfanew + 4);
	_image_optional_header = (_IMAGE_OPTIONAL_HEADER*)((char*)_image_file_header + 20);
	_image_section_header = (_IMAGE_SECTION_HEADER*)((char*)_image_optional_header + _image_file_header->SizeOfOptionalHeader);

	//重新计算newBuffer需要大小
	/*方法一:SizeOfHEADER + 所有节的SizeOfRawData之和
	int sizeofSections = 0;
	_IMAGE_SECTION_HEADER* _image_section_header_temp = _image_section_header;
	for(DWORD int i = 0;i < _image_file_header->NumberOfSections;i++){
		sizeofSections += _image_section_header_temp->SizeOfRawData;
		_image_section_header_temp++;
	}
	char* newBufferp = (char*)malloc(_image_optional_header->SizeOfHeaders + sizeofSections);
	*/

	//方法二:使用最后一个节的文件偏移地址 + 最后一个节对齐后的大小
	DWORD size = compute_NewBuffer_size(imageBufferp);
	char* newBufferp = (char*)malloc(size);
	if (NULL == newBufferp) {
		printf("NewBuffer内存分配失败\n");
		exit(0);
	}

	memset(newBufferp, 0, size);
	memcpy(newBufferp, imageBufferp, _image_optional_header->SizeOfHeaders);

	for (int i = 0; i < _image_file_header->NumberOfSections; i++) {
		memcpy(newBufferp + _image_section_header[i].PointerToRawData, imageBufferp + _image_section_header[i].VirtualAddress, _image_section_header[i].SizeOfRawData);
	}

	return newBufferp;
}

/*NewBuffer存盘函数
参数:需要写出数据的内存首地址,保存绝对路径,写出的数据大小(单位:字节)
返回值:成功返回1,失败返回0
*/
int save_to_disk(char* newBufferp, const char* storagePath, DWORD size) {
	FILE* fp = fopen(storagePath, "wb");
	if (!fp) {
		printf("打开文件失败");
		return 0;
	}
	int isSucceed = fwrite(newBufferp, size, 1, fp);
	if (!isSucceed) {
		free(newBufferp);
		fclose(fp);
		return 0;
	}
	fclose(fp);
	return 1;
}

/*向新增节中注入shellcode函数
参数:要注入的可执行文件的ImageBuffer起始地址
返回值:0为注入失败、1为注入成功
*/
int add_shellcode_to_new_section(char* imageBufferp) {
	_IMAGE_DOS_HEADER* _image_dos_header = NULL;
	_IMAGE_FILE_HEADER* _image_file_header = NULL;
	_IMAGE_OPTIONAL_HEADER* _image_optional_header = NULL;
	_IMAGE_SECTION_HEADER* _image_section_header = NULL;

	_image_dos_header = (_IMAGE_DOS_HEADER*)imageBufferp;
	_image_file_header = (_IMAGE_FILE_HEADER*)(imageBufferp + _image_dos_header->e_lfanew + 4);
	_image_optional_header = (_IMAGE_OPTIONAL_HEADER*)((char*)_image_file_header + 20);
	_image_section_header = (_IMAGE_SECTION_HEADER*)((char*)_image_optional_header + _image_file_header->SizeOfOptionalHeader);

	//定位到新节表起始地址
	_IMAGE_SECTION_HEADER* target_image_section_header = _image_section_header + _image_file_header->NumberOfSections - 1;

	//判断新节是否存的下shellcode
	if ((int)(target_image_section_header->Misc.VirtualSize) < (int)(sizeof(shellcode) / sizeof(shellcode[0]))) {
		printf("新节大小不够,添加失败\n");
		return 0;
	}

	//将shellcode注入到新节
	char* shellcodep = imageBufferp + target_image_section_header->VirtualAddress;
	char* newOEP = shellcodep;  //此shellcode注入起始地址即为新的OEP地址
	memcpy(shellcodep, shellcode, sizeof(shellcode) / sizeof(shellcode[0]));

	//修改call MessageBox的硬编码E8后面的4字节
	DWORD E8_fill_value = MessageBox_Address - ((shellcodep - imageBufferp + _image_optional_header->ImageBase) + 0xD);
	*(DWORD*)(shellcodep + 9) = E8_fill_value;

	//修改jmp 原OEP的硬编码E9后面的4字节
	DWORD E9_fill_value = _image_optional_header->AddressOfEntryPoint + _image_optional_header->ImageBase - ((shellcodep - imageBufferp + _image_optional_header->ImageBase) + 0x12);
	*(DWORD*)(shellcodep + 14) = E9_fill_value;

	//修改OEP指向shellcode
	_image_optional_header->AddressOfEntryPoint = (DWORD)(newOEP - imageBufferp);

	return 1;
}

/*计算最小公倍数函数
参数:传入要计算的两个数
返回值:两数的最小公倍数
*/
int least_common_multiple(int a, int b) {
	if (a <= 0 || b <= 0)
		return -1;
	
	int max = (a > b ? a : b);
	while (1) {
		if (max % a == 0 && max % b == 0) {
			break;
		}
		max++;
	}
	return max;
}

/*在文件最后新增节函数
参数:要新增节的可执行文件的ImageBuffer起始地址
返回值:新增节后的NewImageBuffer内存的起始地址
*/
char* add_one_section(char* imageBufferp) {
	_IMAGE_DOS_HEADER* _image_dos_header = NULL;
	_IMAGE_FILE_HEADER* _image_file_header = NULL;
	_IMAGE_OPTIONAL_HEADER* _image_optional_header = NULL;
	_IMAGE_SECTION_HEADER* _image_section_header = NULL;

	// 定义原来的ImageBuffer几个指针(因为要用到几个值用于创建新的NewImageBuffer内存)
	_image_dos_header = (_IMAGE_DOS_HEADER*)imageBufferp;
	_image_file_header = (_IMAGE_FILE_HEADER*)(imageBufferp + _image_dos_header->e_lfanew + 4);
	_image_optional_header = (_IMAGE_OPTIONAL_HEADER*)((char*)_image_file_header + 20);

	// 先计算添加新节后的NewImageBuffer的大小,并初始化内存
	int expand_value = least_common_multiple(_image_optional_header->FileAlignment, _image_optional_header->SectionAlignment); // 这里为了方便直接把新节大小设为文件对齐粒度和内存对齐粒度的最小公倍数即可
	char* newImageBufferp = (char*)malloc(_image_optional_header->SizeOfImage + expand_value);
	memset(newImageBufferp, 0, _image_optional_header->SizeOfImage + expand_value);
	memcpy(newImageBufferp, imageBufferp, _image_optional_header->SizeOfImage);

	_image_dos_header = (_IMAGE_DOS_HEADER*)newImageBufferp;
	_image_file_header = (_IMAGE_FILE_HEADER*)(newImageBufferp + _image_dos_header->e_lfanew + 4);
	_image_optional_header = (_IMAGE_OPTIONAL_HEADER*)((char*)_image_file_header + 20);
	_image_section_header = (_IMAGE_SECTION_HEADER*)((char*)_image_optional_header + _image_file_header->SizeOfOptionalHeader);

	//判断节表末尾是否有空间新增节表
	if (_image_optional_header->SizeOfHeaders - _image_dos_header->e_lfanew - 4 - 20 - _image_file_header->SizeOfOptionalHeader -
		_image_file_header->NumberOfSections * 40 < 80) {
		printf("空间不足,无法新增节表\n");
		exit(0);
	}
	for (int i = 0; i < 80; i++) {
		if (*((char*)(_image_section_header + _image_file_header->NumberOfSections) + i) != 0) {
			printf("节表后80字节数据不全为0x00,有文件数据,无法新增节表\n");
			exit(0);
		}
	}

	//新增节
	DWORD original_SizeOfImage = _image_optional_header->SizeOfImage; //记录下原来的SizeOfImage
	_image_optional_header->SizeOfImage += expand_value;

	//新增节表
	_IMAGE_SECTION_HEADER* _image_section_header_new = (_IMAGE_SECTION_HEADER*)_image_section_header + _image_file_header->NumberOfSections;
	for (int i = 0; i < 40; i++) {
		*((char*)_image_section_header_new + i) = *((char*)_image_section_header + i);
	}
	_image_file_header->NumberOfSections++;

	//修改新增节表信息
	char* name = (char*)_image_section_header_new->Name;
	const char* newName = ".newsec";
	strncpy(name, newName, IMAGE_SIZEOF_SHORT_NAME);
	_image_section_header_new->Misc.VirtualSize = expand_value;
	_image_section_header_new->VirtualAddress = original_SizeOfImage;
	_image_section_header_new->SizeOfRawData = expand_value;
	_image_section_header_new->PointerToRawData = (_image_section_header_new - 1)->PointerToRawData + (_image_section_header_new - 1)->SizeOfRawData;
	_image_section_header_new->Characteristics = 0x60000020;

	return newImageBufferp;
}

/*扩大最后一个节函数
参数:要扩大节的可执行文件的ImageBuffer起始地址
返回值:扩大节后的NewImageBuffer内存的起始地址
*/
char* expand_last_section(char* imageBufferp) {
	_IMAGE_DOS_HEADER* _image_dos_header = NULL;
	_IMAGE_FILE_HEADER* _image_file_header = NULL;
	_IMAGE_OPTIONAL_HEADER* _image_optional_header = NULL;
	_IMAGE_SECTION_HEADER* _image_section_header = NULL;

	_image_dos_header = (_IMAGE_DOS_HEADER*)imageBufferp;
	_image_file_header = (_IMAGE_FILE_HEADER*)(imageBufferp + _image_dos_header->e_lfanew + 4);
	_image_optional_header = (_IMAGE_OPTIONAL_HEADER*)((char*)_image_file_header + 20);

	//计算扩大多少,并初始化NewImageBuffer
	int expand_value = least_common_multiple(_image_optional_header->FileAlignment, _image_optional_header->SectionAlignment);//这里为了方便直接把需要扩大的大小设为文件对齐粒度和内存对齐粒度的最小公倍数即可
	char* newImageBufferp = (char*)malloc(_image_optional_header->SizeOfImage + expand_value);

	memset(newImageBufferp, 0, _image_optional_header->SizeOfImage + expand_value);
	memcpy(newImageBufferp, imageBufferp, _image_optional_header->SizeOfImage);//不改变指针哦

	_image_dos_header = (_IMAGE_DOS_HEADER*)newImageBufferp;
	_image_file_header = (_IMAGE_FILE_HEADER*)(newImageBufferp + _image_dos_header->e_lfanew + 4);
	_image_optional_header = (_IMAGE_OPTIONAL_HEADER*)((char*)_image_file_header + 20);
	_image_section_header = (_IMAGE_SECTION_HEADER*)((char*)_image_optional_header + _image_file_header->SizeOfOptionalHeader);

	//修改SizeofImage:原来的SizeOfImage + Ex(因为原来的SizeOfImage是内存对齐的整数倍,Ex也是通过内存对齐和文件对齐的最小公倍数算出来的,所以相加一定还是内存对齐的整数倍)
	DWORD original_SizeOfImage = _image_optional_header->SizeOfImage; //记录下原来的SizeOfImage
	_image_optional_header->SizeOfImage += expand_value;

	//修改节表信息,使得节区的内存大小和文件大小一致
	_IMAGE_SECTION_HEADER* _image_section_header_last = _image_section_header + _image_file_header->NumberOfSections - 1;
	_image_section_header_last->Misc.VirtualSize = original_SizeOfImage - _image_section_header_last->VirtualAddress + expand_value;
	_image_section_header_last->SizeOfRawData = _image_section_header_last->Misc.VirtualSize;

	//修改节属性,保证可执行
	_image_section_header_last->Characteristics = _image_section_header_last->Characteristics | 0x60000020;
	return newImageBufferp;
}

/*向扩大的节中注入shellcode函数
参数:要注入的可执行文件的ImageBuffer起始地址
返回值:0表示注入失败,1表示注入成功
*/
int add_shellcode_to_expanded_section(char* imageBufferp) {
	_IMAGE_DOS_HEADER* _image_dos_header = NULL;
	_IMAGE_FILE_HEADER* _image_file_header = NULL;
	_IMAGE_OPTIONAL_HEADER* _image_optional_header = NULL;
	_IMAGE_SECTION_HEADER* _image_section_header = NULL;

	_image_dos_header = (_IMAGE_DOS_HEADER*)imageBufferp;
	_image_file_header = (_IMAGE_FILE_HEADER*)(imageBufferp + _image_dos_header->e_lfanew + 4);
	_image_optional_header = (_IMAGE_OPTIONAL_HEADER*)((char*)_image_file_header + 20);
	_image_section_header = (_IMAGE_SECTION_HEADER*)((char*)_image_optional_header + _image_file_header->SizeOfOptionalHeader);

	//判断是否存的下shellcode,这里直接在Ex区域内注入,不考虑原来的空白区
	if ((int)(least_common_multiple(_image_optional_header->FileAlignment, _image_optional_header->SectionAlignment)) < (int)(sizeof(shellcode) / sizeof(shellcode[0]))) {
		printf("新节大小不够,添加失败\n");
		return 0;
	}

	//将shellcode注入到Ex
	char* shellcodep = imageBufferp + _image_optional_header->SizeOfImage - least_common_multiple(_image_optional_header->FileAlignment, _image_optional_header->SectionAlignment);
	char* newOEP = shellcodep;  //此shellcode注入起始地址即为新的OEP地址
	memcpy(shellcodep, shellcode, sizeof(shellcode) / sizeof(shellcode[0]));

	//修改call MessageBox的硬编码E8后面的4字节
	DWORD E8_fill_value = MessageBox_Address - ((shellcodep - imageBufferp + _image_optional_header->ImageBase) + 0xD);
	*(DWORD*)(shellcodep + 9) = E8_fill_value;

	//修改jmp 原OEP的硬编码E9后面的4字节
	DWORD E9_fill_value = _image_optional_header->AddressOfEntryPoint + _image_optional_header->ImageBase - ((shellcodep - imageBufferp + _image_optional_header->ImageBase) + 0x12);
	*(DWORD*)(shellcodep + 14) = E9_fill_value;

	//修改OEP指向shellcode
	_image_optional_header->AddressOfEntryPoint = (DWORD)(newOEP - imageBufferp);

	return 1;
}

int main(int argc, char* argv[]) {
	char* fileBufferp = to_FileBuffer(filePath);
	char* imageBufferp = fileBuffer_to_ImageBuffer(fileBufferp);

	char* newImageBufferp = expand_last_section(imageBufferp);

	if (!add_shellcode_to_expanded_section(newImageBufferp)) {
		printf("在新节中注入shellcode失败");
		return 0;
	}

	char* newBufferp = imageBuffer_to_NewBuffer(newImageBufferp);
	DWORD size = compute_NewBuffer_size(newBufferp);
	int isSucceed = save_to_disk(newBufferp, storagePath, size);
	if (!isSucceed) {
		printf("存盘失败");
	}
	else
		printf("存盘成功");

	free(fileBufferp);
	free(imageBufferp);
	free(newImageBufferp);
	free(newBufferp);

	return 0;
}

6. 扩大节、合并节

6.1 添加代码的方式

  1. 直接在任意节的空白区添加代码;
  2. 新增节添加代码;
  3. 扩大最后一个节添加代码
  4. 合并节并添加代码

这节学习合并节并添加代码

6.2 合并节思路

使用拉伸后的合并更方便计算节合并后的大小,而且可以保证计算的正确性。如果用FileBuffer算,算地址或者大小时还要担心节中未初始化数据在拉伸后导致节变大等等情况。

合并节就是:把第一个节开始一直到最后一个节的结尾都当成第一个节。

但是可能会造成合并后文件变大,因为文件对齐粒度一般都要小于内存对齐粒度,所以虚拟内存中每个节的空白区一般比文件空白区要大一些,现在合并节是把虚拟内存中从第一个节开始到文件结束都当成一个节,最后再变成FileBuffer就会变大。

步骤:

  1. NumberOfSections改成1
  2. 修改第一个节表的字段
    • VirtualSize:
      • 方法一:SizeOfImage - 第一个节VirtualAddress
      • 方法二:还可以通过最后一个节的VirtualAddress + 最后一个节的VirtualSize内存对齐后的大小 - SizeOfHeader内存对齐后的大小
    • SizeOfRawData:等于VirtualSize即可
  3. 将第一个节的属性改为包含所有节的属性,即用第一个节的属性与其他的节的属性做或运算

6.4 编码实现合并节

  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
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
#pragma warning(disable : 4996)
#include <iostream>
#include <string.h>

typedef unsigned short WORD;
typedef unsigned int DWORD;
typedef unsigned char BYTE;

#define MZ 0x5A4D
#define PE 0x4550
#define IMAGE_SIZEOF_SHORT_NAME 8
#define MessageBox_Address 0x754034B0 //宏定义的MessageBox内存地址

const char* filePath = "fg.exe";  //你要打开的PE文件路径
const char* storagePath = "fg_new.exe"; //保存路径

//shellcode:定义要添加的硬编码全局变量,确定的写好,不确定的先用0x00填充,后面计算完再改
BYTE shellcode[] = {
	0x6A,0x00,0x6A,0x00,0x6A,0x00,0x6A,0x00,
	0xE8,0x00,0x00,0x00,0x00,
	0xE9,0x00,0x00,0x00,0x00
};

//DOS头
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];
	DWORD e_lfanew;  //PE文件真正开始的偏移地址
};

//标准PE头
struct _IMAGE_FILE_HEADER {
	WORD Machine;  //文件运行平台
	WORD NumberOfSections;  //节数量
	DWORD TimeDateStamp;  //时间戳
	DWORD PointerToSymbolTable;
	DWORD NumberOfSymbols;
	WORD SizeOfOptionalHeader;  //可选PE头大小
	WORD Characteristics;  //特征值
};

//可选PE头
struct _IMAGE_OPTIONAL_HEADER {
	WORD Magic;  //文件类型
	BYTE MajorLinkerVersion;
	BYTE MinorLinkerVersion;
	DWORD SizeOfCode;   //代码节文件对齐后的大小
	DWORD SizeOfInitializedData;  //初始化数据文件对齐后的大小
	DWORD SizeOfUninitializedData;  //未初始化数据文件对齐后大小
	DWORD AddressOfEntryPoint;  //程序入口点(偏移量)
	DWORD BaseOfCode;  //代码基址
	DWORD BaseOfData;  //数据基址
	DWORD ImageBase;   //内存镜像基址
	DWORD SectionAlignment;  //内存对齐粒度
	DWORD FileAlignment;  //文件对齐粒度
	WORD MajorOperatingSystemVersion;
	WORD MinorOperatingSystemVersion;
	WORD MajorImageVersion;
	WORD MinorImageVersion;
	WORD MajorSubsystemVersion;
	WORD MinorSubsystemVersion;
	DWORD Win32VersionValue;
	DWORD SizeOfImage;  //文件装入虚拟内存后大小
	DWORD SizeOfHeaders;  //DOS、NT头和节表大小
	DWORD CheckSum;  //校验和
	WORD Subsystem;
	WORD DllCharacteristics;
	DWORD SizeOfStackReserve;  //预留堆栈大小
	DWORD SizeOfStackCommit;  //实际分配堆栈大小
	DWORD SizeOfHeapReserve;  //预留堆大小
	DWORD SizeOfHeapCommit;  //实际分配堆大小
	DWORD LoaderFlags;
	DWORD NumberOfRvaAndSizes;  //目录项数目
	//_IMAGE_DATA_DIRECTORY DataDirectory[16];  //这个先不管
};

//NT头
struct _IMAGE_NT_HEADERS {
	DWORD Signature;  //PE签名
	_IMAGE_FILE_HEADER FileHeader;
	_IMAGE_OPTIONAL_HEADER OptionalHeader;
};

//节表
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;  //该节特征属性
};

/*计算文件大小函数
参数:文件绝对路径
返回值:返回文件大小(单位字节)
*/
int compute_file_size(const char* filePath) {
	FILE* fp = fopen(filePath, "rb");
	if (!fp) {
		printf("打开文件失败");
		exit(0);
	}
	fseek(fp, 0, 2);
	int size = ftell(fp);
	//fseek(fp,0,0); 单纯计算文件大小,就不需要还原指针了
	fclose(fp);
	return size;
}

/*将文件读入FileBuffer函数
参数:文件绝对路径
返回值:FileBuffer起始地址
*/
char* to_FileBuffer(const char* filePath) {
	FILE* fp = fopen(filePath, "rb");
	if (!fp) {
		printf("打开文件失败");
		exit(0);
	}
	int size = compute_file_size(filePath);

	char* mp = (char*)malloc(sizeof(char) * size);  //分配内存空间
	if (!mp) {
		printf("分配空间失败");
		fclose(fp);
		exit(0);
	}

	int isSucceed = fread(mp, size, 1, fp);
	if (!isSucceed) {
		printf("读取数据失败");
		free(mp);
		fclose(fp);
		exit(0);
	}

	fclose(fp);
	return mp;
}

/*FileBuffer到ImageBuffer函数
参数:FileBuffer起始地址
返回值:ImageBuffer起始地址
*/
char* fileBuffer_to_ImageBuffer(char* fileBufferp) {

	_IMAGE_DOS_HEADER* _image_dos_header = NULL;
	_IMAGE_FILE_HEADER* _image_file_header = NULL;
	_IMAGE_OPTIONAL_HEADER* _image_optional_header = NULL;
	_IMAGE_SECTION_HEADER* _image_section_header = NULL;

	_image_dos_header = (_IMAGE_DOS_HEADER*)fileBufferp;
	_image_file_header = (_IMAGE_FILE_HEADER*)(fileBufferp + _image_dos_header->e_lfanew + 4);
	_image_optional_header = (_IMAGE_OPTIONAL_HEADER*)((char*)_image_file_header + 20);
	_image_section_header = (_IMAGE_SECTION_HEADER*)((char*)_image_optional_header + _image_file_header->SizeOfOptionalHeader);

	DWORD sizeofImage = _image_optional_header->SizeOfImage;
	char* imageBufferp = (char*)malloc(sizeofImage);
	if (NULL == imageBufferp) {
		printf("动态申请ImageBuffer内存失败\n");
		exit(0);
	}
	memset(imageBufferp, 0, sizeofImage);

	// 复制所有头
	memcpy(imageBufferp, fileBufferp, _image_optional_header->SizeOfHeaders);


	// 将文件中的节表复制到内存上
	for (int i = 0; i < _image_file_header->NumberOfSections; ++i) {
		memcpy(imageBufferp + _image_section_header[i].VirtualAddress, fileBufferp + _image_section_header[i].PointerToRawData, _image_section_header[i].SizeOfRawData);
	}

	return imageBufferp;
}

/*计算NewBuffer大小函数
参数:NewBuffer起始地址
返回值:unsigned int类型(单位:字节)
*/
DWORD compute_NewBuffer_size(char* newBufferp) {
	_IMAGE_DOS_HEADER* _image_dos_header = NULL;
	_IMAGE_FILE_HEADER* _image_file_header = NULL;
	_IMAGE_OPTIONAL_HEADER* _image_optional_header = NULL;
	_IMAGE_SECTION_HEADER* _image_section_header = NULL;

	_image_dos_header = (_IMAGE_DOS_HEADER*)newBufferp;
	_image_file_header = (_IMAGE_FILE_HEADER*)(newBufferp + _image_dos_header->e_lfanew + 4);
	_image_optional_header = (_IMAGE_OPTIONAL_HEADER*)((char*)_image_file_header + 20);
	_image_section_header = (_IMAGE_SECTION_HEADER*)((char*)_image_optional_header + _image_file_header->SizeOfOptionalHeader);

	_IMAGE_SECTION_HEADER* last_image_section_header = _image_section_header + _image_file_header->NumberOfSections - 1;
	DWORD sizeofNewBuffer = last_image_section_header->PointerToRawData + last_image_section_header->SizeOfRawData;
	return sizeofNewBuffer;
}

/*ImageBuffer到NewBuffer函数
参数:ImageBuffer起始地址
返回值:NewBuffer起始地址
*/
char* imageBuffer_to_NewBuffer(char* imageBufferp) {

	_IMAGE_DOS_HEADER* _image_dos_header = NULL;
	_IMAGE_FILE_HEADER* _image_file_header = NULL;
	_IMAGE_OPTIONAL_HEADER* _image_optional_header = NULL;
	_IMAGE_SECTION_HEADER* _image_section_header = NULL;

	_image_dos_header = (_IMAGE_DOS_HEADER*)imageBufferp;
	_image_file_header = (_IMAGE_FILE_HEADER*)(imageBufferp + _image_dos_header->e_lfanew + 4);
	_image_optional_header = (_IMAGE_OPTIONAL_HEADER*)((char*)_image_file_header + 20);
	_image_section_header = (_IMAGE_SECTION_HEADER*)((char*)_image_optional_header + _image_file_header->SizeOfOptionalHeader);

	//重新计算newBuffer需要大小
	/*方法一:SizeOfHEADER + 所有节的SizeOfRawData之和
	int sizeofSections = 0;
	_IMAGE_SECTION_HEADER* _image_section_header_temp = _image_section_header;
	for(DWORD int i = 0;i < _image_file_header->NumberOfSections;i++){
		sizeofSections += _image_section_header_temp->SizeOfRawData;
		_image_section_header_temp++;
	}
	char* newBufferp = (char*)malloc(_image_optional_header->SizeOfHeaders + sizeofSections);
	*/

	//方法二:使用最后一个节的文件偏移地址 + 最后一个节对齐后的大小
	DWORD size = compute_NewBuffer_size(imageBufferp);
	char* newBufferp = (char*)malloc(size);
	if (NULL == newBufferp) {
		printf("NewBuffer内存分配失败\n");
		exit(0);
	}

	memset(newBufferp, 0, size);
	memcpy(newBufferp, imageBufferp, _image_optional_header->SizeOfHeaders);

	for (int i = 0; i < _image_file_header->NumberOfSections; i++) {
		memcpy(newBufferp + _image_section_header[i].PointerToRawData, imageBufferp + _image_section_header[i].VirtualAddress, _image_section_header[i].SizeOfRawData);
	}

	return newBufferp;
}

/*NewBuffer存盘函数
参数:需要写出数据的内存首地址,保存绝对路径,写出的数据大小(单位:字节)
返回值:成功返回1,失败返回0
*/
int save_to_disk(char* newBufferp, const char* storagePath, DWORD size) {
	FILE* fp = fopen(storagePath, "wb");
	if (!fp) {
		printf("打开文件失败");
		return 0;
	}
	int isSucceed = fwrite(newBufferp, size, 1, fp);
	if (!isSucceed) {
		free(newBufferp);
		fclose(fp);
		return 0;
	}
	fclose(fp);
	return 1;
}

/*向新增节中注入shellcode函数
参数:要注入的可执行文件的ImageBuffer起始地址
返回值:0为注入失败、1为注入成功
*/
int add_shellcode_to_new_section(char* imageBufferp) {
	_IMAGE_DOS_HEADER* _image_dos_header = NULL;
	_IMAGE_FILE_HEADER* _image_file_header = NULL;
	_IMAGE_OPTIONAL_HEADER* _image_optional_header = NULL;
	_IMAGE_SECTION_HEADER* _image_section_header = NULL;

	_image_dos_header = (_IMAGE_DOS_HEADER*)imageBufferp;
	_image_file_header = (_IMAGE_FILE_HEADER*)(imageBufferp + _image_dos_header->e_lfanew + 4);
	_image_optional_header = (_IMAGE_OPTIONAL_HEADER*)((char*)_image_file_header + 20);
	_image_section_header = (_IMAGE_SECTION_HEADER*)((char*)_image_optional_header + _image_file_header->SizeOfOptionalHeader);

	//定位到新节表起始地址
	_IMAGE_SECTION_HEADER* target_image_section_header = _image_section_header + _image_file_header->NumberOfSections - 1;

	//判断新节是否存的下shellcode
	if ((int)(target_image_section_header->Misc.VirtualSize) < (int)(sizeof(shellcode) / sizeof(shellcode[0]))) {
		printf("新节大小不够,添加失败\n");
		return 0;
	}

	//将shellcode注入到新节
	char* shellcodep = imageBufferp + target_image_section_header->VirtualAddress;
	char* newOEP = shellcodep;  //此shellcode注入起始地址即为新的OEP地址
	memcpy(shellcodep, shellcode, sizeof(shellcode) / sizeof(shellcode[0]));

	//修改call MessageBox的硬编码E8后面的4字节
	DWORD E8_fill_value = MessageBox_Address - ((shellcodep - imageBufferp + _image_optional_header->ImageBase) + 0xD);
	*(DWORD*)(shellcodep + 9) = E8_fill_value;

	//修改jmp 原OEP的硬编码E9后面的4字节
	DWORD E9_fill_value = _image_optional_header->AddressOfEntryPoint + _image_optional_header->ImageBase - ((shellcodep - imageBufferp + _image_optional_header->ImageBase) + 0x12);
	*(DWORD*)(shellcodep + 14) = E9_fill_value;

	//修改OEP指向shellcode
	_image_optional_header->AddressOfEntryPoint = (DWORD)(newOEP - imageBufferp);

	return 1;
}

/*计算最小公倍数函数
参数:传入要计算的两个数
返回值:两数的最小公倍数
*/
int least_common_multiple(int a, int b) {
	if (a <= 0 || b <= 0)
		return -1;
	
	int max = (a > b ? a : b);
	while (1) {
		if (max % a == 0 && max % b == 0) {
			break;
		}
		max++;
	}
	return max;
}

/*在文件最后新增节函数
参数:要新增节的可执行文件的ImageBuffer起始地址
返回值:新增节后的NewImageBuffer内存的起始地址
*/
char* add_one_section(char* imageBufferp) {
	_IMAGE_DOS_HEADER* _image_dos_header = NULL;
	_IMAGE_FILE_HEADER* _image_file_header = NULL;
	_IMAGE_OPTIONAL_HEADER* _image_optional_header = NULL;
	_IMAGE_SECTION_HEADER* _image_section_header = NULL;

	// 定义原来的ImageBuffer几个指针(因为要用到几个值用于创建新的NewImageBuffer内存)
	_image_dos_header = (_IMAGE_DOS_HEADER*)imageBufferp;
	_image_file_header = (_IMAGE_FILE_HEADER*)(imageBufferp + _image_dos_header->e_lfanew + 4);
	_image_optional_header = (_IMAGE_OPTIONAL_HEADER*)((char*)_image_file_header + 20);

	// 先计算添加新节后的NewImageBuffer的大小,并初始化内存
	int expand_value = least_common_multiple(_image_optional_header->FileAlignment, _image_optional_header->SectionAlignment); // 这里为了方便直接把新节大小设为文件对齐粒度和内存对齐粒度的最小公倍数即可
	char* newImageBufferp = (char*)malloc(_image_optional_header->SizeOfImage + expand_value);
	memset(newImageBufferp, 0, _image_optional_header->SizeOfImage + expand_value);
	memcpy(newImageBufferp, imageBufferp, _image_optional_header->SizeOfImage);

	_image_dos_header = (_IMAGE_DOS_HEADER*)newImageBufferp;
	_image_file_header = (_IMAGE_FILE_HEADER*)(newImageBufferp + _image_dos_header->e_lfanew + 4);
	_image_optional_header = (_IMAGE_OPTIONAL_HEADER*)((char*)_image_file_header + 20);
	_image_section_header = (_IMAGE_SECTION_HEADER*)((char*)_image_optional_header + _image_file_header->SizeOfOptionalHeader);

	//判断节表末尾是否有空间新增节表
	if (_image_optional_header->SizeOfHeaders - _image_dos_header->e_lfanew - 4 - 20 - _image_file_header->SizeOfOptionalHeader -
		_image_file_header->NumberOfSections * 40 < 80) {
		printf("空间不足,无法新增节表\n");
		exit(0);
	}
	for (int i = 0; i < 80; i++) {
		if (*((char*)(_image_section_header + _image_file_header->NumberOfSections) + i) != 0) {
			printf("节表后80字节数据不全为0x00,有文件数据,无法新增节表\n");
			exit(0);
		}
	}

	//新增节
	DWORD original_SizeOfImage = _image_optional_header->SizeOfImage; //记录下原来的SizeOfImage
	_image_optional_header->SizeOfImage += expand_value;

	//新增节表
	_IMAGE_SECTION_HEADER* _image_section_header_new = (_IMAGE_SECTION_HEADER*)_image_section_header + _image_file_header->NumberOfSections;
	for (int i = 0; i < 40; i++) {
		*((char*)_image_section_header_new + i) = *((char*)_image_section_header + i);
	}
	_image_file_header->NumberOfSections++;

	//修改新增节表信息
	char* name = (char*)_image_section_header_new->Name;
	const char* newName = ".newsec";
	strncpy(name, newName, IMAGE_SIZEOF_SHORT_NAME);
	_image_section_header_new->Misc.VirtualSize = expand_value;
	_image_section_header_new->VirtualAddress = original_SizeOfImage;
	_image_section_header_new->SizeOfRawData = expand_value;
	_image_section_header_new->PointerToRawData = (_image_section_header_new - 1)->PointerToRawData + (_image_section_header_new - 1)->SizeOfRawData;
	_image_section_header_new->Characteristics = 0x60000020;

	return newImageBufferp;
}

/*扩大最后一个节函数
参数:要扩大节的可执行文件的ImageBuffer起始地址
返回值:扩大节后的NewImageBuffer内存的起始地址
*/
char* expand_last_section(char* imageBufferp) {
	_IMAGE_DOS_HEADER* _image_dos_header = NULL;
	_IMAGE_FILE_HEADER* _image_file_header = NULL;
	_IMAGE_OPTIONAL_HEADER* _image_optional_header = NULL;
	_IMAGE_SECTION_HEADER* _image_section_header = NULL;

	_image_dos_header = (_IMAGE_DOS_HEADER*)imageBufferp;
	_image_file_header = (_IMAGE_FILE_HEADER*)(imageBufferp + _image_dos_header->e_lfanew + 4);
	_image_optional_header = (_IMAGE_OPTIONAL_HEADER*)((char*)_image_file_header + 20);

	//计算扩大多少,并初始化NewImageBuffer
	int expand_value = least_common_multiple(_image_optional_header->FileAlignment, _image_optional_header->SectionAlignment);//这里为了方便直接把需要扩大的大小设为文件对齐粒度和内存对齐粒度的最小公倍数即可
	char* newImageBufferp = (char*)malloc(_image_optional_header->SizeOfImage + expand_value);

	memset(newImageBufferp, 0, _image_optional_header->SizeOfImage + expand_value);
	memcpy(newImageBufferp, imageBufferp, _image_optional_header->SizeOfImage);//不改变指针哦

	_image_dos_header = (_IMAGE_DOS_HEADER*)newImageBufferp;
	_image_file_header = (_IMAGE_FILE_HEADER*)(newImageBufferp + _image_dos_header->e_lfanew + 4);
	_image_optional_header = (_IMAGE_OPTIONAL_HEADER*)((char*)_image_file_header + 20);
	_image_section_header = (_IMAGE_SECTION_HEADER*)((char*)_image_optional_header + _image_file_header->SizeOfOptionalHeader);

	//修改SizeofImage:原来的SizeOfImage + Ex(因为原来的SizeOfImage是内存对齐的整数倍,Ex也是通过内存对齐和文件对齐的最小公倍数算出来的,所以相加一定还是内存对齐的整数倍)
	DWORD original_SizeOfImage = _image_optional_header->SizeOfImage; //记录下原来的SizeOfImage
	_image_optional_header->SizeOfImage += expand_value;

	//修改节表信息,使得节区的内存大小和文件大小一致
	_IMAGE_SECTION_HEADER* _image_section_header_last = _image_section_header + _image_file_header->NumberOfSections - 1;
	_image_section_header_last->Misc.VirtualSize = original_SizeOfImage - _image_section_header_last->VirtualAddress + expand_value;
	_image_section_header_last->SizeOfRawData = _image_section_header_last->Misc.VirtualSize;

	//修改节属性,保证可执行
	_image_section_header_last->Characteristics = _image_section_header_last->Characteristics | 0x60000020;
	return newImageBufferp;
}

/*向扩大的节中注入shellcode函数
参数:要注入的可执行文件的ImageBuffer起始地址
返回值:0表示注入失败,1表示注入成功
*/
int add_shellcode_to_expanded_section(char* imageBufferp) {
	_IMAGE_DOS_HEADER* _image_dos_header = NULL;
	_IMAGE_FILE_HEADER* _image_file_header = NULL;
	_IMAGE_OPTIONAL_HEADER* _image_optional_header = NULL;
	_IMAGE_SECTION_HEADER* _image_section_header = NULL;

	_image_dos_header = (_IMAGE_DOS_HEADER*)imageBufferp;
	_image_file_header = (_IMAGE_FILE_HEADER*)(imageBufferp + _image_dos_header->e_lfanew + 4);
	_image_optional_header = (_IMAGE_OPTIONAL_HEADER*)((char*)_image_file_header + 20);
	_image_section_header = (_IMAGE_SECTION_HEADER*)((char*)_image_optional_header + _image_file_header->SizeOfOptionalHeader);

	//判断是否存的下shellcode,这里直接在Ex区域内注入,不考虑原来的空白区
	if ((int)(least_common_multiple(_image_optional_header->FileAlignment, _image_optional_header->SectionAlignment)) < (int)(sizeof(shellcode) / sizeof(shellcode[0]))) {
		printf("新节大小不够,添加失败\n");
		return 0;
	}

	//将shellcode注入到Ex
	char* shellcodep = imageBufferp + _image_optional_header->SizeOfImage - least_common_multiple(_image_optional_header->FileAlignment, _image_optional_header->SectionAlignment);
	char* newOEP = shellcodep;  //此shellcode注入起始地址即为新的OEP地址
	memcpy(shellcodep, shellcode, sizeof(shellcode) / sizeof(shellcode[0]));

	//修改call MessageBox的硬编码E8后面的4字节
	DWORD E8_fill_value = MessageBox_Address - ((shellcodep - imageBufferp + _image_optional_header->ImageBase) + 0xD);
	*(DWORD*)(shellcodep + 9) = E8_fill_value;

	//修改jmp 原OEP的硬编码E9后面的4字节
	DWORD E9_fill_value = _image_optional_header->AddressOfEntryPoint + _image_optional_header->ImageBase - ((shellcodep - imageBufferp + _image_optional_header->ImageBase) + 0x12);
	*(DWORD*)(shellcodep + 14) = E9_fill_value;

	//修改OEP指向shellcode
	_image_optional_header->AddressOfEntryPoint = (DWORD)(newOEP - imageBufferp);

	return 1;
}

/*合并节函数
参数:需要合并节的可执行文件ImageBuffer起始地址
返回值:合并后的ImageBuffer起始地址(因为合并节后并不改变ImageBuffer内存大小,所以返回void也行)
*/
char* merge_section(char* imageBufferp) {
	_IMAGE_DOS_HEADER* _image_dos_header = NULL;
	_IMAGE_FILE_HEADER* _image_file_header = NULL;
	_IMAGE_OPTIONAL_HEADER* _image_optional_header = NULL;
	_IMAGE_SECTION_HEADER* _image_section_header = NULL;

	_image_dos_header = (_IMAGE_DOS_HEADER*)imageBufferp;
	_image_file_header = (_IMAGE_FILE_HEADER*)(imageBufferp + _image_dos_header->e_lfanew + 4);
	_image_optional_header = (_IMAGE_OPTIONAL_HEADER*)((char*)_image_file_header + 20);
	_image_section_header = (_IMAGE_SECTION_HEADER*)((char*)_image_optional_header + _image_file_header->SizeOfOptionalHeader);

	//修改合并节的节表字段
	_image_section_header->Misc.VirtualSize = _image_optional_header->SizeOfImage - _image_section_header->VirtualAddress;
	_image_section_header->SizeOfRawData = _image_section_header->Misc.VirtualSize;
	for (int i = 1; i < _image_file_header->NumberOfSections; i++) {
		_image_section_header->Characteristics = _image_section_header->Characteristics | (_image_section_header + i)->Characteristics;
	}

	// 设置节表末尾应该跟40个0x00
	memset(_image_section_header + 1, 0, _image_file_header->NumberOfSections * 40);

	//修改NumberOfSections字段
	_image_file_header->NumberOfSections = 1;

	return imageBufferp;
}

int main(int argc, char* argv[]) {
	char* fileBufferp = to_FileBuffer(filePath);
	char* imageBufferp = fileBuffer_to_ImageBuffer(fileBufferp);
	imageBufferp = merge_section(imageBufferp);

	char* newBufferp = imageBuffer_to_NewBuffer(imageBufferp);
	DWORD size = compute_NewBuffer_size(newBufferp);
	int isSucceed = save_to_disk(newBufferp, storagePath, size);
	if (!isSucceed) {
		printf("存盘失败");
	}
	else
		printf("存盘成功");

	free(fileBufferp);
	free(imageBufferp);
	free(newBufferp);

	return 0;
}

7. 数据目录

7.1 数据目录概述

7.1.1 数据目录的引入

我们当时解析可选PE头时,有一个字段为DWORD NumberOfRvaAndSizesNumberOfRvaAndSizes的值为多少,说明_IMAGE_DATA_DIRECTORY DataDirectory[16]这种类型的结构体就有多少个。

Win32下,可执行文件的PE结构分节:有代码节,数据节等等节,但是不能理解成这些节中只有程序本身的数据等,编译器也会替我们往每一个节中加很多重要的内容:

  • 比如一个程序还会使用一些系统提供的函数,那么编译器就需要添加这些函数的相关信息,告诉程序需要去哪里找到这个函数并进行系统调用。
  • 一个程序不仅可以使用别的函数,也可以提供函数给别的程序使用,所以此时编译器会添加此程序中供别人使用的函数相关信息。

编译器添加的内容为什么重要?因为这些信息包含了诸如:PE程序的图标在哪里,用到了哪些系统提供的函数,为其他的程序提供哪些函数等,编译器不添加,程序就找不到这些东西的地址。

7.1.2 数据目录的作用

综上所述:一个PE文件中,除了有程序员自己添加的内容(程序自身的数据),编译器也会向当中添加很多内容,编译器添加的内容有很多种类,各有各的目的和使用途径,那么这么多数据和内容肯定需要一个表格记录-–数据目录,而且要编译器在添加时要遵循某些规则,以至于程序在运行时,会很方便的找到需要的信息和数据在哪里

所以可以这么理解:这16个数据目录分别记录了编译器往PE文件中写的16种不同的信息存在哪里

7.1.3 数据目录有哪些

可选PE头最后一个成员,就是数据目录,一共有16个。

分别是:导出表的数据目录、导入表的数据目录、资源表的数据目录、异常信息表的数据目录、安全证书表的数据目录、重定位表的数据目录、调试信息表的数据目录、版权所有表的数据目录、全局指针表的数据目录、TLS表的数据目录、加载配置表的数据目录、绑定导入表的数据目录、IAT表的数据目录、延迟导入表的数据目录、COM信息表的数据目录、最后一个保留未使用。

其中比较重要的就是导出表、导入表、重定位表、IAT表这四张表的数据目录,这四张表和程序的运行有直接关系,不管是加壳、脱壳、破解、病毒或反病毒,这些都是基础中的基础。

7.1.4 数据目录结构

1
2
3
4
struct _IMAGE_DATA_DIRECTORY{
    DWORD VirtualAddress;  //内存偏移,必须有
	DWORD Size;  // 大小,破坏了也不会影响程序运行
}

16个数据目录的大小一样,都是8字节,一行16字节,所以一共占8行:

  1. 可以从节表开始往上倒着推8行,就是数据目录开始
  2. 也可以从可选PE头开始,往后数第24个DWORD就是NumberOfRvaAndSize,第25个DWORD就是数据目录开始

  • 上图中前8字节是导出表的数据目录:前4个字节为导出表的VirtualAddress为0x0,后4字节为导出表的Size为0x0,说明此可执行文件没有导出表。
  • 再接着8字节为导入表的数据目录:前4字节导入表的VirtualAddres为0x6D20,后4字节导入表的Size为0xC8,此可执行文件的导入表在ImageBase + 0x6D20处开始,大小为0xC8。
  • 后面的数据目录以此类推。

7.1.5 数据目录和16种表的关系

数据目录只是记录了对应的表的内存偏移地址和大小。我们通过找到表的数据目录,得到表的内存偏移地址,接着找到这个内存偏移地址处才是表本身真正的所在位置!

比如我们找到了"导出表的数据目录",就可以通过数据目录中的VirtualAddress + ImageBase得到"导出表"所在的地址:

1
2
3
4
5
IMAGE_DIRECTORY_ENTRY_EXPORT
struct _IMAGE_DATA_DIRECTORY {
    DWORD VirtualAddress;
    DWORD Size;
};

7.2 编程输出全部目录项

  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
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
#pragma warning(disable : 4996)
#include <iostream>
#include <string.h>

typedef unsigned short WORD;
typedef unsigned int DWORD;
typedef unsigned char BYTE;

#define MZ 0x5A4D
#define PE 0x4550
#define IMAGE_SIZEOF_SHORT_NAME 8

const char* filePath = "fg.exe";  //你要打开的PE文件路径

//DOS头
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];
	DWORD e_lfanew;  //PE文件真正开始的偏移地址
};

//标准PE头
struct _IMAGE_FILE_HEADER {
	WORD Machine;  //文件运行平台
	WORD NumberOfSections;  //节数量
	DWORD TimeDateStamp;  //时间戳
	DWORD PointerToSymbolTable;
	DWORD NumberOfSymbols;
	WORD SizeOfOptionalHeader;  //可选PE头大小
	WORD Characteristics;  //特征值
};

//数据目录
struct _IMAGE_DATA_DIRECTORY {
	DWORD VirtualAddress;
	DWORD Size;
};

//可选PE头
struct _IMAGE_OPTIONAL_HEADER {
	WORD Magic;  //文件类型
	BYTE MajorLinkerVersion;
	BYTE MinorLinkerVersion;
	DWORD SizeOfCode;   //代码节文件对齐后的大小
	DWORD SizeOfInitializedData;  //初始化数据文件对齐后的大小
	DWORD SizeOfUninitializedData;  //未初始化数据文件对齐后大小
	DWORD AddressOfEntryPoint;  //程序入口点(偏移量)
	DWORD BaseOfCode;  //代码基址
	DWORD BaseOfData;  //数据基址
	DWORD ImageBase;   //内存镜像基址
	DWORD SectionAlignment;  //内存对齐粒度
	DWORD FileAlignment;  //文件对齐粒度
	WORD MajorOperatingSystemVersion;
	WORD MinorOperatingSystemVersion;
	WORD MajorImageVersion;
	WORD MinorImageVersion;
	WORD MajorSubsystemVersion;
	WORD MinorSubsystemVersion;
	DWORD Win32VersionValue;
	DWORD SizeOfImage;  //文件装入虚拟内存后大小
	DWORD SizeOfHeaders;  //DOS、NT头和节表大小
	DWORD CheckSum;  //校验和
	WORD Subsystem;
	WORD DllCharacteristics;
	DWORD SizeOfStackReserve;  //预留堆栈大小
	DWORD SizeOfStackCommit;  //实际分配堆栈大小
	DWORD SizeOfHeapReserve;  //预留堆大小
	DWORD SizeOfHeapCommit;  //实际分配堆大小
	DWORD LoaderFlags;
	DWORD NumberOfRvaAndSizes;  //目录项数目
	_IMAGE_DATA_DIRECTORY DataDirectory[16]; //数据目录
};

//NT头
struct _IMAGE_NT_HEADERS {
	DWORD Signature;  //PE签名
	_IMAGE_FILE_HEADER FileHeader;
	_IMAGE_OPTIONAL_HEADER OptionalHeader;
};

//节表
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;  //该节特征属性
};

/*计算文件大小函数
参数:文件绝对路径
返回值:返回文件大小(单位字节)
*/
int compute_file_size(const char* filePath) {
	FILE* fp = fopen(filePath, "rb");
	if (!fp) {
		printf("打开文件失败");
		exit(0);
	}
	fseek(fp, 0, 2);
	int size = ftell(fp);
	//fseek(fp,0,0); 单纯计算文件大小,就不需要还原指针了
	fclose(fp);
	return size;
}

/*将文件读入FileBuffer函数
参数:文件绝对路径
返回值:FileBuffer起始地址
*/
char* to_FileBuffer(const char* filePath) {
	FILE* fp = fopen(filePath, "rb");
	if (!fp) {
		printf("打开文件失败");
		exit(0);
	}
	int size = compute_file_size(filePath);

	char* mp = (char*)malloc(sizeof(char) * size);  //分配内存空间
	if (!mp) {
		printf("分配空间失败");
		fclose(fp);
		exit(0);
	}

	int isSucceed = fread(mp, size, 1, fp);
	if (!isSucceed) {
		printf("读取数据失败");
		free(mp);
		fclose(fp);
		exit(0);
	}

	fclose(fp);
	return mp;
}

/*输出指定文件的数据目录函数
参数:指定可执行文件绝对路径
返回值:无
*/
void data_directory_print(const char* filePath) {
	char* fileBufferp = to_FileBuffer(filePath);

	_IMAGE_DOS_HEADER* _image_dos_header = NULL;
	_IMAGE_FILE_HEADER* _image_file_header = NULL;
	_IMAGE_OPTIONAL_HEADER* _image_optional_header = NULL;

	_image_dos_header = (_IMAGE_DOS_HEADER*)fileBufferp;
	_image_file_header = (_IMAGE_FILE_HEADER*)(fileBufferp + _image_dos_header->e_lfanew + 4);
	_image_optional_header = (_IMAGE_OPTIONAL_HEADER*)((char*)_image_file_header + 20);

	const char* tableNameArr[16] = {
		"IMAGE_DIRECTORY_ENTRY_EXPORT(导出表)",
		"IMAGE_DIRECTORY_ENTRY_IMPORT(导入表)",
		"IMAGE_DIRECTORY_ENTRY_RESOURCE(资源表)",
		"IMAGE_DIRECTORY_ENTRY_EXCEPTION(异常信息表)",
		"IMAGE_DIRECTORY_ENTRY_SECURITY(安全证书表)",
		"IMAGE_DIRECTORY_ENTRY_BASERELOC(重定位表)",
		"IMAGE_DIRECTORY_ENTRY_DEBUG(调试信息表)",
		"IMAGE_DIRECTORY_ENTRY_COPYRIGHT(版权所有表)",
		"IMAGE_DIRECTORY_ENTRY_GLOBALPTR(全局指针表)",
		"IMAGE_DIRECTORY_ENTRY_TLS(TLS表)",
		"IMAGE_DIRECTORY_ENTRY_LOAD_CONFIG(加载配置表)",
		"IMAGE_DIRECTORY_ENTRY_BOUND_IMPORT(绑定导入表)",
		"IMAGE_DIRECTORY_ENTRY_IAT(IAT表)",
		"IMAGE_DIRECTORY_ENTRY_DELAY_IMPORT(延迟导入表)",
		"IMAGE_DIRECTORY_ENTRY_COM_DESCRIPTOR(COM信息表)",
		"保留"
	};

	//定位到数据目录结构起始地址
	_IMAGE_DATA_DIRECTORY* _image_data_directory = _image_optional_header->DataDirectory;
	for (DWORD i = 0; i < _image_optional_header->NumberOfRvaAndSizes; i++) {
		printf("%s\nVirtualSize:%08X  Size:%08X\n\n", tableNameArr[i], _image_data_directory->VirtualAddress, _image_data_directory->Size);
		_image_data_directory++;
	}
}

int main(int argc, char* argv[])
{
	data_directory_print(filePath);
	return 0;
}

8. 导出表

8.1 导出表概述

8.1.1 引入导出表

一个Win32下的.exe文件,是由多个PE文件组成。比如通过OD打开一个Ipmsg.exe,查看模块M,会发现一个ipmsg.exe由多个.dll(即模块)构成。

这种以动态链接库.dll的方式导出函数时,如果此.exe需要用到某个.dll中的函数,那么.exe怎么知道:

  • 引入的.dll中有哪些函数可以供.exe使用?
  • .dll中的各种函数都存在哪里?

所以.dll中应当有一个目录或者结构提供给.exe文件,来记录.dll中的函数有哪些,函数的起始地址等信息。所以如果.dll或者.exe文件想要给别的程序提供函数,就必须同时给别的程序一个“清单”,这个“清单”就是导出表。

8.1.2 常见误区

  • 不是只有.dll才提供函数给别的程序使用,有些.exe也可以提供函数。
  • 有些.exe的导出表数据目录不是全0,表示有此.exe程序有导出表,即可对外提供函数;有些没有导出表,就不对外提供函数。所以一个PE文件(如.dll/.exe/.sys)。如果对外提供函数,那么就需要有导出表,且需要遵守一定的格式。
  • 一个安全的PE文件,是不会显示导出表的,但可以对外提供函数:正常情况下如果一个PE文件不提供导出表,别人是无法使用其函数的;但是逆向一个游戏时,如果知道了别人的导出表,就不用找call了(不现实)。所以游戏会做安全加固,我们不知道哪些函数是导出的,就算给我函数地址,我也没办法拿这个地址去调用,只能分析反汇编代码,分析程序入口是什么,分析函数的参数是什么。

8.2 导出表位置

先定位到可选PE头,再找到可选PE头的最后一个成员:即一个IMAGE_DATA_DIRECTORY结构体数组,第一个结构体就是导出表数据目录,结构体的第一个成员是导出表的内存偏移地址RVA,第二个成员是导出表的大小。

通过导出表数据目录的RVA(内存位置)转FOA(文件位置)就可以找到导出表。

导出表其实就在这个PE文件的某个节中(后面要学的各种表也是在某个节中)。

注意事项:在解析PE结构时就不用拉伸,用FileBuffer分析即可。因为拉伸只是有助于理解,如果直接在FileBuffer中做,只是需要多使用一个RVA–>FOA的地址转换函数,每次遇到RVA地址时转换一下即可,就省去了将文件整个拉伸的过程,会减少很多代码量和多余操作,而且市面上关于PE的书籍几乎都是直接用FileBuffer讲解操作的,所以现在开始,我们就要学会不拉伸的情况下去分析。

8.3 导出表的结构

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
struct _IMAGE_EXPORT_DIRECTORY{   //40字节		
    DWORD Characteristics;  //未使用		
    DWORD TimeDateStamp;  //时间戳		
    WORD MajorVersion;  //未使用		
    WORD MinorVersion;	//未使用		
    DWORD Name;  //指向该导出表文件名字符串  *
    DWORD Base;  //导出函数起始序号  *
    DWORD NumberOfFunctions;  //所有导出函数的个数  *		
    DWORD NumberOfNames;  //以函数名字导出的函数个数  *
    DWORD AddressOfFunctions;  //导出函数地址表RVA  *					
    DWORD AddressOfNames;  //导出函数名称表RVA  *						
    DWORD AddressOfNameOrdinals;  //导出函数序号表RVA  *						
};
  1. Name:指向该导出表文件名字符串的RVA,比如一个DBGHELP.dll的PE文件提供函数,那么这个PE文件的Name指向的字符串为为dbghelp.dll

  2. Base:导出函数起始序号(最小的序号)

    比如有序号为14、6、10、8的导出函数,那么Base的值为6

  3. NumberOfFunctions:所有导出函数的个数

    注意:这个值是通过导出函数的最大序号 - 最小序号 + 1算出来的;正常来说这个值是多少,那么此PE文件中导出函数的个数就是多少。但是如果使用自定义序号,序号定义时不是连续的,而是中间有空缺的序号,那么此时NumberOfFunctions的值会比实际的定义的导出函数个数多

    比如在.def中定义为Plus @12 、Sub @15 NONAME、 Mul @13 、Div @16 那么NumberOfFunctions值 = 16 - 12 + 1 = 5;而不是4!

  4. NumberOfNames

    • 以函数名字导出的函数个数:比如以动态链接库的方式导出(注意和只以序号导出函数区分)
    • 如果导出时加了NONAME关键字,那么就不计数
  5. AddressOfFunctions

    • 导出函数地址表RVA,即这个地址指向一个表!这个表中记录了此PE文件的所有导出函数的地址

    • 该表中元素宽度:4个字节
    • 元素个数:由NumberOfFunctions决定

    导出函数是有序号的,上图中的下标等于导出函数的相对序号

    (参考上图)比如使用自定义序号导出函数,即.def的方式导出,定义了序号13、14、16、17、19的导出函数,那么Base的值应为13,那么序号为13的函数相对相对下标就是0,序号14的导出函数相对下标就是1,序号为15的导出函数虽然没有,但是会把位置空出来,只是地址值为NULL,即0x00000000,序号16的导出函数相对下标就是3…以此类推

  6. AddressOfNames

    • 导出函数名称表RVA(拉伸后的内存地址偏移,所以要先转成FOA),即这个地址指向一个表,这个表中记录的是导出函数的名称字符串地址!!不是直接存储名称(且此字符串地址也是RVA)。

      就像C语言中的字符串char* name = “abc”;,name变量值为abc这个字符串的首地址。所以导出函数名称表中存储的是name值(即名称的地址),而不是直接存储的abc这个名称

    • 该表中元素宽度:4个字节
    • 表中元素的数量:由NumberOfNames决定
    • 注意:如果函数导出时添加了NONAME,即函数没有名称,那么这个表中就不会出现这个函数名地址
      • 所以AddressOfNames表中元素个数可能比AddressOfFunctions表中元素个数少!(AddressOfFunctions表中不管导出函数有没有名字,都会有地址)
      • 也有可能AddressOfNames表中元素个数比AddressOfFunctions表中元素个数多!因为导出函数时可以让多个不同名字的函数指向同一个函数地址
    • 函数名称表中是按名字排序的(根据ASCII码排序)
  7. AddressOfNameOrdinals

    • 导出函数序号表RVA,即这个地址指向一个表,表中存的是导出函数的相对序号

    • 该表中存储的内容 + Base = 函数的导出序号

    • 表中元素个数:由NumberOfNames决定,且和AddressOfName表一一对应的

    比如:现在要找名字叫Sub的导出函数,经查找在AddressOfNames表的下标为1的位置,那么这个函数所对应的相对序号则在AddressOfNameOrdinals表中下标为1的位置,即由上图可知Sub函数对应的序号为0x0001 + Base

    • 该表中元素宽度:2个字节

NONAME导出函数注意事项:

  • 如果导出时,定义的无名字函数,即Div @13 NONAME,那么函数名称表中就不会有指向Div函数名的元素,同样函数序号表中也不会有Div的相对序号,但是在函数地址表中会留出来一个元素位置存储Div函数地址。

8.4 由导出表获取函数地址

8.4.1 按名字找函数地址

  • 找到导出表后,先根据AddressOfNames,将RVA转成FOA,即可定位到函数名称表,遍历函数名称表,用名字依次与函数名称表每个元素指向的字符串做比较,直到名字匹配,记录下此时元素在函数名称表的下标i

  • 再根据AddressOfNameOrdinals,将RVA转成FOA,即可定位到函数序号表,得到此表下标为i的元素值n

  • 最后根据AddressOfFunctions,将RVA转成FOA,即可定位到函数地址表,则此表下标为n的元素值就是该名字对应函数的RVA地址,将RVA转成FOA就得到了该导出函数在FileBuffer中的地址。

    比如要根据名字查找导出函数mul(),假设在函数名称表中下标为2指向的位置找了"mul"字符串与函数名称匹配,所以再去查找函数序号表下标为2的元素为0x0004,最后再函数地址表中找下标为4的元素值,即为mul函数的RVA,转成FOA即可。

8.4.2 按序号找函数地址

  • 找到导出表后,根据AddressOfFunctions,将RVA转成FOA,定位到函数地址表。

  • 再用给定的序号 - Base = 相对序号i,得到相对序号i

  • 最后找函数地址表中下标为i的元素值即为该序号对应函数RVA地址,将RVA转成FOA就得到了该函数在FileBuffer中的地址。

    • 比如要根据序号找导出函数mul(),因为mul的序号为17,Base值为13,那么17 - 13 = 4,所以直接再函数地址表中找到下标为4的元素值,即为mul函数的RVA,最后转成FOA即可。
    • 所以根据导出序号找,跟函数序号表没有关系!函数序号表的存在就是为了用于按名字查找。
    • 上图可以发现,t_method函数用NONAME导出,所以函数名称表和函数序号表都没有t_method的信息,但是由于此函数序号为19,相对序号为19 - 13 = 6,所以会在函数地址表中下标为6的位置存储t_method函数的RVA。

8.5 编写程序打印导出表的信息

方式1

  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
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
#pragma warning(disable : 4996)
#include <iostream>
#include <string.h>

typedef unsigned short WORD;
typedef unsigned int DWORD;
typedef unsigned char BYTE;

#define MZ 0x5A4D
#define PE 0x4550
#define IMAGE_SIZEOF_SHORT_NAME 8

const char* filePath = "InjectDll.dll";  //选一个有导出表的PE文件

//DOS头
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];
	DWORD e_lfanew;  //PE文件真正开始的偏移地址
};

//标准PE头
struct _IMAGE_FILE_HEADER {
	WORD Machine;  //文件运行平台
	WORD NumberOfSections;  //节数量
	DWORD TimeDateStamp;  //时间戳
	DWORD PointerToSymbolTable;
	DWORD NumberOfSymbols;
	WORD SizeOfOptionalHeader;  //可选PE头大小
	WORD Characteristics;  //特征值
};

//数据目录
struct _IMAGE_DATA_DIRECTORY {
	DWORD VirtualAddress;
	DWORD Size;
};

//可选PE头
struct _IMAGE_OPTIONAL_HEADER {
	WORD Magic;  //文件类型
	BYTE MajorLinkerVersion;
	BYTE MinorLinkerVersion;
	DWORD SizeOfCode;   //代码节文件对齐后的大小
	DWORD SizeOfInitializedData;  //初始化数据文件对齐后的大小
	DWORD SizeOfUninitializedData;  //未初始化数据文件对齐后大小
	DWORD AddressOfEntryPoint;  //程序入口点(偏移量)
	DWORD BaseOfCode;  //代码基址
	DWORD BaseOfData;  //数据基址
	DWORD ImageBase;   //内存镜像基址
	DWORD SectionAlignment;  //内存对齐粒度
	DWORD FileAlignment;  //文件对齐粒度
	WORD MajorOperatingSystemVersion;
	WORD MinorOperatingSystemVersion;
	WORD MajorImageVersion;
	WORD MinorImageVersion;
	WORD MajorSubsystemVersion;
	WORD MinorSubsystemVersion;
	DWORD Win32VersionValue;
	DWORD SizeOfImage;  //文件装入虚拟内存后大小
	DWORD SizeOfHeaders;  //DOS、NT头和节表大小
	DWORD CheckSum;  //校验和
	WORD Subsystem;
	WORD DllCharacteristics;
	DWORD SizeOfStackReserve;  //预留堆栈大小
	DWORD SizeOfStackCommit;  //实际分配堆栈大小
	DWORD SizeOfHeapReserve;  //预留堆大小
	DWORD SizeOfHeapCommit;  //实际分配堆大小
	DWORD LoaderFlags;
	DWORD NumberOfRvaAndSizes;  //目录项数目
	_IMAGE_DATA_DIRECTORY DataDirectory[16]; //数据目录
};

//NT头
struct _IMAGE_NT_HEADERS {
	DWORD Signature;  //PE签名
	_IMAGE_FILE_HEADER FileHeader;
	_IMAGE_OPTIONAL_HEADER OptionalHeader;
};

//节表
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;  //该节特征属性
};

//导出表
struct _IMAGE_EXPORT_DIRECTORY {
	DWORD Characteristics;  //未使用		
	DWORD TimeDateStamp;  //时间戳		
	WORD MajorVersion;  //未使用		
	WORD MinorVersion;	//未使用		
	DWORD Name;  //指向该导出表文件名字符串  *
	DWORD Base;  //导出函数起始序号  *
	DWORD NumberOfFunctions;  //所有导出函数的个数  *		
	DWORD NumberOfNames;  //以函数名字导出的函数个数  *
	DWORD AddressOfFunctions;  //导出函数地址表RVA  *					
	DWORD AddressOfNames;  //导出函数名称表RVA  *						
	DWORD AddressOfNameOrdinals;  //导出函数序号表RVA  *						
};

/*计算文件大小函数
参数:文件绝对路径
返回值:返回文件大小(单位字节)
*/
int compute_file_size(const char* filePath) {
	FILE* fp = fopen(filePath, "rb");
	if (!fp) {
		printf("打开文件失败");
		exit(0);
	}
	fseek(fp, 0, 2);
	int size = ftell(fp);
	//fseek(fp,0,0); 单纯计算文件大小,就不需要还原指针了
	fclose(fp);
	return size;
}

/*将文件读入FileBuffer函数
参数:文件绝对路径
返回值:FileBuffer起始地址
*/
char* to_FileBuffer(const char* filePath) {
	FILE* fp = fopen(filePath, "rb");
	if (!fp) {
		printf("打开文件失败");
		exit(0);
	}
	int size = compute_file_size(filePath);

	char* mp = (char*)malloc(sizeof(char) * size);  //分配内存空间
	if (!mp) {
		printf("分配空间失败");
		fclose(fp);
		exit(0);
	}

	int isSucceed = fread(mp, size, 1, fp);
	if (!isSucceed) {
		printf("读取数据失败");
		free(mp);
		fclose(fp);
		exit(0);
	}

	fclose(fp);
	return mp;
}

/*虚拟内存偏移地址->文件偏移地址函数
参数:FileBuffer起始地址,RVA地址值
返回值:RVA对应的FOA,返回0则表示非法RVA
*/
DWORD RVA_to_FOA(char* fileBufferp, DWORD RVA) {
	_IMAGE_DOS_HEADER* _image_dos_header = NULL;
	_IMAGE_FILE_HEADER* _image_file_header = NULL;
	_IMAGE_OPTIONAL_HEADER* _image_optional_header = NULL;
	_IMAGE_SECTION_HEADER* _image_section_header = NULL;

	_image_dos_header = (_IMAGE_DOS_HEADER*)fileBufferp;
	_image_file_header = (_IMAGE_FILE_HEADER*)(fileBufferp + _image_dos_header->e_lfanew + 4);
	_image_optional_header = (_IMAGE_OPTIONAL_HEADER*)((char*)_image_file_header + 20);
	_image_section_header = (_IMAGE_SECTION_HEADER*)((char*)_image_optional_header +
		_image_file_header->SizeOfOptionalHeader);

	// RVA就在节中
	if (RVA < _image_section_header->VirtualAddress)
		return RVA;

	// 确定RVA 在那个节区中
	int i = 0;
	for (; i < _image_file_header->NumberOfSections; i++) {
		if (RVA >= _image_section_header->VirtualAddress
			&& RVA < _image_section_header->VirtualAddress + _image_section_header->Misc.VirtualSize) {
			break;
		}
		else {
			_image_section_header++;
		}
	}

	// 用来判断最终RVA值是否在节中的非空白区
	if (i == _image_file_header->NumberOfSections) {
		return 0;
	}

	// 确定RVA在该节区的位置
	DWORD mem_offset_from_section = RVA - _image_section_header->VirtualAddress;

	return _image_section_header->PointerToRawData + mem_offset_from_section;
}

/*打印导出表函数
参数:指定可执行文件绝对路径
返回值:无
*/
void exportTable_print(const char* filePath) {
	char* fileBufferp = to_FileBuffer(filePath);

	_IMAGE_DOS_HEADER* _image_dos_header = NULL;
	_IMAGE_FILE_HEADER* _image_file_header = NULL;
	_IMAGE_OPTIONAL_HEADER* _image_optional_header = NULL;
	_IMAGE_DATA_DIRECTORY* _image_data_directory = NULL;
	_IMAGE_EXPORT_DIRECTORY* _image_export_directory = NULL;

	_image_dos_header = (_IMAGE_DOS_HEADER*)fileBufferp;
	_image_file_header = (_IMAGE_FILE_HEADER*)(fileBufferp + _image_dos_header->e_lfanew + 4);
	_image_optional_header = (_IMAGE_OPTIONAL_HEADER*)((char*)_image_file_header + 20);
	_image_data_directory = (_IMAGE_DATA_DIRECTORY*)_image_optional_header->DataDirectory;
	if (_image_data_directory->VirtualAddress == 0) {
		printf("此PE文件没有导出表\n");
		exit(0);
	}

	//打印导出表
	_image_export_directory = (_IMAGE_EXPORT_DIRECTORY*)(RVA_to_FOA(fileBufferp, _image_data_directory->VirtualAddress) + (DWORD)fileBufferp);
	char* name = (char*)(RVA_to_FOA(fileBufferp, _image_export_directory->Name) + (DWORD)fileBufferp);
	printf("***************ExportTable****************\n");
	printf("Name:%08X --> %s\n", _image_export_directory->Name, name);
	printf("Base:%08X\n", _image_export_directory->Base);
	printf("NumberOfFunctions:%08X\n", _image_export_directory->NumberOfFunctions);
	printf("NumberOfNames:%08X\n", _image_export_directory->NumberOfNames);
	printf("AddressOfFunctions:%08X\n", _image_export_directory->AddressOfFunctions);
	printf("AddressOfNames:%08X\n", _image_export_directory->AddressOfNames);
	printf("AddressOfNameOrdinals:%08X\n\n", _image_export_directory->AddressOfNameOrdinals);

	//打印函数地址表
	DWORD* functionAddressTable = (DWORD*)(RVA_to_FOA(fileBufferp, _image_export_directory->AddressOfFunctions) + (DWORD)fileBufferp);
	printf("***************FunctionAddressTable****************\n");
	for (DWORD i = 0; i < _image_export_directory->NumberOfFunctions; i++) {
		printf("%08X\n", *(functionAddressTable + i));
	}

	//打印函数序号表(别忘了加Base)
	WORD* functionOrdinalsTable = (WORD*)(RVA_to_FOA(fileBufferp, _image_export_directory->AddressOfNameOrdinals) + (DWORD)fileBufferp);
	printf("\n***************FunctionOrdinalsTable****************\n");
	for (int i = 0; i < _image_export_directory->NumberOfNames; i++) {
		printf("%04X\n", *(functionOrdinalsTable + i) + _image_export_directory->Base);
	}

	//打印函数名称表(别忘了转FOA)
	DWORD* functionNameTable = (DWORD*)(RVA_to_FOA(fileBufferp, _image_export_directory->AddressOfNames) + (DWORD)fileBufferp);
	printf("\n***************FunctionNameTable****************\n");
	for (int i = 0; i < _image_export_directory->NumberOfNames; i++) {
		printf("%08X --> %s\n", *(functionNameTable + i), (char*)(RVA_to_FOA(fileBufferp, *(functionNameTable + i)) + (DWORD)fileBufferp));
	}
}

int main(int argc, char* argv[])
{
	exportTable_print(filePath);
	return 0;
}

方式2(推荐)

通过函数地址表确定序列表,再通过函数序号表找函数名。

如果在序列表中找不到对于的序号,说明这个函数不是以名字导出的。

  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
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
#pragma warning(disable : 4996)
#include <iostream>
#include <string.h>

typedef unsigned short WORD;
typedef unsigned int DWORD;
typedef unsigned char BYTE;

#define MZ 0x5A4D
#define PE 0x4550
#define IMAGE_SIZEOF_SHORT_NAME 8

//const char* filePath = "InjectDll.dll";  //选一个有导出表的PE文件
const char* filePath = "libpython2.7.dll";  //选一个有导出表的PE文件

//DOS头
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];
	DWORD e_lfanew;  //PE文件真正开始的偏移地址
};

//标准PE头
struct _IMAGE_FILE_HEADER {
	WORD Machine;  //文件运行平台
	WORD NumberOfSections;  //节数量
	DWORD TimeDateStamp;  //时间戳
	DWORD PointerToSymbolTable;
	DWORD NumberOfSymbols;
	WORD SizeOfOptionalHeader;  //可选PE头大小
	WORD Characteristics;  //特征值
};

//数据目录
struct _IMAGE_DATA_DIRECTORY {
	DWORD VirtualAddress;
	DWORD Size;
};

//可选PE头
struct _IMAGE_OPTIONAL_HEADER {
	WORD Magic;  //文件类型
	BYTE MajorLinkerVersion;
	BYTE MinorLinkerVersion;
	DWORD SizeOfCode;   //代码节文件对齐后的大小
	DWORD SizeOfInitializedData;  //初始化数据文件对齐后的大小
	DWORD SizeOfUninitializedData;  //未初始化数据文件对齐后大小
	DWORD AddressOfEntryPoint;  //程序入口点(偏移量)
	DWORD BaseOfCode;  //代码基址
	DWORD BaseOfData;  //数据基址
	DWORD ImageBase;   //内存镜像基址
	DWORD SectionAlignment;  //内存对齐粒度
	DWORD FileAlignment;  //文件对齐粒度
	WORD MajorOperatingSystemVersion;
	WORD MinorOperatingSystemVersion;
	WORD MajorImageVersion;
	WORD MinorImageVersion;
	WORD MajorSubsystemVersion;
	WORD MinorSubsystemVersion;
	DWORD Win32VersionValue;
	DWORD SizeOfImage;  //文件装入虚拟内存后大小
	DWORD SizeOfHeaders;  //DOS、NT头和节表大小
	DWORD CheckSum;  //校验和
	WORD Subsystem;
	WORD DllCharacteristics;
	DWORD SizeOfStackReserve;  //预留堆栈大小
	DWORD SizeOfStackCommit;  //实际分配堆栈大小
	DWORD SizeOfHeapReserve;  //预留堆大小
	DWORD SizeOfHeapCommit;  //实际分配堆大小
	DWORD LoaderFlags;
	DWORD NumberOfRvaAndSizes;  //目录项数目
	_IMAGE_DATA_DIRECTORY DataDirectory[16]; //数据目录
};

//NT头
struct _IMAGE_NT_HEADERS {
	DWORD Signature;  //PE签名
	_IMAGE_FILE_HEADER FileHeader;
	_IMAGE_OPTIONAL_HEADER OptionalHeader;
};

//节表
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;  //该节特征属性
};

//导出表
struct _IMAGE_EXPORT_DIRECTORY {
	DWORD Characteristics;  //未使用		
	DWORD TimeDateStamp;  //时间戳		
	WORD MajorVersion;  //未使用		
	WORD MinorVersion;	//未使用		
	DWORD Name;  //指向该导出表文件名字符串  *
	DWORD Base;  //导出函数起始序号  *
	DWORD NumberOfFunctions;  //所有导出函数的个数  *		
	DWORD NumberOfNames;  //以函数名字导出的函数个数  *
	DWORD AddressOfFunctions;  //导出函数地址表RVA  *					
	DWORD AddressOfNames;  //导出函数名称表RVA  *						
	DWORD AddressOfNameOrdinals;  //导出函数序号表RVA  *						
};

/*计算文件大小函数
参数:文件绝对路径
返回值:返回文件大小(单位字节)
*/
int compute_file_size(const char* filePath) {
	FILE* fp = fopen(filePath, "rb");
	if (!fp) {
		printf("打开文件失败");
		exit(0);
	}
	fseek(fp, 0, 2);
	int size = ftell(fp);
	//fseek(fp,0,0); 单纯计算文件大小,就不需要还原指针了
	fclose(fp);
	return size;
}

/*将文件读入FileBuffer函数
参数:文件绝对路径
返回值:FileBuffer起始地址
*/
char* to_FileBuffer(const char* filePath) {
	FILE* fp = fopen(filePath, "rb");
	if (!fp) {
		printf("打开文件失败");
		exit(0);
	}
	int size = compute_file_size(filePath);

	char* mp = (char*)malloc(sizeof(char) * size);  //分配内存空间
	if (!mp) {
		printf("分配空间失败");
		fclose(fp);
		exit(0);
	}

	int isSucceed = fread(mp, size, 1, fp);
	if (!isSucceed) {
		printf("读取数据失败");
		free(mp);
		fclose(fp);
		exit(0);
	}

	fclose(fp);
	return mp;
}

/*虚拟内存偏移地址->文件偏移地址函数
参数:FileBuffer起始地址,RVA地址值
返回值:RVA对应的FOA,返回0则表示非法RVA
*/
DWORD RVA_to_FOA(char* fileBufferp, DWORD RVA) {
	_IMAGE_DOS_HEADER* _image_dos_header = NULL;
	_IMAGE_FILE_HEADER* _image_file_header = NULL;
	_IMAGE_OPTIONAL_HEADER* _image_optional_header = NULL;
	_IMAGE_SECTION_HEADER* _image_section_header = NULL;

	_image_dos_header = (_IMAGE_DOS_HEADER*)fileBufferp;
	_image_file_header = (_IMAGE_FILE_HEADER*)(fileBufferp + _image_dos_header->e_lfanew + 4);
	_image_optional_header = (_IMAGE_OPTIONAL_HEADER*)((char*)_image_file_header + 20);
	_image_section_header = (_IMAGE_SECTION_HEADER*)((char*)_image_optional_header +
		_image_file_header->SizeOfOptionalHeader);

	if (RVA < _image_section_header->VirtualAddress)
		return 0;

	// 确定RVA 在那个节区中
	int i = 0;
	for (; i < _image_file_header->NumberOfSections; i++) {
		if (RVA >= _image_section_header->VirtualAddress
			&& RVA < _image_section_header->VirtualAddress + _image_section_header->Misc.VirtualSize) {
			break;
		}
		else {
			_image_section_header++;
		}
	}

	// 用来判断最终RVA值是否在节中的非空白区
	if (i == _image_file_header->NumberOfSections) {
		return 0;
	}

	// 确定RVA在该节区的位置
	DWORD mem_offset_from_section = RVA - _image_section_header->VirtualAddress;

	return _image_section_header->PointerToRawData + mem_offset_from_section;
}

/*打印导出表函数
参数:指定可执行文件绝对路径
返回值:无
*/
void exportTable_print(const char* filePath) {
	char* fileBufferp = to_FileBuffer(filePath);

	_IMAGE_DOS_HEADER* _image_dos_header = NULL;
	_IMAGE_FILE_HEADER* _image_file_header = NULL;
	_IMAGE_OPTIONAL_HEADER* _image_optional_header = NULL;
	_IMAGE_DATA_DIRECTORY* _image_data_directory = NULL;
	_IMAGE_EXPORT_DIRECTORY* _image_export_directory = NULL;

	_image_dos_header = (_IMAGE_DOS_HEADER*)fileBufferp;
	_image_file_header = (_IMAGE_FILE_HEADER*)(fileBufferp + _image_dos_header->e_lfanew + 4);
	_image_optional_header = (_IMAGE_OPTIONAL_HEADER*)((char*)_image_file_header + 20);
	_image_data_directory = (_IMAGE_DATA_DIRECTORY*)_image_optional_header->DataDirectory;
	if (_image_data_directory->VirtualAddress == 0) {
		printf("此PE文件没有导出表\n");
		exit(0);
	}

	//打印导出表
	_image_export_directory = (_IMAGE_EXPORT_DIRECTORY*)(RVA_to_FOA(fileBufferp, _image_data_directory->VirtualAddress) + (DWORD)fileBufferp);
	char* name = (char*)(RVA_to_FOA(fileBufferp, _image_export_directory->Name) + (DWORD)fileBufferp);
	printf("***************ExportTable****************\n");
	printf("Name:%08X --> %s\n", _image_export_directory->Name, name);
	printf("Base:%08X\n", _image_export_directory->Base);
	printf("NumberOfFunctions:%08X\n", _image_export_directory->NumberOfFunctions);
	printf("NumberOfNames:%08X\n", _image_export_directory->NumberOfNames);
	printf("AddressOfFunctions:%08X\n", _image_export_directory->AddressOfFunctions);
	printf("AddressOfNames:%08X\n", _image_export_directory->AddressOfNames);
	printf("AddressOfNameOrdinals:%08X\n\n", _image_export_directory->AddressOfNameOrdinals);

	// 函数地址表
	DWORD* functionAddressTable = (DWORD*)(RVA_to_FOA(fileBufferp, _image_export_directory->AddressOfFunctions) + (DWORD)fileBufferp);
	// 函数序号表(别忘了加Base)
	WORD* functionOrdinalsTable = (WORD*)(RVA_to_FOA(fileBufferp, _image_export_directory->AddressOfNameOrdinals) + (DWORD)fileBufferp);
	//函数名称表(别忘了转FOA)
	DWORD* functionNameTable = (DWORD*)(RVA_to_FOA(fileBufferp, _image_export_directory->AddressOfNames) + (DWORD)fileBufferp);

	printf("***************&&&&&****************\n");
	for (DWORD i = 0; i < _image_export_directory->NumberOfFunctions; i++) {
		printf("函数地址(RVA):%08X	", *(functionAddressTable + i));

		// 通过函数地址表确定序列表
		int j = 0;
		for (; j < _image_export_directory->NumberOfNames; j++) {
			if (*(functionOrdinalsTable + j) == i) {
				printf("序数:%04d	", *(functionOrdinalsTable + j) + _image_export_directory->Base);

				// 通过函数序号表找函数名
				printf("%s(%08X(RAV)\n", (char*)(RVA_to_FOA(fileBufferp, *(functionNameTable + j)) + (DWORD)fileBufferp), *(functionNameTable + j));
				break;
			}
		}

		// 说明这个函数不是以名字导出的
		if (j == _image_export_directory->NumberOfNames) {
			printf("序数:%04d\n", i + _image_export_directory->Base);
		}
	}
}

int main(int argc, char* argv[])
{
	exportTable_print(filePath);
	return 0;
}

8.6 编写按名字、序号找导出函数地址信息

说明:昨天用到的GetProcAddress()系统函数,也可以按照这两种方式获取DLL中导出函数的地址,今天要编写的两个函数和GetProcAddress()有相同的功能,只是没有系统函数写的那么好那么复杂。区别是GetProcAddress()第一个参数HMODULE是DLL的ImageBase,即PE文件拉伸后的起始地址;自定义函数的第一个参数FileBuffer指针是PE文件未拉伸时的起始地址,即FileBuffer首地址。

8.6.1 按名字查找

  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
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
#pragma warning(disable : 4996)
#include <iostream>
#include <string.h>

typedef unsigned short WORD;
typedef unsigned int DWORD;
typedef unsigned char BYTE;

#define MZ 0x5A4D
#define PE 0x4550
#define IMAGE_SIZEOF_SHORT_NAME 8

const char* filePath = "libpython2.7.dll";  //选一个有导出表的PE文件
//const char* filePath = "InjectDll.dll";  //选一个有导出表的PE文件

//DOS头
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];
	DWORD e_lfanew;  //PE文件真正开始的偏移地址
};

//标准PE头
struct _IMAGE_FILE_HEADER {
	WORD Machine;  //文件运行平台
	WORD NumberOfSections;  //节数量
	DWORD TimeDateStamp;  //时间戳
	DWORD PointerToSymbolTable;
	DWORD NumberOfSymbols;
	WORD SizeOfOptionalHeader;  //可选PE头大小
	WORD Characteristics;  //特征值
};

//数据目录
struct _IMAGE_DATA_DIRECTORY {
	DWORD VirtualAddress;
	DWORD Size;
};

//可选PE头
struct _IMAGE_OPTIONAL_HEADER {
	WORD Magic;  //文件类型
	BYTE MajorLinkerVersion;
	BYTE MinorLinkerVersion;
	DWORD SizeOfCode;   //代码节文件对齐后的大小
	DWORD SizeOfInitializedData;  //初始化数据文件对齐后的大小
	DWORD SizeOfUninitializedData;  //未初始化数据文件对齐后大小
	DWORD AddressOfEntryPoint;  //程序入口点(偏移量)
	DWORD BaseOfCode;  //代码基址
	DWORD BaseOfData;  //数据基址
	DWORD ImageBase;   //内存镜像基址
	DWORD SectionAlignment;  //内存对齐粒度
	DWORD FileAlignment;  //文件对齐粒度
	WORD MajorOperatingSystemVersion;
	WORD MinorOperatingSystemVersion;
	WORD MajorImageVersion;
	WORD MinorImageVersion;
	WORD MajorSubsystemVersion;
	WORD MinorSubsystemVersion;
	DWORD Win32VersionValue;
	DWORD SizeOfImage;  //文件装入虚拟内存后大小
	DWORD SizeOfHeaders;  //DOS、NT头和节表大小
	DWORD CheckSum;  //校验和
	WORD Subsystem;
	WORD DllCharacteristics;
	DWORD SizeOfStackReserve;  //预留堆栈大小
	DWORD SizeOfStackCommit;  //实际分配堆栈大小
	DWORD SizeOfHeapReserve;  //预留堆大小
	DWORD SizeOfHeapCommit;  //实际分配堆大小
	DWORD LoaderFlags;
	DWORD NumberOfRvaAndSizes;  //目录项数目
	_IMAGE_DATA_DIRECTORY DataDirectory[16]; //数据目录
};

//NT头
struct _IMAGE_NT_HEADERS {
	DWORD Signature;  //PE签名
	_IMAGE_FILE_HEADER FileHeader;
	_IMAGE_OPTIONAL_HEADER OptionalHeader;
};

//节表
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;  //该节特征属性
};

//导出表
struct _IMAGE_EXPORT_DIRECTORY {
	DWORD Characteristics;  //未使用		
	DWORD TimeDateStamp;  //时间戳		
	WORD MajorVersion;  //未使用		
	WORD MinorVersion;	//未使用		
	DWORD Name;  //指向该导出表文件名字符串  *
	DWORD Base;  //导出函数起始序号  *
	DWORD NumberOfFunctions;  //所有导出函数的个数  *		
	DWORD NumberOfNames;  //以函数名字导出的函数个数  *
	DWORD AddressOfFunctions;  //导出函数地址表RVA  *					
	DWORD AddressOfNames;  //导出函数名称表RVA  *						
	DWORD AddressOfNameOrdinals;  //导出函数序号表RVA  *						
};

/*计算文件大小函数
参数:文件绝对路径
返回值:返回文件大小(单位字节)
*/
int compute_file_size(const char* filePath) {
	FILE* fp = fopen(filePath, "rb");
	if (!fp) {
		printf("打开文件失败");
		exit(0);
	}
	fseek(fp, 0, 2);
	int size = ftell(fp);
	//fseek(fp,0,0); 单纯计算文件大小,就不需要还原指针了
	fclose(fp);
	return size;
}

/*将文件读入FileBuffer函数
参数:文件绝对路径
返回值:FileBuffer起始地址
*/
char* to_FileBuffer(const char* filePath) {
	FILE* fp = fopen(filePath, "rb");
	if (!fp) {
		printf("打开文件失败");
		exit(0);
	}
	int size = compute_file_size(filePath);

	char* mp = (char*)malloc(sizeof(char) * size);  //分配内存空间
	if (!mp) {
		printf("分配空间失败");
		fclose(fp);
		exit(0);
	}

	int isSucceed = fread(mp, size, 1, fp);
	if (!isSucceed) {
		printf("读取数据失败");
		free(mp);
		fclose(fp);
		exit(0);
	}

	fclose(fp);
	return mp;
}

/*虚拟内存偏移地址->文件偏移地址函数
参数:FileBuffer起始地址,RVA地址值
返回值:RVA对应的FOA,返回0则表示非法RVA
*/
DWORD RVA_to_FOA(char* fileBufferp, DWORD RVA) {
	_IMAGE_DOS_HEADER* _image_dos_header = NULL;
	_IMAGE_FILE_HEADER* _image_file_header = NULL;
	_IMAGE_OPTIONAL_HEADER* _image_optional_header = NULL;
	_IMAGE_SECTION_HEADER* _image_section_header = NULL;

	_image_dos_header = (_IMAGE_DOS_HEADER*)fileBufferp;
	_image_file_header = (_IMAGE_FILE_HEADER*)(fileBufferp + _image_dos_header->e_lfanew + 4);
	_image_optional_header = (_IMAGE_OPTIONAL_HEADER*)((char*)_image_file_header + 20);
	_image_section_header = (_IMAGE_SECTION_HEADER*)((char*)_image_optional_header +
		_image_file_header->SizeOfOptionalHeader);

	if (RVA < _image_section_header->VirtualAddress)
		return 0;

	// 确定RVA 在那个节区中
	int i = 0;
	for (; i < _image_file_header->NumberOfSections; i++) {
		if (RVA >= _image_section_header->VirtualAddress
			&& RVA < _image_section_header->VirtualAddress + _image_section_header->Misc.VirtualSize) {
			break;
		}
		else {
			_image_section_header++;
		}
	}

	// 用来判断最终RVA值是否在节中的非空白区
	if (i == _image_file_header->NumberOfSections) {
		return 0;
	}

	// 确定RVA在该节区的位置
	DWORD mem_offset_from_section = RVA - _image_section_header->VirtualAddress;

	return _image_section_header->PointerToRawData + mem_offset_from_section;
}

/*按名字查找导出函数地址
参数:PE文件的FileBuffer起始地址,函数名字符串指针
返回值:该导出函数的RVA(如果想返回FOA也可以,代码都在下面)
*/
DWORD GetFunctionAddrByName(char* fileBufferp, const char* funcName) {
	_IMAGE_DOS_HEADER* _image_dos_header = NULL;
	_IMAGE_FILE_HEADER* _image_file_header = NULL;
	_IMAGE_OPTIONAL_HEADER* _image_optional_header = NULL;
	_IMAGE_DATA_DIRECTORY* _image_data_directory = NULL;
	_IMAGE_EXPORT_DIRECTORY* _image_export_directory = NULL;

	_image_dos_header = (_IMAGE_DOS_HEADER*)fileBufferp;
	_image_file_header = (_IMAGE_FILE_HEADER*)(fileBufferp + _image_dos_header->e_lfanew + 4);
	_image_optional_header = (_IMAGE_OPTIONAL_HEADER*)((char*)_image_file_header + 20);
	_image_data_directory = (_IMAGE_DATA_DIRECTORY*)_image_optional_header->DataDirectory;

	if (_image_data_directory->VirtualAddress == 0) {
		printf("此PE文件没有导出表,没有函数导出");
		exit(0);
	}

	_image_export_directory = (_IMAGE_EXPORT_DIRECTORY*)(RVA_to_FOA(fileBufferp, _image_data_directory->VirtualAddress) + (DWORD)fileBufferp);

	//先遍历函数名称表
	DWORD* functionNameTable = (DWORD*)(RVA_to_FOA(fileBufferp, _image_export_directory->AddressOfNames) + (DWORD)fileBufferp);
	DWORD index;
	for (index = 0; index < _image_export_directory->NumberOfNames; index++) {
		char* nameTemp = (char*)(RVA_to_FOA(fileBufferp, *functionNameTable) + (DWORD)fileBufferp);
		if (strcmp(nameTemp, funcName) == 0) {
			break;
		}
		functionNameTable++;
	}
	if (index == _image_export_directory->NumberOfNames) {
		printf("没有此导出函数\n");
		exit(0);
	}

	//再找函数序号表对应下标index中的值funcIndex
	WORD* functionOrdinalsTable = (WORD*)(RVA_to_FOA(fileBufferp, _image_export_directory->AddressOfNameOrdinals) + (DWORD)fileBufferp);
	WORD funcIndex = *(functionOrdinalsTable + index);

	//最后找函数地址表下标为funcIndex中的值
	DWORD* functionAddressTable = (DWORD*)(RVA_to_FOA(fileBufferp, _image_export_directory->AddressOfFunctions) + (DWORD)fileBufferp);

	//返回该导出函数RVA
	return *(functionAddressTable + funcIndex);
	//return RVA_to_FOA(fileBufferp,*(functionAddressTable + funcIndex));
}

int main(int argc, char* argv[])
{
	const char* funcName = "PyArg_ParseTuple"; //使用LordPE看有哪些导出函数
	char* fileBufferp = to_FileBuffer(filePath);
	printf("%08X", GetFunctionAddrByName(fileBufferp, funcName));

	return 0;
}

8.6.2 按序号查找函数

  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
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
#pragma warning(disable : 4996)
#include <iostream>
#include <string.h>

typedef unsigned short WORD;
typedef unsigned int DWORD;
typedef unsigned char BYTE;

#define MZ 0x5A4D
#define PE 0x4550
#define IMAGE_SIZEOF_SHORT_NAME 8

const char* filePath = "libpython2.7.dll";  //选一个有导出表的PE文件
//const char* filePath = "InjectDll.dll";  //选一个有导出表的PE文件

//DOS头
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];
	DWORD e_lfanew;  //PE文件真正开始的偏移地址
};

//标准PE头
struct _IMAGE_FILE_HEADER {
	WORD Machine;  //文件运行平台
	WORD NumberOfSections;  //节数量
	DWORD TimeDateStamp;  //时间戳
	DWORD PointerToSymbolTable;
	DWORD NumberOfSymbols;
	WORD SizeOfOptionalHeader;  //可选PE头大小
	WORD Characteristics;  //特征值
};

//数据目录
struct _IMAGE_DATA_DIRECTORY {
	DWORD VirtualAddress;
	DWORD Size;
};

//可选PE头
struct _IMAGE_OPTIONAL_HEADER {
	WORD Magic;  //文件类型
	BYTE MajorLinkerVersion;
	BYTE MinorLinkerVersion;
	DWORD SizeOfCode;   //代码节文件对齐后的大小
	DWORD SizeOfInitializedData;  //初始化数据文件对齐后的大小
	DWORD SizeOfUninitializedData;  //未初始化数据文件对齐后大小
	DWORD AddressOfEntryPoint;  //程序入口点(偏移量)
	DWORD BaseOfCode;  //代码基址
	DWORD BaseOfData;  //数据基址
	DWORD ImageBase;   //内存镜像基址
	DWORD SectionAlignment;  //内存对齐粒度
	DWORD FileAlignment;  //文件对齐粒度
	WORD MajorOperatingSystemVersion;
	WORD MinorOperatingSystemVersion;
	WORD MajorImageVersion;
	WORD MinorImageVersion;
	WORD MajorSubsystemVersion;
	WORD MinorSubsystemVersion;
	DWORD Win32VersionValue;
	DWORD SizeOfImage;  //文件装入虚拟内存后大小
	DWORD SizeOfHeaders;  //DOS、NT头和节表大小
	DWORD CheckSum;  //校验和
	WORD Subsystem;
	WORD DllCharacteristics;
	DWORD SizeOfStackReserve;  //预留堆栈大小
	DWORD SizeOfStackCommit;  //实际分配堆栈大小
	DWORD SizeOfHeapReserve;  //预留堆大小
	DWORD SizeOfHeapCommit;  //实际分配堆大小
	DWORD LoaderFlags;
	DWORD NumberOfRvaAndSizes;  //目录项数目
	_IMAGE_DATA_DIRECTORY DataDirectory[16]; //数据目录
};

//NT头
struct _IMAGE_NT_HEADERS {
	DWORD Signature;  //PE签名
	_IMAGE_FILE_HEADER FileHeader;
	_IMAGE_OPTIONAL_HEADER OptionalHeader;
};

//节表
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;  //该节特征属性
};

//导出表
struct _IMAGE_EXPORT_DIRECTORY {
	DWORD Characteristics;  //未使用		
	DWORD TimeDateStamp;  //时间戳		
	WORD MajorVersion;  //未使用		
	WORD MinorVersion;	//未使用		
	DWORD Name;  //指向该导出表文件名字符串  *
	DWORD Base;  //导出函数起始序号  *
	DWORD NumberOfFunctions;  //所有导出函数的个数  *		
	DWORD NumberOfNames;  //以函数名字导出的函数个数  *
	DWORD AddressOfFunctions;  //导出函数地址表RVA  *					
	DWORD AddressOfNames;  //导出函数名称表RVA  *						
	DWORD AddressOfNameOrdinals;  //导出函数序号表RVA  *						
};

/*计算文件大小函数
参数:文件绝对路径
返回值:返回文件大小(单位字节)
*/
int compute_file_size(const char* filePath) {
	FILE* fp = fopen(filePath, "rb");
	if (!fp) {
		printf("打开文件失败");
		exit(0);
	}
	fseek(fp, 0, 2);
	int size = ftell(fp);
	//fseek(fp,0,0); 单纯计算文件大小,就不需要还原指针了
	fclose(fp);
	return size;
}

/*将文件读入FileBuffer函数
参数:文件绝对路径
返回值:FileBuffer起始地址
*/
char* to_FileBuffer(const char* filePath) {
	FILE* fp = fopen(filePath, "rb");
	if (!fp) {
		printf("打开文件失败");
		exit(0);
	}
	int size = compute_file_size(filePath);

	char* mp = (char*)malloc(sizeof(char) * size);  //分配内存空间
	if (!mp) {
		printf("分配空间失败");
		fclose(fp);
		exit(0);
	}

	int isSucceed = fread(mp, size, 1, fp);
	if (!isSucceed) {
		printf("读取数据失败");
		free(mp);
		fclose(fp);
		exit(0);
	}

	fclose(fp);
	return mp;
}

/*虚拟内存偏移地址->文件偏移地址函数
参数:FileBuffer起始地址,RVA地址值
返回值:RVA对应的FOA,返回0则表示非法RVA
*/
DWORD RVA_to_FOA(char* fileBufferp, DWORD RVA) {
	_IMAGE_DOS_HEADER* _image_dos_header = NULL;
	_IMAGE_FILE_HEADER* _image_file_header = NULL;
	_IMAGE_OPTIONAL_HEADER* _image_optional_header = NULL;
	_IMAGE_SECTION_HEADER* _image_section_header = NULL;

	_image_dos_header = (_IMAGE_DOS_HEADER*)fileBufferp;
	_image_file_header = (_IMAGE_FILE_HEADER*)(fileBufferp + _image_dos_header->e_lfanew + 4);
	_image_optional_header = (_IMAGE_OPTIONAL_HEADER*)((char*)_image_file_header + 20);
	_image_section_header = (_IMAGE_SECTION_HEADER*)((char*)_image_optional_header +
		_image_file_header->SizeOfOptionalHeader);

	if (RVA < _image_section_header->VirtualAddress)
		return 0;

	// 确定RVA 在那个节区中
	int i = 0;
	for (; i < _image_file_header->NumberOfSections; i++) {
		if (RVA >= _image_section_header->VirtualAddress
			&& RVA < _image_section_header->VirtualAddress + _image_section_header->Misc.VirtualSize) {
			break;
		}
		else {
			_image_section_header++;
		}
	}

	// 用来判断最终RVA值是否在节中的非空白区
	if (i == _image_file_header->NumberOfSections) {
		return 0;
	}

	// 确定RVA在该节区的位置
	DWORD mem_offset_from_section = RVA - _image_section_header->VirtualAddress;

	return _image_section_header->PointerToRawData + mem_offset_from_section;
}

/*按序号查找导出函数地址信息函数
参数:PE文件的FileBuffer起始地址,函数名字符串指针
返回值:该导出函数的RVA(如果想返回FOA也可以,代码都在下面)
*/
DWORD GetFunctionAddrByOrdinals(char* fileBufferp, DWORD ordinal) {
	_IMAGE_DOS_HEADER* _image_dos_header = NULL;
	_IMAGE_FILE_HEADER* _image_file_header = NULL;
	_IMAGE_OPTIONAL_HEADER* _image_optional_header = NULL;
	_IMAGE_DATA_DIRECTORY* _image_data_directory = NULL;
	_IMAGE_EXPORT_DIRECTORY* _image_export_directory = NULL;

	_image_dos_header = (_IMAGE_DOS_HEADER*)fileBufferp;
	_image_file_header = (_IMAGE_FILE_HEADER*)(fileBufferp + _image_dos_header->e_lfanew + 4);
	_image_optional_header = (_IMAGE_OPTIONAL_HEADER*)((char*)_image_file_header + 20);
	_image_data_directory = (_IMAGE_DATA_DIRECTORY*)_image_optional_header->DataDirectory;
	if (_image_data_directory->VirtualAddress == 0) {
		printf("此PE文件没有导出表,没有函数导出\n");
		exit(0);
	}
	_image_export_directory = (_IMAGE_EXPORT_DIRECTORY*)(RVA_to_FOA(fileBufferp, _image_data_directory->VirtualAddress) + (DWORD)fileBufferp);

	//判断导出序号是否在范围内
	if (ordinal - _image_export_directory->Base > _image_export_directory->NumberOfFunctions - 1) {  //因为ordinal用的WORD类型,所以<0的情况不用考虑
		printf("导出序号不合法\n");
		exit(0);
	}

	//定位到函数地址表起始地址
	DWORD* functionAddressTable = (DWORD*)(RVA_to_FOA(fileBufferp, _image_export_directory->AddressOfFunctions) + (DWORD)fileBufferp);

	//返回该序号导出函数的RVA
	return *(functionAddressTable + (ordinal - _image_export_directory->Base));
	//return RVA_to_FOA(fileBufferp,*(functionAddressTable + (ordinal - _image_export_directory->Base)));
}

int main(int argc, char* argv[]) {
	char* fileBufferp = to_FileBuffer(filePath);

	printf("%08X", GetFunctionAddrByOrdinals(fileBufferp, 8)); //使用LordPE看有哪些导出序号

	return 0;
}

9. 重定位表

9.1 引入重定位表

程序加载过程:

  • 每一个可执行程序运行都有一个独立的4GB虚拟内存(32位),地址从0x00000000到0xFFFFFFFF,低2G为用户程序空间、高2G为操作系统内核空间;且一个PE文件有很多个PE文件组成。
  • 程序运行 –> 操作系统会给程序分4GB虚拟内存 –> 剩下的事情就像“拉伸贴图”一样(装载):

  • 先装载自身的.exe:如先把ipmsg.exe拉伸贴到ImageBase(0x00400000),分配空间大小为SizeOfImage(0x3D000)

但并不是所有文件的ImageBase都是0x400000,这个值是可以修改的:打开VC->右键你的项目->setting->选择Link->Category设置为Output->在Base address选项中就可以自定义ImageBase,之后这个程序编译以后,ImageBase就变了

  • 再装载需要用到的.dll:如把ws2help.dll拉伸贴到它的ImageBase(0x71A10000),分配空间大小为SizeofImage(0x8000),其他.dll也是如此。
  • 最后把EIP指向EOP(AddressOfEntryPoint),这个程序就可以执行了。

9.2 引发的问题

9.2.1 DLL装载地址冲突

一般情况下一个PE文件自身的.exe的ImageBase很少会和别的PE文件ImageBase发生冲突

但是.dll就不一定了:默认情况下DLL的ImageBase为0x10000000,所以如果一个程序要用的DLL没有合理的修改分配装载起始地址,就可能出现多个DLL的ImageBase都是同一个地址,造成装载冲突。

解决办法:如果一个DLL在装载时,发现其他DLL已经占用这块空间了,那么这个DLL会依据模块对齐粒度,往后找空余的空间存入,所以此时这个DLL的装载起始地址和它的ImageBase就不一致了(通俗讲叫"换位置")。

模块对齐粒度:和结构体的字节对齐一样:为了提高搜索的速度(时间换空间),模块间地址也是要对齐的。模块对齐粒度为0x10000,也就是64K.

9.2.2 编译后的绝对地址

一个.dll或.exePE文件中的全局变量,可能在编译完成后,全局变量的地址就写死了,即它生成硬编码时地址值为:ImageBase + RVA;相当于编译完成后,这个全局变量的绝对内存地址就写入PE文件了。如:这个文件中a(人为的全局变量)和%d(系统的全局变量),编译完后地址值都是写死的。

那假设如果这个PE文件在装载,没有按照预定的ImageBase装入,而是出现了问题一的情况–“换位置”,但由于这个地址值写死了,程序执行时任然会按这个绝对地址去寻找使用,就会出现找不到这个全局变量!

不光是全局变量,DLL中有对外提供的函数,函数的地址在编译完后函数地址也是写死的,而如果此时DLL装载时ImageBase变了,这些写死的函数地址也就用不了。

9.3 引入

所以如果一个PE文件可能会出现“换位置”的情况,那么就需要重定位表,来记录下来,有哪些地方的数据需要做修改、重新定位,保证“换位置”后,操作系统能正确找到这些数据。

比如编译后的绝对地址中:这个PE文件也没有按照ImageBase去装载,那么全局变量a的存储地址就会发生变化,但是由于硬编码已经生成:A1 30 4A 42 00,那么重定位表就会把30 4A 42 00这个数据的地址记下来,等到运行时操作系统会根据重定位表找到这个数据,做一个重定位修改,即把0x00424a30这个绝对地址做修改,进行重定位,保证全局变量a可以被准确找到(修改的操作全程有操作系统负责)。

为什么很多.exe不提供重定位表,.dll会提供?因为一个PE文件的.exe一般只有一个,且是最先装载,所以装载位置和ImageBase是一致的,也没人跟它抢;但是.dll有很多,就需要考虑装载的位置不是预期的位置,那么这个.dll就需要提供重定位表。

9.4 重定位表

9.4.1 重定位表位置

找到可选PE头中的数据目录项(结构体数组,有16个元素):找到第6个结构体,就是重定位表的数据目录。

根据重定位表数据目录的VirtualAddress,将RVA转成FOA,得到重定位表在ImageBuffer中的起始地址。

9.4.2 重定位表结构

重定位表的结构比较特殊:

1
2
3
4
5
struct _IMAGE_BASE_RELOCATION{
	DWORD VirtualAddress;
	DWORD SizeOfBlock;
    //一堆数据...(如果是最后一个这种结构,就只有RVA和SizeOfBlock,且都为0)
};

可以这么理解:一个重定位表中可能会有多个“块”,每个块的结构都是:4字节VirtualAddress,接着4字节SizeOfBlock,最后是一堆数据。

每个块的大小为SizeOfBlock(字节),最后一个块的RVA和SizeOfBlock都是0x00000000,表示重定位表结束。

  1. SizeOfBlock:表示每个块的大小,单位为字节。
  2. 具体项:
    • 一堆数据中,每两个字节叫一个具体项。
    • 一块中有多少个高4位为0011的具体项,就表示这个页当中有多少个地方需要做重定位修改。
    • 具体项占2字节,高4位表示类型:值为3,代表 低12位 + 该块的VirtualAddress == 地址处的数据需要修改做重定位;值为0,代表这一2字节数据用来做数据对齐(填充用的),可以不用修改。
    • 故我们只用关注高4位值为3的具体项就可以了。具体项的低12位表示要修改的地方相对于所在页的偏移地址(RVA)。
    • 一块中一共有多少个具体项:用(此块的SizeofBlock - 4 - 4 )/ 2
  3. VirtualAddress:
    • 宽度为4字节
    • 为什么需要这个值:假如现在有10000个地方需要重定位,又因为4GB虚拟内存地址需要32位二进制数才能表示的下,即4字节,所以10000个地方要修改,就需要记录下这10000个地方的地址,一个地址4字节,共需大小10000 * 4 = 40000字节空间,按照这种方法记录,重定位表就需要40000 + 8字节,太大了!!
    • 现在如果把一个PE文件分页(操作系统确实会这么干),内存中一个页的大小为0x1000字节,相当于把文件分成了一小页一小页的。
    • 那么一页中如果有要重定位的地方,重定位表就会给这个页安排一块,这个块的VirtualAddress存储此页的偏移起始地址(RVA),由于一页的大小只有0x1000字节(4096),每个字节内存用一个地址表示,即用12位二进制数就可以表示的下4096个地址(比上面的32位二进制数要少的多!),前面学过内存对齐的概念,所以把这个值用16位存放,故具体项占16位,多出来的高4位可以用来表示其他的含义,低12位表示需要修改的地方相对于所在页的偏移地址!
    • 综上所述:一页中如果有要重定位的地方,重定位表给此页安排一块(一块对应一页);此块的VirtualAddress存储此页的起始地址;具体项占16位,高4位表示类型,低12位表示要修改的地方相对于所在页的偏移地址
    • 每一个数据项低12位的值 + VirtualAddress才是真正需要修复重定位的数据的RVA

9.5 页、块、节的关系

  • 一个PE文件(可执行程序)运行时,装入虚拟内存中会对程序进行分页。
  • 重定位表会分块。
  • 一个程序有不同的节,跟分页和分块没有任何关系。

打开一个有重定位表的PE文件,查看重定位表,会发现这个重定位表分了很多块,第175块中记录的是偏移地址为0xAF000的页中要修改重定位的地方,这些要修改的地方在.text节中;第176块中记录的是偏移地址为0xB000的页中要修改重定位的地方,这些要修改的地方在.data节中。

9.6 为什么学重定位表

  1. 破解方面
    • 加密壳:如果想对一个程序加加密壳之前,需要先将程序的各种表—导出表,导入表,重定位表等移动到新增的节当中,移走后对剩下的数据加密(所有的头和节表不能加密!DOS头,NT头,节表)。为什么呢?因为我们知道这些表其实分散在程序的某个节当中,如果直接对整个程序的数据加密,那么最后操作系统也找不到各种表了,无法加载用到的各种DLL了(比如找不到导入表,那么操作系统进行装载时,无法知道有哪些DLL要装到虚拟内存中),程序就无法执行;所以就需要对各种表非常熟悉,才知道从哪里移。
  2. 反反调试
    • 反调试:有些游戏公司为了避免别人调试,会在驱动层(0环)把很多函数加上钩子(hook),比如说有一个函数叫openprocess(),用来打开进程。而如果想调试任何进程,都需要先打开进程,像使用OD–>点击附加,本质上就是使用了0环的openprocess()这个函数,那么由于游戏公司给这个函数加上了钩子,在调用这个函数时,游戏就会判断这个函数的参数是否是游戏进程本身,是的话就返回一个0(NULL),不让别人看到它。所以使用OD点击附加–>找进程打开时,会发现游戏的进程根本不在这里面。
    • 反反调试:即过游戏驱动,一个比较常用的方式叫—-内核重载,即把内核程序(0环)kernel.exe拉伸,把拉伸后的数据往内存中复制一份,只不过这个程序是0环的程序,用的是0环的语法,不能像我们平时在3环写程序用的语法。但是现在不允许把拉伸后的数据往内存复制,因为此时原来的kernel.exe已经把位置占着了,所以只能在它的后面一个模块中复制,这种情况不就和上文中的问题一一样,“换位置”了。那么这个程序中的所有绝对地址都不能用了,都必须要自己根据重定位表把要修正的数据修正重定位。那么我们再想用程序就不用它提供的加过钩子的内核,而使用我们自己复制的这一份。

9.7 打印重定位表信息

  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
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
#pragma warning(disable : 4996)
#include <iostream>
#include <string.h>

typedef unsigned short WORD;
typedef unsigned int DWORD;
typedef unsigned char BYTE;

#define MZ 0x5A4D
#define PE 0x4550
#define IMAGE_SIZEOF_SHORT_NAME 8

//const char* filePath = "libpython2.7.dll";  //选一个有导出表的PE文件
const char* filePath = "Dll_project.dll";  //选一个有导出表的PE文件

//DOS头
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];
	DWORD e_lfanew;  //PE文件真正开始的偏移地址
};

//标准PE头
struct _IMAGE_FILE_HEADER {
	WORD Machine;  //文件运行平台
	WORD NumberOfSections;  //节数量
	DWORD TimeDateStamp;  //时间戳
	DWORD PointerToSymbolTable;
	DWORD NumberOfSymbols;
	WORD SizeOfOptionalHeader;  //可选PE头大小
	WORD Characteristics;  //特征值
};

//数据目录
struct _IMAGE_DATA_DIRECTORY {
	DWORD VirtualAddress;
	DWORD Size;
};

//可选PE头
struct _IMAGE_OPTIONAL_HEADER {
	WORD Magic;  //文件类型
	BYTE MajorLinkerVersion;
	BYTE MinorLinkerVersion;
	DWORD SizeOfCode;   //代码节文件对齐后的大小
	DWORD SizeOfInitializedData;  //初始化数据文件对齐后的大小
	DWORD SizeOfUninitializedData;  //未初始化数据文件对齐后大小
	DWORD AddressOfEntryPoint;  //程序入口点(偏移量)
	DWORD BaseOfCode;  //代码基址
	DWORD BaseOfData;  //数据基址
	DWORD ImageBase;   //内存镜像基址
	DWORD SectionAlignment;  //内存对齐粒度
	DWORD FileAlignment;  //文件对齐粒度
	WORD MajorOperatingSystemVersion;
	WORD MinorOperatingSystemVersion;
	WORD MajorImageVersion;
	WORD MinorImageVersion;
	WORD MajorSubsystemVersion;
	WORD MinorSubsystemVersion;
	DWORD Win32VersionValue;
	DWORD SizeOfImage;  //文件装入虚拟内存后大小
	DWORD SizeOfHeaders;  //DOS、NT头和节表大小
	DWORD CheckSum;  //校验和
	WORD Subsystem;
	WORD DllCharacteristics;
	DWORD SizeOfStackReserve;  //预留堆栈大小
	DWORD SizeOfStackCommit;  //实际分配堆栈大小
	DWORD SizeOfHeapReserve;  //预留堆大小
	DWORD SizeOfHeapCommit;  //实际分配堆大小
	DWORD LoaderFlags;
	DWORD NumberOfRvaAndSizes;  //目录项数目
	_IMAGE_DATA_DIRECTORY DataDirectory[16]; //数据目录
};

//NT头
struct _IMAGE_NT_HEADERS {
	DWORD Signature;  //PE签名
	_IMAGE_FILE_HEADER FileHeader;
	_IMAGE_OPTIONAL_HEADER OptionalHeader;
};

//节表
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;  //该节特征属性
};

//重定位表
struct _IMAGE_BASE_RELOCATION {
	DWORD VirtualAddress;
	DWORD SizeOfBlock;
	//具体项
};

/*计算文件大小函数
参数:文件绝对路径
返回值:返回文件大小(单位字节)
*/
int compute_file_size(const char* filePath) {
	FILE* fp = fopen(filePath, "rb");
	if (!fp) {
		printf("打开文件失败");
		exit(0);
	}
	fseek(fp, 0, 2);
	int size = ftell(fp);
	//fseek(fp,0,0); 单纯计算文件大小,就不需要还原指针了
	fclose(fp);
	return size;
}

/*将文件读入FileBuffer函数
参数:文件绝对路径
返回值:FileBuffer起始地址
*/
char* to_FileBuffer(const char* filePath) {
	FILE* fp = fopen(filePath, "rb");
	if (!fp) {
		printf("打开文件失败");
		exit(0);
	}
	int size = compute_file_size(filePath);

	char* mp = (char*)malloc(sizeof(char) * size);  //分配内存空间
	if (!mp) {
		printf("分配空间失败");
		fclose(fp);
		exit(0);
	}

	int isSucceed = fread(mp, size, 1, fp);
	if (!isSucceed) {
		printf("读取数据失败");
		free(mp);
		fclose(fp);
		exit(0);
	}

	fclose(fp);
	return mp;
}

/*虚拟内存偏移地址->文件偏移地址函数
参数:FileBuffer起始地址,RVA地址值
返回值:RVA对应的FOA,返回0则表示非法RVA
*/
DWORD RVA_to_FOA(char* fileBufferp, DWORD RVA) {
	_IMAGE_DOS_HEADER* _image_dos_header = NULL;
	_IMAGE_FILE_HEADER* _image_file_header = NULL;
	_IMAGE_OPTIONAL_HEADER* _image_optional_header = NULL;
	_IMAGE_SECTION_HEADER* _image_section_header = NULL;

	_image_dos_header = (_IMAGE_DOS_HEADER*)fileBufferp;
	_image_file_header = (_IMAGE_FILE_HEADER*)(fileBufferp + _image_dos_header->e_lfanew + 4);
	_image_optional_header = (_IMAGE_OPTIONAL_HEADER*)((char*)_image_file_header + 20);
	_image_section_header = (_IMAGE_SECTION_HEADER*)((char*)_image_optional_header +
		_image_file_header->SizeOfOptionalHeader);

	if (RVA < _image_section_header->VirtualAddress)
		return 0;

	// 确定RVA 在那个节区中
	int i = 0;
	for (; i < _image_file_header->NumberOfSections; i++) {
		if (RVA >= _image_section_header->VirtualAddress
			&& RVA < _image_section_header->VirtualAddress + _image_section_header->Misc.VirtualSize) {
			break;
		}
		else {
			_image_section_header++;
		}
	}

	// 用来判断最终RVA值是否在节中的非空白区
	if (i == _image_file_header->NumberOfSections) {
		return 0;
	}

	// 确定RVA在该节区的位置
	DWORD mem_offset_from_section = RVA - _image_section_header->VirtualAddress;

	return _image_section_header->PointerToRawData + mem_offset_from_section;
}

/*打印重定位表函数
参数:指定可执行文件绝对路径
返回值:无
*/
void relocationTable_print(const char* filePath) {
	char* fileBufferp = to_FileBuffer(filePath);

	_IMAGE_DOS_HEADER* _image_dos_header = NULL;
	_IMAGE_FILE_HEADER* _image_file_header = NULL;
	_IMAGE_OPTIONAL_HEADER* _image_optional_header = NULL;
	_IMAGE_DATA_DIRECTORY* _image_data_directory = NULL;
	_IMAGE_BASE_RELOCATION* _image_base_relocation = NULL;

	_image_dos_header = (_IMAGE_DOS_HEADER*)fileBufferp;
	_image_file_header = (_IMAGE_FILE_HEADER*)(fileBufferp + _image_dos_header->e_lfanew + 4);
	_image_optional_header = (_IMAGE_OPTIONAL_HEADER*)((char*)_image_file_header + 20);
	//指向数据目录结构体数组的第6个结构体
	_image_data_directory = (_IMAGE_DATA_DIRECTORY*)(_image_optional_header->DataDirectory + 5);

	//判断是否有重定位表
	if (_image_data_directory->VirtualAddress == 0) {
		printf("此PE文件没有重定位表\n");
		exit(0);
	}

	//找到重定位表
	_image_base_relocation = (_IMAGE_BASE_RELOCATION*)(RVA_to_FOA(fileBufferp, _image_data_directory->VirtualAddress) + fileBufferp);

	//打印重定位表
	printf("***************重定位表****************\n");
	for (int i = 0; _image_base_relocation->VirtualAddress != 0; i++) {
		printf("*********第%d块*********\n", i + 1);
		printf("VirtualAddress:%08X   SizeOfBlock:%08X\n", _image_base_relocation->VirtualAddress, _image_base_relocation->SizeOfBlock);
		printf("类型\tRVA\n");
		for (DWORD j = 0; j < (_image_base_relocation->SizeOfBlock - 8) / 2; j++) {
			//这里使用右移12位的方式打印2字节宽度的高4位
			//if ((*((WORD*)((char*)_image_base_relocation + 8 + j * 2)) >> 12) == 0) {
			//	printf("%d\n", *((WORD*)((char*)_image_base_relocation + 8 + j * 2)) >> 12);
			//	//continue;
			//}
			
			//这里用与运算把高4位变成0后输出2字节宽度的值,即为低12位的值(别忘了还要加VirtualAddress)
			printf("%d\t%08X\n", *((WORD*)((char*)_image_base_relocation + 8 + j * 2)) >> 12, _image_base_relocation->VirtualAddress + (*((WORD*)((BYTE*)_image_base_relocation + 8 + j * 2)) & 0x0FFF));
		}
		_image_base_relocation = (_IMAGE_BASE_RELOCATION*)((char*)_image_base_relocation + _image_base_relocation->SizeOfBlock);
	}
}

int main(int argc, char* argv[]) {
	// 选一个有重定位表的PE文件(控制台没法打印太多的数据,所以选一个重定位表小一点的PE文件)
	relocationTable_print(filePath);

	return 0;
}

10. 移动导出表和重定位表

为什么要移动各种表:

  • 一个PE文件中有很多节,有的用来存储代码、有的存储数据,而PE文件的各种表也都分散存储在这些节当中,所以各种表的信息与程序的代码和数据都混在一起了,如果直接对整个程序进行加密,那系统在初始化程序时就会出问题!(比如:在程序启动的时候,这些表的数据被加密了,系统就无法根据这些表将用到的DLL中的函数地址存储到IAT表中),所以对程序加密或破解之前,会先移动各种表到程序新增的节当中,再对剩下的数据(DOS头、NT头等所有头和节表都不能加密,不然操作系统根本就不认这是一个Windows可执行程序了,程序就启动不起来了)进行加密。学会移动各种表,是对程序加密/破解的基础。

10.1 移动导出表

  1. 在PE文件中新增一个节,大小可以按照下面的长度求和对齐计算出来,但其实可以估算0x1000字节足够了。

    新增节别忘了修改PE头当中的一些字段:NumberOfSections、SizeOfImage、重新开辟一个FileBuffer、新增节表、修改节表中的字段(Name、Misc.VirtualSize、VirtualAddress、SizeOfRawData、PointerToRawData、Characteristics)。

  2. 复制AddressOfFunctions指向的函数地址表到新节中(步骤2、3、4别忘了RVA转FOA),长度为4 * NumberOfFunctions字节。
  3. 接着复制AddressOfNameOrdinals指向的函数序号表到新节中,长度为2 * NumberOfNames字节。
  4. 复制AddressOfNames指向的函数名称表到新节中,长度为4 * NumberOfNames字节。
  5. 还要复制函数名称表中元素所指向的函数名称字符串到新节中,长度可以遍历函数名称表,用strlen()函数依次求出每个字符串长度,再求和;每复制一个字符串到新节中,就修改对应的函数名称表中的地址值(而且要注意:函数名称表中存的是RVA,所以把字符串复制到新节后还需要把FOA转成RVA,再存入)。

    为什么需要移动字符串?因为如果光移动函数名称表,对剩下的数据加密(字符串也包含其中),那么当需要根据函数名去调用导出函数时,只能根据函数名称表查到名称字符串所在地址,但是字符串被加密,就无法匹配了

  6. 复制导出表IMAGE_EXPORT_DIRECTORY到新节中,大小固定40字节。
  7. 修改导出表中的成员值,指向三个子表在新节中的位置(下面三个值都是RVA,所以要根据此时三张子表在新节中的FOA转成RVA)。
    • AddressOfFunctions
    • AddressOfNameOrdinals
    • AddressOfNames
  8. 最后修复导出表数据目录中的VirtualAddress,指向导出表IMAGE_EXPORT_DIRECTORY在新节中的位置(也要FOA转RVA)。

代码编写:

  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
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
#pragma warning(disable : 4996)
#include <iostream>
#include <string.h>

typedef unsigned short WORD;
typedef unsigned int DWORD;
typedef unsigned char BYTE;

#define MZ 0x5A4D
#define PE 0x4550
#define IMAGE_SIZEOF_SHORT_NAME 8

//const char* filePath = "libpython2.7.dll";  //选一个有导出表的PE文件
const char* filePath = "OllyDBG.EXE";  //选一个有导出表的PE文件
const char* storagePath = "OllyDBG_new.EXE";

//DOS头
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];
	DWORD e_lfanew;  //PE文件真正开始的偏移地址
};

//标准PE头
struct _IMAGE_FILE_HEADER {
	WORD Machine;  //文件运行平台
	WORD NumberOfSections;  //节数量
	DWORD TimeDateStamp;  //时间戳
	DWORD PointerToSymbolTable;
	DWORD NumberOfSymbols;
	WORD SizeOfOptionalHeader;  //可选PE头大小
	WORD Characteristics;  //特征值
};

//数据目录
struct _IMAGE_DATA_DIRECTORY {
	DWORD VirtualAddress;
	DWORD Size;
};

//可选PE头
struct _IMAGE_OPTIONAL_HEADER {
	WORD Magic;  //文件类型
	BYTE MajorLinkerVersion;
	BYTE MinorLinkerVersion;
	DWORD SizeOfCode;   //代码节文件对齐后的大小
	DWORD SizeOfInitializedData;  //初始化数据文件对齐后的大小
	DWORD SizeOfUninitializedData;  //未初始化数据文件对齐后大小
	DWORD AddressOfEntryPoint;  //程序入口点(偏移量)
	DWORD BaseOfCode;  //代码基址
	DWORD BaseOfData;  //数据基址
	DWORD ImageBase;   //内存镜像基址
	DWORD SectionAlignment;  //内存对齐粒度
	DWORD FileAlignment;  //文件对齐粒度
	WORD MajorOperatingSystemVersion;
	WORD MinorOperatingSystemVersion;
	WORD MajorImageVersion;
	WORD MinorImageVersion;
	WORD MajorSubsystemVersion;
	WORD MinorSubsystemVersion;
	DWORD Win32VersionValue;
	DWORD SizeOfImage;  //文件装入虚拟内存后大小
	DWORD SizeOfHeaders;  //DOS、NT头和节表大小
	DWORD CheckSum;  //校验和
	WORD Subsystem;
	WORD DllCharacteristics;
	DWORD SizeOfStackReserve;  //预留堆栈大小
	DWORD SizeOfStackCommit;  //实际分配堆栈大小
	DWORD SizeOfHeapReserve;  //预留堆大小
	DWORD SizeOfHeapCommit;  //实际分配堆大小
	DWORD LoaderFlags;
	DWORD NumberOfRvaAndSizes;  //目录项数目
	_IMAGE_DATA_DIRECTORY DataDirectory[16]; //数据目录
};

//NT头
struct _IMAGE_NT_HEADERS {
	DWORD Signature;  //PE签名
	_IMAGE_FILE_HEADER FileHeader;
	_IMAGE_OPTIONAL_HEADER OptionalHeader;
};

//节表
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;  //该节特征属性
};

//导出表
struct _IMAGE_EXPORT_DIRECTORY {
	DWORD Characteristics;  //未使用		
	DWORD TimeDateStamp;  //时间戳		
	WORD MajorVersion;  //未使用		
	WORD MinorVersion;	//未使用		
	DWORD Name;  //指向该导出表文件名字符串  *
	DWORD Base;  //导出函数起始序号  *
	DWORD NumberOfFunctions;  //所有导出函数的个数  *		
	DWORD NumberOfNames;  //以函数名字导出的函数个数  *
	DWORD AddressOfFunctions;  //导出函数地址表RVA  *					
	DWORD AddressOfNames;  //导出函数名称表RVA  *						
	DWORD AddressOfNameOrdinals;  //导出函数序号表RVA  *						
};

//重定位表
struct _IMAGE_BASE_RELOCATION {
	DWORD VirtualAddress;
	DWORD SizeOfBlock;
	//具体项
};

/*计算文件大小函数
参数:文件绝对路径
返回值:返回文件大小(单位字节)
*/
int compute_file_size(const char* filePath) {
	FILE* fp = fopen(filePath, "rb");
	if (!fp) {
		printf("打开文件失败");
		exit(0);
	}
	fseek(fp, 0, 2);
	int size = ftell(fp);
	//fseek(fp,0,0); 单纯计算文件大小,就不需要还原指针了
	fclose(fp);
	return size;
}

/*NewBuffer存盘函数
参数:需要写出数据的内存首地址,保存绝对路径,写出的数据大小(单位:字节)
返回值:成功返回1,失败返回0
*/
int save_to_disk(char* newBufferp, const char* storagePath, DWORD size) {
	FILE* fp = fopen(storagePath, "wb");
	if (!fp) {
		printf("打开文件失败");
		return 0;
	}
	int isSucceed = fwrite(newBufferp, size, 1, fp);
	if (!isSucceed) {
		free(newBufferp);
		fclose(fp);
		return 0;
	}
	fclose(fp);
	return 1;
}

/*将文件读入FileBuffer函数
参数:文件绝对路径
返回值:FileBuffer起始地址
*/
char* to_FileBuffer(const char* filePath) {
	FILE* fp = fopen(filePath, "rb");
	if (!fp) {
		printf("打开文件失败");
		exit(0);
	}
	int size = compute_file_size(filePath);

	char* mp = (char*)malloc(sizeof(char) * size);  //分配内存空间
	if (!mp) {
		printf("分配空间失败");
		fclose(fp);
		exit(0);
	}

	int isSucceed = fread(mp, size, 1, fp);
	if (!isSucceed) {
		printf("读取数据失败");
		free(mp);
		fclose(fp);
		exit(0);
	}

	fclose(fp);
	return mp;
}

/*计算NewBuffer大小函数
参数:NewBuffer起始地址
返回值:unsigned int类型(单位:字节)
*/
DWORD compute_NewBuffer_size(char* newBufferp) {
	_IMAGE_DOS_HEADER* _image_dos_header = NULL;
	_IMAGE_FILE_HEADER* _image_file_header = NULL;
	_IMAGE_OPTIONAL_HEADER* _image_optional_header = NULL;
	_IMAGE_SECTION_HEADER* _image_section_header = NULL;

	_image_dos_header = (_IMAGE_DOS_HEADER*)newBufferp;
	_image_file_header = (_IMAGE_FILE_HEADER*)(newBufferp + _image_dos_header->e_lfanew + 4);
	_image_optional_header = (_IMAGE_OPTIONAL_HEADER*)((char*)_image_file_header + 20);
	_image_section_header = (_IMAGE_SECTION_HEADER*)((char*)_image_optional_header + _image_file_header->SizeOfOptionalHeader);

	_IMAGE_SECTION_HEADER* last_image_section_header = _image_section_header + _image_file_header->NumberOfSections - 1;
	DWORD sizeofNewBuffer = last_image_section_header->PointerToRawData + last_image_section_header->SizeOfRawData;
	return sizeofNewBuffer;
}

/*虚拟内存偏移地址->文件偏移地址函数
参数:FileBuffer起始地址,RVA地址值
返回值:RVA对应的FOA,返回0则表示非法RVA
*/
DWORD RVA_to_FOA(char* fileBufferp, DWORD RVA) {
	_IMAGE_DOS_HEADER* _image_dos_header = NULL;
	_IMAGE_FILE_HEADER* _image_file_header = NULL;
	_IMAGE_OPTIONAL_HEADER* _image_optional_header = NULL;
	_IMAGE_SECTION_HEADER* _image_section_header = NULL;

	_image_dos_header = (_IMAGE_DOS_HEADER*)fileBufferp;
	_image_file_header = (_IMAGE_FILE_HEADER*)(fileBufferp + _image_dos_header->e_lfanew + 4);
	_image_optional_header = (_IMAGE_OPTIONAL_HEADER*)((char*)_image_file_header + 20);
	_image_section_header = (_IMAGE_SECTION_HEADER*)((char*)_image_optional_header +
		_image_file_header->SizeOfOptionalHeader);

	if (RVA < _image_section_header->VirtualAddress)
		return 0;

	// 确定RVA 在那个节区中
	int i = 0;
	for (; i < _image_file_header->NumberOfSections; i++) {
		if (RVA >= _image_section_header->VirtualAddress
			&& RVA < _image_section_header->VirtualAddress + _image_section_header->Misc.VirtualSize) {
			break;
		}
		else {
			_image_section_header++;
		}
	}

	// 用来判断最终RVA值是否在节中的非空白区
	if (i == _image_file_header->NumberOfSections) {
		return 0;
	}

	// 确定RVA在该节区的位置
	DWORD mem_offset_from_section = RVA - _image_section_header->VirtualAddress;

	return _image_section_header->PointerToRawData + mem_offset_from_section;
}

/*文件偏移地址函数->虚拟内存偏移地址
参数:FileBuffer起始地址,FOA地址值
返回值:FOA对应的RVA,返回0则表示非法FOA
*/
DWORD FOA_to_RVA(char* fileBufferp, DWORD FOA) {
	_IMAGE_DOS_HEADER* _image_dos_header = NULL;
	_IMAGE_FILE_HEADER* _image_file_header = NULL;
	_IMAGE_OPTIONAL_HEADER* _image_optional_header = NULL;
	_IMAGE_SECTION_HEADER* _image_section_header = NULL;

	_image_dos_header = (_IMAGE_DOS_HEADER*)fileBufferp;
	_image_file_header = (_IMAGE_FILE_HEADER*)(fileBufferp + _image_dos_header->e_lfanew + 4);
	_image_optional_header = (_IMAGE_OPTIONAL_HEADER*)((char*)_image_file_header + 20);
	_image_section_header = (_IMAGE_SECTION_HEADER*)((char*)_image_optional_header +
		_image_file_header->SizeOfOptionalHeader);

	bool flag = 0;//用来判断最终FOA值是否在节中的非空白区
	if (FOA < _image_section_header->PointerToRawData)
		return FOA;
	for (DWORD i = 0; i < _image_file_header->NumberOfSections; i++) {
		DWORD tempSize = (_image_section_header->Misc.VirtualSize < _image_section_header->SizeOfRawData ? _image_section_header->Misc.VirtualSize : _image_section_header->SizeOfRawData);
		if (FOA >= _image_section_header->PointerToRawData && FOA < _image_section_header->PointerToRawData + tempSize) {
			flag = 1;
			break;
		}
		_image_section_header++;
	}

	if (!flag)
		return 0;
	DWORD file_offset_from_section = FOA - _image_section_header->PointerToRawData;
	return _image_section_header->VirtualAddress + file_offset_from_section;
}

/*在文件最后新增节函数(直接在FileBuffer中操作)
参数:要新增节的可执行文件的FileBuffer起始地址
返回值:新增节后的NewFileBuffer内存的起始地址
*/
char* add_one_section_inFileBuffer(char* fileBufferp) {
	_IMAGE_DOS_HEADER* _image_dos_header = NULL;
	_IMAGE_FILE_HEADER* _image_file_header = NULL;
	_IMAGE_OPTIONAL_HEADER* _image_optional_header = NULL;
	_IMAGE_SECTION_HEADER* _image_section_header = NULL;

	//定义原来的FileBuffer几个指针(因为要用到几个值用于创建新的NewFileBuffer内存)
	_image_dos_header = (_IMAGE_DOS_HEADER*)fileBufferp;
	_image_file_header = (_IMAGE_FILE_HEADER*)(fileBufferp + _image_dos_header->e_lfanew + 4);
	_image_optional_header = (_IMAGE_OPTIONAL_HEADER*)((char*)_image_file_header + 20);
	_image_section_header = (_IMAGE_SECTION_HEADER*)((char*)_image_optional_header +
		_image_file_header->SizeOfOptionalHeader);

	//先计算添加新节后的NewFileBuffer的大小,并初始化内存
	//DWORD expand_value = 0x3000; //这里0x3000字节足够了
	DWORD expand_value = 0xF000; //如果对于重定位表很大的PE文件,要视情况而定
	DWORD fileBufferSize = (_image_section_header + _image_file_header->NumberOfSections - 1)->PointerToRawData + (_image_section_header + _image_file_header->NumberOfSections - 1)->SizeOfRawData;
	char* newFileBufferp = (char*)malloc(fileBufferSize + expand_value);

	memset(newFileBufferp, 0, fileBufferSize + expand_value);
	memcpy(newFileBufferp, fileBufferp, fileBufferSize);

	_image_dos_header = (_IMAGE_DOS_HEADER*)newFileBufferp;
	_image_file_header = (_IMAGE_FILE_HEADER*)(newFileBufferp + _image_dos_header->e_lfanew + 4);
	_image_optional_header = (_IMAGE_OPTIONAL_HEADER*)((char*)_image_file_header + 20);
	_image_section_header = (_IMAGE_SECTION_HEADER*)((char*)_image_optional_header + _image_file_header->SizeOfOptionalHeader);

	//判断节表末尾是否有空间新增节表
	if (_image_optional_header->SizeOfHeaders - _image_dos_header->e_lfanew - 4 - 20 - _image_file_header->SizeOfOptionalHeader -
		_image_file_header->NumberOfSections * 40 < 80) {
		printf("空间不足,无法新增节表\n");
		exit(0);
	}
	for (int i = 0; i < 80; i++) {
		if (*((char*)(_image_section_header + _image_file_header->NumberOfSections) + i) != 0) {
			printf("节表后80字节数据不全为0x00,有文件数据,无法新增节表\n");
			exit(0);
		}
	}

	//新增节
	DWORD original_SizeOfImage = _image_optional_header->SizeOfImage; //记录下原来的SizeOfImage
	_image_optional_header->SizeOfImage += expand_value;

	//新增节表
	_IMAGE_SECTION_HEADER* _image_section_header_new = (_IMAGE_SECTION_HEADER*)(char*)_image_section_header + _image_file_header->NumberOfSections;
	memcpy(_image_section_header_new, _image_section_header, 40);
	_image_file_header->NumberOfSections++;

	//修改新增节表信息
	char* name = (char*)_image_section_header_new->Name;
	const char* newName = ".newsec";
	strncpy(name, newName, IMAGE_SIZEOF_SHORT_NAME);
	_image_section_header_new->Misc.VirtualSize = expand_value;
	_image_section_header_new->VirtualAddress = original_SizeOfImage;
	_image_section_header_new->SizeOfRawData = expand_value;
	_image_section_header_new->PointerToRawData = fileBufferSize;

	_image_section_header_new->Characteristics = 0x00000000;
	for (int i = 0; i < _image_file_header->NumberOfSections; i++) {
		_image_section_header_new->Characteristics = _image_section_header_new->Characteristics | (_image_section_header + i)->Characteristics;
	}

	return newFileBufferp;
}

/*将PE文件导出表移到新增节中函数
参数:PE文件的FileBuffer首地址
返回值:新增节并且移动导出表后的NewFileBuffer首地址
*/
char* move_ExportTable(char* fileBufferp) {
	//新增节
	char* newFileBufferp = add_one_section_inFileBuffer(fileBufferp);

	_IMAGE_DOS_HEADER* _image_dos_header = NULL;
	_IMAGE_FILE_HEADER* _image_file_header = NULL;
	_IMAGE_OPTIONAL_HEADER* _image_optional_header = NULL;
	_IMAGE_DATA_DIRECTORY* _image_data_directory = NULL;
	_IMAGE_SECTION_HEADER* _image_section_header = NULL;
	_IMAGE_EXPORT_DIRECTORY* _image_export_directory = NULL;

	_image_dos_header = (_IMAGE_DOS_HEADER*)newFileBufferp;
	_image_file_header = (_IMAGE_FILE_HEADER*)(newFileBufferp + _image_dos_header->e_lfanew + 4);
	_image_optional_header = (_IMAGE_OPTIONAL_HEADER*)((char*)_image_file_header + 20);
	_image_section_header = (_IMAGE_SECTION_HEADER*)((char*)_image_optional_header + _image_file_header->SizeOfOptionalHeader);
	_image_data_directory = (_IMAGE_DATA_DIRECTORY*)_image_optional_header->DataDirectory;

	if (_image_data_directory->VirtualAddress == 0) {
		printf("此PE文件没有导出表\n");
		exit(0);
	}

	_image_export_directory = (_IMAGE_EXPORT_DIRECTORY*)(RVA_to_FOA(newFileBufferp, _image_data_directory->VirtualAddress) + newFileBufferp);

	//复制函数地址表到新节中
	char* functionAddressTablep = RVA_to_FOA(newFileBufferp, _image_export_directory->AddressOfFunctions) + newFileBufferp;
	char* newSectionp = (_image_section_header + _image_file_header->NumberOfSections - 1)->PointerToRawData + newFileBufferp;  //newSectionp指向:向新节中复制的位置
	char* funcAddressTable_in_newSectionp = newSectionp;//把函数地址表在新节中的首地址记下来,后面要用
	memcpy(newSectionp, functionAddressTablep, _image_export_directory->NumberOfFunctions * 4);

	//复制函数序号表到新节中
	char* functionOrdinalTablep = RVA_to_FOA(newFileBufferp, _image_export_directory->AddressOfNameOrdinals) + newFileBufferp;
	newSectionp += _image_export_directory->NumberOfFunctions * 4;
	char* funcOrdinalTable_in_newSectionp = newSectionp;//把函数序号表在新节中的首地址记下来,后面要用
	memcpy(newSectionp, functionOrdinalTablep, _image_export_directory->NumberOfNames * 2);

	//复制函数名称表到新节中
	char* functionNameTablep = RVA_to_FOA(newFileBufferp, _image_export_directory->AddressOfNames) + newFileBufferp;
	newSectionp += _image_export_directory->NumberOfNames * 2;
	char* funcNameTable_in_newSectionp = newSectionp;//把函数名称表在新节中的首地址记下来,后面要用
	memcpy(newSectionp, functionNameTablep, _image_export_directory->NumberOfNames * 4);

	//复制函数名称字符串到新节中,每复制一个就修改函数名称表中的对应地址值RVA
	newSectionp += _image_export_directory->NumberOfNames * 4;
	DWORD* funcNameTable_in_newSectionp_temp = (DWORD*)funcNameTable_in_newSectionp;//用于修改新节中的函数名称表中地址值
	for (DWORD i = 0; i < _image_export_directory->NumberOfNames; i++) {
		char* nameStr = (char*)(RVA_to_FOA(newFileBufferp, *(DWORD*)functionNameTablep) + newFileBufferp);
		memcpy(newSectionp, nameStr, strlen(nameStr) + 1);//字符串结尾的\0别忘了复制
		*funcNameTable_in_newSectionp_temp = FOA_to_RVA(newFileBufferp, (DWORD)(newSectionp - newFileBufferp));//修改对应的函数名称表中的地址值
		newSectionp += (strlen(nameStr) + 1);
		functionNameTablep = (char*)((DWORD*)functionNameTablep + 1);
		funcNameTable_in_newSectionp_temp++;
	}

	//复制导出表到新节中
	memcpy(newSectionp, (char*)_image_export_directory, 40);

	//修正新节中导出表的成员值
	((_IMAGE_EXPORT_DIRECTORY*)newSectionp)->AddressOfFunctions = FOA_to_RVA(newFileBufferp, (DWORD)(funcAddressTable_in_newSectionp - newFileBufferp));
	((_IMAGE_EXPORT_DIRECTORY*)newSectionp)->AddressOfNameOrdinals = FOA_to_RVA(newFileBufferp, (DWORD)(funcOrdinalTable_in_newSectionp - newFileBufferp));
	((_IMAGE_EXPORT_DIRECTORY*)newSectionp)->AddressOfNames = FOA_to_RVA(newFileBufferp, (DWORD)(funcNameTable_in_newSectionp - newFileBufferp));

	//修正导出表数据目录中的成员
	_image_data_directory->VirtualAddress = FOA_to_RVA(newFileBufferp, (DWORD)(newSectionp - newFileBufferp));

	return newFileBufferp;
}

int main(int argc, char* argv[])
{
	char* fileBufferp = to_FileBuffer(filePath);
	char* newFileBufferp = move_ExportTable(fileBufferp);

	//将移动导出表后的PE文件存盘
	DWORD size = compute_NewBuffer_size(newFileBufferp);

	int isSucceed = save_to_disk(newFileBufferp, storagePath, size);
	if (!isSucceed) {
		printf("存盘失败");
	}
	else
		printf("存盘成功");

	free(fileBufferp);
	free(newFileBufferp);
	return 0;
}

10.2 移动重定位表

  1. 新增节,大小可以循环遍历重定位表的每个块,把所有块的SizeOfBlock求和 + 结束标记8字节最后再对齐;也可以估算0x1000就满足大部分情况了。
  2. 复制重定位表到新节中,大小为所有块的SizeOfBlock求和 + 结束标记8字节。
  3. 修改重定位表数据目录中的VirtualAddress,指向重定位表在新节中的位置即可(FOA转RVA)。
  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
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
#pragma warning(disable : 4996)
#include <iostream>
#include <string.h>

typedef unsigned short WORD;
typedef unsigned int DWORD;
typedef unsigned char BYTE;

#define MZ 0x5A4D
#define PE 0x4550
#define IMAGE_SIZEOF_SHORT_NAME 8

const char* filePath = "OllyDBG.EXE";  //选一个有重定位表的PE文件
const char* storagePath = "OllyDBG_new.EXE";

//DOS头
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];
	DWORD e_lfanew;  //PE文件真正开始的偏移地址
};

//标准PE头
struct _IMAGE_FILE_HEADER {
	WORD Machine;  //文件运行平台
	WORD NumberOfSections;  //节数量
	DWORD TimeDateStamp;  //时间戳
	DWORD PointerToSymbolTable;
	DWORD NumberOfSymbols;
	WORD SizeOfOptionalHeader;  //可选PE头大小
	WORD Characteristics;  //特征值
};

//数据目录
struct _IMAGE_DATA_DIRECTORY {
	DWORD VirtualAddress;
	DWORD Size;
};

//可选PE头
struct _IMAGE_OPTIONAL_HEADER {
	WORD Magic;  //文件类型
	BYTE MajorLinkerVersion;
	BYTE MinorLinkerVersion;
	DWORD SizeOfCode;   //代码节文件对齐后的大小
	DWORD SizeOfInitializedData;  //初始化数据文件对齐后的大小
	DWORD SizeOfUninitializedData;  //未初始化数据文件对齐后大小
	DWORD AddressOfEntryPoint;  //程序入口点(偏移量)
	DWORD BaseOfCode;  //代码基址
	DWORD BaseOfData;  //数据基址
	DWORD ImageBase;   //内存镜像基址
	DWORD SectionAlignment;  //内存对齐粒度
	DWORD FileAlignment;  //文件对齐粒度
	WORD MajorOperatingSystemVersion;
	WORD MinorOperatingSystemVersion;
	WORD MajorImageVersion;
	WORD MinorImageVersion;
	WORD MajorSubsystemVersion;
	WORD MinorSubsystemVersion;
	DWORD Win32VersionValue;
	DWORD SizeOfImage;  //文件装入虚拟内存后大小
	DWORD SizeOfHeaders;  //DOS、NT头和节表大小
	DWORD CheckSum;  //校验和
	WORD Subsystem;
	WORD DllCharacteristics;
	DWORD SizeOfStackReserve;  //预留堆栈大小
	DWORD SizeOfStackCommit;  //实际分配堆栈大小
	DWORD SizeOfHeapReserve;  //预留堆大小
	DWORD SizeOfHeapCommit;  //实际分配堆大小
	DWORD LoaderFlags;
	DWORD NumberOfRvaAndSizes;  //目录项数目
	_IMAGE_DATA_DIRECTORY DataDirectory[16]; //数据目录
};

//NT头
struct _IMAGE_NT_HEADERS {
	DWORD Signature;  //PE签名
	_IMAGE_FILE_HEADER FileHeader;
	_IMAGE_OPTIONAL_HEADER OptionalHeader;
};

//节表
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;  //该节特征属性
};

//导出表
struct _IMAGE_EXPORT_DIRECTORY {
	DWORD Characteristics;  //未使用		
	DWORD TimeDateStamp;  //时间戳		
	WORD MajorVersion;  //未使用		
	WORD MinorVersion;	//未使用		
	DWORD Name;  //指向该导出表文件名字符串  *
	DWORD Base;  //导出函数起始序号  *
	DWORD NumberOfFunctions;  //所有导出函数的个数  *		
	DWORD NumberOfNames;  //以函数名字导出的函数个数  *
	DWORD AddressOfFunctions;  //导出函数地址表RVA  *					
	DWORD AddressOfNames;  //导出函数名称表RVA  *						
	DWORD AddressOfNameOrdinals;  //导出函数序号表RVA  *						
};

//重定位表
struct _IMAGE_BASE_RELOCATION {
	DWORD VirtualAddress;
	DWORD SizeOfBlock;
	//具体项
};

/*计算文件大小函数
参数:文件绝对路径
返回值:返回文件大小(单位字节)
*/
int compute_file_size(const char* filePath) {
	FILE* fp = fopen(filePath, "rb");
	if (!fp) {
		printf("打开文件失败");
		exit(0);
	}
	fseek(fp, 0, 2);
	int size = ftell(fp);
	//fseek(fp,0,0); 单纯计算文件大小,就不需要还原指针了
	fclose(fp);
	return size;
}

/*NewBuffer存盘函数
参数:需要写出数据的内存首地址,保存绝对路径,写出的数据大小(单位:字节)
返回值:成功返回1,失败返回0
*/
int save_to_disk(char* newBufferp, const char* storagePath, DWORD size) {
	FILE* fp = fopen(storagePath, "wb");
	if (!fp) {
		printf("打开文件失败");
		return 0;
	}
	int isSucceed = fwrite(newBufferp, size, 1, fp);
	if (!isSucceed) {
		free(newBufferp);
		fclose(fp);
		return 0;
	}
	fclose(fp);
	return 1;
}

/*将文件读入FileBuffer函数
参数:文件绝对路径
返回值:FileBuffer起始地址
*/
char* to_FileBuffer(const char* filePath) {
	FILE* fp = fopen(filePath, "rb");
	if (!fp) {
		printf("打开文件失败");
		exit(0);
	}
	int size = compute_file_size(filePath);

	char* mp = (char*)malloc(sizeof(char) * size);  //分配内存空间
	if (!mp) {
		printf("分配空间失败");
		fclose(fp);
		exit(0);
	}

	int isSucceed = fread(mp, size, 1, fp);
	if (!isSucceed) {
		printf("读取数据失败");
		free(mp);
		fclose(fp);
		exit(0);
	}

	fclose(fp);
	return mp;
}

/*计算NewBuffer大小函数
参数:NewBuffer起始地址
返回值:unsigned int类型(单位:字节)
*/
DWORD compute_NewBuffer_size(char* newBufferp) {
	_IMAGE_DOS_HEADER* _image_dos_header = NULL;
	_IMAGE_FILE_HEADER* _image_file_header = NULL;
	_IMAGE_OPTIONAL_HEADER* _image_optional_header = NULL;
	_IMAGE_SECTION_HEADER* _image_section_header = NULL;

	_image_dos_header = (_IMAGE_DOS_HEADER*)newBufferp;
	_image_file_header = (_IMAGE_FILE_HEADER*)(newBufferp + _image_dos_header->e_lfanew + 4);
	_image_optional_header = (_IMAGE_OPTIONAL_HEADER*)((char*)_image_file_header + 20);
	_image_section_header = (_IMAGE_SECTION_HEADER*)((char*)_image_optional_header + _image_file_header->SizeOfOptionalHeader);

	_IMAGE_SECTION_HEADER* last_image_section_header = _image_section_header + _image_file_header->NumberOfSections - 1;
	DWORD sizeofNewBuffer = last_image_section_header->PointerToRawData + last_image_section_header->SizeOfRawData;
	return sizeofNewBuffer;
}

/*虚拟内存偏移地址->文件偏移地址函数
参数:FileBuffer起始地址,RVA地址值
返回值:RVA对应的FOA,返回0则表示非法RVA
*/
DWORD RVA_to_FOA(char* fileBufferp, DWORD RVA) {
	_IMAGE_DOS_HEADER* _image_dos_header = NULL;
	_IMAGE_FILE_HEADER* _image_file_header = NULL;
	_IMAGE_OPTIONAL_HEADER* _image_optional_header = NULL;
	_IMAGE_SECTION_HEADER* _image_section_header = NULL;

	_image_dos_header = (_IMAGE_DOS_HEADER*)fileBufferp;
	_image_file_header = (_IMAGE_FILE_HEADER*)(fileBufferp + _image_dos_header->e_lfanew + 4);
	_image_optional_header = (_IMAGE_OPTIONAL_HEADER*)((char*)_image_file_header + 20);
	_image_section_header = (_IMAGE_SECTION_HEADER*)((char*)_image_optional_header +
		_image_file_header->SizeOfOptionalHeader);

	if (RVA < _image_section_header->VirtualAddress)
		return RVA;

	// 确定RVA 在那个节区中
	int i = 0;
	for (; i < _image_file_header->NumberOfSections; i++) {
		if (RVA >= _image_section_header->VirtualAddress
			&& RVA < _image_section_header->VirtualAddress + _image_section_header->Misc.VirtualSize) {
			break;
		}
		else {
			_image_section_header++;
		}
	}

	// 用来判断最终RVA值是否在节中的非空白区
	if (i == _image_file_header->NumberOfSections) {
		return 0;
	}

	// 确定RVA在该节区的位置
	DWORD mem_offset_from_section = RVA - _image_section_header->VirtualAddress;

	return _image_section_header->PointerToRawData + mem_offset_from_section;
}

/*文件偏移地址函数->虚拟内存偏移地址
参数:FileBuffer起始地址,FOA地址值
返回值:FOA对应的RVA,返回0则表示非法FOA
*/
DWORD FOA_to_RVA(char* fileBufferp, DWORD FOA) {
	_IMAGE_DOS_HEADER* _image_dos_header = NULL;
	_IMAGE_FILE_HEADER* _image_file_header = NULL;
	_IMAGE_OPTIONAL_HEADER* _image_optional_header = NULL;
	_IMAGE_SECTION_HEADER* _image_section_header = NULL;

	_image_dos_header = (_IMAGE_DOS_HEADER*)fileBufferp;
	_image_file_header = (_IMAGE_FILE_HEADER*)(fileBufferp + _image_dos_header->e_lfanew + 4);
	_image_optional_header = (_IMAGE_OPTIONAL_HEADER*)((char*)_image_file_header + 20);
	_image_section_header = (_IMAGE_SECTION_HEADER*)((char*)_image_optional_header +
		_image_file_header->SizeOfOptionalHeader);

	bool flag = 0;//用来判断最终FOA值是否在节中的非空白区
	if (FOA < _image_section_header->PointerToRawData)
		return FOA;

	for (DWORD i = 0; i < _image_file_header->NumberOfSections; i++) {
		DWORD tempSize = (_image_section_header->Misc.VirtualSize < _image_section_header->SizeOfRawData ? _image_section_header->Misc.VirtualSize : _image_section_header->SizeOfRawData);
		if (FOA >= _image_section_header->PointerToRawData && FOA < _image_section_header->PointerToRawData + tempSize) {
			flag = 1;
			break;
		}
		_image_section_header++;
	}

	if (!flag)
		return 0;
	DWORD file_offset_from_section = FOA - _image_section_header->PointerToRawData;

	return _image_section_header->VirtualAddress + file_offset_from_section;
}

/*在文件最后新增节函数(直接在FileBuffer中操作)
参数:要新增节的可执行文件的FileBuffer起始地址
返回值:新增节后的NewFileBuffer内存的起始地址
*/
char* add_one_section_inFileBuffer(char* fileBufferp) {
	_IMAGE_DOS_HEADER* _image_dos_header = NULL;
	_IMAGE_FILE_HEADER* _image_file_header = NULL;
	_IMAGE_OPTIONAL_HEADER* _image_optional_header = NULL;
	_IMAGE_SECTION_HEADER* _image_section_header = NULL;

	//定义原来的FileBuffer几个指针(因为要用到几个值用于创建新的NewFileBuffer内存)
	_image_dos_header = (_IMAGE_DOS_HEADER*)fileBufferp;
	_image_file_header = (_IMAGE_FILE_HEADER*)(fileBufferp + _image_dos_header->e_lfanew + 4);
	_image_optional_header = (_IMAGE_OPTIONAL_HEADER*)((char*)_image_file_header + 20);
	_image_section_header = (_IMAGE_SECTION_HEADER*)((char*)_image_optional_header +
		_image_file_header->SizeOfOptionalHeader);

	//先计算添加新节后的NewFileBuffer的大小,并初始化内存
	//DWORD expand_value = 0x3000; //这里0x3000字节足够了
	DWORD expand_value = 0xF000; //如果对于重定位表很大的PE文件,要视情况而定
	DWORD fileBufferSize = (_image_section_header + _image_file_header->NumberOfSections - 1)->PointerToRawData + (_image_section_header + _image_file_header->NumberOfSections - 1)->SizeOfRawData;
	char* newFileBufferp = (char*)malloc(fileBufferSize + expand_value);

	memset(newFileBufferp, 0, fileBufferSize + expand_value);
	memcpy(newFileBufferp, fileBufferp, fileBufferSize);

	_image_dos_header = (_IMAGE_DOS_HEADER*)newFileBufferp;
	_image_file_header = (_IMAGE_FILE_HEADER*)(newFileBufferp + _image_dos_header->e_lfanew + 4);
	_image_optional_header = (_IMAGE_OPTIONAL_HEADER*)((char*)_image_file_header + 20);
	_image_section_header = (_IMAGE_SECTION_HEADER*)((char*)_image_optional_header + _image_file_header->SizeOfOptionalHeader);

	//判断节表末尾是否有空间新增节表
	if (_image_optional_header->SizeOfHeaders - _image_dos_header->e_lfanew - 4 - 20 - _image_file_header->SizeOfOptionalHeader -
		_image_file_header->NumberOfSections * 40 < 80) {
		printf("空间不足,无法新增节表\n");
		exit(0);
	}
	for (int i = 0; i < 80; i++) {
		if (*((char*)(_image_section_header + _image_file_header->NumberOfSections) + i) != 0) {
			printf("节表后80字节数据不全为0x00,有文件数据,无法新增节表\n");
			exit(0);
		}
	}

	//新增节
	DWORD original_SizeOfImage = _image_optional_header->SizeOfImage; //记录下原来的SizeOfImage
	_image_optional_header->SizeOfImage += expand_value;

	//新增节表
	_IMAGE_SECTION_HEADER* _image_section_header_new = (_IMAGE_SECTION_HEADER*)(char*)_image_section_header + _image_file_header->NumberOfSections;
	memcpy(_image_section_header_new, _image_section_header, 40);
	_image_file_header->NumberOfSections++;

	//修改新增节表信息
	char* name = (char*)_image_section_header_new->Name;
	const char* newName = ".newsec";
	strncpy(name, newName, IMAGE_SIZEOF_SHORT_NAME);
	_image_section_header_new->Misc.VirtualSize = expand_value;
	_image_section_header_new->VirtualAddress = original_SizeOfImage;
	_image_section_header_new->SizeOfRawData = expand_value;
	_image_section_header_new->PointerToRawData = fileBufferSize;

	_image_section_header_new->Characteristics = 0x00000000;
	for (int i = 0; i < _image_file_header->NumberOfSections; i++) {
		_image_section_header_new->Characteristics = _image_section_header_new->Characteristics | (_image_section_header + i)->Characteristics;
	}

	return newFileBufferp;
}

/*将PE文件重定位表移到新增节中函数
参数:PE文件的FileBuffer首地址
返回值:新增节并且移动重定位表后的NewFileBuffer首地址
*/
char* move_RelocationTable(char* fileBufferp) {
	//新增节
	char* newFileBufferp = add_one_section_inFileBuffer(fileBufferp);

	_IMAGE_DOS_HEADER* _image_dos_header = NULL;
	_IMAGE_FILE_HEADER* _image_file_header = NULL;
	_IMAGE_OPTIONAL_HEADER* _image_optional_header = NULL;
	_IMAGE_DATA_DIRECTORY* _image_data_directory = NULL;
	_IMAGE_SECTION_HEADER* _image_section_header = NULL;
	_IMAGE_BASE_RELOCATION* _image_base_relocation = NULL;

	_image_dos_header = (_IMAGE_DOS_HEADER*)newFileBufferp;
	_image_file_header = (_IMAGE_FILE_HEADER*)(newFileBufferp + _image_dos_header->e_lfanew + 4);
	_image_optional_header = (_IMAGE_OPTIONAL_HEADER*)((char*)_image_file_header + 20);
	_image_section_header = (_IMAGE_SECTION_HEADER*)((char*)_image_optional_header + _image_file_header->SizeOfOptionalHeader);
	_image_data_directory = (_IMAGE_DATA_DIRECTORY*)(_image_optional_header->DataDirectory + 5);
	if (_image_data_directory->VirtualAddress == 0) {
		printf("此PE文件没有重定位表\n");
		exit(0);
	}
	_image_base_relocation = (_IMAGE_BASE_RELOCATION*)(RVA_to_FOA(fileBufferp, _image_data_directory->VirtualAddress) + newFileBufferp);

	//复制重定位表到新节中
	_IMAGE_BASE_RELOCATION* _image_base_relocation_temp = _image_base_relocation;
	char* newSectionp = (_image_section_header + _image_file_header->NumberOfSections - 1)->PointerToRawData + newFileBufferp;  //newSectionp指向:向新节中复制的位置
	int size = 0; //计算重定位表的大小
	for (int num = 0; _image_base_relocation_temp->VirtualAddress != 0; num++) {
		size += _image_base_relocation_temp->SizeOfBlock;
		_image_base_relocation_temp = (_IMAGE_BASE_RELOCATION*)((char*)_image_base_relocation_temp + _image_base_relocation_temp->SizeOfBlock);
	}
	size += 8; //还要加上重定位表结束标记的8字节
	memcpy(newSectionp, (char*)_image_base_relocation, size);

	//修正重定位表数据目录的成员
	_image_data_directory->VirtualAddress = FOA_to_RVA(newFileBufferp, newSectionp - newFileBufferp);

	return newFileBufferp;
}

int main(int argc, char* argv[])
{
	char* fileBufferp = to_FileBuffer(filePath);
	char* newFileBufferp = move_RelocationTable(fileBufferp);

	//将移动重定位表后的PE文件存盘
	DWORD size = compute_NewBuffer_size(newFileBufferp);

	int isSucceed = save_to_disk(newFileBufferp, storagePath, size);
	if (!isSucceed) {
		printf("存盘失败\n");
	}
	else
		printf("存盘成功\n");

	free(fileBufferp);
	free(newFileBufferp);
	return 0;
}

10.3 修复重定位表

注意:修复重定位表不是真的修复重定位表中的数据,而是根据重定位表的各个具体项,去修改具体项指向的地方的值(这个值可能是DLL中全局变量的绝对地址;或者是DLL中函数的绝对地址等)!!!

应用场景:

  • 一般情况下一个PE文件自身的.exe的ImageBase很少会和别的PE文件ImageBase发生冲突;但是.dll就不一定了:day35写过自己发布的DLL,默认情况下DLL的ImageBase为0x10000000,所以如果一个程序要用的DLL没有合理的修改分配装载起始地址,就可能出现多个DLL的ImageBase都是同一个地址,造成装载冲突。所以如果一个DLL在装载时,发现其他DLL已经占用这块空间了,那么这个DLL会依据模块对齐粒度,往后找空余的空间存入,则此时这个DLL的装载起始地址和它的ImageBase就不一致了!这个时候操作系统如果访问DLL当中写死的绝对地址,就会访问出错。所以操作系统就会根据重定位表去修正这些写死的地址。

要求:现在我们自己故意修改一个DLL文件的ImageBase,再根据其重定位表去修正,最后存盘,看此DLL文件是否可以正常使用(即手动模拟操作系统修复重定位表的过程)。

修正过程:比如DLL的ImageBase原来为0x400000,现在修改为0x500000;接着找到重定位表,遍历每个块中的具体项,根据具体项去找哪里的地址值需要修改重定位;找到后,将原来的地址值 + (0x500000 - 0x400000)即可。

注入:平时我们说的注入的本质就是将自己写的DLL复制“贴”到一个程序运行时的虚拟内存中,所以我可以找一个空闲位置,把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
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
#pragma warning(disable : 4996)
#include <iostream>
#include <string.h>

typedef unsigned short WORD;
typedef unsigned int DWORD;
typedef unsigned char BYTE;

#define MZ 0x5A4D
#define PE 0x4550
#define IMAGE_SIZEOF_SHORT_NAME 8

const char* filePath = "PROCS.DLL";  // 选一个有重定位表的PE文件,选LordPE文件下的PROCS.DLL是因为这个DLL的重定位表的块比较少,方便我们验证是否可以正常解析
const char* storagePath = "PROCS_new.DLL";

//DOS头
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];
	DWORD e_lfanew;  //PE文件真正开始的偏移地址
};

//标准PE头
struct _IMAGE_FILE_HEADER {
	WORD Machine;  //文件运行平台
	WORD NumberOfSections;  //节数量
	DWORD TimeDateStamp;  //时间戳
	DWORD PointerToSymbolTable;
	DWORD NumberOfSymbols;
	WORD SizeOfOptionalHeader;  //可选PE头大小
	WORD Characteristics;  //特征值
};

//数据目录
struct _IMAGE_DATA_DIRECTORY {
	DWORD VirtualAddress;
	DWORD Size;
};

//可选PE头
struct _IMAGE_OPTIONAL_HEADER {
	WORD Magic;  //文件类型
	BYTE MajorLinkerVersion;
	BYTE MinorLinkerVersion;
	DWORD SizeOfCode;   //代码节文件对齐后的大小
	DWORD SizeOfInitializedData;  //初始化数据文件对齐后的大小
	DWORD SizeOfUninitializedData;  //未初始化数据文件对齐后大小
	DWORD AddressOfEntryPoint;  //程序入口点(偏移量)
	DWORD BaseOfCode;  //代码基址
	DWORD BaseOfData;  //数据基址
	DWORD ImageBase;   //内存镜像基址
	DWORD SectionAlignment;  //内存对齐粒度
	DWORD FileAlignment;  //文件对齐粒度
	WORD MajorOperatingSystemVersion;
	WORD MinorOperatingSystemVersion;
	WORD MajorImageVersion;
	WORD MinorImageVersion;
	WORD MajorSubsystemVersion;
	WORD MinorSubsystemVersion;
	DWORD Win32VersionValue;
	DWORD SizeOfImage;  //文件装入虚拟内存后大小
	DWORD SizeOfHeaders;  //DOS、NT头和节表大小
	DWORD CheckSum;  //校验和
	WORD Subsystem;
	WORD DllCharacteristics;
	DWORD SizeOfStackReserve;  //预留堆栈大小
	DWORD SizeOfStackCommit;  //实际分配堆栈大小
	DWORD SizeOfHeapReserve;  //预留堆大小
	DWORD SizeOfHeapCommit;  //实际分配堆大小
	DWORD LoaderFlags;
	DWORD NumberOfRvaAndSizes;  //目录项数目
	_IMAGE_DATA_DIRECTORY DataDirectory[16]; //数据目录
};

//NT头
struct _IMAGE_NT_HEADERS {
	DWORD Signature;  //PE签名
	_IMAGE_FILE_HEADER FileHeader;
	_IMAGE_OPTIONAL_HEADER OptionalHeader;
};

//节表
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;  //该节特征属性
};

//导出表
struct _IMAGE_EXPORT_DIRECTORY {
	DWORD Characteristics;  //未使用		
	DWORD TimeDateStamp;  //时间戳		
	WORD MajorVersion;  //未使用		
	WORD MinorVersion;	//未使用		
	DWORD Name;  //指向该导出表文件名字符串  *
	DWORD Base;  //导出函数起始序号  *
	DWORD NumberOfFunctions;  //所有导出函数的个数  *		
	DWORD NumberOfNames;  //以函数名字导出的函数个数  *
	DWORD AddressOfFunctions;  //导出函数地址表RVA  *					
	DWORD AddressOfNames;  //导出函数名称表RVA  *						
	DWORD AddressOfNameOrdinals;  //导出函数序号表RVA  *						
};

//重定位表
struct _IMAGE_BASE_RELOCATION {
	DWORD VirtualAddress;
	DWORD SizeOfBlock;
	//具体项
};

/*计算文件大小函数
参数:文件绝对路径
返回值:返回文件大小(单位字节)
*/
int compute_file_size(const char* filePath) {
	FILE* fp = fopen(filePath, "rb");
	if (!fp) {
		printf("打开文件失败");
		exit(0);
	}
	fseek(fp, 0, 2);
	int size = ftell(fp);
	//fseek(fp,0,0); 单纯计算文件大小,就不需要还原指针了
	fclose(fp);
	return size;
}

/*NewBuffer存盘函数
参数:需要写出数据的内存首地址,保存绝对路径,写出的数据大小(单位:字节)
返回值:成功返回1,失败返回0
*/
int save_to_disk(char* newBufferp, const char* storagePath, DWORD size) {
	FILE* fp = fopen(storagePath, "wb");
	if (!fp) {
		printf("打开文件失败");
		return 0;
	}
	int isSucceed = fwrite(newBufferp, size, 1, fp);
	if (!isSucceed) {
		free(newBufferp);
		fclose(fp);
		return 0;
	}
	fclose(fp);
	return 1;
}

/*将文件读入FileBuffer函数
参数:文件绝对路径
返回值:FileBuffer起始地址
*/
char* to_FileBuffer(const char* filePath) {
	FILE* fp = fopen(filePath, "rb");
	if (!fp) {
		printf("打开文件失败");
		exit(0);
	}
	int size = compute_file_size(filePath);

	char* mp = (char*)malloc(sizeof(char) * size);  //分配内存空间
	if (!mp) {
		printf("分配空间失败");
		fclose(fp);
		exit(0);
	}

	int isSucceed = fread(mp, size, 1, fp);
	if (!isSucceed) {
		printf("读取数据失败");
		free(mp);
		fclose(fp);
		exit(0);
	}

	fclose(fp);
	return mp;
}

/*计算NewBuffer大小函数
参数:NewBuffer起始地址
返回值:unsigned int类型(单位:字节)
*/
DWORD compute_NewBuffer_size(char* newBufferp) {
	_IMAGE_DOS_HEADER* _image_dos_header = NULL;
	_IMAGE_FILE_HEADER* _image_file_header = NULL;
	_IMAGE_OPTIONAL_HEADER* _image_optional_header = NULL;
	_IMAGE_SECTION_HEADER* _image_section_header = NULL;

	_image_dos_header = (_IMAGE_DOS_HEADER*)newBufferp;
	_image_file_header = (_IMAGE_FILE_HEADER*)(newBufferp + _image_dos_header->e_lfanew + 4);
	_image_optional_header = (_IMAGE_OPTIONAL_HEADER*)((char*)_image_file_header + 20);
	_image_section_header = (_IMAGE_SECTION_HEADER*)((char*)_image_optional_header + _image_file_header->SizeOfOptionalHeader);

	_IMAGE_SECTION_HEADER* last_image_section_header = _image_section_header + _image_file_header->NumberOfSections - 1;
	DWORD sizeofNewBuffer = last_image_section_header->PointerToRawData + last_image_section_header->SizeOfRawData;
	return sizeofNewBuffer;
}

/*虚拟内存偏移地址->文件偏移地址函数
参数:FileBuffer起始地址,RVA地址值
返回值:RVA对应的FOA,返回0则表示非法RVA
*/
DWORD RVA_to_FOA(char* fileBufferp, DWORD RVA) {
	_IMAGE_DOS_HEADER* _image_dos_header = NULL;
	_IMAGE_FILE_HEADER* _image_file_header = NULL;
	_IMAGE_OPTIONAL_HEADER* _image_optional_header = NULL;
	_IMAGE_SECTION_HEADER* _image_section_header = NULL;

	_image_dos_header = (_IMAGE_DOS_HEADER*)fileBufferp;
	_image_file_header = (_IMAGE_FILE_HEADER*)(fileBufferp + _image_dos_header->e_lfanew + 4);
	_image_optional_header = (_IMAGE_OPTIONAL_HEADER*)((char*)_image_file_header + 20);
	_image_section_header = (_IMAGE_SECTION_HEADER*)((char*)_image_optional_header +
		_image_file_header->SizeOfOptionalHeader);

	if (RVA < _image_section_header->VirtualAddress)
		return RVA;

	// 确定RVA 在那个节区中
	int i = 0;
	for (; i < _image_file_header->NumberOfSections; i++) {
		if (RVA >= _image_section_header->VirtualAddress
			&& RVA < _image_section_header->VirtualAddress + _image_section_header->Misc.VirtualSize) {
			break;
		}
		else {
			_image_section_header++;
		}
	}

	// 用来判断最终RVA值是否在节中的非空白区
	if (i == _image_file_header->NumberOfSections) {
		return 0;
	}

	// 确定RVA在该节区的位置
	DWORD mem_offset_from_section = RVA - _image_section_header->VirtualAddress;

	return _image_section_header->PointerToRawData + mem_offset_from_section;
}

/*文件偏移地址函数->虚拟内存偏移地址
参数:FileBuffer起始地址,FOA地址值
返回值:FOA对应的RVA,返回0则表示非法FOA
*/
DWORD FOA_to_RVA(char* fileBufferp, DWORD FOA) {
	_IMAGE_DOS_HEADER* _image_dos_header = NULL;
	_IMAGE_FILE_HEADER* _image_file_header = NULL;
	_IMAGE_OPTIONAL_HEADER* _image_optional_header = NULL;
	_IMAGE_SECTION_HEADER* _image_section_header = NULL;

	_image_dos_header = (_IMAGE_DOS_HEADER*)fileBufferp;
	_image_file_header = (_IMAGE_FILE_HEADER*)(fileBufferp + _image_dos_header->e_lfanew + 4);
	_image_optional_header = (_IMAGE_OPTIONAL_HEADER*)((char*)_image_file_header + 20);
	_image_section_header = (_IMAGE_SECTION_HEADER*)((char*)_image_optional_header +
		_image_file_header->SizeOfOptionalHeader);

	bool flag = 0;//用来判断最终FOA值是否在节中的非空白区
	if (FOA < _image_section_header->PointerToRawData)
		return FOA;

	for (DWORD i = 0; i < _image_file_header->NumberOfSections; i++) {
		DWORD tempSize = (_image_section_header->Misc.VirtualSize < _image_section_header->SizeOfRawData ? _image_section_header->Misc.VirtualSize : _image_section_header->SizeOfRawData);
		if (FOA >= _image_section_header->PointerToRawData && FOA < _image_section_header->PointerToRawData + tempSize) {
			flag = 1;
			break;
		}
		_image_section_header++;
	}

	if (!flag)
		return 0;
	DWORD file_offset_from_section = FOA - _image_section_header->PointerToRawData;

	return _image_section_header->VirtualAddress + file_offset_from_section;
}

/*在文件最后新增节函数(直接在FileBuffer中操作)
参数:要新增节的可执行文件的FileBuffer起始地址
返回值:新增节后的NewFileBuffer内存的起始地址
*/
char* add_one_section_inFileBuffer(char* fileBufferp) {
	_IMAGE_DOS_HEADER* _image_dos_header = NULL;
	_IMAGE_FILE_HEADER* _image_file_header = NULL;
	_IMAGE_OPTIONAL_HEADER* _image_optional_header = NULL;
	_IMAGE_SECTION_HEADER* _image_section_header = NULL;

	//定义原来的FileBuffer几个指针(因为要用到几个值用于创建新的NewFileBuffer内存)
	_image_dos_header = (_IMAGE_DOS_HEADER*)fileBufferp;
	_image_file_header = (_IMAGE_FILE_HEADER*)(fileBufferp + _image_dos_header->e_lfanew + 4);
	_image_optional_header = (_IMAGE_OPTIONAL_HEADER*)((char*)_image_file_header + 20);
	_image_section_header = (_IMAGE_SECTION_HEADER*)((char*)_image_optional_header +
		_image_file_header->SizeOfOptionalHeader);

	//先计算添加新节后的NewFileBuffer的大小,并初始化内存
	//DWORD expand_value = 0x3000; //这里0x3000字节足够了
	DWORD expand_value = 0xF000; //如果对于重定位表很大的PE文件,要视情况而定
	DWORD fileBufferSize = (_image_section_header + _image_file_header->NumberOfSections - 1)->PointerToRawData + (_image_section_header + _image_file_header->NumberOfSections - 1)->SizeOfRawData;
	char* newFileBufferp = (char*)malloc(fileBufferSize + expand_value);

	memset(newFileBufferp, 0, fileBufferSize + expand_value);
	memcpy(newFileBufferp, fileBufferp, fileBufferSize);

	_image_dos_header = (_IMAGE_DOS_HEADER*)newFileBufferp;
	_image_file_header = (_IMAGE_FILE_HEADER*)(newFileBufferp + _image_dos_header->e_lfanew + 4);
	_image_optional_header = (_IMAGE_OPTIONAL_HEADER*)((char*)_image_file_header + 20);
	_image_section_header = (_IMAGE_SECTION_HEADER*)((char*)_image_optional_header + _image_file_header->SizeOfOptionalHeader);

	//判断节表末尾是否有空间新增节表
	if (_image_optional_header->SizeOfHeaders - _image_dos_header->e_lfanew - 4 - 20 - _image_file_header->SizeOfOptionalHeader -
		_image_file_header->NumberOfSections * 40 < 80) {
		printf("空间不足,无法新增节表\n");
		exit(0);
	}
	for (int i = 0; i < 80; i++) {
		if (*((char*)(_image_section_header + _image_file_header->NumberOfSections) + i) != 0) {
			printf("节表后80字节数据不全为0x00,有文件数据,无法新增节表\n");
			exit(0);
		}
	}

	//新增节
	DWORD original_SizeOfImage = _image_optional_header->SizeOfImage; //记录下原来的SizeOfImage
	_image_optional_header->SizeOfImage += expand_value;

	//新增节表
	_IMAGE_SECTION_HEADER* _image_section_header_new = (_IMAGE_SECTION_HEADER*)(char*)_image_section_header + _image_file_header->NumberOfSections;
	memcpy(_image_section_header_new, _image_section_header, 40);
	_image_file_header->NumberOfSections++;

	//修改新增节表信息
	char* name = (char*)_image_section_header_new->Name;
	const char* newName = ".newsec";
	strncpy(name, newName, IMAGE_SIZEOF_SHORT_NAME);
	_image_section_header_new->Misc.VirtualSize = expand_value;
	_image_section_header_new->VirtualAddress = original_SizeOfImage;
	_image_section_header_new->SizeOfRawData = expand_value;
	_image_section_header_new->PointerToRawData = fileBufferSize;

	_image_section_header_new->Characteristics = 0x00000000;
	for (int i = 0; i < _image_file_header->NumberOfSections; i++) {
		_image_section_header_new->Characteristics = _image_section_header_new->Characteristics | (_image_section_header + i)->Characteristics;
	}

	return newFileBufferp;
}

/*修改ImageBase后修复重定位表函数
参数:要修复重定位表的PE文件的FileBuffer
返回值:无
*/
void repair_RelocationTable(char* fileBufferp) {
	_IMAGE_DOS_HEADER* _image_dos_header = NULL;
	_IMAGE_FILE_HEADER* _image_file_header = NULL;
	_IMAGE_OPTIONAL_HEADER* _image_optional_header = NULL;
	_IMAGE_DATA_DIRECTORY* _image_data_directory = NULL;
	_IMAGE_BASE_RELOCATION* _image_base_relocation = NULL;

	_image_dos_header = (_IMAGE_DOS_HEADER*)fileBufferp;
	_image_file_header = (_IMAGE_FILE_HEADER*)(fileBufferp + _image_dos_header->e_lfanew + 4);
	_image_optional_header = (_IMAGE_OPTIONAL_HEADER*)((char*)_image_file_header + 20);
	//指向数据目录结构体数组的第6个结构体
	_image_data_directory = (_IMAGE_DATA_DIRECTORY*)(_image_optional_header->DataDirectory + 5);
	//判断是否有重定位表
	if (_image_data_directory->VirtualAddress == 0) {
		printf("此PE文件没有重定位表\n");
		exit(0);
	}
	//找到重定位表
	_image_base_relocation = (_IMAGE_BASE_RELOCATION*)(RVA_to_FOA(fileBufferp, _image_data_directory->VirtualAddress) + fileBufferp);

	//手动修改ImageBase(这里可以先查一下原ImageBase(0x20000000)是多少,方便修改)
	DWORD original_ImageBase = _image_optional_header->ImageBase;
	_image_optional_header->ImageBase = 0x30000000;
	int offset = (int)(_image_optional_header->ImageBase - original_ImageBase);//现ImageBase比原ImageBase可能大也可能小,所以设置为有符号整型类型

	//根据重定位表中每个块的具体项去修复
	while (_image_base_relocation->VirtualAddress != 0) {
		for (DWORD j = 0; j < (_image_base_relocation->SizeOfBlock - 8) / 2; j++) {
			//这里使用右移12位的方式2字节宽度的高4位
			if ((*((WORD*)((char*)_image_base_relocation + 8 + j * 2)) >> 12) == 0) {
				continue;
			}
			//这里用与运算把高4位变成0后输出2字节宽度的值,即为低12位的值(别忘了还要加该块VirtualAddress)
			DWORD* repairAddress = (DWORD*)(fileBufferp + RVA_to_FOA(fileBufferp, _image_base_relocation->VirtualAddress + (*((WORD*)((BYTE*)_image_base_relocation + 8 + j * 2)) & 0x0FFF)));
			*repairAddress += offset;
		}
		_image_base_relocation = (_IMAGE_BASE_RELOCATION*)((char*)_image_base_relocation + _image_base_relocation->SizeOfBlock);
	}
}


int main(int argc, char* argv[])
{
	char* fileBufferp = to_FileBuffer(filePath);
	repair_RelocationTable(fileBufferp);

	//将修复重定位表后的PE文件存盘
	DWORD size = compute_NewBuffer_size(fileBufferp);
	int isSucceed = save_to_disk(fileBufferp, storagePath, size);
	if (!isSucceed) {
		printf("存盘失败\n");
	}
	else
		printf("存盘成功\n");

	free(fileBufferp);
	return 0;
}

11. IAT表和导入表

11.1 IAT表

11.2 导入表

11.2.1 导入表是什么

导入表:即一个PE文件(.dll/.exe等),它如果需要使用使用别的PE文件的函数,就需要一个清单来记录使用的别的PE文件的函数相关信息(使用了哪些DLL、使用了这个DLL里的哪些函数、叫什么名、去哪找等)。

一般而言:程序通过导入表中的信息来装载DLL到虚拟内存中(比如通过导入表的所有Name成员,知道要装载哪些DLL;或者如果导入表的某个结构中OriginalFirstThunk和FirstThunk都为0,系统就不会加载这个导入表结构对应的DLL,因为操作系统知道你没有使用这个DLL中的任何函数)

程序需要导入表中的一些信息来加载DLL,但修复导入表中的某些信息是在DLL装载完成后(比如修复IAT表),所以这些都是有过程的,不要混为一谈。

.exe一般没有导出表、有导入表;.dll一般既有导出表、又有导入表(因为一个.dll可能也需要使用别的.dll提供的函数,且.dll也会提供函数给别的PE文件用)

11.2.2 定位导入表

一个PE文件的导入表数据目录在PE文件第二个数据目录结构体,再根据导入表数据目录的VirtualAddress成员找到导出表(RVA转FOA)。

11.2.3 导入表结构

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
struct _IMAGE_IMPORT_DESCRIPTOR{							
    union{							
        DWORD Characteristics;           							
        DWORD OriginalFirstThunk;  //RVA,指向IMAGE_THUNK_DATA结构数组(INT表)
    };							
    DWORD TimeDateStamp;  //时间戳	(用于判断是否有绑定导入表/IAT表中是否已经绑定绝对地址)
    DWORD ForwarderChain;              							
    DWORD Name;  //RVA,指向dll名字字符串存储地址
    DWORD FirstThunk;  //RVA,指向IMAGE_THUNK_DATA结构数组(IAT表)
};
//导入表有很多个这种结构(成员全为0,表示结束)

注意:导入表不可能就一个这种结构,因为一个PE文件可能会使用多个DLL中的多个函数,一个这样的结构只表示一个DLL中的函数;所以PE文件使用了多少个DLL中的函数,就有多少个这种结构。

当遇到有sizeof(_IMAGE_IMPORT_DESCRIPTOR)个0x00时,表示导出表结束(相当于一个导入表结构中的成员全为0x00000000,就表示导入表结束)

由于IAT表在程序运行前后是不一样的,所以导入表和IAT表的具体关系结构图示如下:

  1. PE文件运行前

  1. PE文件运行后

  • OriginalFirstThunk:RVA,指向INT表(导入名称表),如果想得到内存地址,即用ImageBase + RVA即可;如果想得到文件地址,即把RVA转FOA即可。
  • Name:RVA,指向DLL名字字符串所在地址;比如Name指向的DLL名称为“user32.dll\0”,那么这个结构中记录的就是user32.dll中函数的相关信息;所以一个PE文件的导入表有多少中这种结构,就使用了多少个其他DLL中的函数。
  • FirstThunk:RVA,指向IAT表(导入地址表)。
  • TimeDataStamp:时间戳字段
    • 为0(即0x00000000):表示这个导入表结构对应的DLL中的函数绝对地址没有绑定到IAT表中。
    • 为-1(即0xFFFFFFFF):表示这个导入表结构对应的DLL中函数绝对地址已经绑定到IAT表中。

11.2.4 IAT表和INT表

定位IAT表和INT表:

  1. 第一种方法:通过导入表中的FirstThunk(RVA)成员,找到IAT表;通过OriginalFirstThunk找到INT表。
  2. 第二种方法:直接通过PE文件的第13个数据目录结构体中的VirtualAddress(RVA)成员,找到IAT表。

INT表(导入名称表):

由于有的DLL中的函数可以以函数名称导出,也可以只以序号(NONAME)导出,所以当一个PE文件使用别的DLL中的函数时要考虑函数有名字和函数只有序号两种情况:故INT表中的元素有IMAGE_THUNK_DATA序号两种(都是32位,4字节),判断方法如下:

  • 如果INT表的元素最高位为1:那么除去最高位剩下31位的值,就是函数的导出序号。
  • 如果INT表的元素最高位为0:那么这整个值是一个RVA,指向IMAGE_IMPORT_BY_NAME表中对应的函数名称结构体。

IMAGE_THUNK_DATA32结构体

1
2
3
4
5
6
7
8
struct _IMAGE_THUNK_DATA32{								
    union{								
        BYTE ForwarderString;								
        DWORD Function;								
        DWORD Ordinal;  //序号		
        _IMAGE_IMPORT_BY_NAME* AddressOfData;  //RVA,指向IMAGE_IMPORT_BY_NAME		
    };								
};						

这个结构体中就是一个联合体,占4字节;所以可以直接把INT表中的元素全部当成DWORD宽度即可(当成DWORD宽度的话,遍历INT表或者读取当中元素要方便很多)。

_IMAGE_IMPORT_BY_NAME结构体

1
2
3
4
struct _IMAGE_IMPORT_BY_NAME{							
    WORD Hint;  //可能为空(编译器决定);如果不为空,表示函数在导出表中的索引	
    BYTE Name[1];  //函数名称,以0结尾	
};							
  • Hint:这个值不能作为查找函数的手段,很多编译器都会把这个值设置为0x0000,表示为空
  • Name:因为这里要找一个结构来存储函数名称,一般都会想到字符数组,但是函数名称的字符个数是不确定的,那么到底定义多大的字符数组合适呢?为了解决这个问题,干脆定义一个1字节的字符数组,只存储函数名称的第一个字符!那么通过Name就可以得到该字符串的首地址!再依次往后遍历,直到遇到字符串的结尾字符\0,整个函数名称就取到了。

IAT表(导入地址表):

  • PE文件运行前:IAT表中的内容和INT表的一模一样!
  • PE文件运行后:IAT表中的内容就变成了对应DLL中的函数在内存中的绝对地址。
  • IAT表修改过程:当PE文件运行后,先装载.exe,再装载各个使用到的.dll到PE文件的虚拟内存中;等所有DLL装载完成后,再修改IAT表:遍历导出表中每个结构的INT表,无论遍历到的是函数名还是序号,都会作为其中一个参数传给系统函数:GetProcAddress(),此函数根据函数名或序号返回对应函数在内存中的绝对地址,接着把绝对地址存入到IAT表的对应位置。

当IAT表修改完成后,系统会把程序中调用DLL函数的call语句后面的地址也改了!改成什么?——程序的call后面跟的是间接寻址,即call [0x….],实际上这里的地址改成了IAT表中对应函数绝对地址值的地址!

即运行前:call [0x....]后面间接寻址的地址0x….是INT表中某个元素的地址;而运行后:call [0x....]后面间接寻址的地址0x….,改成了IAT表中某个元素的地址;

为什么有IAT表了,还需要INT表?

  • 原因之一是PE文件加载后,INT表作为函数名字的备份。运行前后INT表中的元素永远都指向函数名称或者序号,所以如果需要按照名字来查找该程序使用的别的DLL中的函数地址,就可以先通过遍历该程序导出表的INT表(别忘了导出表是由多个相同的结构构成的,每一个结构都对应一个DLL,每一个结构中都有INT表),最终在某一个导出表结构中的INT表中匹配到函数名称(或序号),那么把函数名称和所属结构的DLL通过LoadLibrary()返回的句柄(即所属DLL的ImageBase)传入GetProcAddress()函数,就可以得到该函数地址。
  • 如果没有INT表,那么当程序运行后,IAT表中的元素变成了函数的绝对地址,就无法通过函数名来查找了。

小结:一个PE文件的导入表有很多个_IMAGE_IMPORT_DESCRIPTOR这种结构构成,每一个这种结构都对应一个DLL以及该DLL中的函数,通过每个结构中的Name可以知道该PE文件使用了哪些DLL中的函数,再通过这个结构中的INT表和IAT表可以知道该PE文件使用了这个DLL中的哪些函数。

11.3 打印程序运行前的导入表

要求:打印ipmsg.exe和notepad.exe运行前的导入表,以及INT表、IAT表和函数名称或序号,最后对比两个PE文件的IAT表在运行前有什么不同。

  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
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
#pragma warning(disable : 4996)
#include <iostream>
#include <string.h>

typedef unsigned short WORD;
typedef unsigned int DWORD;
typedef unsigned char BYTE;

#define MZ 0x5A4D
#define PE 0x4550
#define IMAGE_SIZEOF_SHORT_NAME 8

//const char* filePath = "fg.exe";
//const char* filePath = "notepad.exe";		// 没有导入表???
const char* filePath = "InjectDll.dll";

//DOS头
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];
	DWORD e_lfanew;  //PE文件真正开始的偏移地址
};

//标准PE头
struct _IMAGE_FILE_HEADER {
	WORD Machine;  //文件运行平台
	WORD NumberOfSections;  //节数量
	DWORD TimeDateStamp;  //时间戳
	DWORD PointerToSymbolTable;
	DWORD NumberOfSymbols;
	WORD SizeOfOptionalHeader;  //可选PE头大小
	WORD Characteristics;  //特征值
};

//数据目录
struct _IMAGE_DATA_DIRECTORY {
	DWORD VirtualAddress;
	DWORD Size;
};

//可选PE头
struct _IMAGE_OPTIONAL_HEADER {
	WORD Magic;  //文件类型
	BYTE MajorLinkerVersion;
	BYTE MinorLinkerVersion;
	DWORD SizeOfCode;   //代码节文件对齐后的大小
	DWORD SizeOfInitializedData;  //初始化数据文件对齐后的大小
	DWORD SizeOfUninitializedData;  //未初始化数据文件对齐后大小
	DWORD AddressOfEntryPoint;  //程序入口点(偏移量)
	DWORD BaseOfCode;  //代码基址
	DWORD BaseOfData;  //数据基址
	DWORD ImageBase;   //内存镜像基址
	DWORD SectionAlignment;  //内存对齐粒度
	DWORD FileAlignment;  //文件对齐粒度
	WORD MajorOperatingSystemVersion;
	WORD MinorOperatingSystemVersion;
	WORD MajorImageVersion;
	WORD MinorImageVersion;
	WORD MajorSubsystemVersion;
	WORD MinorSubsystemVersion;
	DWORD Win32VersionValue;
	DWORD SizeOfImage;  //文件装入虚拟内存后大小
	DWORD SizeOfHeaders;  //DOS、NT头和节表大小
	DWORD CheckSum;  //校验和
	WORD Subsystem;
	WORD DllCharacteristics;
	DWORD SizeOfStackReserve;  //预留堆栈大小
	DWORD SizeOfStackCommit;  //实际分配堆栈大小
	DWORD SizeOfHeapReserve;  //预留堆大小
	DWORD SizeOfHeapCommit;  //实际分配堆大小
	DWORD LoaderFlags;
	DWORD NumberOfRvaAndSizes;  //目录项数目
	_IMAGE_DATA_DIRECTORY DataDirectory[16]; //数据目录
};

//NT头
struct _IMAGE_NT_HEADERS {
	DWORD Signature;  //PE签名
	_IMAGE_FILE_HEADER FileHeader;
	_IMAGE_OPTIONAL_HEADER OptionalHeader;
};

//节表
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;  //该节特征属性
};

//导入表
struct _IMAGE_IMPORT_DESCRIPTOR {
	union {
		DWORD Characteristics;
		DWORD OriginalFirstThunk;  //RVA,指向IMAGE_THUNK_DATA结构数组(INT表)
	};
	DWORD TimeDateStamp;  //时间戳	
	DWORD ForwarderChain;
	DWORD Name;  //RVA,指向dll名字字符串存储地址
	DWORD FirstThunk;  //RVA,指向IMAGE_THUNK_DATA结构数组(IAT表)
};//导入表有很多个这种结构(成员全为0,表示结束)

//INT表中元素指向的函数名称表
struct _IMAGE_IMPORT_BY_NAME {
	WORD Hint;  //可能为空(编译器决定);如果不为空,表示函数在导出表中的索引	
	BYTE Name[1];  //函数名称,以0结尾	
};

//INT表和运行前IAT表
struct _IMAGE_THUNK_DATA32 {
	union {
		BYTE ForwarderString;
		DWORD Function;
		DWORD Ordinal;  //序号		
		_IMAGE_IMPORT_BY_NAME* AddressOfData;  //RVA,指向IMAGE_IMPORT_BY_NAME		
	};
};

/*计算文件大小函数
参数:文件绝对路径
返回值:返回文件大小(单位字节)
*/
int compute_file_size(const char* filePath) {
	FILE* fp = fopen(filePath, "rb");
	if (!fp) {
		printf("打开文件失败");
		exit(0);
	}
	fseek(fp, 0, 2);
	int size = ftell(fp);
	//fseek(fp,0,0); 单纯计算文件大小,就不需要还原指针了
	fclose(fp);
	return size;
}

/*将文件读入FileBuffer函数
参数:文件绝对路径
返回值:FileBuffer起始地址
*/
char* to_FileBuffer(const char* filePath) {
	FILE* fp = fopen(filePath, "rb");
	if (!fp) {
		printf("打开文件失败");
		exit(0);
	}
	int size = compute_file_size(filePath);

	char* mp = (char*)malloc(sizeof(char) * size);  //分配内存空间
	if (!mp) {
		printf("分配空间失败");
		fclose(fp);
		exit(0);
	}

	int isSucceed = fread(mp, size, 1, fp);
	if (!isSucceed) {
		printf("读取数据失败");
		free(mp);
		fclose(fp);
		exit(0);
	}

	fclose(fp);
	return mp;
}

/*虚拟内存偏移地址->文件偏移地址函数
参数:FileBuffer起始地址,RVA地址值
返回值:RVA对应的FOA,返回0则表示非法RVA
*/
DWORD RVA_to_FOA(char* fileBufferp, DWORD RVA) {
	_IMAGE_DOS_HEADER* _image_dos_header = NULL;
	_IMAGE_FILE_HEADER* _image_file_header = NULL;
	_IMAGE_OPTIONAL_HEADER* _image_optional_header = NULL;
	_IMAGE_SECTION_HEADER* _image_section_header = NULL;

	_image_dos_header = (_IMAGE_DOS_HEADER*)fileBufferp;
	_image_file_header = (_IMAGE_FILE_HEADER*)(fileBufferp + _image_dos_header->e_lfanew + 4);
	_image_optional_header = (_IMAGE_OPTIONAL_HEADER*)((char*)_image_file_header + 20);
	_image_section_header = (_IMAGE_SECTION_HEADER*)((char*)_image_optional_header +
		_image_file_header->SizeOfOptionalHeader);

	if (RVA < _image_section_header->VirtualAddress)
		return RVA;

	// 确定RVA 在那个节区中
	int i = 0;
	for (; i < _image_file_header->NumberOfSections; i++) {
		if (RVA >= _image_section_header->VirtualAddress
			&& RVA < _image_section_header->VirtualAddress + _image_section_header->Misc.VirtualSize) {
			break;
		}
		else {
			_image_section_header++;
		}
	}

	// 用来判断最终RVA值是否在节中的非空白区
	if (i == _image_file_header->NumberOfSections) {
		return 0;
	}

	// 确定RVA在该节区的位置
	DWORD mem_offset_from_section = RVA - _image_section_header->VirtualAddress;

	return _image_section_header->PointerToRawData + mem_offset_from_section;
}

/*打印导入表、INT表、运行前IAT表、函数名称或序号
参数:需要打印的PE文件绝对路径
返回值:无
*/
void importTable_print(const char* filePath) {
	char* fileBufferp = to_FileBuffer(filePath);

	_IMAGE_DOS_HEADER* _image_dos_header = NULL;
	_IMAGE_FILE_HEADER* _image_file_header = NULL;
	_IMAGE_OPTIONAL_HEADER* _image_optional_header = NULL;
	_IMAGE_DATA_DIRECTORY* _image_data_directory = NULL;
	_IMAGE_IMPORT_DESCRIPTOR* _image_import_descriptor = NULL;
	_IMAGE_THUNK_DATA32* _image_thunk_data32 = NULL;  //指向INT表或IAT表
	_IMAGE_IMPORT_BY_NAME* _image_import_by_name = NULL;  //指向函数名称表


	_image_dos_header = (_IMAGE_DOS_HEADER*)fileBufferp;
	_image_file_header = (_IMAGE_FILE_HEADER*)(fileBufferp + _image_dos_header->e_lfanew + 4);
	_image_optional_header = (_IMAGE_OPTIONAL_HEADER*)((char*)_image_file_header + 20);
	_image_data_directory = (_IMAGE_DATA_DIRECTORY*)(_image_optional_header->DataDirectory + 1);

	if (_image_data_directory->VirtualAddress == 0) {
		printf("此文件没有导入表\n");
		exit(0);
	}
	
	_image_import_descriptor = (_IMAGE_IMPORT_DESCRIPTOR*)(fileBufferp + RVA_to_FOA(fileBufferp, _image_data_directory->VirtualAddress));

	printf("***************导入表***************\n");
	while (1) {
		//根据导入表结构中是否为全0判断导入表是否结束
		int i;
		for (i = 0; i < sizeof(*_image_import_descriptor); i++) {
			if (*((char*)_image_import_descriptor + i) != 0) {
				break;
			}
		}
		if (i == sizeof(*_image_import_descriptor))
			break;

		//打印导入表
		char* dllName = fileBufferp + RVA_to_FOA(fileBufferp, _image_import_descriptor->Name);
		printf("\nName:%08X--->%s\n", _image_import_descriptor->Name, dllName);
		printf("TimeDateStamp:%08X\n", _image_import_descriptor->TimeDateStamp);
		printf("OriginalFirstThunk:%08X\n", _image_import_descriptor->OriginalFirstThunk);
		printf("FirstThunk:%08X\n", _image_import_descriptor->FirstThunk);

		//打印INT表和函数名
		printf("-----------------INT表-----------------\n");
		if (_image_import_descriptor->OriginalFirstThunk != 0) {
			_image_thunk_data32 = (_IMAGE_THUNK_DATA32*)(fileBufferp + RVA_to_FOA(fileBufferp, _image_import_descriptor->OriginalFirstThunk));
			while (*((DWORD*)_image_thunk_data32) != 0) {  //直接把INT表中元素当成DWORD来判断即可
				if ((_image_thunk_data32->Ordinal & 0x80000000) == 0x80000000) {
					printf("(以序号方式导出)序号:%d\n", _image_thunk_data32->Ordinal & 0x7FFFFFFF);
				}
				else {
					_image_import_by_name = (_IMAGE_IMPORT_BY_NAME*)(fileBufferp + RVA_to_FOA(fileBufferp, _image_thunk_data32->Ordinal));
					printf("(以函数名方式导出)函数名RVA:%08X--->(hint:%04X)%s\n", _image_thunk_data32->Function, _image_import_by_name->Hint, _image_import_by_name->Name);
				}
				_image_thunk_data32++;
			}
		}
		else {
			printf("无INT表\n");
		}

		//打印运行前的IAT表
		printf("***********运行前IAT表***********\n");
		_image_thunk_data32 = (_IMAGE_THUNK_DATA32*)(fileBufferp + RVA_to_FOA(fileBufferp, _image_import_descriptor->FirstThunk));
		while (*((DWORD*)_image_thunk_data32) != 0) {
			if ((_image_thunk_data32->Ordinal & 0x80000000) == 0x80000000) {
				printf("(以序号方式导出)序号:%d\n", _image_thunk_data32->Ordinal & 0x7FFFFFFF);
			}
			else {
				_image_import_by_name = (_IMAGE_IMPORT_BY_NAME*)(fileBufferp + RVA_to_FOA(fileBufferp, _image_thunk_data32->Ordinal));
				printf("(以函数名方式导出)函数名RVA:%08X--->(hint:%04X)%s\n", _image_thunk_data32->Function, _image_import_by_name->Hint, _image_import_by_name->Name);
			}
			_image_thunk_data32++;
		}

		//导出表后移一个结构
		_image_import_descriptor++;
	}  //在这里加断点可以方便导出表一个结构一个结构的打印

}

int main(int argc, char* argv[])
{
	importTable_print(filePath);
	return 0;
}

12. 绑定导入表

12.1 引入绑定导入表

notepad.exe在运行前IAT表和INT表中的内容不一样,且通过观察,运行前notepad.exe的IAT表中存储的数据直接就是函数在虚拟内存中的绝对地址,而不是像ipmsg.exe程序:在程序运行时—-程序的.exe和使用到的DLL加载完成后,再把使用到的DLL中函数的绝对地址写到IAT表中。

为什么Windows32位系统自带的一些程序在运行前就把使用到的DLL中的函数的绝对地址绑定到IAT表中呢?

  • 优点:程序启动速度变快。

    因为双击程序后,系统会给程序分配一个4GB虚拟内存,接着把程序的.exe和使用的.dll装载到虚拟内存中,装载完成后再遍历INT表,通过系统API得到对应函数的绝对地址,再写到IAT表中;而如果在程序运行前就已经把函数绝对地址绑定到IAT表中,程序启动时就无需上述修改IAT表的步骤,节省时间。

  • 缺点:如果程序使用到的DLL装载时没有按照ImageBase位置装载;或者当程序使用到的DLL中的函数被修改了导致函数地址发生变化,那么IAT表中的地址数据就不准确了

综上:系统自带程序之所以敢这样做,可能就是因为这些程序使用的DLL都是设计好的,会按照ImageBase装载,不会出现冲突;且这些DLL不经常修改,函数地址不经常发生变化。

程序加载后,系统如何判断一个IAT表中数据是已经绑定好的绝对地址呢?还是指向对应函数名字符串地址的RVA或序号呢?

因为导入表有多个_IMAGE_IMPORT_DESCRIPTOR这种结构,每一个结构对应一个程序使用到的DLL;比如这个导入表结构对应的DLL是user32.dll,那么根据这个导入表结构中的TimeDataStamp字段,就可以判断此导入表结构的IAT表中的数据是绝对地址还是RVA。

可以通过导入表的时间戳(TimeDataStamp),判断是否有绑定导入表:

  • 为0(即0x00000000):表示这个导入表结构对应的DLL中的函数绝对地址没有绑定到IAT表中。
  • 为-1(即0xFFFFFFFF):表示这个导入表结构对应的DLL中函数绝对地址已经绑定到IAT表中。

如果一个导入表结构的TimeDataStamp为-1,说明绝对地址已经绑定到IAT表中,那如何知道对应DLL中函数的绝对地址什么时间绑定的?就需要另一个表—-绑定导入表,绑定导入表中的TimeDataStamp时间戳,才表示函数地址真正的绑定时间。

12.2 绑定导入表

数据目录的第12个结构,就是绑定导入表数据目录项;再通过绑定导入表数据目录中VirtualAddress字段,RVA转成FOA,定位到绑定导入表地址。

绑定导入表结构:

1
2
3
4
5
struct _IMAGE_BOUND_IMPORT_DESCRIPTOR{						
    DWORD TimeDateStamp;  //时间戳					
    WORD OffsetModuleName;	//DLL的名字RVA(加第一个结构中RVA才是字符串真正RVA,详见下面)	
    WORD NumberOfModuleForwarderRefs;  //这个绑定导入表结构后面还有几个_IMAGE_BOUND_FORWARDER_REF这种结构					
};  //绑定导入表有很多这种结构或者_IMAGE_BOUND_FORWARDER_REF这种结构,最后如果有sizeof(_IMAGE_BOUND_IMPORT_DESCRIPTOR)个0,表示绑定导入表结束	

TimeDateStamp:绑定导入表的时间戳:表示程序使用到的DLL中的函数绝对地址真正绑定到IAT表中的时间。

作用:系统通过程序使用到的DLL对应的绑定导入表结构中TimeDateStamp和该DLL的可选PE头中的TimeDateStamp对比。

  • 如果两个时间戳一样:表明DLL的创建时间和把DLL中的函数绝对地址绑定到IAT表的时间是一样,则在绑定以后,DLL没有更新或者修改。
  • 如果两个时间戳不一样:表明在绑定以后,此DLL又被更新或者修改了,那么绑定到IAT表中的地址可能不准确了!(因为DLL中的函数地址可能变了,但绑定到IAT中的数据可能是以前的函数地址)

OffsetModuleName:对应DLL的名字:因为一个程序的导入表结构对应一个使用到的DLL;一个程序的绑定导入表结构也对应一个程序使用到的DLL,这个绑定导入表结构记录了该DLL中函数绝对地址绑定到IAT表的时间戳、该DLL的名字、还有该DLL使用到别的DLL的个数。

注意:不管是_IMAGE_BOUND_IMPORT_DESCRIPTOR结构中的OffsetModuleName、还是后面要讲的_IMAGE_BOUND_FORWARDER_REF 结构中的OffsetModuleName,都必须加上绑定导入表起始RVA值,才是这个结构对应DLL名字的真正RVA。

因为通过Winhex打开notepad.exe发现:绑定导入表的起始地址是在节表后面!!而上述DLL名字字符串紧跟在绑定导入表后面!(这就解决了以前学新增节时,为什么说节表后面如果有数据,不要随便动!)

NumberOfModuleForwarderRefs:因为一个DLL可能还会使用到别的DLL中的函数,所以NumberOfModuleForwarderRefs字段的值是多少,就表明当前绑定导入表对应的DLL还使用了多少个别的DLL,同样表示这个绑定导入表结构后面跟了多少个_IMAGE_BOUND_FORWARDER_REF这种结构。

_IMAGE_BOUND_FORWARDER_REF结构如下:

1
2
3
4
5
struct _IMAGE_BOUND_FORWARDER_REF {			
    DWORD TimeDateStamp;  //时间戳		
    WORD OffsetModuleName;  //对应DLL的名字
    WORD Reserved;  //保留(未使用)	
};
  • TimeDataStamp:和绑定导入表结构中的时间戳含义和用途是一样的。
  • OffsetModuleName:加上绑定导入表起始RVA,才是真正对应DLL的名字。
  • Reserved:保留字段(未使用),没有任何含义。

具体结构关系如下:比如一个程序的绑定导入表先是一个_IMAGE_BOUND_IMPORT_DESCRIPTOR结构,该结构中NumberOfModuleForwarderRefs字段值为2(表示该DLL使用了另外两个DLL中的函数),则该结构后面跟了两个_IMAGE_BOUND_FORWARDER_REF结构(每一个REF结构对应一个DLL);后面以此类推,直到有8字节个0x00表示绑定导入表结束。

综上:当IMAGE_BOUND_IMPORT_DESCRIPTOR结构中的TimeDateStamp与DLL文件标准PE头中的TimeDateStamp值不相符时(或者DLL需要重定位的时候),程序在装载完成后才会修改对应DLL的导入表结构中的IAT表中的数据。

12.3 打印绑定导入表

  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
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
#pragma warning(disable : 4996)
#include <iostream>
#include <string.h>

typedef unsigned short WORD;
typedef unsigned int DWORD;
typedef unsigned char BYTE;

#define MZ 0x5A4D
#define PE 0x4550
#define IMAGE_SIZEOF_SHORT_NAME 8

//const char* filePath = "notepad.exe";	// 此文件没有绑定导入表???
const char* filePath = "CRACKME.EXE";

//DOS头
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];
	DWORD e_lfanew;  //PE文件真正开始的偏移地址
};

//标准PE头
struct _IMAGE_FILE_HEADER {
	WORD Machine;  //文件运行平台
	WORD NumberOfSections;  //节数量
	DWORD TimeDateStamp;  //时间戳
	DWORD PointerToSymbolTable;
	DWORD NumberOfSymbols;
	WORD SizeOfOptionalHeader;  //可选PE头大小
	WORD Characteristics;  //特征值
};

//数据目录
struct _IMAGE_DATA_DIRECTORY {
	DWORD VirtualAddress;
	DWORD Size;
};

//可选PE头
struct _IMAGE_OPTIONAL_HEADER {
	WORD Magic;  //文件类型
	BYTE MajorLinkerVersion;
	BYTE MinorLinkerVersion;
	DWORD SizeOfCode;   //代码节文件对齐后的大小
	DWORD SizeOfInitializedData;  //初始化数据文件对齐后的大小
	DWORD SizeOfUninitializedData;  //未初始化数据文件对齐后大小
	DWORD AddressOfEntryPoint;  //程序入口点(偏移量)
	DWORD BaseOfCode;  //代码基址
	DWORD BaseOfData;  //数据基址
	DWORD ImageBase;   //内存镜像基址
	DWORD SectionAlignment;  //内存对齐粒度
	DWORD FileAlignment;  //文件对齐粒度
	WORD MajorOperatingSystemVersion;
	WORD MinorOperatingSystemVersion;
	WORD MajorImageVersion;
	WORD MinorImageVersion;
	WORD MajorSubsystemVersion;
	WORD MinorSubsystemVersion;
	DWORD Win32VersionValue;
	DWORD SizeOfImage;  //文件装入虚拟内存后大小
	DWORD SizeOfHeaders;  //DOS、NT头和节表大小
	DWORD CheckSum;  //校验和
	WORD Subsystem;
	WORD DllCharacteristics;
	DWORD SizeOfStackReserve;  //预留堆栈大小
	DWORD SizeOfStackCommit;  //实际分配堆栈大小
	DWORD SizeOfHeapReserve;  //预留堆大小
	DWORD SizeOfHeapCommit;  //实际分配堆大小
	DWORD LoaderFlags;
	DWORD NumberOfRvaAndSizes;  //目录项数目
	_IMAGE_DATA_DIRECTORY DataDirectory[16]; //数据目录
};

//NT头
struct _IMAGE_NT_HEADERS {
	DWORD Signature;  //PE签名
	_IMAGE_FILE_HEADER FileHeader;
	_IMAGE_OPTIONAL_HEADER OptionalHeader;
};

//节表
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;  //该节特征属性
};

//绑定导入表
struct _IMAGE_BOUND_IMPORT_DESCRIPTOR {
	DWORD TimeDateStamp;  //时间戳					
	WORD OffsetModuleName;	//DLL的名字RVA(加第一个结构中RVA才是字符串真正RVA,详见下面)	
	WORD NumberOfModuleForwarderRefs;  //这个绑定导入表结构后面还有几个_IMAGE_BOUND_FORWARDER_REF这种结构					
};  //绑定导入表有很多这种结构或者_IMAGE_BOUND_FORWARDER_REF这种结构,最后如果有sizeof(_IMAGE_BOUND_IMPORT_DESCRIPTOR)个0,表示绑定导入表结束
struct _IMAGE_BOUND_FORWARDER_REF {
	DWORD TimeDateStamp;  //时间戳		
	WORD OffsetModuleName;  //对应DLL的名字
	WORD Reserved;  //保留(未使用)	
};

/*计算文件大小函数
参数:文件绝对路径
返回值:返回文件大小(单位字节)
*/
int compute_file_size(const char* filePath) {
	FILE* fp = fopen(filePath, "rb");
	if (!fp) {
		printf("打开文件失败");
		exit(0);
	}
	fseek(fp, 0, 2);
	int size = ftell(fp);
	//fseek(fp,0,0); 单纯计算文件大小,就不需要还原指针了
	fclose(fp);
	return size;
}

/*将文件读入FileBuffer函数
参数:文件绝对路径
返回值:FileBuffer起始地址
*/
char* to_FileBuffer(const char* filePath) {
	FILE* fp = fopen(filePath, "rb");
	if (!fp) {
		printf("打开文件失败");
		exit(0);
	}
	int size = compute_file_size(filePath);

	char* mp = (char*)malloc(sizeof(char) * size);  //分配内存空间
	if (!mp) {
		printf("分配空间失败");
		fclose(fp);
		exit(0);
	}

	int isSucceed = fread(mp, size, 1, fp);
	if (!isSucceed) {
		printf("读取数据失败");
		free(mp);
		fclose(fp);
		exit(0);
	}

	fclose(fp);
	return mp;
}

/*虚拟内存偏移地址->文件偏移地址函数
参数:FileBuffer起始地址,RVA地址值
返回值:RVA对应的FOA,返回0则表示非法RVA
*/
DWORD RVA_to_FOA(char* fileBufferp, DWORD RVA) {
	_IMAGE_DOS_HEADER* _image_dos_header = NULL;
	_IMAGE_FILE_HEADER* _image_file_header = NULL;
	_IMAGE_OPTIONAL_HEADER* _image_optional_header = NULL;
	_IMAGE_SECTION_HEADER* _image_section_header = NULL;

	_image_dos_header = (_IMAGE_DOS_HEADER*)fileBufferp;
	_image_file_header = (_IMAGE_FILE_HEADER*)(fileBufferp + _image_dos_header->e_lfanew + 4);
	_image_optional_header = (_IMAGE_OPTIONAL_HEADER*)((char*)_image_file_header + 20);
	_image_section_header = (_IMAGE_SECTION_HEADER*)((char*)_image_optional_header +
		_image_file_header->SizeOfOptionalHeader);

	if (RVA < _image_section_header->VirtualAddress)
		return RVA;

	// 确定RVA 在那个节区中
	int i = 0;
	for (; i < _image_file_header->NumberOfSections; i++) {
		if (RVA >= _image_section_header->VirtualAddress
			&& RVA < _image_section_header->VirtualAddress + _image_section_header->Misc.VirtualSize) {
			break;
		}
		else {
			_image_section_header++;
		}
	}

	// 用来判断最终RVA值是否在节中的非空白区
	if (i == _image_file_header->NumberOfSections) {
		return 0;
	}

	// 确定RVA在该节区的位置
	DWORD mem_offset_from_section = RVA - _image_section_header->VirtualAddress;

	return _image_section_header->PointerToRawData + mem_offset_from_section;
}

/*打印绑定导入表
参数:需要打印的PE文件绝对路径
返回值:无
*/
void boundImportTable_print(const char* filePath) {
	char* fileBufferp = to_FileBuffer(filePath);

	_IMAGE_DOS_HEADER* _image_dos_header = NULL;
	_IMAGE_FILE_HEADER* _image_file_header = NULL;
	_IMAGE_OPTIONAL_HEADER* _image_optional_header = NULL;
	_IMAGE_DATA_DIRECTORY* _image_data_directory = NULL;
	_IMAGE_BOUND_IMPORT_DESCRIPTOR* _image_bound_import_descriptor = NULL;
	_IMAGE_BOUND_FORWARDER_REF* _image_bound_forwarder_ref = NULL;

	_image_dos_header = (_IMAGE_DOS_HEADER*)fileBufferp;
	_image_file_header = (_IMAGE_FILE_HEADER*)(fileBufferp + _image_dos_header->e_lfanew + 4);
	_image_optional_header = (_IMAGE_OPTIONAL_HEADER*)((char*)_image_file_header + 20);
	_image_data_directory = (_IMAGE_DATA_DIRECTORY*)(_image_optional_header->DataDirectory + 11);  //指向绑定导入表数据目录项
	if (_image_data_directory->VirtualAddress == 0) {
		printf("此文件没有绑定导入表\n");
		exit(0);
	}

	_image_bound_import_descriptor = (_IMAGE_BOUND_IMPORT_DESCRIPTOR*)(fileBufferp + RVA_to_FOA(fileBufferp, _image_data_directory->VirtualAddress));
	DWORD bound_import_table_RVA = (DWORD)_image_data_directory->VirtualAddress; //记录下绑定导入表起始RVA

	//打印绑定导入表
	printf("\n***************绑定导入表***************\n");
	while (1) {
		//根据绑定导入表结构中是否为全0判断绑定导入表是否结束
		int i;
		for (i = 0; i < sizeof(*_image_bound_import_descriptor); i++) {
			if (*((char*)_image_bound_import_descriptor) != 0) {
				break;
			}
		}
		if (i == sizeof(*_image_bound_import_descriptor)) {
			break;
		}

		printf("OffsetModuleName:%04X--->%s\n", _image_bound_import_descriptor->OffsetModuleName, fileBufferp + RVA_to_FOA(fileBufferp, _image_bound_import_descriptor->OffsetModuleName + bound_import_table_RVA));
		printf("TimeDateStamp:%08X\n", _image_bound_import_descriptor->TimeDateStamp);
		printf("NumberOfModuleForwarderRefs:%04X\n", _image_bound_import_descriptor->NumberOfModuleForwarderRefs);

		//打印_IMAGE_BOUND_FORWARDER_REF结构
		if (_image_bound_import_descriptor->NumberOfModuleForwarderRefs != 0) {
			printf("-----ModuleForwarders-----\n");
			for (i = 0; (WORD)i < _image_bound_import_descriptor->NumberOfModuleForwarderRefs; i++) {
				_image_bound_forwarder_ref = (_IMAGE_BOUND_FORWARDER_REF*)(_image_bound_import_descriptor + 1 + i);
				printf("OffsetModuleName:%04X--->%s\n", _image_bound_forwarder_ref->OffsetModuleName, fileBufferp + RVA_to_FOA(fileBufferp, _image_bound_forwarder_ref->OffsetModuleName + bound_import_table_RVA));
				printf("TimeDateStamp:%08X\n", _image_bound_forwarder_ref->TimeDateStamp);
				printf("Reserved(未使用):%04X\n\n", _image_bound_forwarder_ref->Reserved);
			}
		}

		printf("\n");
		_image_bound_import_descriptor = (_IMAGE_BOUND_IMPORT_DESCRIPTOR*)(_image_bound_import_descriptor + 1 + _image_bound_import_descriptor->NumberOfModuleForwarderRefs);
	}

}

int main(int argc, char* argv[])
{
	boundImportTable_print(filePath);

	return 0;
}

13. 导入表注入

一个游戏辅助(.exe):有自动打怪、加血、自动任务等功能。那么当点击自动打怪按钮时,怎么让游戏(.exe)去执行这个功能呢?游戏的打怪功能就是一个函数,传入相关的参数、调用打怪函数就可以实现打怪,只不过我们用鼠标或者键盘是一种快捷键的方式操作,而不是直接操控代码。

如果我们把①定位游戏打怪函数的call、②传入相关参数、③执行这部分代码等功能封装到一个DLL中,把这个DLL注入到游戏运行时的虚拟空间中,接着去调用执行这个DLL,不就实现了自动打怪的功能。(上述过程就是我们提过的DLL注入

那么如何指挥这个DLL去做事呢?就需要知道进程通信,即游戏辅助(.exe)与游戏(.exe)之间进行通信。(所以需要学习进程通信相关API,即Win32API,以及线程相关概念)

13.1 注入

在操作系统3环层面有8种常见注入方式:

  1. 注册表注入;
  2. 导入表注入;
  3. 特洛伊注入;
  4. 远程线程注入;
  5. 无DLL注入;
  6. Apc注入;
  7. Windows挂钩注入DLL;
  8. 输入法注入

当exe被加载时,系统会根据exe的导入表信息来加载此程序需要用到的DLL。比如通过导入表的所有Name成员,知道要装载哪些DLL,接着操作系统会去程序所在当前文件夹下查找DLL去装载,找不到再去系统盘中找;或者如果导入表的某个结构中OriginalFirstThunk和FirstThunk都为0,系统就不会加载这个导入表结构对应的DLL,因为操作系统知道你没有使用这个DLL中的任何函数,加载没有意义。所以在下面导入表注入实操时,一定要创建一个完整的“链”:新增一个导入表结构的同时,还需要新增对应的INT表以及IAT表、以及函数名称表,不能只创建一个导入表结构

导入表注入的原理就是:修改exe导入表,将自己写的DLL相关信息添加到exe的导入表中,这样exe运行时就可以将自己的DLL加载到exe的进程虚拟内存中。

13.2 移动导入表

为什么要移动导入表?

结合导入表注入的原理思考:由于一个导入表结构对应一个DLL,所以你把DLL添加到.exe进程空间中,就需要在.exe的导入表中新增一个导入表结构(还要新增对应INT表、IAT表和函数名称表),但是原来的导入表结构是一个紧挨着一个存储的,最后有sizeof(导入表结构体)个0结尾,再后面的数据肯定都是有用的。所以我们没办法直接在导入表个结构中插入一个新的导入表结构,也没办法在原有的导入表结尾处新增导入表结构,那就只能把导入表移走!再在它结尾处新增导入表结构和全0结尾即可。

但是注意:只用把原来的导入表结构移走即可,不需要移走原来各个导入表结构对应的INT表和IAT表,也就不需要修改原来导入表结构中的OriginalFirstThunk和FirstThunk等字段。而且IAT表是一定不能移动的!因为当程序运行时,系统会把程序中调用DLL函数的call语句后面的地址也改了!改成什么?——程序的call后面跟的是间接寻址,即call [0x....],实际上这里的地址改成了IAT表中对应函数绝对地址值的地址!即运行前:call [0x....]后面间接寻址的地址0x....是INT表中某个元素的地址;而运行后:call [0x....]后面间接寻址的地址0x....,改成了IAT表中某个元素的地址;所以,要是移动IAT表,IAT表地址变了,那么你就要把程序中所有调用DLL函数的call语句后面的间接寻址的地址也改掉!但是这个工作量是非常大的!

导入表注入步骤

  1. 设计要注入的DLL
  2. 先找到可选PE头中最后一个成员数据目录,第二个结构就是导入表数据目录,通过VirtualAddress定位到导入表地址
  3. 依次移动原来导入表所有结构到新增节或者节与节之间的空白区或者扩大节中(哪种方法都行)
  4. 在移动后的导入表后面,新增一个导入表结构(别忘了还要在结尾留下sizeof(导入表结构体)个0)
  5. 新增一个至少8字节的INT表和IAT表,并且修正新增导入表结构中的OriginalFirstThunkFirstThunk

因为导入表结构中的OriginalFirstThunk和FirstThunk字段不能为0,即必须要使用对应DLL中的至少一个函数才行,不然系统不会加载此DLL到程序虚拟内存中的。所以INT表和IAT表中至少要有一个函数信息还有4字节0表示结束(具体大小为多少,还是要看你的DLL有多少函数给程序使用)

  1. 还要新增至少一个IMAGE_IMPORT_BY_NAME结构,即函数名称表:前两个字节为0即可,后面是函数名称字符串
  2. 修改INT表和IAT表(运行前)中的元素值,指向IMAGE_IMPORT_BY_NAME结构中元素。
  3. 再在后面分配空间存储此DLL名称字符串,并将该字符串的起始RVA赋值给导入表结构中的Name
  4. 最后修正导入表数据目录中的VirtualAddressSize

13.3 设计要注入的DLL

我们先自己编写一个DLL,用于后面导入表注入实验,先用vs 2019创建一个DLL:名为InjectDll,接着在InjectDll.h头文件中声明三个函数,前两个DLL内部使用,最后一个为导出函数供别的程序使用。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
// InjectDll.h: interface for the InjectDll class.
//
//
#if !defined(AFX_INJECTDLL_H__0713C052_B98E_4101_AB64_77B2AE5596C8__INCLUDED_)
#define AFX_INJECTDLL_H__0713C052_B98E_4101_AB64_77B2AE5596C8__INCLUDED_
#if _MSC_VER > 1000
#pragma once
#endif // _MSC_VER > 1000

void Init();  //这两个函数不用导出
void Destroy();
extern "C" _declspec(dllexport) void ExportFunction();  //这个函数要导出

#endif // !defined(AFX_INJECTDLL_H__0713C052_B98E_4101_AB64_77B2AE5596C8__INCLUDED_)

再在InjectDll.cpp中定义三个函数:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
// InjectDll.cpp: implementation of the InjectDll class.
#include "InjectDll.h"
#include <windows.h>

void Init(){
	MessageBox(0,"Init","Init",MB_OK); //简单的一个弹窗功能
}
void Destroy(){
	MessageBox(0,"Destroy","Destroy",MB_OK);
}
void ExportFunction(){
	MessageBox(0,"ExportFunction","ExportFunction",MB_OK);
}

最后定义DLL入口函数:即当程序运行、装载此DLL时以及程序退出、卸载此DLL时会调用DllMain()函数,调用DllMain()方法后,如果传入的ul_reason_for_call参数为DLL_PROCESS_ATTACH(这是一个宏),表示正在装载DLL,则会调用Init()方法;如果传入的ul_reason_for_call参数为DLL_PROCESS_DETACH,表示正在卸载DLL,则会调用Destroy()方法。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
BOOL APIENTRY DllMain(HANDLE hModule,DWORD ul_reason_for_call,LPVOID lpReserved){  //定义DllMain方法
	switch(ul_reason_for_call){
		case DLL_PROCESS_ATTACH:  //当程序运行,装载DLL时会调用一次DllMain方法,执行Init()
			Init();
			break;
		case DLL_PROCESS_DETACH:  //当程序结束,卸载DLL时会调用一次DllMain方法,执行Destroy()
			Destroy();
			break;
    }
    return TRUE;
}
0%