1- using System . Runtime . InteropServices ;
1+ using System . Diagnostics ;
2+ using System . Runtime . InteropServices ;
23using AssetRipper . Primitives ;
34using Cpp2IL . Core . Api ;
45using 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