main
cs 461 lines 20.3 KB
Raw
1 // Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3 namespace WixToolset.Core.Burn.Bundles
4 {
5 using System;
6 using System.Collections.Generic;
7 using System.Diagnostics;
8 using System.IO;
9 using WixToolset.Data;
10 using WixToolset.Extensibility.Services;
11
12 /// <summary>
13 /// Common functionality for Burn PE Writer &amp; Reader for the WiX toolset.
14 /// </summary>
15 /// <remarks>This class encapsulates common functionality related to
16 /// bundled/chained setup packages.</remarks>
17 /// <example>
18 /// </example>
19 internal abstract class BurnCommon : IDisposable
20 {
21 public const string BurnV3Namespace = "http://schemas.microsoft.com/wix/2008/Burn";
22 public const string BurnNamespace = "http://wixtoolset.org/schemas/v4/2008/Burn";
23 public const string BurnUXContainerEmbeddedIdFormat = "u{0}";
24 public const string BurnAuthoredContainerEmbeddedIdFormat = "a{0}";
25
26 public const string BADataFileName = "BootstrapperApplicationData.xml";
27
28 public const string BootstrapperExtensionDataFileName = "BootstrapperExtensionData.xml";
29
30 // See WinNT.h for details about the PE format, including the
31 // structure and offsets for IMAGE_DOS_HEADER, IMAGE_NT_HEADERS32,
32 // IMAGE_FILE_HEADER, etc.
33 protected const uint IMAGE_DOS_HEADER_SIZE = 64;
34 protected const uint IMAGE_DOS_HEADER_OFFSET_MAGIC = 0;
35 protected const uint IMAGE_DOS_HEADER_OFFSET_NTHEADER = 60;
36
37 protected const uint IMAGE_NT_HEADER_SIZE = 24; // signature DWORD (4) + IMAGE_FILE_HEADER (20)
38 protected const uint IMAGE_NT_HEADER_OFFSET_SIGNATURE = 0;
39 protected const uint IMAGE_NT_HEADER_OFFSET_MACHINE = 4;
40 protected const uint IMAGE_NT_HEADER_OFFSET_NUMBEROFSECTIONS = 6;
41 protected const uint IMAGE_NT_HEADER_OFFSET_SIZEOFOPTIONALHEADER = 20;
42
43 protected const uint IMAGE_OPTIONAL_OFFSET_CHECKSUM = 4 * 16; // checksum is 16 DWORDs into IMAGE_OPTIONAL_HEADER which is right after the IMAGE_NT_HEADER.
44 protected const uint IMAGE_OPTIONAL_NEGATIVE_OFFSET_CERTIFICATETABLE = (IMAGE_DATA_DIRECTORY_SIZE * (IMAGE_NUMBEROF_DIRECTORY_ENTRIES - IMAGE_DIRECTORY_ENTRY_SECURITY));
45
46 protected const uint IMAGE_SECTION_HEADER_SIZE = 40;
47 protected const uint IMAGE_SECTION_HEADER_OFFSET_NAME = 0;
48 protected const uint IMAGE_SECTION_HEADER_OFFSET_VIRTUALSIZE = 8;
49 protected const uint IMAGE_SECTION_HEADER_OFFSET_SIZEOFRAWDATA = 16;
50 protected const uint IMAGE_SECTION_HEADER_OFFSET_POINTERTORAWDATA = 20;
51
52 protected const uint IMAGE_DATA_DIRECTORY_SIZE = 8; // struct of two DWORDs.
53 protected const uint IMAGE_DIRECTORY_ENTRY_SECURITY = 4;
54 protected const uint IMAGE_NUMBEROF_DIRECTORY_ENTRIES = 16;
55
56 protected const ushort IMAGE_DOS_SIGNATURE = 0x5A4D;
57 protected const uint IMAGE_NT_SIGNATURE = 0x00004550;
58 protected const ulong IMAGE_SECTION_WIXBURN_NAME = 0x6E7275627869772E; // ".wixburn", as a qword.
59
60 public const ushort IMAGE_FILE_MACHINE_AM33 = 0x1D3;
61 public const ushort IMAGE_FILE_MACHINE_AMD64 = 0x8664;
62 public const ushort IMAGE_FILE_MACHINE_ARM = 0x1C0;
63 public const ushort IMAGE_FILE_MACHINE_ARM64 = 0xAA64;
64 public const ushort IMAGE_FILE_MACHINE_ARMNT = 0x1C4;
65 public const ushort IMAGE_FILE_MACHINE_EBC = 0xEBC;
66 public const ushort IMAGE_FILE_MACHINE_I386 = 0x14C;
67 public const ushort IMAGE_FILE_MACHINE_IA64 = 0x200;
68 public const ushort IMAGE_FILE_MACHINE_LOONGARCH32 = 0x6232;
69 public const ushort IMAGE_FILE_MACHINE_LOONGARCH64 = 0x6264;
70 public const ushort IMAGE_FILE_MACHINE_M32R = 0x9041;
71 public const ushort IMAGE_FILE_MACHINE_MIPS16 = 0x266;
72 public const ushort IMAGE_FILE_MACHINE_MIPSFPU = 0x366;
73 public const ushort IMAGE_FILE_MACHINE_MIPSFPU16 = 0x466;
74 public const ushort IMAGE_FILE_MACHINE_POWERPC = 0x1F0;
75 public const ushort IMAGE_FILE_MACHINE_POWERPCFP = 0x1F1;
76 public const ushort IMAGE_FILE_MACHINE_R4000 = 0x166;
77 public const ushort IMAGE_FILE_MACHINE_RISCV32 = 0x5032;
78 public const ushort IMAGE_FILE_MACHINE_RISCV64 = 0x5064;
79 public const ushort IMAGE_FILE_MACHINE_RISCV128 = 0x5128;
80 public const ushort IMAGE_FILE_MACHINE_SH3 = 0x1A2;
81 public const ushort IMAGE_FILE_MACHINE_SH3DSP = 0x1A3;
82 public const ushort IMAGE_FILE_MACHINE_SH4 = 0x1A6;
83 public const ushort IMAGE_FILE_MACHINE_SH5 = 0x1A8;
84 public const ushort IMAGE_FILE_MACHINE_THUMB = 0x1C2;
85 public const ushort IMAGE_FILE_MACHINE_WCEMIPSV2 = 0x169;
86
87 // The ".wixburn" section contains:
88 // 0- 3: magic number
89 // 4- 7: version
90 // 8-23: bundle GUID
91 // 24-27: engine (stub) size
92 // 28-31: original checksum
93 // 32-35: original signature offset
94 // 36-39: original signature size
95 // 40-43: container type (1 = CAB)
96 // 44-47: container count
97 // 48-51: byte count of manifest + UX container
98 // 52-512: byte count of attached containers (4 bytes for each container)
99 protected const uint BURN_SECTION_OFFSET_MAGIC = 0;
100 protected const uint BURN_SECTION_OFFSET_VERSION = 4;
101 protected const uint BURN_SECTION_OFFSET_BUNDLEGUID = 8;
102 protected const uint BURN_SECTION_OFFSET_STUBSIZE = 24;
103 protected const uint BURN_SECTION_OFFSET_ORIGINALCHECKSUM = 28;
104 protected const uint BURN_SECTION_OFFSET_ORIGINALSIGNATUREOFFSET = 32;
105 protected const uint BURN_SECTION_OFFSET_ORIGINALSIGNATURESIZE = 36;
106 protected const uint BURN_SECTION_OFFSET_FORMAT = 40;
107 protected const uint BURN_SECTION_OFFSET_COUNT = 44;
108 protected const uint BURN_SECTION_OFFSET_UXSIZE = 48;
109 protected const uint BURN_SECTION_OFFSET_ATTACHEDCONTAINERSIZE0 = 52;
110 protected const uint BURN_SECTION_MIN_SIZE = BURN_SECTION_OFFSET_ATTACHEDCONTAINERSIZE0;
111
112 // Keep in sync with burn\engine\inc\engine.h
113 protected const uint BURN_SECTION_MAGIC = 0x00f14300;
114 protected const uint BURN_SECTION_VERSION = 0x00000002;
115 public const uint BURN_PROTOCOL_VERSION = 0x00000001;
116
117 protected string fileExe;
118 protected uint peOffset = UInt32.MaxValue;
119 protected ushort sections = UInt16.MaxValue;
120 protected uint firstSectionOffset = UInt32.MaxValue;
121 protected uint checksumOffset;
122 protected uint certificateTableSignatureOffset;
123 protected uint certificateTableSignatureSize;
124 protected uint wixburnDataOffset = UInt32.MaxValue;
125 protected uint wixburnRawDataSize;
126 protected uint wixburnMaxContainers;
127
128 // TODO: does this enum exist in another form somewhere?
129 /// <summary>
130 /// The types of attached containers that BurnWriter supports.
131 /// </summary>
132 public enum Container
133 {
134 Nothing = 0,
135 UX,
136 Attached,
137 }
138
139 /// <summary>
140 /// Creates a BurnCommon for re-writing a PE file.
141 /// </summary>
142 /// <param name="messaging"></param>
143 /// <param name="fileExe">File to modify in-place.</param>
144 public BurnCommon(IMessaging messaging, string fileExe)
145 {
146 this.Messaging = messaging;
147 this.fileExe = fileExe;
148 this.AttachedContainers = new List<ContainerSlot>();
149 }
150
151 public bool Invalid { get; protected set; }
152 public ushort MachineType { get; private set; }
153 public uint Checksum { get; protected set; }
154 public uint SignatureOffset { get; protected set; }
155 public uint SignatureSize { get; protected set; }
156 public uint Version { get; protected set; }
157 public Guid BundleId { get; protected set; }
158 public uint StubSize { get; protected set; }
159 public uint OriginalChecksum { get; protected set; }
160 public uint OriginalSignatureOffset { get; protected set; }
161 public uint OriginalSignatureSize { get; protected set; }
162 public uint EngineSize { get; protected set; }
163 public uint UXAddress { get { return this.StubSize; } }
164 public List<ContainerSlot> AttachedContainers { get; protected set; }
165
166 protected IMessaging Messaging { get; }
167
168 public void Dispose()
169 {
170 this.Dispose(true);
171
172 GC.SuppressFinalize(this);
173 }
174
175 /// <summary>
176 /// Copies one stream to another.
177 /// </summary>
178 /// <param name="input">Input stream.</param>
179 /// <param name="output">Output stream.</param>
180 /// <param name="size">Optional count of bytes to copy. 0 indicates whole input stream from current should be copied.</param>
181 protected static int CopyStream(Stream input, Stream output, int size)
182 {
183 var bytes = new byte[4096];
184 var total = 0;
185 do
186 {
187 var read = Math.Min(bytes.Length, size - total);
188 read = input.Read(bytes, 0, read);
189 if (0 == read)
190 {
191 break;
192 }
193
194 output.Write(bytes, 0, read);
195 total += read;
196 } while (0 == size || total < size);
197
198 return total;
199 }
200
201 /// <summary>
202 /// Initialize the common information about a Burn engine.
203 /// </summary>
204 /// <param name="reader">Binary reader open against a Burn engine.</param>
205 protected void Initialize(BinaryReader reader)
206 {
207 this.Invalid = !this.TryInitialize(reader);
208 }
209
210 private bool TryInitialize(BinaryReader reader)
211 {
212 if (!this.GetWixburnSectionInfo(reader))
213 {
214 return false;
215 }
216
217 reader.BaseStream.Seek(this.wixburnDataOffset, SeekOrigin.Begin);
218 var bytes = reader.ReadBytes((int)this.wixburnRawDataSize);
219
220 var uint32 = BurnCommon.ReadUInt32(bytes, BURN_SECTION_OFFSET_MAGIC);
221 if (BURN_SECTION_MAGIC != uint32)
222 {
223 this.Messaging.Write(ErrorMessages.InvalidBundle(this.fileExe));
224 return false;
225 }
226
227 this.Version = BurnCommon.ReadUInt32(bytes, BURN_SECTION_OFFSET_VERSION);
228 if (BURN_SECTION_VERSION != this.Version)
229 {
230 this.Messaging.Write(BurnBackendErrors.IncompatibleWixBurnSection(this.fileExe, this.Version));
231 return false;
232 }
233
234 uint32 = BurnCommon.ReadUInt32(bytes, BURN_SECTION_OFFSET_FORMAT); // We only know how to deal with CABs right now
235 if (1 != uint32)
236 {
237 this.Messaging.Write(ErrorMessages.InvalidBundle(this.fileExe));
238 return false;
239 }
240
241 this.BundleId = BurnCommon.ReadGuid(bytes, BURN_SECTION_OFFSET_BUNDLEGUID);
242 this.StubSize = BurnCommon.ReadUInt32(bytes, BURN_SECTION_OFFSET_STUBSIZE);
243 this.OriginalChecksum = BurnCommon.ReadUInt32(bytes, BURN_SECTION_OFFSET_ORIGINALCHECKSUM);
244 this.OriginalSignatureOffset = BurnCommon.ReadUInt32(bytes, BURN_SECTION_OFFSET_ORIGINALSIGNATUREOFFSET);
245 this.OriginalSignatureSize = BurnCommon.ReadUInt32(bytes, BURN_SECTION_OFFSET_ORIGINALSIGNATURESIZE);
246
247 var containerCount = BurnCommon.ReadUInt32(bytes, BURN_SECTION_OFFSET_COUNT);
248 uint uxSize = 0;
249 if (this.wixburnMaxContainers < containerCount)
250 {
251 this.Messaging.Write(ErrorMessages.InvalidBundle(this.fileExe));
252 return false;
253 }
254 else if (containerCount > 0)
255 {
256 this.AttachedContainers.Clear();
257 for (uint i = 0; i < containerCount; ++i)
258 {
259 uint sizeOffset = BURN_SECTION_OFFSET_UXSIZE + (i * 4);
260 var size = BurnCommon.ReadUInt32(bytes, sizeOffset);
261 this.AttachedContainers.Add(new ContainerSlot(size));
262 }
263 uxSize = this.AttachedContainers[0].Size;
264 }
265
266 // If there is an original signature use that to determine the engine size.
267 if (0 < this.OriginalSignatureOffset)
268 {
269 this.EngineSize = this.OriginalSignatureOffset + this.OriginalSignatureSize;
270 }
271 else if (0 < this.SignatureOffset && 2 > containerCount) // if there is a signature and no attached containers, use the current signature.
272 {
273 this.EngineSize = this.SignatureOffset + this.SignatureSize;
274 }
275 else // just use the stub and UX container as the size of the engine.
276 {
277 this.EngineSize = this.UXAddress + uxSize;
278 }
279
280 return true;
281 }
282
283 protected virtual void Dispose(bool disposing)
284 {
285 }
286
287 /// <summary>
288 /// Finds the ".wixburn" section in the current exe.
289 /// </summary>
290 /// <returns>true if the ".wixburn" section is successfully found; false otherwise</returns>
291 private bool GetWixburnSectionInfo(BinaryReader reader)
292 {
293 if (UInt32.MaxValue == this.wixburnDataOffset)
294 {
295 if (!this.EnsureNTHeader(reader))
296 {
297 return false;
298 }
299
300 var wixburnSectionOffset = UInt32.MaxValue;
301 var bytes = new byte[IMAGE_SECTION_HEADER_SIZE];
302
303 reader.BaseStream.Seek(this.firstSectionOffset, SeekOrigin.Begin);
304 for (ushort sectionIndex = 0; sectionIndex < this.sections; ++sectionIndex)
305 {
306 reader.Read(bytes, 0, bytes.Length);
307
308 if (IMAGE_SECTION_WIXBURN_NAME == BurnCommon.ReadUInt64(bytes, IMAGE_SECTION_HEADER_OFFSET_NAME))
309 {
310 wixburnSectionOffset = this.firstSectionOffset + (IMAGE_SECTION_HEADER_SIZE * sectionIndex);
311 break;
312 }
313 }
314
315 if (UInt32.MaxValue == wixburnSectionOffset)
316 {
317 this.Messaging.Write(ErrorMessages.StubMissingWixburnSection(this.fileExe));
318 return false;
319 }
320
321 this.wixburnRawDataSize = BurnCommon.ReadUInt32(bytes, IMAGE_SECTION_HEADER_OFFSET_SIZEOFRAWDATA);
322
323 // we need 52 bytes for the manifest header, which is always going to fit in
324 // the smallest alignment (512 bytes), but just to be paranoid...
325 if (BURN_SECTION_MIN_SIZE > this.wixburnRawDataSize)
326 {
327 this.Messaging.Write(ErrorMessages.StubWixburnSectionTooSmall(this.fileExe));
328 return false;
329 }
330
331 this.wixburnMaxContainers = (this.wixburnRawDataSize - BURN_SECTION_OFFSET_UXSIZE) / sizeof(uint);
332 this.wixburnDataOffset = BurnCommon.ReadUInt32(bytes, IMAGE_SECTION_HEADER_OFFSET_POINTERTORAWDATA);
333 }
334
335 return true;
336 }
337
338 /// <summary>
339 /// Checks for a valid Windows PE signature (IMAGE_NT_SIGNATURE) in the current exe.
340 /// </summary>
341 /// <returns>true if the exe is a Windows executable; false otherwise</returns>
342 private bool EnsureNTHeader(BinaryReader reader)
343 {
344 if (UInt32.MaxValue == this.firstSectionOffset)
345 {
346 if (!this.EnsureDosHeader(reader))
347 {
348 return false;
349 }
350
351 reader.BaseStream.Seek(this.peOffset, SeekOrigin.Begin);
352 var bytes = reader.ReadBytes((int)IMAGE_NT_HEADER_SIZE);
353
354 // Verify the NT signature...
355 if (IMAGE_NT_SIGNATURE != BurnCommon.ReadUInt32(bytes, IMAGE_NT_HEADER_OFFSET_SIGNATURE))
356 {
357 this.Messaging.Write(ErrorMessages.InvalidStubExe(this.fileExe));
358 return false;
359 }
360
361 this.MachineType = BurnCommon.ReadUInt16(bytes, IMAGE_NT_HEADER_OFFSET_MACHINE);
362
363 var sizeOptionalHeader = BurnCommon.ReadUInt16(bytes, IMAGE_NT_HEADER_OFFSET_SIZEOFOPTIONALHEADER);
364
365 this.sections = BurnCommon.ReadUInt16(bytes, IMAGE_NT_HEADER_OFFSET_NUMBEROFSECTIONS);
366 this.firstSectionOffset = this.peOffset + IMAGE_NT_HEADER_SIZE + sizeOptionalHeader;
367
368 this.checksumOffset = this.peOffset + IMAGE_NT_HEADER_SIZE + IMAGE_OPTIONAL_OFFSET_CHECKSUM;
369 this.certificateTableSignatureOffset = this.peOffset + IMAGE_NT_HEADER_SIZE + sizeOptionalHeader - IMAGE_OPTIONAL_NEGATIVE_OFFSET_CERTIFICATETABLE;
370 this.certificateTableSignatureSize = this.certificateTableSignatureOffset + 4; // size is in the DWORD after the offset.
371
372 bytes = reader.ReadBytes(sizeOptionalHeader);
373 this.Checksum = BurnCommon.ReadUInt32(bytes, IMAGE_OPTIONAL_OFFSET_CHECKSUM);
374 this.SignatureOffset = BurnCommon.ReadUInt32(bytes, sizeOptionalHeader - IMAGE_OPTIONAL_NEGATIVE_OFFSET_CERTIFICATETABLE);
375 this.SignatureSize = BurnCommon.ReadUInt32(bytes, sizeOptionalHeader - IMAGE_OPTIONAL_NEGATIVE_OFFSET_CERTIFICATETABLE + 4);
376 }
377
378 return true;
379 }
380
381 /// <summary>
382 /// Checks for a valid DOS header in the current exe.
383 /// </summary>
384 /// <returns>true if the exe starts with a DOS stub; false otherwise</returns>
385 private bool EnsureDosHeader(BinaryReader reader)
386 {
387 if (UInt32.MaxValue == this.peOffset)
388 {
389 var bytes = reader.ReadBytes((int)IMAGE_DOS_HEADER_SIZE);
390
391 // Verify the DOS 'MZ' signature.
392 if (IMAGE_DOS_SIGNATURE != BurnCommon.ReadUInt16(bytes, IMAGE_DOS_HEADER_OFFSET_MAGIC))
393 {
394 this.Messaging.Write(ErrorMessages.InvalidStubExe(this.fileExe));
395 return false;
396 }
397
398 this.peOffset = BurnCommon.ReadUInt32(bytes, IMAGE_DOS_HEADER_OFFSET_NTHEADER);
399 }
400
401 return true;
402 }
403
404 internal static Guid ReadGuid(byte[] bytes, uint offset)
405 {
406 var guidBytes = new byte[16];
407 for (var i = 0; i < 16; ++i)
408 {
409 guidBytes[i] = bytes[offset + i];
410 }
411
412 return new Guid(guidBytes);
413 }
414
415 /// <summary>
416 /// Reads a UInt16 value in little-endian format from an offset in an array of bytes.
417 /// </summary>
418 /// <param name="bytes">Array from which to read.</param>
419 /// <param name="offset">Beginning offset from which to read.</param>
420 /// <returns>value at offset</returns>
421 internal static ushort ReadUInt16(byte[] bytes, uint offset)
422 {
423 Debug.Assert(offset + 2 <= bytes.Length);
424 return (ushort)(bytes[offset] + (bytes[offset + 1] << 8));
425 }
426
427 /// <summary>
428 /// Reads a UInt32 value in little-endian format from an offset in an array of bytes.
429 /// </summary>
430 /// <param name="bytes">Array from which to read.</param>
431 /// <param name="offset">Beginning offset from which to read.</param>
432 /// <returns>value at offset</returns>
433 internal static uint ReadUInt32(byte[] bytes, uint offset)
434 {
435 Debug.Assert(offset + 4 <= bytes.Length);
436 return BurnCommon.ReadUInt16(bytes, offset) + ((uint)BurnCommon.ReadUInt16(bytes, offset + 2) << 16);
437 }
438
439 /// <summary>
440 /// Reads a UInt64 value in little-endian format from an offset in an array of bytes.
441 /// </summary>
442 /// <param name="bytes">Array from which to read.</param>
443 /// <param name="offset">Beginning offset from which to read.</param>
444 /// <returns>value at offset</returns>
445 internal static ulong ReadUInt64(byte[] bytes, uint offset)
446 {
447 Debug.Assert(offset + 8 <= bytes.Length);
448 return BurnCommon.ReadUInt32(bytes, offset) + ((ulong)BurnCommon.ReadUInt32(bytes, offset + 4) << 32);
449 }
450 }
451
452 internal struct ContainerSlot
453 {
454 public ContainerSlot(uint size)
455 {
456 this.Size = size;
457 }
458
459 public uint Size { get; set; }
460 }
461 }