using System; using System.Collections.Generic; using System.IO; using System.Linq; using BinaryObjectScanner.Interfaces; using BinaryObjectScanner.Matching; using BinaryObjectScanner.Utilities; using BinaryObjectScanner.Wrappers; using Wise = WiseUnpacker.WiseUnpacker; namespace BinaryObjectScanner.Packer { // https://raw.githubusercontent.com/wolfram77web/app-peid/master/userdb.txt public class WiseInstaller : IExtractable, INewExecutableCheck, IPortableExecutableCheck { /// public string CheckNewExecutable(string file, NewExecutable nex, bool includeDebug) { /// Check we have a valid executable if (nex == null) return null; // If we match a known header if (MatchesNEVersion(nex) != null) return "Wise Installation Wizard Module"; // TODO: Investigate STUB.EXE in nonresident-name table // TODO: Don't read entire file var data = nex.ReadArbitraryRange(); if (data == null) return null; var neMatchSets = new List { // WiseInst new ContentMatchSet(new byte?[] { 0x57, 0x69, 0x73, 0x65, 0x49, 0x6E, 0x73, 0x74 }, "Wise Installation Wizard Module"), // WiseMain new ContentMatchSet(new byte?[] { 0x57, 0x69, 0x73, 0x65, 0x4D, 0x61, 0x69, 0x6E }, "Wise Installation Wizard Module"), }; return MatchUtil.GetFirstMatch(file, data, neMatchSets, includeDebug); } /// public string CheckPortableExecutable(string file, PortableExecutable pex, bool includeDebug) { // Get the sections from the executable, if possible var sections = pex?.SectionTable; if (sections == null) return null; // If we match a known header if (GetPEFormat(pex) != null) return "Wise Installation Wizard Module"; // TODO: Investigate STUB32.EXE in export directory table // Get the .data/DATA section strings, if they exist List strs = pex.GetFirstSectionStrings(".data") ?? pex.GetFirstSectionStrings("DATA"); if (strs != null) { if (strs.Any(s => s.Contains("WiseMain"))) return "Wise Installation Wizard Module"; } // Get the .rdata section strings, if they exist strs = pex.GetFirstSectionStrings(".rdata"); if (strs != null) { if (strs.Any(s => s.Contains("WiseMain"))) return "Wise Installation Wizard Module"; } return null; } /// public string Extract(string file, bool includeDebug) { if (!File.Exists(file)) return null; using (var fs = File.Open(file, FileMode.Open, FileAccess.Read, FileShare.Read)) { return Extract(fs, file, includeDebug); } } /// public string Extract(Stream stream, string file, bool includeDebug) { try { // Try to parse as a New Executable NewExecutable nex = NewExecutable.Create(stream); if (nex != null) return ExtractNewExecutable(nex, file, includeDebug); // Try to parse as a Portable Executable PortableExecutable pex = PortableExecutable.Create(stream); if (pex != null) return ExtractPortableExecutable(pex, file, includeDebug); return null; } catch (Exception ex) { if (includeDebug) Console.WriteLine(ex); return null; } } /// /// Checks an NE header to see if it matches a known signature /// /// New executable to check /// True if it matches a known version, false otherwise private FormatProperty MatchesNEVersion(NewExecutable nex) { // TODO: Offset is _not_ the EXE header address, rather where the data starts. Fix this. switch (nex.Stub_NewExeHeaderAddr) { case 0x84b0: return new FormatProperty { Dll = false, ArchiveStart = 0x11, ArchiveEnd = -1, InitText = false, FilenamePosition = 0x04, NoCrc = true }; case 0x3e10: return new FormatProperty { Dll = false, ArchiveStart = 0x1e, ArchiveEnd = -1, InitText = false, FilenamePosition = 0x04, NoCrc = false }; case 0x3e50: return new FormatProperty { Dll = false, ArchiveStart = 0x1e, ArchiveEnd = -1, InitText = false, FilenamePosition = 0x04, NoCrc = false }; case 0x3c20: return new FormatProperty { Dll = false, ArchiveStart = 0x1e, ArchiveEnd = -1, InitText = false, FilenamePosition = 0x04, NoCrc = false }; case 0x3c30: return new FormatProperty { Dll = false, ArchiveStart = 0x22, ArchiveEnd = -1, InitText = false, FilenamePosition = 0x04, NoCrc = false }; case 0x3660: return new FormatProperty { Dll = false, ArchiveStart = 0x40, ArchiveEnd = 0x3c, InitText = false, FilenamePosition = 0x04, NoCrc = false }; case 0x36f0: return new FormatProperty { Dll = false, ArchiveStart = 0x48, ArchiveEnd = 0x44, InitText = false, FilenamePosition = 0x1c, NoCrc = false }; case 0x3770: return new FormatProperty { Dll = false, ArchiveStart = 0x50, ArchiveEnd = 0x4c, InitText = false, FilenamePosition = 0x1c, NoCrc = false }; case 0x3780: return new FormatProperty { Dll = true, ArchiveStart = 0x50, ArchiveEnd = 0x4c, InitText = false, FilenamePosition = 0x1c, NoCrc = false }; case 0x37b0: return new FormatProperty { Dll = true, ArchiveStart = 0x50, ArchiveEnd = 0x4c, InitText = false, FilenamePosition = 0x1c, NoCrc = false }; case 0x37d0: return new FormatProperty { Dll = true, ArchiveStart = 0x50, ArchiveEnd = 0x4c, InitText = false, FilenamePosition = 0x1c, NoCrc = false }; case 0x3c80: return new FormatProperty { Dll = true, ArchiveStart = 0x5a, ArchiveEnd = 0x4c, InitText = true, FilenamePosition = 0x1c, NoCrc = false }; case 0x3bd0: return new FormatProperty { Dll = true, ArchiveStart = 0x5a, ArchiveEnd = 0x4c, InitText = true, FilenamePosition = 0x1c, NoCrc = false }; case 0x3c10: return new FormatProperty { Dll = true, ArchiveStart = 0x5a, ArchiveEnd = 0x4c, InitText = true, FilenamePosition = 0x1c, NoCrc = false }; default: return null; } } /// /// Checks a PE header to see if it matches a known signature /// /// Portable executable to check /// True if it matches a known version, false otherwise private FormatProperty GetPEFormat(PortableExecutable pex) { if (pex.OverlayAddress == 0x6e00 && pex.GetFirstSection(".text")?.VirtualSize == 0x3cf4 && pex.GetFirstSection(".data")?.VirtualSize == 0x1528) return new FormatProperty { Dll = false, ArchiveStart = 0x50, ArchiveEnd = 0x4c, InitText = false, FilenamePosition = 0x1c, NoCrc = false }; else if (pex.OverlayAddress == 0x6e00 && pex.GetFirstSection(".text")?.VirtualSize == 0x3cf4 && pex.GetFirstSection(".data")?.VirtualSize == 0x1568) return new FormatProperty { Dll = false, ArchiveStart = 0x50, ArchiveEnd = 0x4c, InitText = false, FilenamePosition = 0x1c, NoCrc = false }; else if (pex.OverlayAddress == 0x6e00 && pex.GetFirstSection(".text")?.VirtualSize == 0x3d54) return new FormatProperty { Dll = false, ArchiveStart = 0x50, ArchiveEnd = 0x4c, InitText = false, FilenamePosition = 0x1c, NoCrc = false }; else if (pex.OverlayAddress == 0x6e00 && pex.GetFirstSection(".text")?.VirtualSize == 0x3d44) return new FormatProperty { Dll = false, ArchiveStart = 0x50, ArchiveEnd = 0x4c, InitText = false, FilenamePosition = 0x1c, NoCrc = false }; else if (pex.OverlayAddress == 0x6e00 && pex.GetFirstSection(".text")?.VirtualSize == 0x3d04) return new FormatProperty { Dll = false, ArchiveStart = 0x50, ArchiveEnd = 0x4c, InitText = false, FilenamePosition = 0x1c, NoCrc = false }; // Found in Binary.WiseCustomCalla else if (pex.OverlayAddress == 0x6200) return new FormatProperty { Dll = true, ArchiveStart = 0x62, ArchiveEnd = 0x4c, InitText = true, FilenamePosition = 0x1c, NoCrc = false }; else if (pex.OverlayAddress == 0x3000) return new FormatProperty { Dll = false, ArchiveStart = 0x50, ArchiveEnd = 0x4c, InitText = false, FilenamePosition = 0x1c, NoCrc = false }; else if (pex.OverlayAddress == 0x3800) return new FormatProperty { Dll = true, ArchiveStart = 0x5a, ArchiveEnd = 0x4c, InitText = true, FilenamePosition = 0x1c, NoCrc = false }; else if (pex.OverlayAddress == 0x3a00) return new FormatProperty { Dll = true, ArchiveStart = 0x5a, ArchiveEnd = 0x4c, InitText = true, FilenamePosition = 0x1c, NoCrc = false }; return null; } /// /// Attempt to extract Wise data from a New Executable /// /// New executable to check /// Path to the input file /// True to include debug data, false otherwise /// True if it matches a known version, false otherwise private string ExtractNewExecutable(NewExecutable nex, string file, bool includeDebug) { string tempPath = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString()); Directory.CreateDirectory(tempPath); try { // TODO: Try to find where the file data lives and how to get it Wise unpacker = new Wise(); if (!unpacker.ExtractTo(file, tempPath)) { try { Directory.Delete(tempPath, true); } catch (Exception ex) { if (includeDebug) Console.WriteLine(ex); } return null; } } catch (Exception ex) { if (includeDebug) Console.WriteLine(ex); return null; } return tempPath; } /// /// Attempt to extract Wise data from a Portable Executable /// /// Portable executable to check /// Path to the input file /// True to include debug data, false otherwise /// True if it matches a known version, false otherwise private string ExtractPortableExecutable(PortableExecutable pex, string file, bool includeDebug) { try { // Get the matching PE format var format = GetPEFormat(pex); if (format == null) return null; // 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) { // 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; } } // Check if flags are consistent if (!format.NoCrc) { // Unlike WiseUnpacker, we ignore the flag value here _ = overlayData.ReadUInt32(ref overlayOffset); } // 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' }); string tempPath = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString()); Directory.CreateDirectory(tempPath); // If we have PKZIP if (pkzip) { 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); } } // If we have DEFLATE -- TODO: Port implementation here or use DeflateStream else { Wise unpacker = new Wise(); if (!unpacker.ExtractTo(file, tempPath)) { try { Directory.Delete(tempPath, true); } catch (Exception ex) { if (includeDebug) Console.WriteLine(ex); } return null; } } return tempPath; } catch (Exception ex) { if (includeDebug) Console.WriteLine(ex); 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; } } } }