Files

415 lines
17 KiB
C#
Raw Permalink Normal View History

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using BinaryObjectScanner.Interfaces;
2023-03-07 16:59:14 -05:00
using BinaryObjectScanner.Matching;
2023-03-07 12:04:48 -05:00
using BinaryObjectScanner.Utilities;
2023-03-07 16:59:14 -05:00
using BinaryObjectScanner.Wrappers;
2022-12-27 10:53:28 -08:00
using Wise = WiseUnpacker.WiseUnpacker;
2023-03-09 23:52:58 -05:00
namespace BinaryObjectScanner.Packer
{
2022-07-13 12:54:42 -07:00
// https://raw.githubusercontent.com/wolfram77web/app-peid/master/userdb.txt
2023-03-09 16:02:51 -05:00
public class WiseInstaller : IExtractable, INewExecutableCheck, IPortableExecutableCheck
{
/// <inheritdoc/>
2022-05-01 17:17:15 -07:00
public string CheckNewExecutable(string file, NewExecutable nex, bool includeDebug)
{
/// Check we have a valid executable
if (nex == null)
2022-03-14 11:00:17 -07:00
return null;
2022-12-27 10:53:28 -08:00
// If we match a known header
2023-01-04 22:53:52 -08:00
if (MatchesNEVersion(nex) != null)
2022-12-27 10:53:28 -08:00
return "Wise Installation Wizard Module";
2023-01-04 23:13:45 -08:00
// TODO: Investigate STUB.EXE in nonresident-name table
2022-03-15 11:18:53 -07:00
// TODO: Don't read entire file
var data = nex.ReadArbitraryRange();
if (data == null)
return null;
2022-03-14 11:00:17 -07:00
var neMatchSets = new List<ContentMatchSet>
2021-09-10 15:32:37 -07:00
{
2023-01-04 23:53:46 -08:00
// WiseInst
new ContentMatchSet(new byte?[] { 0x57, 0x69, 0x73, 0x65, 0x49, 0x6E, 0x73, 0x74 }, "Wise Installation Wizard Module"),
2022-03-14 11:00:17 -07:00
// WiseMain
new ContentMatchSet(new byte?[] { 0x57, 0x69, 0x73, 0x65, 0x4D, 0x61, 0x69, 0x6E }, "Wise Installation Wizard Module"),
};
2022-12-27 10:53:28 -08:00
2022-03-15 11:18:53 -07:00
return MatchUtil.GetFirstMatch(file, data, neMatchSets, includeDebug);
2022-03-14 11:00:17 -07:00
}
2021-09-10 15:32:37 -07:00
2022-03-14 11:00:17 -07:00
/// <inheritdoc/>
2022-05-01 17:17:15 -07:00
public string CheckPortableExecutable(string file, PortableExecutable pex, bool includeDebug)
2022-03-14 11:00:17 -07:00
{
2022-03-14 11:20:11 -07:00
// Get the sections from the executable, if possible
2022-03-14 11:00:17 -07:00
var sections = pex?.SectionTable;
if (sections == null)
return null;
2022-12-27 10:53:28 -08:00
// If we match a known header
if (GetPEFormat(pex) != null)
2022-12-27 10:53:28 -08:00
return "Wise Installation Wizard Module";
2022-12-08 00:07:11 -08:00
// TODO: Investigate STUB32.EXE in export directory table
// Get the .data/DATA section strings, if they exist
List<string> 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;
}
/// <inheritdoc/>
2023-03-09 17:16:39 -05:00
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))
{
2023-03-09 17:16:39 -05:00
return Extract(fs, file, includeDebug);
}
}
/// <inheritdoc/>
2023-03-09 17:16:39 -05:00
public string Extract(Stream stream, string file, bool includeDebug)
{
2023-03-09 17:16:39 -05:00
try
{
// Try to parse as a New Executable
NewExecutable nex = NewExecutable.Create(stream);
if (nex != null)
return ExtractNewExecutable(nex, file, includeDebug);
2023-03-09 15:46:48 -05:00
2023-03-09 17:16:39 -05:00
// Try to parse as a Portable Executable
PortableExecutable pex = PortableExecutable.Create(stream);
if (pex != null)
return ExtractPortableExecutable(pex, file, includeDebug);
2023-03-09 15:46:48 -05:00
2023-03-09 17:16:39 -05:00
return null;
}
catch (Exception ex)
{
if (includeDebug) Console.WriteLine(ex);
return null;
}
}
2023-01-04 22:43:11 -08:00
/// <summary>
/// Checks an NE header to see if it matches a known signature
/// </summary>
/// <param name="nex">New executable to check</param>
/// <returns>True if it matches a known version, false otherwise</returns>
2023-01-04 22:53:52 -08:00
private FormatProperty MatchesNEVersion(NewExecutable nex)
2023-01-04 22:43:11 -08:00
{
// TODO: Offset is _not_ the EXE header address, rather where the data starts. Fix this.
switch (nex.Stub_NewExeHeaderAddr)
{
2023-01-04 22:53:52 -08:00
case 0x84b0:
return new FormatProperty { Dll = false, ArchiveStart = 0x11, ArchiveEnd = -1, InitText = false, FilenamePosition = 0x04, NoCrc = true };
2023-01-04 22:43:11 -08:00
2023-01-04 22:53:52 -08:00
case 0x3e10:
return new FormatProperty { Dll = false, ArchiveStart = 0x1e, ArchiveEnd = -1, InitText = false, FilenamePosition = 0x04, NoCrc = false };
2023-01-04 22:43:11 -08:00
2023-01-04 22:53:52 -08:00
case 0x3e50:
return new FormatProperty { Dll = false, ArchiveStart = 0x1e, ArchiveEnd = -1, InitText = false, FilenamePosition = 0x04, NoCrc = false };
2023-01-04 22:43:11 -08:00
2023-01-04 22:53:52 -08:00
case 0x3c20:
return new FormatProperty { Dll = false, ArchiveStart = 0x1e, ArchiveEnd = -1, InitText = false, FilenamePosition = 0x04, NoCrc = false };
2023-01-04 22:43:11 -08:00
2023-01-04 22:53:52 -08:00
case 0x3c30:
return new FormatProperty { Dll = false, ArchiveStart = 0x22, ArchiveEnd = -1, InitText = false, FilenamePosition = 0x04, NoCrc = false };
2023-01-04 22:43:11 -08:00
2023-01-04 22:53:52 -08:00
case 0x3660:
return new FormatProperty { Dll = false, ArchiveStart = 0x40, ArchiveEnd = 0x3c, InitText = false, FilenamePosition = 0x04, NoCrc = false };
2023-01-04 22:43:11 -08:00
2023-01-04 22:53:52 -08:00
case 0x36f0:
return new FormatProperty { Dll = false, ArchiveStart = 0x48, ArchiveEnd = 0x44, InitText = false, FilenamePosition = 0x1c, NoCrc = false };
2023-01-04 22:43:11 -08:00
2023-01-04 22:53:52 -08:00
case 0x3770:
return new FormatProperty { Dll = false, ArchiveStart = 0x50, ArchiveEnd = 0x4c, InitText = false, FilenamePosition = 0x1c, NoCrc = false };
2023-01-04 22:43:11 -08:00
2023-01-04 22:53:52 -08:00
case 0x3780:
return new FormatProperty { Dll = true, ArchiveStart = 0x50, ArchiveEnd = 0x4c, InitText = false, FilenamePosition = 0x1c, NoCrc = false };
2023-01-04 22:43:11 -08:00
2023-01-04 22:53:52 -08:00
case 0x37b0:
return new FormatProperty { Dll = true, ArchiveStart = 0x50, ArchiveEnd = 0x4c, InitText = false, FilenamePosition = 0x1c, NoCrc = false };
2023-01-04 22:43:11 -08:00
2023-01-04 22:53:52 -08:00
case 0x37d0:
return new FormatProperty { Dll = true, ArchiveStart = 0x50, ArchiveEnd = 0x4c, InitText = false, FilenamePosition = 0x1c, NoCrc = false };
2023-01-04 22:43:11 -08:00
2023-01-04 22:53:52 -08:00
case 0x3c80:
return new FormatProperty { Dll = true, ArchiveStart = 0x5a, ArchiveEnd = 0x4c, InitText = true, FilenamePosition = 0x1c, NoCrc = false };
2023-01-04 22:43:11 -08:00
2023-01-04 22:53:52 -08:00
case 0x3bd0:
return new FormatProperty { Dll = true, ArchiveStart = 0x5a, ArchiveEnd = 0x4c, InitText = true, FilenamePosition = 0x1c, NoCrc = false };
2023-01-04 22:43:11 -08:00
2023-01-04 22:53:52 -08:00
case 0x3c10:
return new FormatProperty { Dll = true, ArchiveStart = 0x5a, ArchiveEnd = 0x4c, InitText = true, FilenamePosition = 0x1c, NoCrc = false };
2023-01-04 22:43:11 -08:00
2023-01-04 22:53:52 -08:00
default:
return null;
2023-01-04 22:43:11 -08:00
}
}
/// <summary>
/// Checks a PE header to see if it matches a known signature
/// </summary>
/// <param name="pex">Portable executable to check</param>
/// <returns>True if it matches a known version, false otherwise</returns>
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;
}
2023-03-09 15:46:48 -05:00
/// <summary>
/// Attempt to extract Wise data from a New Executable
/// </summary>
/// <param name="nex">New executable to check</param>
/// <param name="file">Path to the input file</param>
2023-03-09 17:16:39 -05:00
/// <param name="includeDebug">True to include debug data, false otherwise</param>
2023-03-09 15:46:48 -05:00
/// <returns>True if it matches a known version, false otherwise</returns>
2023-03-09 17:16:39 -05:00
private string ExtractNewExecutable(NewExecutable nex, string file, bool includeDebug)
2023-03-09 15:46:48 -05:00
{
string tempPath = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString());
Directory.CreateDirectory(tempPath);
2023-03-09 17:16:39 -05:00
try
{
// TODO: Try to find where the file data lives and how to get it
Wise unpacker = new Wise();
2023-03-13 22:53:57 -04:00
if (!unpacker.ExtractTo(file, tempPath))
{
try
{
Directory.Delete(tempPath, true);
}
catch (Exception ex)
{
if (includeDebug) Console.WriteLine(ex);
}
return null;
}
2023-03-09 17:16:39 -05:00
}
catch (Exception ex)
{
if (includeDebug) Console.WriteLine(ex);
return null;
}
2023-03-09 15:46:48 -05:00
return tempPath;
}
2023-01-04 22:43:11 -08:00
/// <summary>
/// Attempt to extract Wise data from a Portable Executable
2023-03-09 15:46:48 -05:00
/// </summary>
/// <param name="pex">Portable executable to check</param>
/// <param name="file">Path to the input file</param>
2023-03-09 17:16:39 -05:00
/// <param name="includeDebug">True to include debug data, false otherwise</param>
2023-03-09 15:46:48 -05:00
/// <returns>True if it matches a known version, false otherwise</returns>
2023-03-09 17:16:39 -05:00
private string ExtractPortableExecutable(PortableExecutable pex, string file, bool includeDebug)
2023-03-09 15:46:48 -05:00
{
2023-03-09 17:16:39 -05:00
try
2023-03-09 15:46:48 -05:00
{
2023-03-09 17:16:39 -05:00
// 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;
2023-03-09 15:46:48 -05:00
2023-03-09 17:16:39 -05:00
// Skip over the additional DLL name, if we expect it
if (format.Dll)
2023-03-09 15:46:48 -05:00
{
2023-03-09 17:16:39 -05:00
// 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;
}
}
2023-03-09 15:46:48 -05:00
2023-03-09 17:16:39 -05:00
// Check if flags are consistent
if (!format.NoCrc)
{
// Unlike WiseUnpacker, we ignore the flag value here
2023-03-09 15:46:48 -05:00
_ = overlayData.ReadUInt32(ref overlayOffset);
}
2023-03-09 17:16:39 -05:00
// 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;
}
2023-03-09 15:46:48 -05:00
2023-03-09 17:16:39 -05:00
// Skip to the start of the archive
overlayOffset = dataStart + format.ArchiveStart;
2023-03-09 15:46:48 -05:00
2023-03-09 17:16:39 -05:00
// Skip over the initialization text, if we expect it
if (format.InitText)
{
int initTextLength = overlayData.ReadByte(ref overlayOffset);
_ = overlayData.ReadBytes(ref overlayOffset, initTextLength);
}
2023-03-09 15:46:48 -05:00
2023-03-09 17:16:39 -05:00
// Cache the current offset in the overlay as the "start of data"
int offsetReal = overlayOffset;
2023-03-09 15:46:48 -05:00
2023-03-09 17:16:39 -05:00
// 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' });
2023-03-09 15:46:48 -05:00
2023-03-09 17:16:39 -05:00
string tempPath = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString());
Directory.CreateDirectory(tempPath);
2023-03-09 15:46:48 -05:00
2023-03-09 17:16:39 -05:00
// 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);
}
}
2023-03-09 15:46:48 -05:00
2023-03-09 17:16:39 -05:00
// If we have DEFLATE -- TODO: Port implementation here or use DeflateStream
else
2023-03-09 15:46:48 -05:00
{
2023-03-09 17:16:39 -05:00
Wise unpacker = new Wise();
2023-03-13 22:53:57 -04:00
if (!unpacker.ExtractTo(file, tempPath))
{
try
{
Directory.Delete(tempPath, true);
}
catch (Exception ex)
{
if (includeDebug) Console.WriteLine(ex);
}
return null;
}
2023-03-09 15:46:48 -05:00
}
2023-03-09 17:16:39 -05:00
return tempPath;
}
catch (Exception ex)
2023-03-09 15:46:48 -05:00
{
2023-03-09 17:16:39 -05:00
if (includeDebug) Console.WriteLine(ex);
return null;
2023-03-09 15:46:48 -05:00
}
}
/// <summary>
/// Class representing the properties of each recognized Wise installer format
/// </summary>
/// <see href="https://github.com/mnadareski/WiseUnpacker/blob/master/WiseUnpacker/FormatProperty.cs"/>
private class FormatProperty
{
/// <summary>
/// Offset to the executable data
/// </summary>
public int ExecutableOffset { get; set; }
/// <summary>
/// Indicates if this format includes a DLL at the start or not
/// </summary>
public bool Dll { get; set; }
/// <summary>
/// Offset within the data where the archive starts
/// </summary>
public int ArchiveStart { get; set; }
/// <summary>
/// Position in the archive head of the archive end
/// </summary>
public int ArchiveEnd { get; set; }
/// <summary>
/// Format includes initialization text
/// </summary>
public bool InitText { get; set; }
/// <summary>
/// Position of the filename within the data
/// </summary>
public int FilenamePosition { get; set; }
/// <summary>
/// Format does not include a CRC
/// </summary>
public bool NoCrc { get; set; }
2022-12-27 10:53:28 -08:00
}
}
}