diff --git a/Il2CppInspector.Common/Cpp/UnityHeaders/UnityHeaders.cs b/Il2CppInspector.Common/Cpp/UnityHeaders/UnityHeaders.cs index a3db07c..e734bc8 100644 --- a/Il2CppInspector.Common/Cpp/UnityHeaders/UnityHeaders.cs +++ b/Il2CppInspector.Common/Cpp/UnityHeaders/UnityHeaders.cs @@ -133,7 +133,7 @@ namespace Il2CppInspector.Cpp.UnityHeaders var apis = GetAllAPIHeaders().Where(a => a.VersionRange.Intersect(totalRange) != null).ToList(); // Get the API exports for the binary - var exports = binary.GetAPIExports(); + var exports = binary.APIExports; // No il2cpp exports? Just return the earliest version from the header range // The API version may be incorrect but should be a subset of the real API and won't cause C++ compile errors diff --git a/Il2CppInspector.Common/IL2CPP/Il2CppBinary.cs b/Il2CppInspector.Common/IL2CPP/Il2CppBinary.cs index 224af15..327ebbf 100644 --- a/Il2CppInspector.Common/IL2CPP/Il2CppBinary.cs +++ b/Il2CppInspector.Common/IL2CPP/Il2CppBinary.cs @@ -19,6 +19,9 @@ namespace Il2CppInspector { public IFileFormatReader Image { get; } + // IL2CPP-only API exports with decrypted names + public Dictionary APIExports { get; } = new Dictionary(); + public Il2CppCodeRegistration CodeRegistration { get; protected set; } public Il2CppMetadataRegistration MetadataRegistration { get; protected set; } @@ -92,12 +95,14 @@ namespace Il2CppInspector protected Il2CppBinary(IFileFormatReader stream, EventHandler statusCallback = null) { Image = stream; OnStatusUpdate = statusCallback; + DiscoverAPIExports(); } protected Il2CppBinary(IFileFormatReader stream, uint codeRegistration, uint metadataRegistration, EventHandler statusCallback = null) { Image = stream; OnStatusUpdate = statusCallback; - Configure(codeRegistration, metadataRegistration); + DiscoverAPIExports(); + PrepareMetadata(codeRegistration, metadataRegistration); } // Load and initialize a binary of any supported architecture @@ -119,7 +124,7 @@ namespace Il2CppInspector // Load binary without a global-metadata.dat available public static Il2CppBinary Load(IFileFormatReader stream, double metadataVersion, EventHandler statusCallback = null) { var inst = LoadImpl(stream, statusCallback); - return inst.Initialize(metadataVersion) ? inst : null; + return inst.FindRegistrationStructs(metadataVersion) ? inst : null; } // Load binary with a global-metadata.dat available @@ -129,7 +134,7 @@ namespace Il2CppInspector // If it is not specified, data analysis will not be performed public static Il2CppBinary Load(IFileFormatReader stream, Metadata metadata, EventHandler statusCallback = null) { var inst = LoadImpl(stream, statusCallback); - return inst.Initialize(metadata) ? inst : null; + return inst.FindRegistrationStructs(metadata) ? inst : null; } // Save binary to file, overwriting if necessary @@ -141,24 +146,24 @@ namespace Il2CppInspector } // Initialize binary without a global-metadata.dat available - public bool Initialize(double metadataVersion, EventHandler statusCallback = null) { + public bool FindRegistrationStructs(double metadataVersion, EventHandler statusCallback = null) { Image.Version = metadataVersion; if (!((FindMetadataFromSymbols() ?? FindMetadataFromCode()) is var ptrs)) return false; - Configure(ptrs.Value.Item1, ptrs.Value.Item2); + PrepareMetadata(ptrs.Value.Item1, ptrs.Value.Item2); return true; } // Initialize binary with a global-metadata.dat available - public bool Initialize(Metadata metadata, EventHandler statusCallback = null) { + public bool FindRegistrationStructs(Metadata metadata, EventHandler statusCallback = null) { Image.Version = metadata.Version; if (!((FindMetadataFromSymbols() ?? FindMetadataFromCode() ?? FindMetadataFromData(metadata)) is var ptrs)) return false; - Configure(ptrs.Value.Item1, ptrs.Value.Item2, metadata); + PrepareMetadata(ptrs.Value.Item1, ptrs.Value.Item2, metadata); return true; } @@ -230,7 +235,7 @@ namespace Il2CppInspector protected abstract (ulong, ulong) ConsiderCode(IFileFormatReader image, uint loc); // Load all of the discovered metadata in the binary - private void Configure(ulong codeRegistration, ulong metadataRegistration, Metadata metadata = null) { + private void PrepareMetadata(ulong codeRegistration, ulong metadataRegistration, Metadata metadata = null) { // Store locations CodeRegistrationPointer = codeRegistration; MetadataRegistrationPointer = metadataRegistration; @@ -367,22 +372,36 @@ namespace Il2CppInspector // This strips leading underscores and selects only il2cpp_* symbols which can be mapped into the binary // (therefore ignoring extern imports) // Some binaries have functions starting "il2cpp_z_" - ignore these too - public Dictionary GetAPIExports() { - var exports = Image.GetExports()? - .Where(e => (e.Name.StartsWith("il2cpp_") || e.Name.StartsWith("_il2cpp_") || e.Name.StartsWith("__il2cpp_")) - && !e.Name.Contains("il2cpp_z_")); - + private void DiscoverAPIExports() { + var exports = Image.GetExports()?.ToList(); if (exports == null) - return new Dictionary(); + return; var exportRgx = new Regex(@"^_+"); - var il2cppExports = new Dictionary(); - - foreach (var export in exports) - if (Image.TryMapVATR(export.VirtualAddress, out _)) - il2cppExports.Add(exportRgx.Replace(export.Name, ""), export.VirtualAddress); - return il2cppExports; + // Handle name rot encryption found in some binaries + var anyEncrypted = false; + + for (var rotKey = 0; rotKey <= 25; rotKey++) { + var possibleExports = exports.Select(e => new { + Name = string.Join("", e.Name.Select(x => (char) (x >= 'a' && x <= 'z'? (x - 'a' + rotKey) % 26 + 'a' : x))), + VirtualAddress = e.VirtualAddress + }).ToList(); + + var foundExports = possibleExports + .Where(e => (e.Name.StartsWith("il2cpp_") || e.Name.StartsWith("_il2cpp_") || e.Name.StartsWith("__il2cpp_")) + && !e.Name.Contains("il2cpp_z_")) + .Select(e => e); + + if (foundExports.Any() && rotKey != 0 && !anyEncrypted) { + Console.WriteLine("Found encrypted export names - decrypting"); + anyEncrypted = true; + } + + foreach (var export in foundExports) + if (Image.TryMapVATR(export.VirtualAddress, out _)) + APIExports.Add(exportRgx.Replace(export.Name, ""), export.VirtualAddress); + } } } } diff --git a/Il2CppInspector.Common/Model/AppModel.cs b/Il2CppInspector.Common/Model/AppModel.cs index 0bf2d53..886bcc4 100644 --- a/Il2CppInspector.Common/Model/AppModel.cs +++ b/Il2CppInspector.Common/Model/AppModel.cs @@ -162,7 +162,7 @@ namespace Il2CppInspector.Model // Populate AvailableAPIs with actual API symbols from Binary.GetAPIExports() and their matching header signatures // NOTE: This will only be filled with exports that actually exist in both the binary and the API header, // and have a mappable address. This prevents exceptions when cross-querying the header and binary APIs. - var exports = TypeModel.Package.Binary.GetAPIExports() + var exports = TypeModel.Package.Binary.APIExports .Where(e => CppTypeCollection.TypedefAliases.ContainsKey(e.Key)) .Select(e => new { VirtualAddress = e.Value, diff --git a/Il2CppInspector.Common/Outputs/CppScaffolding.cs b/Il2CppInspector.Common/Outputs/CppScaffolding.cs index d2e0e3c..c6ff709 100644 --- a/Il2CppInspector.Common/Outputs/CppScaffolding.cs +++ b/Il2CppInspector.Common/Outputs/CppScaffolding.cs @@ -138,7 +138,7 @@ typedef size_t uintptr_t; // We could use model.AvailableAPIs here but that would exclude outputting the address // of API exports which for some reason aren't defined in our selected API header, // so although it doesn't affect the C++ compilation, we use GetAPIExports() instead for completeness - var exports = model.Package.Binary.GetAPIExports(); + var exports = model.Package.Binary.APIExports; foreach (var export in exports) { writeCode($"#define {export.Key}_ptr 0x{model.Package.BinaryImage.MapVATR(export.Value):X8}");