From c242a84f0fdb7fe40b0472face9453572c6206c2 Mon Sep 17 00:00:00 2001 From: Katy Coe Date: Mon, 7 Dec 2020 20:49:21 +0100 Subject: [PATCH] IL2CPP: Reconstruct scrambled pointer field offset --- .../IL2CPP/ReconstructMetadata.cs | 116 ++++++++++++++---- 1 file changed, 89 insertions(+), 27 deletions(-) diff --git a/Il2CppInspector.Common/IL2CPP/ReconstructMetadata.cs b/Il2CppInspector.Common/IL2CPP/ReconstructMetadata.cs index ef5fa89..ed89179 100644 --- a/Il2CppInspector.Common/IL2CPP/ReconstructMetadata.cs +++ b/Il2CppInspector.Common/IL2CPP/ReconstructMetadata.cs @@ -111,8 +111,6 @@ namespace Il2CppInspector Console.WriteLine("Reconstructing obfuscated registration metadata..."); UpdateProgress(0); - // Some heuristic constants - // Counts from minimal compiles // v21 test project: @@ -129,6 +127,8 @@ namespace Il2CppInspector // v24.2 without Unity: // genericInsts - 0x06D4, genericMethodTable - 0x31E8, types - 0x318A, methodSpecs - 0x3AD8, fieldOffsets - 0x0B3D, metadataUsages - 0x3BA8 + // Some heuristic constants + // The maximum address gap in a sequential list of pointers before the sequence is considered to be 'broken' const int MAX_SEQUENCE_GAP = 0x10000; @@ -170,10 +170,14 @@ namespace Il2CppInspector const double MAX_FIELD_OFFSET_INVERSION = 0.6; // The minimum and maximum proportions of zeroes we expect in a non-pointer field offset list - // Example values: 0.303, 0.321, 0.385 - const double MIN_FIELD_OFFSET_ZEROES = 0.2; + // Example values: 0.116, 0.179, 0.303, 0.321, 0.385 + // The main thing is to force enough zeroes to prevent it being confused with a list with no zeroes (eg. genericClasses) + const double MIN_FIELD_OFFSET_ZEROES = 0.10; const double MAX_FIELD_OFFSET_ZEROES = 0.5; + // The maximum allowed gap between two field offsets + const int MAX_FIELD_OFFSET_GAP = 0x10000; + // Things we need from Il2CppCodeRegistration // methodPointers (<=24.1) -> list of function pointers (1st count) (non-sequential) @@ -592,36 +596,94 @@ namespace Il2CppInspector // for the binary's function calling convention, and never greater than the maximum heap offset of the last parameter // Try as sequence of uints - var max = limit / sizeof(uint); + if (metadata.Version <= 21) { + var max = limit / sizeof(uint); - if (max >= MIN_FIELD_OFFSETS) { + if (max >= MIN_FIELD_OFFSETS) { - var testItems = Image.ReadMappedArray(ptr, max); - var previousItem = 0u; - var inversions = 0; - var zeroes = 0; + var testItems = Image.ReadMappedArray(ptr, max); + var previousItem = 0u; + var inversions = 0; + var zeroes = 0; - foreach (var item in testItems) { - if (item > MAX_FIELD_OFFSET && item != 0xffffffff) - break; - // Count zeroes and inversions (equality counts as inversion here since two arguments can't share a heap offset) - if (item <= previousItem) - inversions++; - if (item == 0) - zeroes++; - previousItem = item; + foreach (var item in testItems) { + if (item > MAX_FIELD_OFFSET && item != 0xffffffff) + break; + if (item > previousItem + MAX_FIELD_OFFSET_GAP && item != 0xffffffff) + break; + // Count zeroes and inversions (equality counts as inversion here since two arguments can't share a heap offset) + if (item <= previousItem) + inversions++; + if (item == 0) + zeroes++; + previousItem = item; - foundData.count++; + foundData.count++; + } + + if (foundData.count >= MIN_FIELD_OFFSETS) { + var inversionsPc = (double) inversions / foundData.count; + var zeroesPc = (double) zeroes / foundData.count; + + if (inversionsPc >= MIN_FIELD_OFFSET_INVERSION && inversionsPc <= MAX_FIELD_OFFSET_INVERSION + && zeroesPc >= MIN_FIELD_OFFSET_ZEROES && zeroesPc <= MAX_FIELD_OFFSET_ZEROES) { + foundItem = ref fieldOffsets; + break; + } + } } + } - if (foundData.count >= MIN_FIELD_OFFSETS) { - var inversionsPc = (double) inversions / foundData.count; - var zeroesPc = (double) zeroes / foundData.count; + // Try as a sequence of pointers to sets of uints + if (metadata.Version >= 21) { + foundData.count = 0; + var max = limit / (Image.Bits / 8); - if (inversionsPc >= MIN_FIELD_OFFSET_INVERSION && inversionsPc <= MAX_FIELD_OFFSET_INVERSION - && zeroesPc >= MIN_FIELD_OFFSET_ZEROES && zeroesPc <= MAX_FIELD_OFFSET_ZEROES) { - foundItem = ref fieldOffsets; - break; + if (max >= MIN_FIELD_OFFSETS) { + + var testItems = Image.ReadMappedArray(ptr, max); + var zeroes = 0; + + foreach (var item in testItems) { + // Every pointer must either be zero or mappable into a data or BSS section + if (item != 0ul && !dataSections.Any(s => item >= s.VirtualStart && item < s.VirtualEnd) + && !bssSections.Any(s => item >= s.VirtualStart && item < s.VirtualEnd)) + break; + + // Count zeroes + if (item == 0ul) + zeroes++; + + // Every valid pointer must point to a series of incrementing offsets until an inversion or a large gap + else if (dataSections.Any(s => item >= s.VirtualStart && item < s.VirtualEnd)) { + Image.Position = Image.MapVATR(item); + var previous = 0u; + var offset = 0u; + var valid = true; + while (offset != 0xffffffff && offset > previous && valid) { + previous = offset; + offset = Image.ReadUInt32(); + // Consider a large gap as the end of the sequence + if (offset > previous + MAX_FIELD_OFFSET_GAP && offset != 0xffffffff) + break; + // A few offsets seem to have the top bit set for some reason + if (offset >= previous && (offset & 0x7fffffff) > MAX_FIELD_OFFSET && offset != 0xffffffff) + valid = false; + } + if (!valid) + break; + } + + foundData.count++; + } + + if (foundData.count >= MIN_FIELD_OFFSETS) { + var zeroesPc = (double) zeroes / foundData.count; + + if (zeroesPc >= MIN_FIELD_OFFSET_ZEROES && zeroesPc <= MAX_FIELD_OFFSET_ZEROES) { + foundItem = ref fieldOffsets; + break; + } } } }