PE文件结构(三)

Last updated on October 7, 2023 pm

PE练习,FileBuffer–ImageBuffer–FileBuffer

回顾

目前我们已经知道PE文件结构大致组成部分,即

  • DOS头,主要成员

    • MZ标志
    • e_lfanew NT头的文件偏移

    NT头又可以分为三个部分 Signature 标准PE头 可选PE头

  • Signature,即“PE”标识

  • 标准PE头,主要成员

    • Machine 可运行的CPU
    • NumberOfSections 节区数量
    • TimeDateStamp 时间戳
    • Characteristics 文件属性
  • 可选PE头主要成员

    • Magic PE类型 32位 or 64位?
    • AddressOfEntryPoint 程序入口RVA
    • ImageBase 程序优先装载地址
    • SectionAlignment 内存对齐粒度
    • FileAlignment 文件对齐粒度
    • SizeOfImage 内存中PE文件映像大小
    • SizeOfHeaders 所有头包括节表按文件对齐后的大小
  • 节表

    • BYTE 8字节名字
    • Misc 在内存中真实大小
    • VirtualAddress 内存中偏移 即RVA
    • SizeOfRawData 文件对齐后尺寸
    • PointerToRawData 节区在文件中偏移
    • Characteristics 节区属性

实际上这些部分就已经可以决定一个PE文件的全部的东西了,可以通过头找到节表,由节表找到每一个节区,每一个节区就存放了运行时的信息。

练习

要求

将一个EXE文件从文件形式拉伸成内存形式,再由内存形式压缩回文件形式,存盘后能运行

note:这里的内存是指由malloc分配的堆空间地址,不是程序ImageBase的地址

需要解决的问题

Q1:要拷贝的数据在哪

A1:各种头的数据在FileBuffer最前面,而且紧紧相连,中间没有空,同时大小由SizeOfHeaders决定;节区地址可以由节表中的PointerToRawData找到

Q2:要分配多少空间来存储copy数据

A2:在可选PE头中有一个SizeOfImage 成员,它决定了在内存中PE文件的大小,因此分配SizeOfImage 空间即可

Q3:要把数据copy去哪里

A3:各种头的数据可以直接copy到ImageBuffer的前面,节区地址可以由节表中每一个结构体的VirtualAddress决定

同理从ImageBuffer拷贝到FileBuffer时,头部数据也不变,节区地址则可由节表中的PointerToRawData 决定

Q4:节区大小如何确定

A4:节区大小可与节表两个成员相关,第一个是Misc,第二个是SizeOfRawData。从图中可能会觉得SizeOfRawData一定大于Misc,其实并不一定。考虑这个情形,在编写程序时,仅声明一个全部变量char ch[1000]而并不初始化,那么在文件中就不会为这个变量分配空间存储,只有在内存中时才会分配。而Misc所指的大小就是在内存中的大小,那么这时候Misc就有可能大于SizeOfRawData

那么是不是谁大用谁呢?也不是,还是以刚刚为例,如果执意使用Misc作为要copy的节区大小,由于在文件中并没有为其分配大小,那么这时候就有可能会把下一个节区数据也copy进来,这就乱套了。

所以最终答案就是,用小的,或者只用SizeOfRawData,这样能保证精确地把该节区数据全部正确copy,即使还要需要内存,那么也是紧跟已copy数据之后,而且下一个节区地VirtualAddress保证了有足够空间

代码实现

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

DWORD Size(FILE* fp){
DWORD size = 0;
fseek(fp,0,SEEK_END);
size = ftell(fp);
fseek(fp,0,SEEK_SET);
return size;
}


char* FileToImage(char* filebuffer){

//定义一些指针
PIMAGE_DOS_HEADER DosHeader = NULL;
PIMAGE_NT_HEADERS NTHeader = NULL;
PIMAGE_FILE_HEADER FileHeader = NULL;
PIMAGE_OPTIONAL_HEADER OptionalHeader = NULL;
PIMAGE_SECTION_HEADER SectionHeader = NULL;
char* TemBuffer = NULL;

//简单判断有效性
if (!filebuffer)
{
printf("Invalid buffer!\n");
return 0;
}

if (*(PWORD)filebuffer != IMAGE_DOS_SIGNATURE)
{
printf("not a PE file!\n");
return 0;
}

//开始赋值相应指针
DosHeader = (PIMAGE_DOS_HEADER)filebuffer;
NTHeader = (PIMAGE_NT_HEADERS) (filebuffer + DosHeader->e_lfanew);
FileHeader = (PIMAGE_FILE_HEADER)(filebuffer + DosHeader->e_lfanew + 4);
OptionalHeader = (PIMAGE_OPTIONAL_HEADER)(filebuffer + DosHeader->e_lfanew + 4 + IMAGE_SIZEOF_FILE_HEADER);
SectionHeader = (PIMAGE_SECTION_HEADER)(filebuffer + DosHeader->e_lfanew + 4 + IMAGE_SIZEOF_FILE_HEADER + FileHeader->SizeOfOptionalHeader);

//申请内存中大小
TemBuffer = (char*)malloc(OptionalHeader->SizeOfImage);

//初始化并复制头部数据
memset(TemBuffer,0,OptionalHeader->SizeOfImage);
memcpy(TemBuffer,DosHeader,OptionalHeader->SizeOfHeaders);

//开始复制节区数据
for(int i=0;i<FileHeader->NumberOfSections;i++){
memcpy((TemBuffer + SectionHeader->VirtualAddress),(filebuffer + SectionHeader->PointerToRawData),SectionHeader->SizeOfRawData);
//更新为下一个节区的结构体,此方式为重新计算
SectionHeader = (PIMAGE_SECTION_HEADER)(filebuffer + DosHeader->e_lfanew + 4 + IMAGE_SIZEOF_FILE_HEADER + FileHeader->SizeOfOptionalHeader + 40 * (i + 1));
// 也可以使用这种方式更新,此方式为相对当前结构体计算
// 注意C语言中指针加法规则,这里加1指的是加1一个这个结构体大小
// SectionHeader +=1 ;

}

return TemBuffer;
}

//==========================image to file==========================
char* ImageToFile(char* imagebuffer){

//定义一些指针
PIMAGE_DOS_HEADER DosHeader = NULL;
PIMAGE_NT_HEADERS NTHeader = NULL;
PIMAGE_FILE_HEADER FileHeader = NULL;
PIMAGE_OPTIONAL_HEADER OptionalHeader = NULL;
PIMAGE_SECTION_HEADER SectionHeader = NULL;
char* TemBuffer = NULL;


if (!imagebuffer)
{
printf("Invalid buffer!\n");
return 0;
}

if (*(PWORD)imagebuffer != IMAGE_DOS_SIGNATURE)
{
printf("not a PE file!\n");
return 0;
}

DosHeader = (PIMAGE_DOS_HEADER)imagebuffer;
NTHeader = (PIMAGE_NT_HEADERS)(imagebuffer + DosHeader->e_lfanew);
FileHeader = (PIMAGE_FILE_HEADER)(imagebuffer + DosHeader->e_lfanew + 4);
OptionalHeader = (PIMAGE_OPTIONAL_HEADER)(imagebuffer + DosHeader->e_lfanew + 4 + IMAGE_SIZEOF_FILE_HEADER);
SectionHeader = (PIMAGE_SECTION_HEADER)(imagebuffer + DosHeader->e_lfanew + 4 + IMAGE_SIZEOF_FILE_HEADER + FileHeader->SizeOfOptionalHeader);

//计算原文件大小用于分配空间
DWORD new_size = OptionalHeader->SizeOfHeaders;
for (int i = 0; i < FileHeader->NumberOfSections; i++)
{
new_size += (SectionHeader + IMAGE_SIZEOF_SECTION_HEADER * i)->SizeOfRawData;
}

TemBuffer = (char*)malloc(new_size);

//初始化TemBuffer及头部
memset(TemBuffer, 0, new_size);
memcpy(TemBuffer, imagebuffer, OptionalHeader->SizeOfHeaders);

for (int i = 0; i < FileHeader->NumberOfSections; i++)
{
memcpy((TemBuffer + SectionHeader->PointerToRawData), (imagebuffer + SectionHeader->VirtualAddress), SectionHeader->SizeOfRawData);
SectionHeader = (PIMAGE_SECTION_HEADER)(imagebuffer + DosHeader->e_lfanew + 4 + IMAGE_SIZEOF_FILE_HEADER + FileHeader->SizeOfOptionalHeader + 40 * (i + 1));
//同理可用 SectionHeader +=1 ;
}

return TemBuffer;
}

int main(){
char* buffer;

FILE* fp1 = NULL;
FILE* fp2 = NULL;
errno_t err_1 = fopen_s(&fp1, "C:\\Users\\yongrin\\Desktop\\PE_info.exe", "rb");
errno_t err_2 = fopen_s(&fp2, "C:\\Users\\yongrin\\Desktop\\PE_info_changed.exe", "wb");

//将exe读入内存,是filebuffer形式
DWORD size = Size(fp1);
buffer = (char*)malloc(size);
fread(buffer,size,1,fp1);

char* newbuffer;
newbuffer = FileToImage(buffer);
newbuffer = ImageToFile(newbuffer);

fwrite(newbuffer,size,1,fp2);


return 0;
}

PE文件结构(三)
http://example.com/2023/07/02/PE_2/
Author
yring
Posted on
July 2, 2023
Licensed under