步骤

扩大节区会修改原本的节表属性,新增节不会,通常大部分加壳软件都会新增节来移动或者备份各种目录项数据

新增一个0x1000字节大小的节区

  1. 判断头中是否有空间存下一个节表,为了便于验证默认空间不足。最后一个节表后面必须跟40个字节0,所以必须有大于80个字节的空间
  2. 抹除DOS Stub,将PE标识开始移动到DOS Stub处
  3. 修改IMAGE_DOS_HEADER.e_lfanew 为0x40(DOS Stub开始处)
  4. 在最后一个节表的结尾处填充刚才删除的字节数(IMAGE_NT_HEADERS.Signature的地址 - 0x40 = 填充字节数),一定要在节表的末尾处填充,只能移动头部,节区是不能移动的。
  5. 在最后一个节表后添加一个新的节表,将其VirtualSize和SizeOfRawData修改为0x1000,将其PointerToRawData修改为上一个节的SizeOfRawData + PointerToRawData(结果也就是PE文件结尾处)。
    将其VirtualAddress修改为上一个节的VirtualAddress + VirtualSize,结果按照内存对齐
  6. 修改IMAGE_FILE_HEADER.NumberOfSections
  7. 修改IMAGE_OPTIONAL_HEADER.SizeOfImage
  8. 修改节区包含代码段和可执行属性,修改IMAGE_SECTION_HEADER.Characteristics,将节的IMAGE_SCN_CNT_CODE和IMAGE_SCN_MEM_EXECUTE标志位置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
#include<Windows.h>
#include<stdio.h>
#include<stdlib.h>

#define FILEPATH TEXT("test.exe")

/*对齐宏函数*/
#define ALIGN(x,y) ((x)+((y)-((x)%(y))))

typedef struct {
LPVOID lpBuf;
DWORD size;
}FILEINFO;

BOOL readFile(LPCTSTR filePath, FILEINFO *fileInfo) {
HANDLE hFile = CreateFile(
filePath,
GENERIC_READ,
0,
NULL,
OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL,
0
);

if (!hFile) {
OutputDebugString(TEXT("CreateFile Error\n"));
return FALSE;
}

LPVOID fileBuf;

DWORD fileSize = GetFileSize(hFile, NULL);

fileBuf = malloc(fileSize);

memset(fileBuf, 0, fileSize);

if (!ReadFile(
hFile,
fileBuf,
fileSize,
NULL,
NULL
)) {
return FALSE;
}

CloseHandle(hFile);

fileInfo->lpBuf = fileBuf;
fileInfo->size = fileSize;
return TRUE;
}

//移动PE头到DOS Stub处,扩大节表的空白区
BOOL movePEHeaderToDOSStub(LPVOID lpBuffer) {
PIMAGE_DOS_HEADER pDOS = (PIMAGE_DOS_HEADER)lpBuffer;
PIMAGE_NT_HEADERS pNT = (PIMAGE_NT_HEADERS)((DWORD)pDOS + pDOS->e_lfanew);
PIMAGE_FILE_HEADER pFile = (PIMAGE_FILE_HEADER)((DWORD)pNT + 4);
PIMAGE_OPTIONAL_HEADER pOptional = (PIMAGE_OPTIONAL_HEADER)((DWORD)pFile + 20);
PIMAGE_SECTION_HEADER pSections = (PIMAGE_SECTION_HEADER)((DWORD)pOptional + pFile->SizeOfOptionalHeader);
//定位到最后一个节头
PIMAGE_SECTION_HEADER pLastSection = (PIMAGE_SECTION_HEADER)((DWORD)pSections + (pFile->NumberOfSections - 1) * 40);

/*计算节表后的空白区,为了便于验证不作判断*/
DWORD dwSpace = ((DWORD)pDOS + pOptional->SizeOfHeaders) - ((DWORD)pLastSection + 40);

/*计算移动数据的大小(PE头到最后一个节表的结尾处)*/
DWORD dwMoveSize = ((DWORD)pSections + (pFile->NumberOfSections) * 40) - (DWORD)pNT;
/*分配临时内存,储存移动数据*/
LPVOID lpTemp = malloc(dwMoveSize);
if (!lpTemp) {
return FALSE;
}
memset(lpTemp, 0, dwMoveSize);

/*备份数据*/
memcpy(lpTemp, pNT, dwMoveSize);

/*清空原本PE头*/
memset(pNT, 0, dwMoveSize);

/*在原本DOS Stub处写入PE头到最后一个节表结束的数据
IMAGE_DOS_HEADER占64字节,偏移64字节就是DOS Stub*/
memcpy((LPVOID)(DWORD(pDOS) + 64), lpTemp, dwMoveSize);

/*修正PE头偏移*/
pDOS->e_lfanew = 0x40;

free(lpTemp);

return TRUE;
}

BOOL addNewSection(LPVOID lpBuffer) {
PIMAGE_DOS_HEADER pDOS = (PIMAGE_DOS_HEADER)lpBuffer;
PIMAGE_NT_HEADERS pNT = (PIMAGE_NT_HEADERS)((DWORD)pDOS + pDOS->e_lfanew);
PIMAGE_FILE_HEADER pFile = (PIMAGE_FILE_HEADER)((DWORD)pNT + 4);
PIMAGE_OPTIONAL_HEADER pOptional = (PIMAGE_OPTIONAL_HEADER)((DWORD)pFile + 20);
PIMAGE_SECTION_HEADER pSections = (PIMAGE_SECTION_HEADER)((DWORD)pOptional + pFile->SizeOfOptionalHeader);
//定位到最后一个节头
PIMAGE_SECTION_HEADER pLastSection = (PIMAGE_SECTION_HEADER)((DWORD)pSections + (pFile->NumberOfSections - 1) * 40);

/*新增节的地址*/
PIMAGE_SECTION_HEADER pNewSection = PIMAGE_SECTION_HEADER((DWORD)pLastSection + 40);

memcpy(pNewSection, pLastSection, 40);

/*增加节的记数*/
pFile->NumberOfSections = pFile->NumberOfSections + 1;

/*修改内存和文件对齐后的大小*/
pNewSection->SizeOfRawData = 0x1000;
pNewSection->Misc.VirtualSize = 0x1000;

/*上一个节在文件中的偏移加上节在文件中的大小
也就是文件末尾*/
pNewSection->PointerToRawData =
pLastSection->PointerToRawData + pLastSection->SizeOfRawData;

/*内存偏移同理,但要内存对齐*/
pNewSection->VirtualAddress =
ALIGN((pLastSection->VirtualAddress + pLastSection->Misc.VirtualSize), pOptional->SectionAlignment);

/*修改包含代码段和可执行属性*/
pNewSection->Characteristics =
(pNewSection->Characteristics | IMAGE_SCN_CNT_CODE) | IMAGE_SCN_MEM_EXECUTE;

/*修改内存*/
pOptional->SizeOfImage =
ALIGN((pOptional->SizeOfImage + 0x1000), pOptional->SectionAlignment);

return TRUE;

}

BOOL writeFile(FILEINFO *fileInfo) {
LPVOID lpBuffer = fileInfo->lpBuf;
LPVOID lpTemp;
DWORD dwSize = fileInfo->size;

HANDLE hFile = CreateFile(
FILEPATH,
GENERIC_WRITE,
0,
NULL,
CREATE_ALWAYS,
FILE_ATTRIBUTE_NORMAL,
0
);

if (!hFile) {
return FALSE;
}

SetFilePointer(hFile, 0, 0, FILE_BEGIN);

if (!WriteFile(
hFile,
lpBuffer,
dwSize,
NULL,
NULL
)) {
CloseHandle(hFile);
return FALSE;
}

/**/
lpTemp = malloc(0x1000);

if (!lpTemp) {
CloseHandle(hFile);
return FALSE;
}

memset(lpTemp, 0, 0x1000);

/*追加写入新的节区*/
SetFilePointer(hFile, 0, 0, FILE_END);
if (!WriteFile(
hFile,
lpTemp,
0x1000,
NULL,
NULL
)) {
CloseHandle(hFile);
free(lpTemp);
return FALSE;
}

CloseHandle(hFile);
free(lpTemp);
return TRUE;
}

int main(int argc, char *argv[]) {
HANDLE hFile;
LPVOID zeroMem;
FILEINFO fileInfo;

readFile(FILEPATH, &fileInfo);

/*判断节表空白区大小,如果不足则抹除DOS Stub*/
movePEHeaderToDOSStub(fileInfo.lpBuf);

/*在最后一个节表后面新增节*/
addNewSection(fileInfo.lpBuf);

/*写入新的PE文件*/
writeFile(&fileInfo);

free(fileInfo.lpBuf);

}

修改PE文件后程序可以正常执行,DOS Stub消失了,PE头直接从0x40开始。并且NumberOfSection也增加了1个,原本为0x9。

节表的内存大小和SizeOfImage一定要按照拓展PE头里的SectionAlignment内存对齐,否则是无法正常执行的。