Skip to content

Commit b9e514b

Browse files
committed
Mfuscator plugin: Support wider set of parameters
1 parent 10db0e5 commit b9e514b

File tree

2 files changed

+91
-27
lines changed

2 files changed

+91
-27
lines changed

Cpp2IL.Plugin.Mfuscator/Cpp2IL.Plugin.Mfuscator.csproj

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,6 @@
55
<ImplicitUsings>enable</ImplicitUsings>
66
<DebugType>embedded</DebugType>
77
<Nullable>enable</Nullable>
8-
<OutputPath>..\Cpp2IL\bin\Release\net9.0\Plugins</OutputPath>
9-
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
108
</PropertyGroup>
119

1210
<ItemGroup>

Cpp2IL.Plugin.Mfuscator/MfuscatorSupportPlugin.cs

Lines changed: 91 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
1-
using System.Runtime.InteropServices;
1+
using System.Diagnostics;
2+
using System.Runtime.InteropServices;
23
using AssetRipper.Primitives;
34
using Cpp2IL.Core.Api;
45
using Cpp2IL.Core.Attributes;
6+
using LibCpp2IL.Logging;
57

68
[assembly:RegisterCpp2IlPlugin(typeof(Cpp2IL.Plugin.Mfuscator.MfuscatorSupportPlugin))]
79

@@ -22,6 +24,15 @@ private record struct ReconstructedSection(int OffsetAccordingToHeader, int Leng
2224
{
2325
public int ActualOffset => OffsetAccordingToHeader + Delta;
2426
}
27+
28+
private record struct DeadEnd(int depth, int deadEndNumber, int actualPos, string reason, List<ReconstructedSection> sections) : IComparable<DeadEnd>
29+
{
30+
public int CompareTo(DeadEnd other)
31+
{
32+
//inverted so largest first
33+
return other.depth.CompareTo(depth);
34+
}
35+
}
2536

2637
private class SectionRangeComparer : IEqualityComparer<(int Start, int End)[]>
2738
{
@@ -139,11 +150,16 @@ private static byte[] DecryptHeader(Span<byte> encryptedHeader, out byte stringL
139150
throw new Exception("Failed to determine header size");
140151
}
141152

142-
private List<List<ReconstructedSection>> FindPathsThroughMetadata(uint[] headerWords, int dataStart, int fileEnd, int maxResults = 10, int? expectedSectionCount = null, Dictionary<int, int>? alignBefore = null, int? originalHeaderSize = null)
153+
private List<List<ReconstructedSection>> FindPathsThroughMetadata(uint[] headerWords, int dataStart, int fileEnd, out SortedCollection<DeadEnd> bestDeadEnds, int maxResults = 10, int debugBestN = 10, int? expectedSectionCount = null, Dictionary<int, int>? alignBefore = null, int? originalHeaderSize = null)
143154
{
144155
alignBefore ??= new();
145156
var realOriginalHeaderSize = originalHeaderSize ?? dataStart;
146157
var maxAlignPad = alignBefore.Values.DefaultIfEmpty(1).Max() - 1;
158+
159+
var totalDeadEnds = 0;
160+
var deadEndCounter = 0;
161+
List<DeadEnd> deadEnds = new();
162+
var localBestDeadEnds = bestDeadEnds = new();
147163

148164
//Keep track of how many times each word appears so we can find a path using only the values which exist
149165
var pool = new SortedCollection<uint>(headerWords);
@@ -154,13 +170,33 @@ private List<List<ReconstructedSection>> FindPathsThroughMetadata(uint[] headerW
154170

155171
return results;
156172

173+
void TrackDeadEnd(int actualPos, List<ReconstructedSection> sections, string reason)
174+
{
175+
if(debugBestN <= 0)
176+
return; //we're not tracking dead ends, so ignore this
177+
178+
totalDeadEnds++;
179+
deadEndCounter++;
180+
var depth = sections.Count;
181+
182+
if(localBestDeadEnds.Count > 0 && depth < localBestDeadEnds[0].depth)
183+
return; //we've already got better dead ends, so ignore this one
184+
185+
var entry = new DeadEnd(depth, deadEndCounter, actualPos, reason, sections.ToList());
186+
deadEnds.Add(entry);
187+
188+
localBestDeadEnds.Add(entry);
189+
if (localBestDeadEnds.Count > debugBestN)
190+
localBestDeadEnds.RemoveAt(localBestDeadEnds.Count - 1);
191+
}
192+
157193
bool OffsetInRange(uint candidateOffset, int actualPos)
158194
{
159-
const int MinDelta = 0x0F;
195+
const int MinDelta = 0x10;
160196
const int MaxDelta = 0x40;
161197

162198
var delta = Math.Abs(actualPos - candidateOffset);
163-
return delta is > MinDelta and < MaxDelta;
199+
return delta is >= MinDelta and <= MaxDelta;
164200
}
165201

166202
//Alignment is according to the header before it was mangled, i.e. with original header size
@@ -201,9 +237,12 @@ void DepthFirstSearch(int actualPos, SortedCollection<uint> remainingPool, List<
201237
if(OffsetInRange(offset, actualPos))
202238
candidateOffsets.Add(offset);
203239
}
204-
205-
if(candidateOffsets.Count == 0)
240+
241+
if (candidateOffsets.Count == 0)
242+
{
243+
TrackDeadEnd(actualPos, sections, "No valid candidate offsets");
206244
return; //no more valid offsets, so this is a dead end
245+
}
207246

208247
var anyLengthFound = false;
209248
candidateOffsets.Sort();
@@ -245,7 +284,7 @@ void DepthFirstSearch(int actualPos, SortedCollection<uint> remainingPool, List<
245284

246285
if (!anyLengthFound)
247286
{
248-
//dead end, no-op as we don't track dead ends.
287+
TrackDeadEnd(actualPos, sections, "No valid length found for any candidate offset");
249288
}
250289
}
251290
}
@@ -255,22 +294,45 @@ private Dictionary<int, byte[]> DecryptEncryptedSections(byte[] encryptedMetadat
255294
var decryptedSectionBytes = new Dictionary<int, byte[]>();
256295

257296
//Use the size of the section and the information we worked out earlier to derive the key shared between all sections
297+
var stringLiteralsStart = sections[StringLiteralsSectionIndex].Start;
258298
var stringLiteralsSize = sections[StringLiteralsSectionIndex].End - sections[StringLiteralsSectionIndex].Start;
259-
var sectionsXorKeyAddend = (byte) ((stringLiteralsIsPlus ? (stringLiteralsXorKey - stringLiteralsSize) : (stringLiteralsXorKey + stringLiteralsSize)) & 0xFF);
299+
300+
var usingOffsetNotSize = false;
301+
byte sectionsXorKeyAddend = 0;
260302

261-
//Now decrypt the string literals section
262-
var decryptedLiterals = decryptedSectionBytes[StringLiteralsSectionIndex] = new byte[stringLiteralsSize];
263-
CyclicXor(
264-
encryptedMetadata.AsSpan(sections[StringLiteralsSectionIndex].Start, stringLiteralsSize),
265-
decryptedLiterals,
266-
sectionsXorKeyAddend,
267-
stringLiteralsIsPlus,
268-
stringLiteralsSize
269-
);
303+
foreach (var testUsingOffset in stackalloc bool[] { true, false })
304+
{
305+
var stringLiteralsKeyComponent = testUsingOffset ? stringLiteralsStart : stringLiteralsSize;
306+
var testAddend = (byte)((stringLiteralsIsPlus ? (stringLiteralsXorKey - stringLiteralsKeyComponent) : (stringLiteralsXorKey + stringLiteralsKeyComponent)) & 0xFF);
307+
308+
//Now decrypt the string literals section
309+
var decryptedLiterals = new byte[stringLiteralsSize];
310+
CyclicXor(
311+
encryptedMetadata.AsSpan(sections[StringLiteralsSectionIndex].Start, stringLiteralsSize),
312+
decryptedLiterals,
313+
testAddend,
314+
stringLiteralsIsPlus,
315+
stringLiteralsStart
316+
);
317+
318+
if(decryptedLiterals[0] == 0 && decryptedLiterals[1] == 0)
319+
{
320+
usingOffsetNotSize = testUsingOffset;
321+
sectionsXorKeyAddend = testAddend;
322+
decryptedSectionBytes[StringLiteralsSectionIndex] = decryptedLiterals;
323+
break;
324+
}
325+
}
270326

327+
if(!decryptedSectionBytes.ContainsKey(StringLiteralsSectionIndex))
328+
throw new Exception("Failed to determine whether section keys are based on offsets or sizes");
329+
330+
Logger.VerboseNewline($"Section keys are based on {(usingOffsetNotSize ? "offsets" : "sizes")}, with addend 0x{sectionsXorKeyAddend:X2}");
331+
271332
//String literal data starts with 2 00 bytes, so we can get the direction from that
272333
var stringLiteralDataStart = sections[StringLiteralsDataSectionIndex].Start;
273334
var stringLiteralDataSize = sections[StringLiteralsDataSectionIndex].End - sections[StringLiteralsDataSectionIndex].Start;
335+
var stringLiteralDataKeyComponent = usingOffsetNotSize ? stringLiteralDataStart : stringLiteralDataSize;
274336

275337
var firstByte = encryptedMetadata[stringLiteralDataStart];
276338
var secondByte = encryptedMetadata[stringLiteralDataStart + 1];
@@ -286,12 +348,13 @@ private Dictionary<int, byte[]> DecryptEncryptedSections(byte[] encryptedMetadat
286348
decryptedLiteralData,
287349
sectionsXorKeyAddend,
288350
stringLiteralDataIsPlus,
289-
stringLiteralDataSize
351+
stringLiteralDataKeyComponent
290352
);
291353

292354
//Strings are a bit harder, we need to look for the null terminators in the first 32 bytes
293355
var stringsSectionStart = sections[StringsSectionIndex].Start;
294356
var stringsSectionSize = sections[StringsSectionIndex].End - sections[StringsSectionIndex].Start;
357+
var stringsSectionKeyComponent = usingOffsetNotSize ? stringsSectionStart : stringsSectionSize;
295358
var stringsFirstXorByteOffset = 0;
296359
var stringsIsPlus = false;
297360
var foundZeroBytes = 0;
@@ -301,8 +364,8 @@ private Dictionary<int, byte[]> DecryptEncryptedSections(byte[] encryptedMetadat
301364
for (var i = 0; i < 32; i++)
302365
{
303366
var assumedXorKey = (byte) ((testIsPlus
304-
? (i + stringsSectionSize + sectionsXorKeyAddend)
305-
: (i - stringsSectionSize - sectionsXorKeyAddend)) & 0xFF);
367+
? (i + stringsSectionKeyComponent + sectionsXorKeyAddend)
368+
: (i - stringsSectionKeyComponent - sectionsXorKeyAddend)) & 0xFF);
306369
var xorByte = (byte) (encryptedMetadata[stringsSectionStart + i] ^ assumedXorKey);
307370
if (xorByte == 0)
308371
{
@@ -320,8 +383,8 @@ private Dictionary<int, byte[]> DecryptEncryptedSections(byte[] encryptedMetadat
320383

321384
//sanity check
322385
var stringsXorByte = (byte) ((stringsIsPlus
323-
? (stringsFirstXorByteOffset + stringsSectionSize + sectionsXorKeyAddend)
324-
: (stringsFirstXorByteOffset - stringsSectionSize - sectionsXorKeyAddend)) & 0xFF);
386+
? (stringsFirstXorByteOffset + stringsSectionKeyComponent + sectionsXorKeyAddend)
387+
: (stringsFirstXorByteOffset - stringsSectionKeyComponent - sectionsXorKeyAddend)) & 0xFF);
325388

326389
if(encryptedMetadata[stringsSectionStart + stringsFirstXorByteOffset] != stringsXorByte)
327390
throw new Exception("Strings section XOR key doesn't seem to be correct");
@@ -333,7 +396,7 @@ private Dictionary<int, byte[]> DecryptEncryptedSections(byte[] encryptedMetadat
333396
decryptedStrings,
334397
sectionsXorKeyAddend,
335398
stringsIsPlus,
336-
stringsSectionSize
399+
stringsSectionKeyComponent
337400
);
338401

339402
//for the rest of the sections we can just check the 3rd byte is 0 to determine the direction
@@ -342,6 +405,7 @@ private Dictionary<int, byte[]> DecryptEncryptedSections(byte[] encryptedMetadat
342405
{
343406
var sectionStart = sections[sectionIndex].Start;
344407
var sectionSize = sections[sectionIndex].End - sections[sectionIndex].Start;
408+
var sectionKeyComponent = usingOffsetNotSize ? sectionStart : sectionSize;
345409

346410
var decryptedSection = new byte[sectionSize];
347411
foreach (var testIsPlus in new bool[] { true, false })
@@ -351,7 +415,7 @@ private Dictionary<int, byte[]> DecryptEncryptedSections(byte[] encryptedMetadat
351415
decryptedSection,
352416
sectionsXorKeyAddend,
353417
testIsPlus,
354-
sectionSize
418+
sectionKeyComponent
355419
);
356420
if(decryptedSection[3] == 0)
357421
{
@@ -450,11 +514,13 @@ private byte[] RebuildMetadata(byte[] encryptedMetadata, List<(int Start, int En
450514

451515
Logger.InfoNewline($"Mfuscator header decrypted successfully. Header length: {headerLength} bytes. String literals XOR key: 0x{stringLiteralsXorKey:X2}. String literals use {(stringLiteralsIsPlus ? "plus" : "minus")} rotation. Will rebuild as version {MetadataVersion} metadata with assemblies section at index {assembliesSectionIndex}.");
452516

517+
Logger.VerboseNewline("Decrypted header: " + string.Join("", decryptedHeader.Select(b => b.ToString("X2"))));
518+
453519
while (metadataLength > headerLength)
454520
{
455521
Logger.VerboseNewline($"Trying metadata length 0x{metadataLength:X4}");
456522

457-
var paths = FindPathsThroughMetadata(headerWords, headerLength, metadataLength, maxResults: 65536, expectedSectionCount: 31, alignBefore: sectionAlignments, originalHeaderSize: OriginalHeaderSize);
523+
var paths = FindPathsThroughMetadata(headerWords, headerLength, metadataLength, out var bestDeadEnds, maxResults: 65536, debugBestN: 0, expectedSectionCount: 31, alignBefore: sectionAlignments, originalHeaderSize: OriginalHeaderSize);
458524

459525
if (paths.Count > 0)
460526
{

0 commit comments

Comments
 (0)