IL2CPP: Reconstruct scrambled pointer field offset

This commit is contained in:
Katy Coe
2020-12-07 20:49:21 +01:00
parent 61fdf6b59e
commit c242a84f0f

View File

@@ -111,8 +111,6 @@ namespace Il2CppInspector
Console.WriteLine("Reconstructing obfuscated registration metadata..."); Console.WriteLine("Reconstructing obfuscated registration metadata...");
UpdateProgress(0); UpdateProgress(0);
// Some heuristic constants
// Counts from minimal compiles // Counts from minimal compiles
// v21 test project: // v21 test project:
@@ -129,6 +127,8 @@ namespace Il2CppInspector
// v24.2 without Unity: // v24.2 without Unity:
// genericInsts - 0x06D4, genericMethodTable - 0x31E8, types - 0x318A, methodSpecs - 0x3AD8, fieldOffsets - 0x0B3D, metadataUsages - 0x3BA8 // 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' // The maximum address gap in a sequential list of pointers before the sequence is considered to be 'broken'
const int MAX_SEQUENCE_GAP = 0x10000; const int MAX_SEQUENCE_GAP = 0x10000;
@@ -170,10 +170,14 @@ namespace Il2CppInspector
const double MAX_FIELD_OFFSET_INVERSION = 0.6; const double MAX_FIELD_OFFSET_INVERSION = 0.6;
// The minimum and maximum proportions of zeroes we expect in a non-pointer field offset list // The minimum and maximum proportions of zeroes we expect in a non-pointer field offset list
// Example values: 0.303, 0.321, 0.385 // Example values: 0.116, 0.179, 0.303, 0.321, 0.385
const double MIN_FIELD_OFFSET_ZEROES = 0.2; // 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; 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 // Things we need from Il2CppCodeRegistration
// methodPointers (<=24.1) -> list of function pointers (1st count) (non-sequential) // methodPointers (<=24.1) -> list of function pointers (1st count) (non-sequential)
@@ -592,6 +596,7 @@ namespace Il2CppInspector
// for the binary's function calling convention, and never greater than the maximum heap offset of the last parameter // for the binary's function calling convention, and never greater than the maximum heap offset of the last parameter
// Try as sequence of uints // Try as sequence of uints
if (metadata.Version <= 21) {
var max = limit / sizeof(uint); var max = limit / sizeof(uint);
if (max >= MIN_FIELD_OFFSETS) { if (max >= MIN_FIELD_OFFSETS) {
@@ -604,6 +609,8 @@ namespace Il2CppInspector
foreach (var item in testItems) { foreach (var item in testItems) {
if (item > MAX_FIELD_OFFSET && item != 0xffffffff) if (item > MAX_FIELD_OFFSET && item != 0xffffffff)
break; 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) // Count zeroes and inversions (equality counts as inversion here since two arguments can't share a heap offset)
if (item <= previousItem) if (item <= previousItem)
inversions++; inversions++;
@@ -627,6 +634,61 @@ namespace Il2CppInspector
} }
} }
// Try as a sequence of pointers to sets of uints
if (metadata.Version >= 21) {
foundData.count = 0;
var max = limit / (Image.Bits / 8);
if (max >= MIN_FIELD_OFFSETS) {
var testItems = Image.ReadMappedArray<ulong>(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;
}
}
}
}
}
foundData = (0ul, -1); foundData = (0ul, -1);
} }