diff --git a/BinaryObjectScanner/Protection/CopyX.cs b/BinaryObjectScanner/Protection/CopyX.cs new file mode 100644 index 00000000..c0d54e43 --- /dev/null +++ b/BinaryObjectScanner/Protection/CopyX.cs @@ -0,0 +1,193 @@ +using System; +#if NET40_OR_GREATER || NETCOREAPP +using System.Collections.Concurrent; +#endif +using System.Collections.Generic; +using System.IO; +using System.Text; +using System.Linq; +using BinaryObjectScanner.Interfaces; +using SabreTools.IO; +using SabreTools.IO.Extensions; +using SabreTools.Matching; +using SabreTools.Matching.Content; +using SabreTools.Matching.Paths; +using SabreTools.Serialization.Wrappers; + +namespace BinaryObjectScanner.Protection +{ + // TODO: Technically not necessary, but just check for light/pro first and only if it isn't found look for the other. + // It should be an Or situation and not an And situation. + // TODO: Figure out if Light and Professional are what designate rings and rings+disccheck + public class CopyX : IPathCheck, IPortableExecutableCheck + { + // https://web.archive.org/web/20011016234742/http://www.optimal-online.de:80/product/copy_x.htm + // There are four kinds of copy-X; Light, Profesisonal, audio, and Trial Maker. + // Audio is for Audio CDs. Might be scannable, might not. Samples needed to confirm. + // No samples of Trial are known at the moment, so it can't be checked for either. + // There are two kinds of copy-X generally observed; those with only rings, and those with rings and a disc check. + // These comments assume with 0 evidence that the former is Light and the latter is Professional. + // Because there is no evidence, only copy-X is being returned for now. This check has pre-emptively separated + // the two, just for when a designation can be applied for sure. + + // Overall: + // Whenever these comments state "at the end of" or "at the start of" pertaining to the filesystem, they refer + // to alphabetical order, because this is how copy-X images seem to be mastered usually (not always, but w/e). + // Both Light and Professional have a directory at the end of the image. The files within this directory are + // intersected by the physical ring. + // This file is usually called ZDAT, but not always. At least one instance of Light calls it ZDATA. At least one + // instance of Professional calls it System. + // Seemingly it can be anything. It doesn't help that most known samples are specifically from one company's + // games, Tivola. Still, most use ZDAT. + + // Professional: + // All instances of professional contain a disc check, performed via optgraph.dll. + // All instances of professional contain in the directory at the end of the image 3 files. gov_[something].x64, + // iofile.x64, and sound.x64. + // Due to gov's minor name variance, sound.x64 sometimes being intersected by a ring at the start, and + // iofile.x64 being referenced directly in optgraph.x64, only iofile.x64 is being checked for now. + // TODO: optgraph.dll also contains DRM to prevent kernel debugger SoftICE from being used, via a process called + // SoftICE-Test. It is not currently known if this is specifically part of copy-X, or if it's an external + // solution employed by both copy-X and also other companies. If it's the latter, it should have its own check. + // It has none here since it wouldn't be necessary. + + // Light: + // All instances of light contain 1 or more files in the directory at the end of the image. They all consist of + // either 0x00, or some data that matches between entries (and also is present in the 3 Professional files), + // except for the parts with the rings running through them. + // TODO: Check the last directory alphabetically and not just ZDAT* + + /// + public string? CheckPortableExecutable(string file, PortableExecutable pex, bool includeDebug) + { + + // Checks for Professional + // PEX checks intentionally only detect Professional + + var sections = pex.Model.SectionTable; + if (sections == null) + return null; + + if (pex.OverlayStrings != null) + { + // Checks if main executable contains reference to optgraph.dll. + // This might be better removed later, as Redump ID 82475 is a false positive, and also doesn't actually + // contain the actual optgraph.dll file. + // TODO: Find a way to check for situations like Redump ID 48393, where the string is spaced out with + // 0x00 between letters and does not show up on string checks. + // TODO: This might need to check every single section. Unsure until more samples are acquired. + // TODO: TKKG also has an NE 3.1x executable with a reference. This can be added later. + // Samples: Redump ID 108150 + if (pex.OverlayStrings.Any(s => s.Contains("optgraph.dll"))) + return "copy-X"; + } + + var strs = pex.GetFirstSectionStrings(".rdata"); + if (strs != null) + { + // Samples: Redump ID 82475, German Emergency 2 Deluxe, Redump ID 48393 + if (strs.Any(s => s.Contains("optgraph.dll"))) + return "copy-X"; + } + + return null; + } + + /// +#if NET20 || NET35 + public Queue CheckDirectoryPath(string path, IEnumerable? files) +#else + public ConcurrentQueue CheckDirectoryPath(string path, IEnumerable? files) +#endif + { +#if NET20 || NET35 + var protections = new Queue(); +#else + var protections = new ConcurrentQueue(); +#endif + + // Checks for Light + // Directory checks intentionally only detect Light + + if (files == null) + return protections; + + // Excludes files with .x64 extension to avoid flagging Professional files. + // Sorts list of files in ZDAT* so just the first file gets pulled, later ones have a chance of the ring + // intersecting the start of the file. + var fileList = files.Where(f => !f.EndsWith(".x64", StringComparison.OrdinalIgnoreCase)) + .Where(f => + { + // TODO: Compensate for the check being run a directory or more higher + f = f.Remove(0, path.Length); + f = f.TrimStart('/', '\\'); + return f.StartsWith("ZDAT", StringComparison.OrdinalIgnoreCase); + }) + .OrderBy(f => f) + .ToList(); + + if (fileList.Count > 0) + { + try + { + using var stream = File.OpenRead(fileList[0]); + byte[] block = stream.ReadBytes(1024); + + var matchers = new List + { + // Checks if the file contains 0x00 + // Samples: Redump ID 81628 + new(Enumerable.Repeat(0x00, 1024).ToArray(), "copy-X"), + + // Checks for whatever this data is. + // Samples: Redump ID 84759, Redump ID 107929. Professional discs also have this data, hence the exclusion check. + new( + [ + 0x02, 0xFE, 0x4A, 0x4F, 0x52, 0x4B, 0x1C, 0xE0, + 0x79, 0x8C, 0x7F, 0x85, 0x04, 0x00, 0x46, 0x46, + 0x49, 0x46, 0x07, 0xF9, 0x9F, 0xA0, 0xA1, 0x9D, + 0xDA, 0xB6, 0x2C, 0x2D, 0x2D, 0x2C, 0xFF, 0x00, + 0x6F, 0x6E, 0x71, 0x6A, 0xFC, 0x06, 0x64, 0x62, + 0x65, 0x5F, 0xFB, 0x06, 0x31, 0x31, 0x31, 0x31, + 0x00, 0x00, 0x1D, 0x1D, 0x1F, 0x1D, 0xFE, 0xFD, + 0x51, 0x57, 0x56, 0x51, 0xFB, 0x06, 0x33, 0x34, + ], "copy-X"), + }; + var match = MatchUtil.GetFirstMatch(fileList[0], block, matchers, false); + if (!string.IsNullOrEmpty(match)) + protections.Enqueue(match!); + } + catch { } + } + + return protections; + } + + /// + public string? CheckFilePath(string path) + { + + // Checks for Professional + // File Path checks intentionally only detect Professional + + var matchers = new List + { + // Samples: Redump ID 108150, Redump ID 48393 + + // File responsible for disc check + new(new FilePathMatch("optgraph.dll"), "copy-X"), + + // Seemingly comorbid file, referenced in above file + new(new FilePathMatch("iofile.x64"), "copy-X"), + + // Seemingly comorbid file + new(new FilePathMatch("sound.x64"), "copy-X"), + + // Seemingly comorbid file + // Check commented out until implementation can be decided + // new(new FilePathMatch("gov_*.x64"), "copy-X"), + }; + return MatchUtil.GetFirstMatch(path, matchers, any: true); + } + } +} diff --git a/README.md b/README.md index 68ce7ce2..340fa263 100644 --- a/README.md +++ b/README.md @@ -73,6 +73,7 @@ Below is a list of protections detected by BinaryObjectScanner. The two columns | Cenga ProtectDVD | True | True | | | Channelware | True | True | Version finding and detection of later versions unimplemented | | ChosenBytes CodeLock | True | True | Partially unconfirmed² | +| copy-X | True | True | | | CopyKiller | True | True | | | CopyLok/CodeLok | True | False | | | CrypKey | True | True | | @@ -133,7 +134,7 @@ Below is a list of protections detected by BinaryObjectScanner. The two columns | Sysiphus / Sysiphus DVD | True | False | | | TAGES | True | True | Partially unconfirmed² | | Themida/WinLicense/Code Virtualizer | True | False | Only certain products/versions currently detected | -| Tivola Ring Protection | False | True | | +| ~~Tivola Ring Protection~~ | False | True | Existing checks found to actually be indicators of copy-X, rather than some Tivola-specific ring protection. | | TZCopyProtection | False | True | Partially unconfirmed² | | Uplay | True | True | | | Windows Media Data Session DRM | True | True | |