From 65fa2f84812262f1e4ba453140ee0eed617e235d Mon Sep 17 00:00:00 2001 From: SilasLaspada Date: Mon, 21 Jun 2021 22:48:25 -0600 Subject: [PATCH] Greatly improve WinZip SFX version detection (#39) --- BurnOutSharp/PackerType/WinZipSFX.cs | 305 ++++++++++++++++++++++++++- BurnOutSharp/Utilities.cs | 57 ++++- 2 files changed, 356 insertions(+), 6 deletions(-) diff --git a/BurnOutSharp/PackerType/WinZipSFX.cs b/BurnOutSharp/PackerType/WinZipSFX.cs index dcb8229b..6c43a998 100644 --- a/BurnOutSharp/PackerType/WinZipSFX.cs +++ b/BurnOutSharp/PackerType/WinZipSFX.cs @@ -32,6 +32,8 @@ namespace BurnOutSharp.PackerType return MatchUtil.GetFirstMatch(file, fileContent, matchers, includePosition); } + // TODO: Find a way to generically detect 2.X versions and improve exact version detection for SFX PE versions bundled with WinZip 11+ + /// public Dictionary> Scan(Scanner scanner, string file) { @@ -96,8 +98,11 @@ namespace BurnOutSharp.PackerType { // Check the manifest version first string version = Utilities.GetManifestVersion(fileContent); + string description = Utilities.GetManifestDescription(fileContent); + if (description != "WinZip Self-Extractor") + return GetV2Version(file, fileContent); if (!string.IsNullOrEmpty(version)) - return GetV3PlusVersion(version); + return GetV3PlusVersion(file, fileContent, version); // Assume an earlier version return GetV2Version(file, fileContent); @@ -253,6 +258,48 @@ namespace BurnOutSharp.PackerType 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, }, "Software Installation 2.1 (16-bit)"), + new ContentMatchSet(new byte?[] + { + 0x4E, 0x45, 0x11, 0x20, 0x86, 0x00, 0x02, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x0A, 0x03, 0x03, 0x00, + 0x00, 0x20, 0x00, 0x40, 0x7C, 0x31, 0x01, 0x00, + 0x00, 0x00, 0x03, 0x00, 0x03, 0x00, 0x04, 0x00, + 0x4B, 0x00, 0x40, 0x00, 0x58, 0x00, 0x58, 0x00, + 0x64, 0x00, 0x6C, 0x00, 0x98, 0x01, 0x00, 0x00, + 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, + }, "Personal Edition (16-bit)"), + + new ContentMatchSet(new byte?[] + { + 0x4E, 0x45, 0x11, 0x20, 0xBE, 0x00, 0x02, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x02, 0x03, 0x03, 0x00, + 0x00, 0x20, 0x00, 0x3C, 0x7C, 0x3E, 0x01, 0x00, + 0x00, 0x00, 0x03, 0x00, 0x03, 0x00, 0x04, 0x00, + 0x4B, 0x00, 0x40, 0x00, 0x58, 0x00, 0x90, 0x00, + 0x9C, 0x00, 0xA4, 0x00, 0xD0, 0x01, 0x00, 0x00, + 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, + }, "Personal Edition 32-bit (16-bit)"), + + new ContentMatchSet(new byte?[] + { + 0x4E, 0x45, 0x11, 0x20, 0xC6, 0x00, 0x02, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x02, 0x03, 0x03, 0x00, + 0xDC, 0x43, 0x08, 0x27, 0xDC, 0x4A, 0x01, 0x00, + 0x00, 0x00, 0x03, 0x00, 0x03, 0x00, 0x05, 0x00, + 0x4B, 0x00, 0x40, 0x00, 0x58, 0x00, 0x90, 0x00, + 0x9C, 0x00, 0xA6, 0x00, 0xD8, 0x01, 0x00, 0x00, + 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, + }, "Personal Edition 32-bit Build 1260/1285 (16-bit)"), + + // WZ-SE-01 + new ContentMatchSet(new byte?[] + { + 0x57, 0x5A, 0x2D, 0x53, 0x45, 0x2D, 0x30, 0x31 + }, "Unknow Version (16-bit)"), + #endregion #region 32-bit SFX Header Checks @@ -335,10 +382,191 @@ namespace BurnOutSharp.PackerType 0x2E, 0x53, 0x46, 0x58, }, "Software Installation 2.1 (32-bit)"), + // .............ñ½;5....ˆ`..............ˆ`..ˆ`..ˆ`..SI32E.SFX + new ContentMatchSet(new byte?[] + { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0xF1, 0xBD, 0x3B, + 0x35, 0x00, 0x00, 0x00, 0x00, 0x88, 0x60, 0x00, + 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x88, 0x60, 0x00, + 0x00, 0x88, 0x60, 0x00, 0x00, 0x88, 0x60, 0x00, + 0x00, 0x53, 0x49, 0x33, 0x32, 0x45, 0x2E, 0x53, + 0x46, 0x58, + }, "Software Installation 2.2.1110 (32-bit)"), + + // .............á.^2....ˆP..............ˆP..ˆP..ˆP..VW95LE.SFX + new ContentMatchSet(new byte?[] + { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0xE1, 0x9D, 0x5E, + 0x32, 0x00, 0x00, 0x00, 0x00, 0x88, 0x50, 0x00, + 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x88, 0x50, 0x00, + 0x00, 0x88, 0x50, 0x00, 0x00, 0x88, 0x50, 0x00, + 0x00, 0x56, 0x57, 0x39, 0x35, 0x4C, 0x45, 0x2E, + 0x53, 0x46, 0x58, + }, "Personal Edition (32-bit)"), + + // .............ïAÁ3....ˆP..............ˆP..ˆP..ˆP..VW95LE.SFX + new ContentMatchSet(new byte?[] + { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0xEF, 0x41, 0xC1, + 0x33, 0x00, 0x00, 0x00, 0x00, 0x88, 0x50, 0x00, + 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x88, 0x50, 0x00, + 0x00, 0x88, 0x50, 0x00, 0x00, 0x88, 0x50, 0x00, + 0x00, 0x56, 0x57, 0x39, 0x35, 0x4C, 0x45, 0x2E, + 0x53, 0x46, 0x58, + }, "Personal Edition 32-bit (32-bit)"), + + // .............'..6....ˆ`..............ˆ`..ˆ`..ˆ`..PE32E.SFX + new ContentMatchSet(new byte?[] + { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x27, 0x0F, 0x01, + 0x36, 0x00, 0x00, 0x00, 0x00, 0x88, 0x60, 0x00, + 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x88, 0x60, 0x00, + 0x00, 0x88, 0x60, 0x00, 0x00, 0x88, 0x60, 0x00, + 0x00, 0x50, 0x45, 0x33, 0x32, 0x45, 0x2E, 0x53, + 0x46, 0x58, + }, "Personal Edition 32-bit Build 1260 (32-bit)"), + + // .............Ó‘(6....ˆ`..............ˆ`..ˆ`..ˆ`..PE32E.SFX + new ContentMatchSet(new byte?[] + { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0xD3, 0x91, 0x28, + 0x36, 0x00, 0x00, 0x00, 0x00, 0x88, 0x60, 0x00, + 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x88, 0x60, 0x00, + 0x00, 0x88, 0x60, 0x00, 0x00, 0x88, 0x60, 0x00, + 0x00, 0x50, 0x45, 0x33, 0x32, 0x45, 0x2E, 0x53, + 0x46, 0x58, + }, "Personal Edition 32-bit Build 1285 (32-bit)"), + + // ......]ïý8....˜z..............˜z..˜z..˜z..PE32E.SFX + new ContentMatchSet(new byte?[] + { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x5D, 0xEF, + 0xFD, 0x38, 0x00, 0x00, 0x00, 0x00, 0x98, 0x7A, + 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x98, 0x7A, + 0x00, 0x00, 0x98, 0x7A, 0x00, 0x00, 0x98, 0x7A, + 0x00, 0x00, 0x50, 0x45, 0x33, 0x32, 0x45, 0x2E, + 0x53, 0x46, 0x58, + }, "Personal Edition 32-bit Build 3063"), + + // ...................½û;....ˆj..............ˆj..ˆj..ˆj..PE32E.SFX + new ContentMatchSet(new byte?[] + { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x1F, 0xBD, 0xFB, 0x3B, 0x00, 0x00, + 0x00, 0x00, 0x88, 0x6A, 0x00, 0x00, 0x01, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x88, 0x6A, 0x00, 0x00, 0x88, 0x6A, + 0x00, 0x00, 0x88, 0x6A, 0x00, 0x00, 0x50, 0x45, + 0x33, 0x32, 0x45, 0x2E, 0x53, 0x46, 0x58, + }, "Personal Edition 32-bit Build 4325"), + + // ................rS*@....Xƒ..............Xƒ..Xƒ..Xƒ..PE32E.SFX + new ContentMatchSet(new byte?[] + { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x72, 0x53, 0x2A, 0x40, 0x00, 0x00, 0x00, 0x00, + 0x58, 0x83, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x58, 0x83, 0x00, 0x00, 0x58, 0x83, 0x00, 0x00, + 0x58, 0x83, 0x00, 0x00, 0x50, 0x45, 0x33, 0x32, + 0x45, 0x2E, 0x53, 0x46, 0x58, + }, "Personal Edition 32-bit Build 6028"), + + // ................±.!A....Xƒ..............Xƒ..Xƒ..Xƒ..PE32E.SFX + new ContentMatchSet(new byte?[] + { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0xB1, 0x1A, 0x21, 0x41, 0x00, 0x00, 0x00, 0x00, + 0x58, 0x83, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x58, 0x83, 0x00, 0x00, 0x58, 0x83, 0x00, 0x00, + 0x58, 0x83, 0x00, 0x00, 0x50, 0x45, 0x33, 0x32, + 0x45, 0x2E, 0x53, 0x46, 0x58, + }, "Personal Edition 32-bit Build 6224"), + + // ................¯D.C....x„..............x„..x„..x„..PE32E.SFX + new ContentMatchSet(new byte?[] + { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0xAF, 0x44, 0x0F, 0x43, 0x00, 0x00, 0x00, 0x00, + 0x78, 0x84, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x78, 0x84, 0x00, 0x00, 0x78, 0x84, 0x00, 0x00, + 0x78, 0x84, 0x00, 0x00, 0x50, 0x45, 0x33, 0x32, + 0x45, 0x2E, 0x53, 0x46, + }, "Personal Edition 32-bit Build 6604"), + + //................·Å\C....x„..............x„..x„..x„..PE32E.SFX + new ContentMatchSet(new byte?[] + { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0xB7, 0xC5, 0x5C, 0x43, 0x00, 0x00, 0x00, 0x00, + 0x78, 0x84, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x78, 0x84, 0x00, 0x00, 0x78, 0x84, 0x00, 0x00, + 0x78, 0x84, 0x00, 0x00, 0x50, 0x45, 0x33, 0x32, + 0x45, 0x2E, 0x53, 0x46, 0x58, + }, "Personal Edition 32-bit Build 6663"), + + // VW95SE.SFX + new ContentMatchSet(new byte?[] + { + 0x56, 0x57, 0x39, 0x35, 0x53, 0x45, 0x2E, 0x53, + 0x46, 0x58, + }, "Unknown Version (32-bit)"), + + // VW95SRE.SFX + new ContentMatchSet(new byte?[] + { + 0x56, 0x57, 0x39, 0x35, 0x53, 0x52, 0x45, 0x2E, + 0x53, 0x46, 0x58, + }, "Unknown Version Software Installation (32-bit)"), + + // VW95LE.SFX + new ContentMatchSet(new byte?[] + { + 0x56, 0x57, 0x39, 0x35, 0x4C, 0x45, 0x2E, 0x53, + 0x46, 0x58, + }, "Unknown Version before build 1285 Personal Edition (32-bit)"), + + // PE32E.SFX + new ContentMatchSet(new byte?[] + { + 0x50, 0x45, 0x33, 0x32, 0x45, 0x2E, 0x53, 0x46, + 0x58, + }, "Unknown Version after 1285 Personal Edition (32-bit)"), + #endregion #region 32-bit PE Header Checks + new ContentMatchSet(new byte?[] + { + 0x50, 0x45, 0x00, 0x00, 0x4C, 0x01, 0x05, 0x00, + 0xC9, 0x7A, 0xBE, 0x38, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0xE0, 0x00, 0x0F, 0x01, + 0x0B, 0x01, 0x05, 0x0A, 0x00, 0x5C, 0x00, 0x00, + 0x00, 0x4C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x71, 0x3E, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, + 0x00, 0x70, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, + }, "2.2.3063"), + new ContentMatchSet(new byte?[] { 0x50, 0x45, 0x00, 0x00, 0x4C, 0x01, 0x05, 0x00, @@ -358,33 +586,100 @@ namespace BurnOutSharp.PackerType 0x0B, 0x01, 0x05, 0x0A, 0x00, 0x56, 0x00, 0x00, 0x00, 0x2A, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x8F, 0x3F, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, - 0x00, 0x70, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, + 0x00, 0x70, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, }, "Software Installation 2.2.4003"), + new ContentMatchSet(new byte?[] + { + 0x50, 0x45, 0x00, 0x00, 0x4C, 0x01, 0x05, 0x00, + 0xFA, 0xB8, 0xFB, 0x3B, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0xE0, 0x00, 0x0F, 0x01, + 0x0B, 0x01, 0x06, 0x00, 0x00, 0x60, 0x00, 0x00, + 0x00, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0xF0, 0x3E, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, + 0x00, 0x70, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, + 0x00, 0x10, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, + }, "2.2.4325"), + + new ContentMatchSet(new byte?[] + { + 0x50, 0x45, 0x00, 0x00, 0x4C, 0x01, 0x05, 0x00, + 0xAD, 0xFC, 0x2A, 0x3D, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0xE0, 0x00, 0x0F, 0x01, + 0x0B, 0x01, 0x07, 0x00, 0x00, 0x70, 0x00, 0x00, + 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x54, 0x45, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, + 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, + 0x00, 0x10, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, }, "2.2.5196"), + + new ContentMatchSet(new byte?[] + { + 0x50, 0x45, 0x00, 0x00, 0x4C, 0x01, 0x05, 0x00, + 0x76, 0xF7, 0x00, 0x41, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0xE0, 0x00, 0x0F, 0x01, + 0x0B, 0x01, 0x07, 0x00, 0x00, 0x70, 0x00, 0x00, + 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x03, 0x46, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, + 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, + }, "2.2.6202"), + #endregion + + // WinZip Self-Extractor header corrupt. + new ContentMatchSet(new byte?[] + { + 0x57, 0x69, 0x6E, 0x5A, 0x69, 0x70, 0x20, 0x53, + 0x65, 0x6C, 0x66, 0x2D, 0x45, 0x78, 0x74, 0x72, + 0x61, 0x63, 0x74, 0x6F, 0x72, 0x20, 0x68, 0x65, + 0x61, 0x64, 0x65, 0x72, 0x20, 0x63, 0x6F, 0x72, + 0x72, 0x75, 0x70, 0x74, 0x2E, + }, "Unknown 2.x version"), + + // winzip\shell\open\command + new ContentMatchSet(new byte?[] + { + 0x77, 0x69, 0x6E, 0x7A, 0x69, 0x70, 0x5C, 0x73, + 0x68, 0x65, 0x6C, 0x6C, 0x5C, 0x6F, 0x70, 0x65, + 0x6E, 0x5C, 0x63, 0x6F, 0x6D, 0x6D, 0x61, 0x6E, + 0x64, + }, "Unknown 2.x version"), }; string match = MatchUtil.GetFirstMatch(file, fileContent, matchers, false); - return match ?? "Unknown 2.x"; + return match ?? null; } - private static string GetV3PlusVersion(string version) + private static string GetV3PlusVersion(string file, byte[] fileContent, string version) { // Some version strings don't exactly match the public version number switch (version) { + case "2.3.6594.0": + return "Personal Edition Build 6604"; + case "2.3.6602.0": + return "Personal Edition Build 6663"; + case "2.3.7305.0": + return "Personal Edition Build 7305"; + case "2.3.7382.0": + return "Personal Edition Build 7452+"; case "3.0.7158.0": return "3.0.7158"; + case "3.0.7454.0": + return "3.0.7454+"; + case "3.0.7212.0": + return "3.0.7212"; case "3.1.7556.0": return "3.1.7556"; case "3.1.8421.0": return "4.0.8421"; + case "4.0.8421.0": + return "4.0.8421"; case "3.1.8672.0": return "4.0.8672"; case "4.0.1221.0": return "4.0.12218"; default: - return $"(Unknown - {version})"; + return $"(Unknown - internal version {version})"; } } } diff --git a/BurnOutSharp/Utilities.cs b/BurnOutSharp/Utilities.cs index 5de86d92..a3c189f3 100644 --- a/BurnOutSharp/Utilities.cs +++ b/BurnOutSharp/Utilities.cs @@ -317,7 +317,62 @@ namespace BurnOutSharp return null; } } - + + /// + /// Get the assembly version as determined by an embedded assembly manifest + /// + /// Byte array representing the file contents + /// Version string, null on error + /// TODO: How do we find the manifest specifically better? + public static string GetManifestDescription(byte[] fileContent) + { + // + byte?[] manifestEnd = new byte?[] { 0x3C, 0x2F, 0x61, 0x73, 0x73, 0x65, 0x6D, 0x62, 0x6C, 0x79, 0x3E }; + if (!fileContent.FirstPosition(manifestEnd, out int manifestEndPosition, start: manifestStartPosition)) + return null; + + // Read in the manifest to a string + int manifestLength = manifestEndPosition + "".Length - manifestStartPosition; + string manifestString = Encoding.ASCII.GetString(fileContent, manifestStartPosition, manifestLength); + + // Try to read the XML in from the string + try + { + // Load the XML string as a document + var manifestDoc = new XmlDocument(); + manifestDoc.LoadXml(manifestString); + + // If the XML has no children, it's invalid + if (!manifestDoc.HasChildNodes) + return null; + + // Try to read the assembly node + var assemblyNode = manifestDoc["assembly"]; + if (assemblyNode == null) + return null; + + // Try to read the assemblyIdentity + var assemblyIdentityNode = assemblyNode["assemblyIdentity"]; + if (assemblyIdentityNode == null) + return null; + + // Return the content of the description node, if possible + var DescriptionNode = assemblyNode["description"]; + if (DescriptionNode == null) + return null; + return DescriptionNode.InnerXml; + } + catch + { + return null; + } + } + #endregion } }