Skip to content
147 changes: 140 additions & 7 deletions BinaryObjectScanner/Protection/SecuROM.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
using System;
using System;
using System.Collections.Generic;
using System.Text;
using BinaryObjectScanner.Interfaces;
Expand All @@ -13,6 +13,89 @@ namespace BinaryObjectScanner.Protection
// TODO: Investigate SecuROM for Macintosh
public class SecuROM : IExecutableCheck<PortableExecutable>, IPathCheck
{
/// <summary>
/// Matches hash of the Release Control-encrypted executable to known hashes
/// </summary>
// Allegedly, some version of Runaway: A Twist of Fate has RC
private static readonly Dictionary<string, string> MatroschkaHashDictionary = new Dictionary<string, string>()
{
{"C6DFF6B08EE126893840E107FD4EC9F6", "Alice - Madness Returns (USA)+(Europe)"},
{"D7703D32B72185358D58448B235BD55E", "Arcania - Gothic 4 - Not in redump yet"},
{"FAF6DD75DDB335101CB77A714793DC28", "Batman - Arkham City - Game of the Year Edition (UK)"},
{"77999579EE4378BDFAC9438CC9CDB44E", "Batman - Arkham City (USA)+(Europe)"},
{"73114CF3DEEDD0FA2BF52ACB70B048BC", "Battlefield - Bad Company 2 (GFWM)"},
{"56C23D930F885BA5BF026FEABFC31856", "Battlefield 3 (USA)+(Europe, Asia)"},
{"631C0ACE596722488E3393BD1AFCE731", "Battlefield 3 (Russia)"},
{"6E481CDEBDB30B8889340CEC3300C931", "Battlefield 3 (UK)"},
{"C5AB3931A3CBB0141CC5A4638C391F4F", "BioShock 2 (Argentina)+(Europe, Australia)+(Europe)+(Europe) (Alt)+(Netherlands)+(USA) - Multiplayer executable"},
{"73DB35419A651CB69E78A641BBC88A4C", "BioShock 2 (Argentina)+(Europe, Australia)+(Europe)+(Europe) (Alt)+(Netherlands)+(USA) - Singleplayer executable"},
{"E5D63D369023A1D1074E7B13952FA0F2", "BioShock 2 (Russia) - Multiplayer executable"},
{"C39F3BCB74EA8E1215D39AC308F64229", "BioShock 2 (Russia) - Singleplayer executable"},
{"3C340B2D4DA25039C136FEE1DC2DDE17", "Borderlands (USA)+(Europe) (En,Fr,De,Es,It)"},
{"D35122E0E3F7B35C98BEFD706C260F83", "Crysis Warhead (Europe)+(Russia)+(USA)+(USA) (Alt)"},
{"D9254D3353AB229806A806FCFCEABDBD", "Crysis Warhead (Japan)"},
{"D69798C9198A6DB6A265833B350AC544", "Crysis Warhead (Turkey)"},
{"9F574D56F1A4D7847C6A258DC2AF61A5", "Crysis Wars (Europe)+(Japan)+(Russia)+(Turkey)+(USA)+(USA) (Rerelease)"},
{"C200ABC342A56829A5356AA0BEA5F2DF", "Dead Space 2 (Europe)+(Russia)+(USA)"},
{"81B3415AF21C8691A1CD55A422BA64D5", "Disney TRON - Evolution (Europe) (En,Fr,De,Es,It,Nl)"},
{"DF9609EDE95A1F89F7A39A08778CC3B8", "Disney Tron - Evolution (Europe) (Pl,Cs)"},
{"B8698C7C05D7F9E049DC038B9868FCF7", "Disney TRON - Evolution (Russia) (En,Ru)"},
{"0D5800F94643633CD3F025CFFD968DF2", "Dragon Age II (Europe)+(USA) - PC executable"},
{"3F1AFA4783F9001AACF0379A2A432A13", "Dragon Age II (Europe)+(USA) - Mac executable"},
{"530A3EB454570EEE5519ABE6BAE0187C", "Far Cry 2 (Europe)+(USA) (En,Fr,De,Es,It)"},
{"4B3B130A70F3711BFA8AF06195FE4250", "FIFA 12 (Europe)"},
{"F43F777696B0FAD3A331298C48104B31", "FIFA 13 (Europe)"},
{"1DF0E096068839C12E4B353AC50E41FA", "Grand Theft Auto - Episodes from Liberty City (Russia)"},
{"F3ADC6D08BEC42FB988F2F62B5C731FA", "Grand Theft Auto - Episodes from Liberty City (USA)"},
{"5B90D42A650A8F08095984AEE3D961B9", "Grand Theft Auto IV (Europe, Asia)+(Europe)+(Latin America)+(USA) (Rev 1)"},
{"4510F0BDD58D30D072952E225E294F9B", "Grand Theft Auto IV (USA)"},
{"2AC9616A7FE46D142F653D798EAA07FD", "Harry Potter and the Deathly Hallows Part 2 (GFWM)"},
{"AE144755FB12062780E4E4CCD29B5296", "Kingdoms of Amalur - Reckoning (Germany)"},
{"6E4AB6416D91F85954150BC50D02688E", "Kingdoms of Amalur - Reckoning (USA) (En,Fr,Es,It,Nl)"},
{"935103B1600F1C743AF892A0DD761913", "Mass Effect 2 (GFWM)"},
{"EEB2AE163AEEF6BE54C5A9BDD38C600E", "Mass Effect 3 (Europe, Australia)+(USA)"},
{"2D08B73217B722A4F9E01523F07E118E", "Mass Effect 3 (UK)"},
{"4EA3CE0670DECD0A74FA312714C22025", "Need for Speed - The Run (Europe)"},
{"88AB0D4A4EE7867F740AD063400FCDB5", "Need for Speed - The Run (Russia)"},
{"EAD8E224D0F44706BA92BD9B27FEBA7D", "Need for Speed - The Run (USA)"},
{"316FF217BD129F9EEBD05A321A8FBE60", "Syndicate (USA)+(Europe) (En,Fr,De,Es,It,Ru)"},
};

/// <summary>
/// If hash isn't currently known, check size and pathname of the encrypted executable to determine if alt or entirely missing
/// </summary>
private static readonly Dictionary<uint, string> MatroschkaSizeFilenameDictionary = new Dictionary<uint, string>()
{
{4646091, "hp8.aec"},
{5124592, "output\\LaunchGTAIV.aec"},
{5445032, "output\\Crysis.aec"},
{5531004, "output\\FarCry2.aec"},
{6716108, "LaunchEFLC.aec"},
{6728396, "./Bioshock2Launcher.aec"},
{6732492, "./BioShock2Launcher.aec"},
{7150283, "GridGameLauncher.aec"},
{7154379, "GridGameLauncher.aec"},
{8705763, "temp0.aec"},
{12137051, "dragonage2.aec"},
{12896904, "output\\crysis.aec"},
{12917384, "output\\crysis.aec"},
{12925576, "output\\crysis.aec"},
{16415836, "output\\MassEffect2.aec"},
{17199339, "AliceMadnessReturns.aec"},
{22357747, "MassEffect3.aec"},
{23069931, "fifa.aec"},
{25823091, "Arcania.aec"},
{27564780, "output\\BFBC2Game.aec"},
{30470419, "temp0.aec"},
{32920811, "temp0.aec"},
{35317996, "output\\ShippingPC-WillowGame-SecuROM.aec"},
{35610875, "temp0.aec"},
{37988075, "temp0.aec"},
{43612419, "BatmanAC.aec"},
{45211355, "BatmanAC.aec"},
{48093043, "deadspace_f.aec"},
};

/// <inheritdoc/>
public string? CheckExecutable(string file, PortableExecutable exe, bool includeDebug)
{
Expand All @@ -21,13 +104,15 @@ public class SecuROM : IExecutableCheck<PortableExecutable>, IPathCheck
if (paModule != null)
return paModule;

// Get the matrosch section, if it exists
if (exe.ContainsSection("matrosch", exact: true))
return $"SecuROM Matroschka Package";
// Check if executable contains a SecuROM Matroschka Package

// Get the rcpacker section, if it exists
if (exe.ContainsSection("rcpacker", exact: true))
return $"SecuROM Release Control";
var package = exe.MatroschkaPackage;
if (package == null)
return null;

var matroschka = CheckMatroschkaPackage(package, includeDebug);
if (matroschka != null)
return matroschka;

if (exe.ContainsSection(".dsstext", exact: true))
return $"SecuROM 8.03.03+";
Expand Down Expand Up @@ -341,5 +426,53 @@ private static string GetV8WhiteLabelVersion(PortableExecutable exe)

return null;
}

/// <summary>
/// Helper method to run checks on a SecuROM Matroschka Package
/// </summary>
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);

var fileData = package.ReadFileData(entry, includeDebug); // Not used yet, but will be in the future

// Check if encrypted executable is known via hash
if (MatroschkaHashDictionary.TryGetValue(md5String, out var gameName))
{
// Returning "SecuROM Matroschka Package" technically redundant since implied.
// Since non-debug is more common, return first
if (!includeDebug)
return "SecuROM Release Control";

// TODO: I'd like to have this debug output be the normal output, but I assume I'm not allowed to do that
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 ON GITHUB IMMEDIATELY!!!";

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 ON GITHUB IMMEDIATELY!!!";
}
}
}