From 9eb74a411cc2a152bbfb44f9d2b5082559638260 Mon Sep 17 00:00:00 2001 From: Matt Nadareski Date: Sat, 20 Sep 2025 14:27:28 -0400 Subject: [PATCH] SecuROM cleanup --- BinaryObjectScanner/Protection/SecuROM.cs | 109 ++++++++++++---------- 1 file changed, 58 insertions(+), 51 deletions(-) diff --git a/BinaryObjectScanner/Protection/SecuROM.cs b/BinaryObjectScanner/Protection/SecuROM.cs index 658164a9..bd286cc1 100644 --- a/BinaryObjectScanner/Protection/SecuROM.cs +++ b/BinaryObjectScanner/Protection/SecuROM.cs @@ -16,8 +16,8 @@ namespace BinaryObjectScanner.Protection /// /// Matches hash of the Release Control-encrypted executable to known hashes /// - // Allegedly, some version of Runaway: A Twist of Fate has RC - private static readonly Dictionary MatroschkaHashDictionary = new Dictionary() + /// Allegedly, some version of Runaway: A Twist of Fate has RC + private static readonly Dictionary MatroschkaHashDictionary = new() { {"C6DFF6B08EE126893840E107FD4EC9F6", "Alice - Madness Returns (USA)+(Europe)"}, {"D7703D32B72185358D58448B235BD55E", "Arcania - Gothic 4 - Not in redump yet"}, @@ -62,9 +62,10 @@ namespace BinaryObjectScanner.Protection }; /// - /// If hash isn't currently known, check size and pathname of the encrypted executable to determine if alt or entirely missing + /// If hash isn't currently known, check size and pathname of the encrypted executable + /// to determine if alt or entirely missing /// - private static readonly Dictionary MatroschkaSizeFilenameDictionary = new Dictionary() + private static readonly Dictionary MatroschkaSizeFilenameDictionary = new() { {4646091, "hp8.aec"}, {5124592, "output\\LaunchGTAIV.aec"}, @@ -105,7 +106,6 @@ namespace BinaryObjectScanner.Protection return paModule; // Check if executable contains a SecuROM Matroschka Package - var package = exe.MatroschkaPackage; if (package != null) { @@ -230,7 +230,6 @@ namespace BinaryObjectScanner.Protection /// /// Try to get the SecuROM v4 version from the overlay, if possible /// - /// Executable to retrieve the overlay from /// The version on success, null otherwise private static string? GetV4Version(PortableExecutable exe) { @@ -276,6 +275,10 @@ namespace BinaryObjectScanner.Protection return $"{major}.{minor}.{patch}.{revision}"; } + /// + /// Try to get the SecuROM v5 version from section data, if possible + /// + /// The version on success, null otherwise private static string? GetV5Version(string file, byte[]? fileContent, List positions) { // If we have no content @@ -313,7 +316,10 @@ namespace BinaryObjectScanner.Protection return $"{major}.{minor[0]}{minor[1]}.{patch[0]}{patch[1]}.{revision[0]}{revision[1]}{revision[2]}{revision[3]}"; } - // These live in the MS-DOS stub, for some reason + /// + /// Try to get the SecuROM v7 version from MS-DOS stub data, if possible + /// + /// The version on success, null otherwise private static string GetV7Version(PortableExecutable exe) { // If SecuROM is stripped, the MS-DOS stub might be shorter. @@ -348,6 +354,10 @@ namespace BinaryObjectScanner.Protection return "7 remnants"; } + /// + /// Try to get the SecuROM v8 (White Label) version from the .data section, if possible + /// + /// The version on success, null otherwise private static string GetV8WhiteLabelVersion(PortableExecutable exe) { // Get the .data/DATA section, if it exists @@ -379,6 +389,47 @@ namespace BinaryObjectScanner.Protection return $"{major}.{minor:00}.{patch:0000}"; } + /// + /// Helper method to run checks on a SecuROM Matroschka Package + /// + private static string? CheckMatroschkaPackage(SecuROMMatroschkaPackage package, bool includeDebug) + { + // Check for all 0x00 required, as at least one known non-RC matroschka has the field, just empty. + if (package.KeyHexString == null || package.KeyHexString.Trim('\0').Length == 0) + return "SecuROM Matroschka Package"; + + if (package.Entries == null || package.Entries.Length == 0) + return "SecuROM Matroschka Package - No Entries? - Please report to us on GitHub"; + + // The second entry in a Release Control matroschka package is always the encrypted executable + var entry = package.Entries[1]; + + if (entry.MD5 == null || entry.MD5.Length == 0) + return "SecuROM Matroschka Package - No MD5? - Please report to us on GitHub"; + + string md5String = BitConverter.ToString(entry.MD5!); + md5String = md5String.ToUpperInvariant().Replace("-", string.Empty); + + // TODO: Not used yet, but will be in the future + var fileData = package.ReadFileData(entry, includeDebug); + + // Check if encrypted executable is known via hash + if (MatroschkaHashDictionary.TryGetValue(md5String, out var gameName)) + return $"SecuROM Release Control - {gameName}"; + + // If not known, check if encrypted executable is likely an alt signing of a known executable + // Filetime could be checked here, but if it was signed at a different time, the time will vary anyways + var readPathBytes = entry.Path; + if (readPathBytes == null || readPathBytes.Length == 0) + return $"SecuROM Release Control - Unknown executable {md5String},{entry.Size} - Please report to us on GitHub!"; + + var readPathName = Encoding.ASCII.GetString(readPathBytes).TrimEnd('\0'); + if (MatroschkaSizeFilenameDictionary.TryGetValue(entry.Size, out var pathName) && pathName == readPathName) + return $"SecuROM Release Control - Unknown possible alt executable of size {entry.Size} - Please report to us on GitHub"; + + return $"SecuROM Release Control - Unknown executable {readPathName},{md5String},{entry.Size} - Please report to us on GitHub"; + } + /// /// Helper method to check if a given PortableExecutable is a SecuROM PA module. /// @@ -426,49 +477,5 @@ namespace BinaryObjectScanner.Protection return null; } - - /// - /// Helper method to run checks on a SecuROM Matroschka Package - /// - private static string? CheckMatroschkaPackage(SecuROMMatroschkaPackage package, bool includeDebug) - { - // Check for all 0x00 required, as at least one known non-RC matroschka has the field, just empty. - if (package.KeyHexString == null || package.KeyHexString.Trim('\0').Length == 0) - return "SecuROM Matroschka Package"; - - if (package.Entries == null || package.Entries.Length == 0) - return "SecuROM Matroschka Package - No Entries? Please report"; - - // The second entry in a Release Control matroschka package is always the encrypted executable - var entry = package.Entries[1]; - - if (entry.MD5 == null || entry.MD5.Length == 0) - return "SecuROM Matroschka Package - No MD5? Please report"; - - string md5String = BitConverter.ToString(entry.MD5!); - md5String = md5String.ToUpperInvariant().Replace("-", string.Empty); - - // Not used yet, but will be in the future - var fileData = package.ReadFileData(entry, includeDebug); - - // Check if encrypted executable is known via hash - if (MatroschkaHashDictionary.TryGetValue(md5String, out var gameName)) - { - // Returning "SecuROM Matroschka Package" technically redundant since implied. - return $"SecuROM Release Control - {gameName}"; - } - - // If not known, check if encrypted executable is likely an alt signing of a known executable - // Filetime could be checked here, but if it was signed at a different time, the time will vary anyways - var readPathBytes = entry.Path; - if (readPathBytes == null || readPathBytes.Length == 0) - return $"SecuROM Release Control - Unknown executable {md5String},{entry.Size}, please report to us on Github!"; - - var readPathName = Encoding.ASCII.GetString(readPathBytes).TrimEnd('\0'); - if (MatroschkaSizeFilenameDictionary.TryGetValue(entry.Size, out var pathName) && pathName == readPathName) - return $"SecuROM Release Control - Unknown possible alt executable of size {entry.Size}, please report to us on Github!"; - - return $"SecuROM Release Control - Unknown executable {readPathName},{md5String},{entry.Size}, please report to us on Github!"; - } } }