VirtualBox

source: vbox/trunk/src/bldprogs/VBoxPeSetVersion.cpp@ 99039

Last change on this file since 99039 was 98103, checked in by vboxsync, 22 months ago

Copyright year updates by scm.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 17.2 KB
Line 
1/* $Id: VBoxPeSetVersion.cpp 98103 2023-01-17 14:15:46Z vboxsync $ */
2/** @file
3 * IPRT - Change the OS and SubSystem version to value suitable for NT v3.1.
4 *
5 * Also make sure the IAT is writable, since NT v3.1 expects this. These are
6 * tricks necessary to make binaries created by newer Visual C++ linkers work
7 * on ancient NT version like W2K, NT4 and NT 3.x.
8 */
9
10/*
11 * Copyright (C) 2012-2023 Oracle and/or its affiliates.
12 *
13 * This file is part of VirtualBox base platform packages, as
14 * available from https://www.virtualbox.org.
15 *
16 * This program is free software; you can redistribute it and/or
17 * modify it under the terms of the GNU General Public License
18 * as published by the Free Software Foundation, in version 3 of the
19 * License.
20 *
21 * This program is distributed in the hope that it will be useful, but
22 * WITHOUT ANY WARRANTY; without even the implied warranty of
23 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
24 * General Public License for more details.
25 *
26 * You should have received a copy of the GNU General Public License
27 * along with this program; if not, see <https://www.gnu.org/licenses>.
28 *
29 * SPDX-License-Identifier: GPL-3.0-only
30 */
31
32
33/*********************************************************************************************************************************
34* Header Files *
35*********************************************************************************************************************************/
36#include <iprt/formats/mz.h>
37#include <iprt/formats/pecoff.h>
38#include <stdio.h>
39#include <stdlib.h>
40#include <string.h>
41
42
43/*********************************************************************************************************************************
44* Defined Constants And Macros *
45*********************************************************************************************************************************/
46#define MK_VER(a_uHi, a_uLo) ( ((a_uHi) << 8) | (a_uLo))
47
48
49/*********************************************************************************************************************************
50* Global Variables *
51*********************************************************************************************************************************/
52static const char *g_pszFilename;
53static unsigned g_cVerbosity = 0;
54
55
56static int Error(const char *pszFormat, ...)
57{
58 va_list va;
59 va_start(va, pszFormat);
60 char szTmp[1024];
61 _vsnprintf(szTmp, sizeof(szTmp), pszFormat, va);
62 va_end(va);
63 fprintf(stderr, "VBoxPeSetVersion: %s: error: %s\n", g_pszFilename, szTmp);
64 return RTEXITCODE_FAILURE;
65}
66
67
68static void Info(unsigned iLevel, const char *pszFormat, ...)
69{
70 if (iLevel <= g_cVerbosity)
71 {
72 va_list va;
73 va_start(va, pszFormat);
74 char szTmp[1024];
75 _vsnprintf(szTmp, sizeof(szTmp), pszFormat, va);
76 va_end(va);
77 fprintf(stderr, "VBoxPeSetVersion: %s: info: %s\n", g_pszFilename, szTmp);
78 }
79}
80
81
82static int UpdateFile(FILE *pFile, unsigned uNtVersion, PIMAGE_SECTION_HEADER *ppaShdr)
83{
84 /*
85 * Locate and read the PE header.
86 *
87 * Note! We'll be reading the 64-bit size even for 32-bit since the difference
88 * is 16 bytes, which is less than a section header, so it won't be a problem.
89 */
90 unsigned long offNtHdrs;
91 {
92 IMAGE_DOS_HEADER MzHdr;
93 if (fread(&MzHdr, sizeof(MzHdr), 1, pFile) != 1)
94 return Error("Failed to read MZ header: %s", strerror(errno));
95 if (MzHdr.e_magic != IMAGE_DOS_SIGNATURE)
96 return Error("Invalid MZ magic: %#x", MzHdr.e_magic);
97 offNtHdrs = MzHdr.e_lfanew;
98 }
99
100 if (fseek(pFile, offNtHdrs, SEEK_SET) != 0)
101 return Error("Failed to seek to PE header at %#lx: %s", offNtHdrs, strerror(errno));
102 union
103 {
104 IMAGE_NT_HEADERS32 x32;
105 IMAGE_NT_HEADERS64 x64;
106 } NtHdrs,
107 NtHdrsNew;
108 if (fread(&NtHdrs, sizeof(NtHdrs), 1, pFile) != 1)
109 return Error("Failed to read PE header at %#lx: %s", offNtHdrs, strerror(errno));
110
111 /*
112 * Validate it a little bit.
113 */
114 if (NtHdrs.x32.Signature != IMAGE_NT_SIGNATURE)
115 return Error("Invalid PE signature: %#x", NtHdrs.x32.Signature);
116 uint32_t cbNewHdrs;
117 if (NtHdrs.x32.FileHeader.Machine == IMAGE_FILE_MACHINE_AMD64)
118 {
119 if (NtHdrs.x64.FileHeader.SizeOfOptionalHeader != sizeof(NtHdrs.x64.OptionalHeader))
120 return Error("Invalid optional header size: %#x", NtHdrs.x64.FileHeader.SizeOfOptionalHeader);
121 if (NtHdrs.x64.OptionalHeader.Magic != IMAGE_NT_OPTIONAL_HDR64_MAGIC)
122 return Error("Invalid optional header magic: %#x", NtHdrs.x64.OptionalHeader.Magic);
123 if (!uNtVersion)
124 uNtVersion = MK_VER(5, 2);
125 else if (uNtVersion < MK_VER(5, 2))
126 return Error("Selected version is too old for AMD64: %u.%u", uNtVersion >> 8, uNtVersion & 0xff);
127 cbNewHdrs = sizeof(NtHdrsNew.x64);
128 }
129 else if (NtHdrs.x32.FileHeader.Machine != IMAGE_FILE_MACHINE_I386)
130 return Error("Not I386 or AMD64 machine: %#x", NtHdrs.x32.FileHeader.Machine);
131 else
132 {
133 if (NtHdrs.x32.FileHeader.SizeOfOptionalHeader != sizeof(NtHdrs.x32.OptionalHeader))
134 return Error("Invalid optional header size: %#x", NtHdrs.x32.FileHeader.SizeOfOptionalHeader);
135 if (NtHdrs.x32.OptionalHeader.Magic != IMAGE_NT_OPTIONAL_HDR32_MAGIC)
136 return Error("Invalid optional header magic: %#x", NtHdrs.x32.OptionalHeader.Magic);
137 if (!uNtVersion)
138 uNtVersion = MK_VER(3, 10);
139 cbNewHdrs = sizeof(NtHdrsNew.x32);
140 }
141
142 /*
143 * Do the header modifications.
144 */
145 memcpy(&NtHdrsNew, &NtHdrs, sizeof(NtHdrsNew));
146 NtHdrsNew.x32.OptionalHeader.MajorOperatingSystemVersion = uNtVersion >> 8;
147 NtHdrsNew.x32.OptionalHeader.MinorOperatingSystemVersion = uNtVersion & 0xff;
148 NtHdrsNew.x32.OptionalHeader.MajorSubsystemVersion = uNtVersion >> 8;
149 NtHdrsNew.x32.OptionalHeader.MinorSubsystemVersion = uNtVersion & 0xff;
150 AssertCompileMembersAtSameOffset(IMAGE_NT_HEADERS32, OptionalHeader.MajorOperatingSystemVersion, IMAGE_NT_HEADERS64, OptionalHeader.MajorOperatingSystemVersion);
151 AssertCompileMembersAtSameOffset(IMAGE_NT_HEADERS32, OptionalHeader.MinorOperatingSystemVersion, IMAGE_NT_HEADERS64, OptionalHeader.MinorOperatingSystemVersion);
152 AssertCompileMembersAtSameOffset(IMAGE_NT_HEADERS32, OptionalHeader.MajorSubsystemVersion, IMAGE_NT_HEADERS64, OptionalHeader.MajorSubsystemVersion);
153 AssertCompileMembersAtSameOffset(IMAGE_NT_HEADERS32, OptionalHeader.MinorSubsystemVersion, IMAGE_NT_HEADERS64, OptionalHeader.MinorSubsystemVersion);
154
155 if (uNtVersion <= MK_VER(3, 50))
156 {
157 NtHdrsNew.x32.OptionalHeader.MajorOperatingSystemVersion = 1;
158 NtHdrsNew.x32.OptionalHeader.MinorOperatingSystemVersion = 0;
159 AssertCompileMembersAtSameOffset(IMAGE_NT_HEADERS32, OptionalHeader.MajorOperatingSystemVersion, IMAGE_NT_HEADERS64, OptionalHeader.MajorOperatingSystemVersion);
160 AssertCompileMembersAtSameOffset(IMAGE_NT_HEADERS32, OptionalHeader.MinorOperatingSystemVersion, IMAGE_NT_HEADERS64, OptionalHeader.MinorOperatingSystemVersion);
161 }
162
163 if (memcmp(&NtHdrsNew, &NtHdrs, sizeof(NtHdrs)))
164 {
165 /** @todo calc checksum. */
166 NtHdrsNew.x32.OptionalHeader.CheckSum = 0;
167 AssertCompileMembersAtSameOffset(IMAGE_NT_HEADERS32, OptionalHeader.MinorOperatingSystemVersion, IMAGE_NT_HEADERS64, OptionalHeader.MinorOperatingSystemVersion);
168
169 if ( NtHdrsNew.x32.OptionalHeader.MajorOperatingSystemVersion != NtHdrs.x32.OptionalHeader.MajorOperatingSystemVersion
170 || NtHdrsNew.x32.OptionalHeader.MinorOperatingSystemVersion != NtHdrs.x32.OptionalHeader.MinorOperatingSystemVersion)
171 Info(1,"OperatingSystemVersion %u.%u -> %u.%u",
172 NtHdrs.x32.OptionalHeader.MajorOperatingSystemVersion, NtHdrs.x32.OptionalHeader.MinorOperatingSystemVersion,
173 NtHdrsNew.x32.OptionalHeader.MajorOperatingSystemVersion, NtHdrsNew.x32.OptionalHeader.MinorOperatingSystemVersion);
174 if ( NtHdrsNew.x32.OptionalHeader.MajorSubsystemVersion != NtHdrs.x32.OptionalHeader.MajorSubsystemVersion
175 || NtHdrsNew.x32.OptionalHeader.MinorSubsystemVersion != NtHdrs.x32.OptionalHeader.MinorSubsystemVersion)
176 Info(1,"SubsystemVersion %u.%u -> %u.%u",
177 NtHdrs.x32.OptionalHeader.MajorSubsystemVersion, NtHdrs.x32.OptionalHeader.MinorSubsystemVersion,
178 NtHdrsNew.x32.OptionalHeader.MajorSubsystemVersion, NtHdrsNew.x32.OptionalHeader.MinorSubsystemVersion);
179
180 if (fseek(pFile, offNtHdrs, SEEK_SET) != 0)
181 return Error("Failed to seek to PE header at %#lx: %s", offNtHdrs, strerror(errno));
182 if (fwrite(&NtHdrsNew, cbNewHdrs, 1, pFile) != 1)
183 return Error("Failed to write PE header at %#lx: %s", offNtHdrs, strerror(errno));
184 }
185
186 /*
187 * Make the IAT writable for NT 3.1 and drop the non-cachable flag from .bss.
188 *
189 * The latter is a trick we use to prevent the linker from merging .data and .bss,
190 * because NT 3.1 does not honor Misc.VirtualSize and won't zero padd the .bss part
191 * if it's not zero padded in the file. This seemed simpler than adding zero padding.
192 */
193 if ( uNtVersion <= MK_VER(3, 10)
194 && NtHdrsNew.x32.FileHeader.NumberOfSections > 0)
195 {
196 uint32_t cbShdrs = sizeof(IMAGE_SECTION_HEADER) * NtHdrsNew.x32.FileHeader.NumberOfSections;
197 PIMAGE_SECTION_HEADER paShdrs = (PIMAGE_SECTION_HEADER)calloc(1, cbShdrs);
198 if (!paShdrs)
199 return Error("Out of memory");
200 *ppaShdr = paShdrs;
201
202 unsigned long offShdrs = offNtHdrs
203 + RT_UOFFSETOF_DYN(IMAGE_NT_HEADERS32,
204 OptionalHeader.DataDirectory[NtHdrsNew.x32.OptionalHeader.NumberOfRvaAndSizes]);
205 if (fseek(pFile, offShdrs, SEEK_SET) != 0)
206 return Error("Failed to seek to section headers at %#lx: %s", offShdrs, strerror(errno));
207 if (fread(paShdrs, cbShdrs, 1, pFile) != 1)
208 return Error("Failed to read section headers at %#lx: %s", offShdrs, strerror(errno));
209
210 bool fFoundBss = false;
211 uint32_t uRvaEnd = NtHdrsNew.x32.OptionalHeader.SizeOfImage;
212 uint32_t uRvaIat = NtHdrsNew.x32.OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IAT].Size > 0
213 ? NtHdrsNew.x32.OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IAT].VirtualAddress : UINT32_MAX;
214 uint32_t i = NtHdrsNew.x32.FileHeader.NumberOfSections;
215 while (i-- > 0)
216 if (!(paShdrs[i].Characteristics & IMAGE_SCN_TYPE_NOLOAD))
217 {
218 bool fModified = false;
219 if (uRvaIat >= paShdrs[i].VirtualAddress && uRvaIat < uRvaEnd)
220 {
221 if (!(paShdrs[i].Characteristics & IMAGE_SCN_MEM_WRITE))
222 {
223 paShdrs[i].Characteristics |= IMAGE_SCN_MEM_WRITE;
224 fModified = true;
225 }
226 uRvaIat = UINT32_MAX;
227 }
228
229 if ( !fFoundBss
230 && strcmp((const char *)paShdrs[i].Name, ".bss") == 0)
231 {
232 if (paShdrs[i].Characteristics & IMAGE_SCN_MEM_NOT_CACHED)
233 {
234 paShdrs[i].Characteristics &= ~IMAGE_SCN_MEM_NOT_CACHED;
235 fModified = true;
236 }
237 fFoundBss = true;
238 }
239
240 if (fModified)
241 {
242 unsigned long offShdr = offShdrs + i * sizeof(IMAGE_SECTION_HEADER);
243 if (fseek(pFile, offShdr, SEEK_SET) != 0)
244 return Error("Failed to seek to section header #%u at %#lx: %s", i, offShdr, strerror(errno));
245 if (fwrite(&paShdrs[i], sizeof(IMAGE_SECTION_HEADER), 1, pFile) != 1)
246 return Error("Failed to write %8.8s section header header at %#lx: %s",
247 paShdrs[i].Name, offShdr, strerror(errno));
248 if (uRvaIat == UINT32_MAX && fFoundBss)
249 break;
250 }
251
252 /* Advance */
253 uRvaEnd = paShdrs[i].VirtualAddress;
254 }
255
256 }
257
258 return RTEXITCODE_SUCCESS;
259}
260
261
262static int Usage(FILE *pOutput)
263{
264 fprintf(pOutput,
265 "Usage: VBoxPeSetVersion [options] <PE-image>\n"
266 "Options:\n"
267 " -v, --verbose\n"
268 " Increases verbosity.\n"
269 " -q, --quiet\n"
270 " Quiet operation (default).\n"
271 " --nt31, --nt350, --nt351, --nt4, --w2k, --xp, --w2k3, --vista,\n"
272 " --w7, --w8, --w81, --w10\n"
273 " Which version to set. Default: --nt31\n"
274 );
275 return RTEXITCODE_SYNTAX;
276}
277
278
279/** @todo Rewrite this so it can take options and print out error messages. */
280int main(int argc, char **argv)
281{
282 /*
283 * Parse arguments.
284 * This stucks
285 */
286 unsigned uNtVersion = 0;
287 const char *pszFilename = NULL;
288 bool fAcceptOptions = true;
289 for (int i = 1; i < argc; i++)
290 {
291 const char *psz = argv[i];
292 if (fAcceptOptions && *psz == '-')
293 {
294 char ch = psz[1];
295 psz += 2;
296 if (ch == '-')
297 {
298 if (!*psz)
299 {
300 fAcceptOptions = false;
301 continue;
302 }
303
304 if (strcmp(psz, "verbose") == 0)
305 ch = 'v';
306 else if (strcmp(psz, "quiet") == 0)
307 ch = 'q';
308 else if (strcmp(psz, "help") == 0)
309 ch = 'h';
310 else if (strcmp(psz, "version") == 0)
311 ch = 'V';
312 else
313 {
314 if (strcmp(psz, "nt31") == 0)
315 uNtVersion = MK_VER(3,10);
316 else if (strcmp(psz, "nt350") == 0)
317 uNtVersion = MK_VER(3,50);
318 else if (strcmp(psz, "nt351") == 0)
319 uNtVersion = MK_VER(3,51);
320 else if (strcmp(psz, "nt4") == 0)
321 uNtVersion = MK_VER(4,0);
322 else if (strcmp(psz, "w2k") == 0)
323 uNtVersion = MK_VER(5,0);
324 else if (strcmp(psz, "xp") == 0)
325 uNtVersion = MK_VER(5,1);
326 else if (strcmp(psz, "w2k3") == 0)
327 uNtVersion = MK_VER(5,2);
328 else if (strcmp(psz, "vista") == 0)
329 uNtVersion = MK_VER(6,0);
330 else if (strcmp(psz, "w7") == 0)
331 uNtVersion = MK_VER(6,1);
332 else if (strcmp(psz, "w8") == 0)
333 uNtVersion = MK_VER(6,2);
334 else if (strcmp(psz, "w81") == 0)
335 uNtVersion = MK_VER(6,3);
336 else if (strcmp(psz, "w10") == 0)
337 uNtVersion = MK_VER(10,0);
338 else
339 {
340 fprintf(stderr, "VBoxPeSetVersion: syntax error: Unknown option: --%s\n", psz);
341 return RTEXITCODE_SYNTAX;
342 }
343 continue;
344 }
345 psz = " ";
346 }
347 do
348 {
349 switch (ch)
350 {
351 case 'q':
352 g_cVerbosity = 0;
353 break;
354 case 'v':
355 g_cVerbosity++;
356 break;
357 case 'V':
358 printf("2.0\n");
359 return RTEXITCODE_SUCCESS;
360 case 'h':
361 Usage(stdout);
362 return RTEXITCODE_SUCCESS;
363 default:
364 fprintf(stderr, "VBoxPeSetVersion: syntax error: Unknown option: -%c\n", ch ? ch : ' ');
365 return RTEXITCODE_SYNTAX;
366 }
367 } while ((ch = *psz++) != '\0');
368
369 }
370 else if (!pszFilename)
371 pszFilename = psz;
372 else
373 {
374 fprintf(stderr, "VBoxPeSetVersion: syntax error: More than one PE-image specified!\n");
375 return RTEXITCODE_SYNTAX;
376 }
377 }
378
379 if (!pszFilename)
380 {
381 fprintf(stderr, "VBoxPeSetVersion: syntax error: No PE-image specified!\n");
382 return RTEXITCODE_SYNTAX;
383 }
384 g_pszFilename = pszFilename;
385
386 /*
387 * Process the file.
388 */
389 int rcExit;
390 FILE *pFile = fopen(pszFilename, "r+b");
391 if (pFile)
392 {
393 PIMAGE_SECTION_HEADER paShdrs = NULL;
394 rcExit = UpdateFile(pFile, uNtVersion, &paShdrs);
395 if (paShdrs)
396 free(paShdrs);
397 if (fclose(pFile) != 0)
398 rcExit = Error("fclose failed on '%s': %s", pszFilename, strerror(errno));
399 }
400 else
401 rcExit = Error("Failed to open '%s' for updating: %s", pszFilename, strerror(errno));
402 return rcExit;
403}
404
Note: See TracBrowser for help on using the repository browser.

© 2024 Oracle Support Privacy / Do Not Sell My Info Terms of Use Trademark Policy Automated Access Etiquette