Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Move Crunch to its own Packer, improve SmartE detection. #353

Merged
merged 9 commits into from
Jan 14, 2025
36 changes: 36 additions & 0 deletions BinaryObjectScanner.Test/Packer/CrunchTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
using System.IO;
using BinaryObjectScanner.Packer;
using Xunit;

namespace BinaryObjectScanner.Test.Packer
{
public class CrunchTests
{
[Fact]
public void CheckPortableExecutableTest()
{
string file = "filename";
SabreTools.Models.PortableExecutable.Executable model = new();
Stream source = new MemoryStream();
SabreTools.Serialization.Wrappers.PortableExecutable pex = new(model, source);

var checker = new Crunch();
string? actual = checker.CheckExecutable(file, pex, includeDebug: false);
Assert.Null(actual);
}

[Fact]
public void ExtractPortableExecutableTest()
{
string file = "filename";
SabreTools.Models.PortableExecutable.Executable model = new();
Stream source = new MemoryStream();
SabreTools.Serialization.Wrappers.PortableExecutable pex = new(model, source);
string outputDir = string.Empty;

var checker = new Crunch();
bool actual = checker.Extract(file, pex, outputDir, includeDebug: false);
Assert.False(actual);
}
}
}
32 changes: 32 additions & 0 deletions BinaryObjectScanner/Packer/Crunch.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
using BinaryObjectScanner.Interfaces;
using SabreTools.Serialization.Wrappers;

namespace BinaryObjectScanner.Packer
{
// Packer used by all known SmartE games, but also used by some other non-SmartE protected software as well.
// https://web.archive.org/web/20020806102129/http://www.bit-arts.com/windows_solutions.html
// TODO: Other BitArts products may also use this same string. No samples have yet been found.
public class Crunch : IExtractableExecutable<PortableExecutable>
{
/// <inheritdoc/>
public string? CheckExecutable(string file, PortableExecutable pex, bool includeDebug)
{
// Get the last section strings, if they exist
var sections = pex.Model.SectionTable ?? [];
var strs = pex.GetSectionStrings(sections.Length - 1);
if (strs != null)
{
if (strs.Exists(s => s.Contains("BITARTS")))
return "Crunch";
}

return null;
}

/// <inheritdoc/>
public bool Extract(string file, PortableExecutable pex, string outDir, bool includeDebug)
{
return false;
}
}
}
67 changes: 56 additions & 11 deletions BinaryObjectScanner/Protection/SmartE.cs
Original file line number Diff line number Diff line change
@@ -1,26 +1,45 @@
using System.Collections.Generic;
using BinaryObjectScanner.Interfaces;
using SabreTools.Matching;
using SabreTools.Matching.Content;
using SabreTools.Matching.Paths;
using SabreTools.Serialization.Wrappers;

namespace BinaryObjectScanner.Protection
{
public class SmartE : IExecutableCheck<PortableExecutable>, IPathCheck
public class SmartE : IPathCheck, IExecutableCheck<PortableExecutable>
{
/// <inheritdoc/>
public string? CheckExecutable(string file, PortableExecutable pex, bool includeDebug)
{
// Get the last section strings, if they exist
var sections = pex.Model.SectionTable ?? [];
var strs = pex.GetSectionStrings(sections.Length - 1);
if (strs != null)
{
if (strs.Exists(s => s.Contains("BITARTS")))
return "SmartE";
}
// Only works on stub generated from running the program yourself
if (pex.InternalName.OptionalEquals("SmarteSECURE"))
return "SmartE";

var sections = pex.Model.SectionTable ?? [];

return null;
if (sections.Length > 0)
HeroponRikiBestest marked this conversation as resolved.
Show resolved Hide resolved
{
// Get the last section data, if it exists
var lastSectionData = pex.GetSectionData(sections.Length - 1);
if (lastSectionData != null)
{
// All sections seen so far are the last sections, so this is "technically"
// the only known needed check so far. Others kept as backups if this fails
// on some future entry
var matchers = GenerateMatchers();
var match = MatchUtil.GetFirstMatch(file, lastSectionData, matchers, includeDebug);
if (!string.IsNullOrEmpty(match))
return match;
}
}

// Specific known named sections:
// .bss (Rise of Nations)
// .tls (Zoo Tycoon 2)
// .idata (http://redump.org/disc/58561/ and http://redump.org/disc/71983/)
// .edata (http://redump.org/disc/36619/)

return null;
}
HeroponRikiBestest marked this conversation as resolved.
Show resolved Hide resolved

/// <inheritdoc/>
Expand Down Expand Up @@ -49,5 +68,31 @@ public List<string> CheckDirectoryPath(string path, List<string>? files)

return MatchUtil.GetFirstMatch(path, matchers, any: true);
}

/// <summary>
HeroponRikiBestest marked this conversation as resolved.
Show resolved Hide resolved
/// Generate the set of matchers used for each section
/// </summary>
private static List<ContentMatchSet> GenerateMatchers()
{
return
[
// Matches most games, but a few like http://redump.org/disc/16541/
// are only matched on the 00001/2.TMP files. PiD and other programs
// don't detect this game either, though (Aside from the stub)
new(new byte?[]
{
0xEB, 0x15, 0x03, 0x00, 0x00, 0x00, null, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x68, 0x00, 0x00, 0x00, 0x00, 0x55,
0xE8, 0x00, 0x00, 0x00, 0x00, 0x5D, 0x81, 0xED,
0x1D, 0x00, 0x00, 0x00, 0x8B, 0xC5, 0x55, 0x60,
0x9C, 0x2B, 0x85, 0x8F, 0x07, 0x00, 0x00, 0x89,
0x85, 0x83, 0x07, 0x00, 0x00, 0xFF, 0x74, 0x24,
0x2C, 0xE8, 0xBB, 0x01, 0x00, 0x00, 0x0F, 0x82,
0x2F, 0x06, 0x00, 0x00, 0xE8, 0x8E, 0x04, 0x00,
0x00, 0x49, 0x0F, 0x88, 0x23, 0x06
}, "SmartE"),
];
}
}
}
Loading