计算NewBuffer的大小

可以通过最后一个节表的信息知道最后一个节表的偏移地址+ImageBase+节的大小,就是NewBuffer的大小

image-20241026112055951

Work

参考代码

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
#include <iostream>
#include <windows.h>


//变量声明
PIMAGE_DOS_HEADER dosHeader = nullptr; //DOS结构
PIMAGE_FILE_HEADER fileHeader = nullptr; //FILE结构
PIMAGE_OPTIONAL_HEADER32 optionalHeader = nullptr; //OPTIONAL结构
PIMAGE_SECTION_HEADER* sectionArr = nullptr; //SECTION数组
LPVOID MemoryData = nullptr; //拉伸后的pe结构

// 读取程序数据
LPVOID ReadProgramData(LPCSTR programPath)
{
FILE* program = nullptr;
size_t size = NULL;
LPVOID data = nullptr;

//打开程序
fopen_s(&program, programPath, "rb");
if (program == nullptr)
{
printf("failed to open by program!\n");
goto END;
}

//获取程序字节大小
if (fseek(program, 0, SEEK_END) == 0)
{
size = ftell(program);
if (fseek(program, 0, SEEK_SET) != 0)
{
printf("failed to move the pointer to begin!\n");
goto END;
}
}
else
{
printf("failed to move the pointer to end!\n");
goto END;
}

//申请内存空间存储数据
data = malloc(size);
if (data != nullptr)
{
memset(data, '\0', size);
fread_s(data, size, 1, size, program);
}
else
{
printf("failed to apply by memory\n");
goto END;
}

END:
if (program)
fclose(program);

return data;
}

//解析pe结构
void AnalyzePeStruct(PCHAR fileData, PIMAGE_DOS_HEADER& dos, PIMAGE_FILE_HEADER& file, PIMAGE_OPTIONAL_HEADER32& optional, PIMAGE_SECTION_HEADER*& section)
{
//解析DOS结构
dos = (PIMAGE_DOS_HEADER)fileData;
fileData = &fileData[dos->e_lfanew + 4];

//解析FILE结构
file = (PIMAGE_FILE_HEADER)fileData;
fileData = &fileData[20];

//解析OPTIONAL结构
optional = (PIMAGE_OPTIONAL_HEADER32)fileData;
fileData = &fileData[file->SizeOfOptionalHeader];

//解析节表
section = (PIMAGE_SECTION_HEADER*)malloc(file->NumberOfSections * sizeof(IMAGE_SECTION_HEADER));
if (section != nullptr)
{
for (size_t index = 0; index < file->NumberOfSections; index++)
{
section[index] = (PIMAGE_SECTION_HEADER)fileData;
fileData = &fileData[40];
}
}
}

//缩小pe结构
LPVOID ShrinkData(LPVOID memoryData)
{
LPVOID fileData = nullptr;
LPVOID tempMemoryDataPointer = nullptr;
LPVOID tempFileDataPointer = nullptr;
int copyCharNumber = NULL;

//计算硬盘pe结构大小
size_t fileDataSize = optionalHeader->SizeOfHeaders;
for (size_t index = 0; index < fileHeader->NumberOfSections; index++)
{
fileDataSize += sectionArr[index]->SizeOfRawData;
}

//申请内存空间
fileData = malloc(fileDataSize);
if (fileData != nullptr)
{
memset(fileData, '\0', fileDataSize);
tempMemoryDataPointer = memoryData;
tempFileDataPointer = fileData;
copyCharNumber = optionalHeader->FileAlignment;

while (copyCharNumber < optionalHeader->SizeOfHeaders)
{
copyCharNumber *= 2;
}
//写入所有头+节表
memcpy_s(tempFileDataPointer, copyCharNumber, tempMemoryDataPointer, optionalHeader->SizeOfHeaders);
//写入节区
for (size_t index = 0; index < fileHeader->NumberOfSections; index++)
{
copyCharNumber = optionalHeader->FileAlignment;
tempFileDataPointer = (LPVOID)((UINT_PTR)fileData + sectionArr[index]->PointerToRawData);
tempMemoryDataPointer = (LPVOID)((UINT_PTR)memoryData + sectionArr[index]->VirtualAddress);
while (copyCharNumber < sectionArr[index]->SizeOfRawData)
{
copyCharNumber *= 2;
}
memcpy_s(tempFileDataPointer, copyCharNumber, tempMemoryDataPointer, sectionArr[index]->SizeOfRawData);
}
}
return fileData;
}

//拉伸pe结构
LPVOID StretchData(PCHAR fileData)
{
LPVOID tempMemoryDataPointer = nullptr; //拉伸后的pe结构指针(用于指向写入的位置)
PCHAR tempFileDataPointer = nullptr; //拉伸前的pe结构指针(用于指向读取的位置)
int copyCharNumber = NULL; //内存对其

//读取pe结构
AnalyzePeStruct(fileData, dosHeader, fileHeader, optionalHeader, sectionArr);
//申请内存pe结构空间
MemoryData = malloc(optionalHeader->SizeOfImage);
if (MemoryData != nullptr)
{
memset(MemoryData, '\0', optionalHeader->SizeOfImage);
tempMemoryDataPointer = MemoryData;
tempFileDataPointer = fileData;
copyCharNumber = optionalHeader->SectionAlignment;

//写入所有头+节表
while (copyCharNumber < optionalHeader->SizeOfHeaders)
{
copyCharNumber *= 0x2;
}
memcpy_s(tempMemoryDataPointer, copyCharNumber, tempFileDataPointer, optionalHeader->SizeOfHeaders);

//写入节区
for (size_t index = 0; index < fileHeader->NumberOfSections; index++)
{
copyCharNumber = optionalHeader->SectionAlignment;
tempMemoryDataPointer = (LPVOID)((UINT_PTR)MemoryData + sectionArr[index]->VirtualAddress);
tempFileDataPointer = (PCHAR)((UINT_PTR)fileData + sectionArr[index]->PointerToRawData);
while (copyCharNumber < sectionArr[index]->SizeOfRawData)
{
copyCharNumber *= 2;
}
memcpy_s(tempMemoryDataPointer, copyCharNumber, tempFileDataPointer, sectionArr[index]->SizeOfRawData);
}
}

return MemoryData;
}

//写入缩小后的pe结构
bool WriteMyFilePeData(LPVOID fileData, LPCSTR newProgramPath)
{
FILE* newProgram = nullptr;
bool result = true;
size_t size = NULL;

//打开文件
fopen_s(&newProgram, newProgramPath, "wb");
if (newProgram == nullptr)
{
printf("failed to create by program!\n");
result = false;
goto END;
}

size = _msize(fileData);
fwrite(fileData, 1, size, newProgram);



END:
if (newProgram)
fclose(newProgram);

return result;
}

//内存地址转文件地址
UINT_PTR RvaToFoa(UINT_PTR address)
{
//节区开始地址
UINT_PTR thisSectionBegin = NULL;
//节区结束地址
UINT_PTR thisSectionEnd = NULL;
//在硬盘pe结构中的位置
UINT_PTR offset = NULL;

//开始查找
for (size_t index = 0; index < fileHeader->NumberOfSections; index++)
{
//获取当前节区开始和结束地址
thisSectionBegin = (UINT_PTR)MemoryData + sectionArr[index]->VirtualAddress;
thisSectionEnd = thisSectionBegin + sectionArr[index]->Misc.VirtualSize;
//如果转换地址是这个节区的地址
if (thisSectionBegin <= address && thisSectionEnd >= address)
{
//转换为硬盘pe地址
offset = address - (UINT_PTR)MemoryData; //减去ImageBase得到在内存中的偏移
offset = offset - sectionArr[index]->VirtualAddress; //减去VirtualAddress得到地址距离节区开始的偏移
offset = offset + sectionArr[index]->PointerToRawData; //加上当前节区在硬盘PE中的偏移得到最终结果
break;
}
}
return offset;
}

bool WriteNewDataToSection(char* memoryData)
{
//写入的字节
char data[] = {
0x6A, 0x00,0x6A, 0x00, 0x6A, 0x00, 0x6A, 0x00, //push 0 push 0 push 0 push 0
0xE8,0x00,0x00,0x00,0x00, //call xxxx
0xE9,0x00,0x00,0x00,0x00 //jmp xxxx
};
//指向要写入的地方
char* pointer = nullptr;
//指向要写入data数据的地方(E8 + 1,E9 + 1)
char* tempDataPointer = nullptr;
//数值类型的pointer
UINT_PTR entryPointer = NULL;
//E8后面四个字节的值
UINT_PTR E8_X = NULL;
//E9后面四个字节的值
UINT_PTR E9_X = NULL;
//E8真正要跳转的地址
UINT_PTR message = (UINT_PTR)&MessageBoxA;
//新OEP在内存中的偏移(也是写入OEP的值)
UINT_PTR newEntryPointer = NULL;
//开始循环所有节区
for (DWORD index = 0; index < fileHeader->NumberOfSections; index++)
{
//判断节区是否可执行(这里可以使用二进制运算判断,偷懒了就不写了)
if (sectionArr[index]->Characteristics != 0x60000020)
{
continue; //如果不是可执行节区就开始下一次循环
}
//如果节区空白字节大于写入字节
if (sectionArr[index]->SizeOfRawData - (sectionArr[index]->Misc.VirtualSize + sizeof(data)) >= 0)
{
//指针指向空白节区地址
pointer = (char*)((UINT_PTR)memoryData + sectionArr[index]->VirtualAddress + sectionArr[index]->Misc.VirtualSize);
entryPointer = (UINT_PTR)pointer;

//指针指向data中的E8后面一个的位置(也就是X(视频中运算公式中的X)的最高位)
tempDataPointer = &data[9];
/* 通过运算获取X
* 公式 = 真正要跳转的地址 - 当前指令的下一行地址(在真是运行中内存的地址,不是现在代码中的地址)
* 1.message = 真正要跳转的地址
* 2.optionalHeader->ImageBase + sectionArr[index]->VirtualAddress = 真是运行中内存中节区的开始地址
* 3.entryPointer - sectionArr[index]->Misc.VirtualSize = 现在代码内存中节区的开始地址(!!!不是真实运行中的节区开始地址)
* 4.(UINT_PTR)&pointer[13] = 现在代码内存中E8指令下一行的地址
* 5. 4 - 3 = 地址在节区中的偏移(现在这个偏移跟在内存中是一致的)
* 6. 2 + 5 = 当前指令下一行的地址
*/
E8_X = message - (optionalHeader->ImageBase + sectionArr[index]->VirtualAddress + ((UINT_PTR)&pointer[13] - (entryPointer - sectionArr[index]->Misc.VirtualSize)));
//通过右移分别获取X的四个二进制八位写入到E8后面的四个字节
tempDataPointer[0] = E8_X;
tempDataPointer[1] = E8_X >> 8;
tempDataPointer[2] = E8_X >> 16;
tempDataPointer[3] = E8_X >> 24;

//以下同上
tempDataPointer = &data[14];
E9_X = (optionalHeader->ImageBase + optionalHeader->AddressOfEntryPoint) - (optionalHeader->ImageBase + sectionArr[index]->VirtualAddress + ((UINT_PTR)&pointer[18] - (entryPointer - sectionArr[index]->Misc.VirtualSize)));;
tempDataPointer[0] = E9_X;
tempDataPointer[1] = E9_X >> 8;
tempDataPointer[2] = E9_X >> 16;
tempDataPointer[3] = E9_X >> 24;

//将字节写入到拉伸后的PE结构中
memcpy_s(pointer, sizeof(data), data, sizeof(data));
/* 计算写入字节处的地址在真实运行内存中的偏移
* 公式 = 地址在节区中的偏移 + 节区开始地址
* 1.entryPointer = 写入的开始地址
* 2.entryPointer - sectionArr[index]->Misc.VirtualSize = 节区开始地址(!!!不是真实运行的内存地址)
* 3.1 - 2 = 地址在节区中的偏移(也是在真实运行内存节区的偏移)
*/
newEntryPointer = (entryPointer - (entryPointer - sectionArr[index]->Misc.VirtualSize)) + sectionArr[index]->VirtualAddress;
//修改OEP
optionalHeader->AddressOfEntryPoint = newEntryPointer;
//指针指向PE结构的OPTIONAL开始地址
pointer = &memoryData[dosHeader->e_lfanew + 4 + sizeof(*fileHeader)];
//将新的OPTIONAL结构覆盖原本的OPTIONAL结构
memcpy_s(pointer, sizeof(*optionalHeader), optionalHeader, sizeof(*optionalHeader));
return true;
}
}
return false;
}

int main()
{
char* filePe = (char*)ReadProgramData("C:\\Users\\lenovo\\Desktop\\家庭帐本\\家庭帐本 - 副本0.exe");
char* memoryPe = (char*)StretchData(filePe);
if (WriteNewDataToSection(memoryPe) != false)
{
LPVOID meFilePe = ShrinkData(memoryPe);
WriteMyFilePeData(meFilePe, "C:\\Users\\lenovo\\Desktop\\家庭帐本\\家庭帐本 - 副本0.exe");
}
else
{
printf("程序可执行节区没有空白区域可供写入!\n");
}
system("pause");
free(filePe);
free(memoryPe);
return 0;
}

运行结果

image-20241026112529178

打开后会有messagebox弹出

代码分析

全局变量声明

  1. PIMAGE_DOS_HEADER dosHeader = nullptr;
    表示DOS头结构,用于读取PE文件的起始部分信息。

  2. PIMAGE_FILE_HEADER fileHeader = nullptr;
    表示PE文件头结构,包含文件的基本信息,例如节区数量等。

  3. PIMAGE_OPTIONAL_HEADER32 optionalHeader = nullptr;
    表示PE的可选头结构,包含文件的内存大小、入口点地址等关键信息。

  4. PIMAGE_SECTION_HEADER* sectionArr = nullptr;
    表示节区头的数组,用于保存PE文件中的每个节区的信息。

  5. LPVOID MemoryData = nullptr;
    用于保存拉伸后的PE文件在内存中的表示形式。

函数分析

ReadProgramData

功能:从指定路径读取PE文件的数据并存储在内存中。

  • FILE* program:用于打开和读取PE文件。
  • fopen_s(&program, programPath, "rb"):以二进制方式打开文件,如果失败则输出错误信息并跳到END关闭文件。
  • fseekftell:分别用于获取文件的大小并将指针定位到文件开头。
  • malloc(size):分配文件大小的内存空间,并通过fread_s将文件数据读取到该内存中。

AnalyzePeStruct

功能:解析PE文件的结构,包括DOS、FILE、OPTIONAL和SECTION信息。

  • dos = (PIMAGE_DOS_HEADER)fileData:将文件数据指针转为DOS头指针。
  • file = (PIMAGE_FILE_HEADER)fileData:将偏移后的文件数据指针转为PE文件头指针。
  • optional = (PIMAGE_OPTIONAL_HEADER32)fileData:进一步偏移并解析PE的可选头信息。
  • section:通过分配内存空间和循环将文件数据指针解析为节区头数组。

ShrinkData

功能:缩小PE结构,将内存中扩展后的PE文件数据转换为硬盘中保存的PE格式。

  • fileDataSize:计算PE文件在硬盘上的总大小,包括所有头和节区的大小。
  • memset(fileData, '\0', fileDataSize):初始化内存空间,填充为0。
  • memcpy_s:首先复制所有头信息,然后逐节区复制节区内容到硬盘格式的PE文件数据。

StretchData

功能:将硬盘上的PE文件扩展为内存中的PE格式。

  • AnalyzePeStruct(fileData, ...):首先解析PE文件结构。
  • MemoryData:为拉伸后的PE结构申请内存空间。
  • memcpy_s:按内存对齐要求,将所有头信息和节区数据写入内存格式的PE文件。

WriteMyFilePeData

功能:将缩小后的PE文件数据写入指定的文件路径中。

  • fopen_s(&newProgram, newProgramPath, "wb"):以写二进制的方式打开或创建新的PE文件。
  • fwrite:将内存中的PE文件数据写入到硬盘文件中。

RvaToFoa

功能:将相对虚拟地址(RVA)转换为文件偏移地址(FOA),以便在硬盘PE文件中查找对应地址。

  • thisSectionBeginthisSectionEnd:计算当前节区的起始和结束地址。
  • 如果地址在节区范围内,则计算文件偏移(FOA)。

WriteNewDataToSection

功能:在可执行节区中插入一段自定义指令(字节数据),并更新PE的入口点。

  • data[]:定义了要插入到节区的数据(包括calljmp指令)。
  • pointer:指向PE结构中可以插入数据的节区空白区域。
  • 通过位移和地址运算,计算E8E9指令的跳转目标,并将其写入到节区中。
  • 更新PE文件的入口点,使程序从插入的新指令开始运行。

main

功能:程序入口,执行PE文件的读取、拉伸、修改、缩小并保存的流程。

  • ReadProgramData:读取PE文件。
  • StretchData:将PE文件数据拉伸到内存格式。
  • WriteNewDataToSection:将新指令数据写入可执行节区。
  • ShrinkData:缩小内存中的PE结构为硬盘格式。
  • WriteMyFilePeData:将修改后的PE文件写回硬盘