diff --git a/BurnOutSharp.Wrappers/PortableExecutable.cs b/BurnOutSharp.Wrappers/PortableExecutable.cs index 861e937a..81578207 100644 --- a/BurnOutSharp.Wrappers/PortableExecutable.cs +++ b/BurnOutSharp.Wrappers/PortableExecutable.cs @@ -413,6 +413,70 @@ namespace BurnOutSharp.Wrappers } } + /// + /// Address of the overlay, if it exists + /// + /// + public int OverlayAddress + { + get + { + lock (_sourceDataLock) + { + // Use the cached data if possible + if (_overlayAddress != null) + return _overlayAddress.Value; + + // Get the end of the file, if possible + int endOfFile = GetEndOfFile(); + if (endOfFile == -1) + return -1; + + // If we have certificate data, use that as the end + if (OH_CertificateTable != null) + { + var certificateTable = _executable.OptionalHeader.CertificateTable; + int certificateTableAddress = (int)certificateTable.VirtualAddress.ConvertVirtualAddress(_executable.SectionTable); + if (certificateTableAddress != 0 && certificateTableAddress < endOfFile) + endOfFile = certificateTableAddress; + } + + // Search through all sections and find the furthest a section goes + int endOfSectionData = -1; + foreach (var section in _executable.SectionTable) + { + // If we have an invalid section address + int sectionAddress = (int)section.VirtualAddress.ConvertVirtualAddress(_executable.SectionTable); + if (sectionAddress == 0) + continue; + + // If we have an invalid section size + if (section.SizeOfRawData == 0 && section.VirtualSize == 0) + continue; + + // Get the real section size + int sectionSize; + if (section.SizeOfRawData < section.VirtualSize) + sectionSize = (int)section.VirtualSize; + else + sectionSize = (int)section.SizeOfRawData; + + // Compare and set the end of section data + if (sectionAddress + sectionSize > endOfSectionData) + endOfSectionData = sectionAddress + sectionSize; + } + + // If we didn't find the end of section data + if (endOfSectionData <= 0) + endOfSectionData = -1; + + // Cache and return the position + _overlayAddress = endOfSectionData; + return _overlayAddress.Value; + } + } + } + /// /// Overlay data, if it exists /// @@ -830,6 +894,11 @@ namespace BurnOutSharp.Wrappers /// private byte[] _entryPointData = null; + /// + /// Address of the overlay, if it exists + /// + private int? _overlayAddress = null; + /// /// Overlay data, if it exists /// diff --git a/BurnOutSharp/PackerType/WiseInstaller.cs b/BurnOutSharp/PackerType/WiseInstaller.cs index b473e689..bd9c2a0f 100644 --- a/BurnOutSharp/PackerType/WiseInstaller.cs +++ b/BurnOutSharp/PackerType/WiseInstaller.cs @@ -5,6 +5,7 @@ using System.IO; using System.Linq; using BurnOutSharp.Interfaces; using BurnOutSharp.Matching; +using BurnOutSharp.Utilities; using BurnOutSharp.Wrappers; using static BurnOutSharp.Utilities.Dictionary; using Wise = WiseUnpacker.WiseUnpacker; @@ -50,7 +51,7 @@ namespace BurnOutSharp.PackerType return null; // If we match a known header - if (MatchesPEVersion(pex)) + if (GetPEFormat(pex) != null) return "Wise Installation Wizard Module"; // TODO: Investigate STUB32.EXE in export directory table @@ -95,26 +96,135 @@ namespace BurnOutSharp.PackerType string tempPath = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString()); Directory.CreateDirectory(tempPath); - Wise unpacker = new Wise(); - unpacker.ExtractTo(file, tempPath); + // Parse into an executable again for easier extraction + PortableExecutable pex = PortableExecutable.Create(stream); + if (pex == null) + return null; - // Collect and format all found protections - var protections = scanner.GetProtections(tempPath); + // Get the matching PE format + var format = GetPEFormat(pex); + if (format == null) + return null; - // If temp directory cleanup fails - try + // Get the overlay data for easier reading + int overlayOffset = 0, dataStart = 0; + byte[] overlayData = pex.OverlayData; + if (overlayData == null) + return null; + + // Skip over the additional DLL name, if we expect it + if (format.Dll) { - Directory.Delete(tempPath, true); - } - catch (Exception ex) - { - if (scanner.IncludeDebug) Console.WriteLine(ex); + // Read the name length + byte dllNameLength = overlayData.ReadByte(ref overlayOffset); + dataStart++; + + // Read the name, if it exists + if (dllNameLength != 0) + { + // Ignore the name for now + _ = overlayData.ReadBytes(ref overlayOffset, dllNameLength); + dataStart += dllNameLength; + + // Named DLLs also have a DLL length that we ignore + _ = overlayData.ReadUInt32(ref overlayOffset); + dataStart += 4; + } } - // Remove temporary path references - StripFromKeys(protections, tempPath); + // Check if flags are consistent + if (!format.NoCrc) + { + // Unlike WiseUnpacker, we ignore the flag value here + _ = overlayData.ReadUInt32(ref overlayOffset); + } - return protections; + // Ensure that we have an archive end + if (format.ArchiveEnd > 0) + { + overlayOffset = dataStart + format.ArchiveEnd; + int archiveEndLoaded = overlayData.ReadInt32(ref overlayOffset); + if (archiveEndLoaded != 0) + format.ArchiveEnd = archiveEndLoaded; + } + + // Skip to the start of the archive + overlayOffset = dataStart + format.ArchiveStart; + + // Skip over the initialization text, if we expect it + if (format.InitText) + { + int initTextLength = overlayData.ReadByte(ref overlayOffset); + _ = overlayData.ReadBytes(ref overlayOffset, initTextLength); + } + + // Cache the current offset in the overlay as the "start of data" + int offsetReal = overlayOffset; + + // If the first entry is PKZIP, we assume it's an embedded zipfile + byte[] magic = overlayData.ReadBytes(ref overlayOffset, 4); overlayOffset -= 4; + bool pkzip = magic.StartsWith(new byte?[] { (byte)'P', (byte)'K' }); + + // If we have PKZIP + if (pkzip) + { + try + { + string tempFile = Path.Combine(tempPath, "WISEDATA.zip"); + using (Stream tempStream = File.Open(tempFile, FileMode.Create, FileAccess.Write, FileShare.ReadWrite)) + { + tempStream.Write(overlayData, overlayOffset, overlayData.Length - overlayOffset); + } + + // Collect and format all found protections + var protections = scanner.GetProtections(tempPath); + + // If temp directory cleanup fails + try + { + Directory.Delete(tempPath, true); + } + catch (Exception ex) + { + if (scanner.IncludeDebug) Console.WriteLine(ex); + } + + // Remove temporary path references + StripFromKeys(protections, tempPath); + + return protections; + } + catch (Exception ex) + { + if (scanner.IncludeDebug) Console.WriteLine(ex); + return null; + } + } + + // If we have DEFLATE -- TODO: Port implementation here or use DeflateStream + else + { + Wise unpacker = new Wise(); + unpacker.ExtractTo(file, tempPath); + + // Collect and format all found protections + var protections = scanner.GetProtections(tempPath); + + // If temp directory cleanup fails + try + { + Directory.Delete(tempPath, true); + } + catch (Exception ex) + { + if (scanner.IncludeDebug) Console.WriteLine(ex); + } + + // Remove temporary path references + StripFromKeys(protections, tempPath); + + return protections; + } } catch (Exception ex) { @@ -131,6 +241,7 @@ namespace BurnOutSharp.PackerType /// True if it matches a known version, false otherwise private bool MatchesNEVersion(NewExecutable nex) { + // TODO: Offset is _not_ the EXE header address, rather where the data starts. Fix this. switch (nex.Stub_NewExeHeaderAddr) { // Dll = false, ArchiveStart = 0x11, ArchiveEnd = -1, InitText = false, FilenamePosition = 0x04, NoCrc = true @@ -184,48 +295,86 @@ namespace BurnOutSharp.PackerType /// /// Portable executable to check /// True if it matches a known version, false otherwise - private bool MatchesPEVersion(PortableExecutable pex) + private FormatProperty GetPEFormat(PortableExecutable pex) { - // Dll = false, ArchiveStart = 0x50, ArchiveEnd = 0x4c, InitText = false, FilenamePosition = 0x1c, NoCrc = false - if (pex.Stub_NewExeHeaderAddr == 0x6e00 + if (pex.OverlayAddress == 0x6e00 && pex.GetFirstSection(".text")?.VirtualSize == 0x3cf4 && pex.GetFirstSection(".data")?.VirtualSize == 0x1528) - return true; + return new FormatProperty { Dll = false, ArchiveStart = 0x50, ArchiveEnd = 0x4c, InitText = false, FilenamePosition = 0x1c, NoCrc = false }; - // Dll = true, ArchiveStart = 0x50, ArchiveEnd = 0x4c, InitText = false, FilenamePosition = 0x1c, NoCrc = false - else if (pex.Stub_NewExeHeaderAddr == 0x6e00 + else if (pex.OverlayAddress == 0x6e00 && pex.GetFirstSection(".text")?.VirtualSize == 0x3cf4 && pex.GetFirstSection(".data")?.VirtualSize == 0x1568) - return true; + return new FormatProperty { Dll = false, ArchiveStart = 0x50, ArchiveEnd = 0x4c, InitText = false, FilenamePosition = 0x1c, NoCrc = false }; - // Dll = true, ArchiveStart = 0x50, ArchiveEnd = 0x4c, InitText = false, FilenamePosition = 0x1c, NoCrc = false - else if (pex.Stub_NewExeHeaderAddr == 0x6e00 + else if (pex.OverlayAddress == 0x6e00 && pex.GetFirstSection(".text")?.VirtualSize == 0x3d54) - return true; + return new FormatProperty { Dll = false, ArchiveStart = 0x50, ArchiveEnd = 0x4c, InitText = false, FilenamePosition = 0x1c, NoCrc = false }; - // Dll = true, ArchiveStart = 0x50, ArchiveEnd = 0x4c, InitText = false, FilenamePosition = 0x1c, NoCrc = false - else if (pex.Stub_NewExeHeaderAddr == 0x6e00 + else if (pex.OverlayAddress == 0x6e00 && pex.GetFirstSection(".text")?.VirtualSize == 0x3d44) - return true; + return new FormatProperty { Dll = false, ArchiveStart = 0x50, ArchiveEnd = 0x4c, InitText = false, FilenamePosition = 0x1c, NoCrc = false }; - // Dll = true, ArchiveStart = 0x50, ArchiveEnd = 0x4c, InitText = false, FilenamePosition = 0x1c, NoCrc = false - else if (pex.Stub_NewExeHeaderAddr == 0x6e00 + else if (pex.OverlayAddress == 0x6e00 && pex.GetFirstSection(".text")?.VirtualSize == 0x3d04) - return true; + return new FormatProperty { Dll = false, ArchiveStart = 0x50, ArchiveEnd = 0x4c, InitText = false, FilenamePosition = 0x1c, NoCrc = false }; - // Dll = true, ArchiveStart = 0x50, ArchiveEnd = 0x4c, InitText = false, FilenamePosition = 0x1c, NoCrc = false - else if (pex.Stub_NewExeHeaderAddr == 0x3000) - return true; + // Found in Binary.WiseCustomCalla + else if (pex.OverlayAddress == 0x6200) + return new FormatProperty { Dll = true, ArchiveStart = 0x62, ArchiveEnd = 0x4c, InitText = true, FilenamePosition = 0x1c, NoCrc = false }; - // Dll = true, ArchiveStart = 0x5a, ArchiveEnd = 0x4c, InitText = true, FilenamePosition = 0x1c, NoCrc = false - else if (pex.Stub_NewExeHeaderAddr == 0x3800) - return true; + else if (pex.OverlayAddress == 0x3000) + return new FormatProperty { Dll = false, ArchiveStart = 0x50, ArchiveEnd = 0x4c, InitText = false, FilenamePosition = 0x1c, NoCrc = false }; - // Dll = true, ArchiveStart = 0x5a, ArchiveEnd = 0x4c, InitText = true, FilenamePosition = 0x1c, NoCrc = false - else if (pex.Stub_NewExeHeaderAddr == 0x3a00) - return true; + else if (pex.OverlayAddress == 0x3800) + return new FormatProperty { Dll = true, ArchiveStart = 0x5a, ArchiveEnd = 0x4c, InitText = true, FilenamePosition = 0x1c, NoCrc = false }; - return false; + else if (pex.OverlayAddress == 0x3a00) + return new FormatProperty { Dll = true, ArchiveStart = 0x5a, ArchiveEnd = 0x4c, InitText = true, FilenamePosition = 0x1c, NoCrc = false }; + + return null; + } + + /// + /// Class representing the properties of each recognized Wise installer format + /// + /// + private class FormatProperty + { + /// + /// Offset to the executable data + /// + public int ExecutableOffset { get; set; } + + /// + /// Indicates if this format includes a DLL at the start or not + /// + public bool Dll { get; set; } + + /// + /// Offset within the data where the archive starts + /// + public int ArchiveStart { get; set; } + + /// + /// Position in the archive head of the archive end + /// + public int ArchiveEnd { get; set; } + + /// + /// Format includes initialization text + /// + public bool InitText { get; set; } + + /// + /// Position of the filename within the data + /// + public int FilenamePosition { get; set; } + + /// + /// Format does not include a CRC + /// + public bool NoCrc { get; set; } } } }