IL2CPP: Reconstruct scrambled pointer field offset
This commit is contained in:
@@ -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,36 +596,94 @@ 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
|
||||||
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<uint>(ptr, max);
|
var testItems = Image.ReadMappedArray<uint>(ptr, max);
|
||||||
var previousItem = 0u;
|
var previousItem = 0u;
|
||||||
var inversions = 0;
|
var inversions = 0;
|
||||||
var zeroes = 0;
|
var zeroes = 0;
|
||||||
|
|
||||||
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;
|
||||||
// Count zeroes and inversions (equality counts as inversion here since two arguments can't share a heap offset)
|
if (item > previousItem + MAX_FIELD_OFFSET_GAP && item != 0xffffffff)
|
||||||
if (item <= previousItem)
|
break;
|
||||||
inversions++;
|
// Count zeroes and inversions (equality counts as inversion here since two arguments can't share a heap offset)
|
||||||
if (item == 0)
|
if (item <= previousItem)
|
||||||
zeroes++;
|
inversions++;
|
||||||
previousItem = item;
|
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) {
|
// Try as a sequence of pointers to sets of uints
|
||||||
var inversionsPc = (double) inversions / foundData.count;
|
if (metadata.Version >= 21) {
|
||||||
var zeroesPc = (double) zeroes / foundData.count;
|
foundData.count = 0;
|
||||||
|
var max = limit / (Image.Bits / 8);
|
||||||
|
|
||||||
if (inversionsPc >= MIN_FIELD_OFFSET_INVERSION && inversionsPc <= MAX_FIELD_OFFSET_INVERSION
|
if (max >= MIN_FIELD_OFFSETS) {
|
||||||
&& zeroesPc >= MIN_FIELD_OFFSET_ZEROES && zeroesPc <= MAX_FIELD_OFFSET_ZEROES) {
|
|
||||||
foundItem = ref fieldOffsets;
|
var testItems = Image.ReadMappedArray<ulong>(ptr, max);
|
||||||
break;
|
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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user