From 19543451ed975388bbf9440d7354fbe575ea11a4 Mon Sep 17 00:00:00 2001 From: Jeremy Pritts <49847914+ds5678@users.noreply.github.com> Date: Sun, 31 Dec 2023 10:53:50 -0500 Subject: [PATCH 1/3] Native method detection --- Cpp2IL.Core/Cpp2IlCorePlugin.cs | 1 + .../Contexts/NativeMethodAnalysisContext.cs | 38 ++++++++ .../NativeMethodDetectionProcessingLayer.cs | 95 +++++++++++++++++++ LibCpp2IL/Il2CppBinary.cs | 6 ++ LibCpp2IL/PE/PE.cs | 33 ++++++- 5 files changed, 171 insertions(+), 2 deletions(-) create mode 100644 Cpp2IL.Core/Model/Contexts/NativeMethodAnalysisContext.cs create mode 100644 Cpp2IL.Core/ProcessingLayers/NativeMethodDetectionProcessingLayer.cs diff --git a/Cpp2IL.Core/Cpp2IlCorePlugin.cs b/Cpp2IL.Core/Cpp2IlCorePlugin.cs index 8cb89a1b..04258280 100644 --- a/Cpp2IL.Core/Cpp2IlCorePlugin.cs +++ b/Cpp2IL.Core/Cpp2IlCorePlugin.cs @@ -51,6 +51,7 @@ public override void OnLoad() ProcessingLayerRegistry.Register(); ProcessingLayerRegistry.Register(); ProcessingLayerRegistry.Register(); + ProcessingLayerRegistry.Register(); ProcessingLayerRegistry.Register(); ProcessingLayerRegistry.Register(); diff --git a/Cpp2IL.Core/Model/Contexts/NativeMethodAnalysisContext.cs b/Cpp2IL.Core/Model/Contexts/NativeMethodAnalysisContext.cs new file mode 100644 index 00000000..e149ef1c --- /dev/null +++ b/Cpp2IL.Core/Model/Contexts/NativeMethodAnalysisContext.cs @@ -0,0 +1,38 @@ +using System; +using System.Reflection; +using LibCpp2IL; + +namespace Cpp2IL.Core.Model.Contexts; + +public sealed class NativeMethodAnalysisContext : MethodAnalysisContext +{ + public override ulong UnderlyingPointer { get; } + + public override string DefaultName { get; } + + public override bool IsStatic => true; + + public override bool IsVoid { get; } + + public override MethodAttributes Attributes => MethodAttributes.Public | MethodAttributes.Static | MethodAttributes.HideBySig; + + protected override int CustomAttributeIndex => -1; + + public NativeMethodAnalysisContext(TypeAnalysisContext parent, ulong address, bool voidReturn) : base(null, parent) + { + if (address == 0) + throw new ArgumentOutOfRangeException(nameof(address)); + + IsVoid = voidReturn; + UnderlyingPointer = address; + if (LibCpp2IlMain.Binary?.TryGetExportedFunctionName(UnderlyingPointer, out var name) ?? false) + { + DefaultName = name; + } + else + { + DefaultName = $"NativeMethod_0x{UnderlyingPointer:X}"; + } + RawBytes = AppContext.InstructionSet.GetRawBytesForMethod(this, false); + } +} diff --git a/Cpp2IL.Core/ProcessingLayers/NativeMethodDetectionProcessingLayer.cs b/Cpp2IL.Core/ProcessingLayers/NativeMethodDetectionProcessingLayer.cs new file mode 100644 index 00000000..b3e20bb9 --- /dev/null +++ b/Cpp2IL.Core/ProcessingLayers/NativeMethodDetectionProcessingLayer.cs @@ -0,0 +1,95 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Cpp2IL.Core.Api; +using Cpp2IL.Core.ISIL; +using Cpp2IL.Core.Model.Contexts; + +namespace Cpp2IL.Core.ProcessingLayers; + +public class NativeMethodDetectionProcessingLayer : Cpp2IlProcessingLayer +{ + public override string Name => "Native Method Detection"; + + public override string Id => "nativemethoddetector"; + + public override void Process(ApplicationAnalysisContext appContext, Action? progressCallback = null) + { + var nativeMethodInfoStack = new Stack<(ulong, bool)>(); + var cppNativeMethodsType = appContext.AssembliesByName["mscorlib"].InjectType("Cpp2ILInjected", "CppNativeMethods", null); + foreach (var assemblyAnalysisContext in appContext.Assemblies) + { + foreach (var m in assemblyAnalysisContext.Types.SelectMany(t => t.Methods)) + { + AnalyzeMethod(appContext, m, nativeMethodInfoStack); + } + } + while (nativeMethodInfoStack.Count > 0) + { + (var address, var isVoid) = nativeMethodInfoStack.Pop(); + if (!appContext.MethodsByAddress.ContainsKey(address)) + { + var m = new NativeMethodAnalysisContext(cppNativeMethodsType, address, isVoid); + cppNativeMethodsType.Methods.Add(m); + m.InjectedReturnType = isVoid ? appContext.SystemTypes.SystemVoidType : appContext.SystemTypes.SystemObjectType; + appContext.MethodsByAddress.Add(address, new(1) { m }); + AnalyzeMethod(appContext, m, nativeMethodInfoStack); + } + } + } + + private static void AnalyzeMethod(ApplicationAnalysisContext appContext, MethodAnalysisContext m, Stack<(ulong, bool)> nativeMethodInfoStack) + { + if (m.UnderlyingPointer == 0) + return; + + try + { + m.Analyze(); + } + catch (Exception ex) + { +#if DEBUG + if (ex.Message is not "Failed to convert to ISIL. Reason: Jump target not found in method. Ruh roh" && !ex.Message.StartsWith("Instruction ")) + { + } +#endif + m.ConvertedIsil = null; + return; + } + + if (m.ConvertedIsil is { Count: 0 }) + { + return; + } + + foreach (var instruction in m.ConvertedIsil) + { + if (instruction.OpCode == InstructionSetIndependentOpCode.Call) + { + if (TryGetAddressFromInstruction(instruction, out var address) && !appContext.MethodsByAddress.ContainsKey(address)) + { + nativeMethodInfoStack.Push((address, true)); + } + } + else if (instruction.OpCode == InstructionSetIndependentOpCode.CallNoReturn) + { + if (TryGetAddressFromInstruction(instruction, out var address) && !appContext.MethodsByAddress.ContainsKey(address)) + { + nativeMethodInfoStack.Push((address, m.IsVoid)); + } + } + } + } + + private static bool TryGetAddressFromInstruction(InstructionSetIndependentInstruction instruction, out ulong address) + { + if (instruction.Operands.Length > 0 && instruction.Operands[0].Data is IsilImmediateOperand operand) + { + address = operand.Value.ToUInt64(null); + return true; + } + address = default; + return false; + } +} diff --git a/LibCpp2IL/Il2CppBinary.cs b/LibCpp2IL/Il2CppBinary.cs index f85ace4d..1e44f408 100644 --- a/LibCpp2IL/Il2CppBinary.cs +++ b/LibCpp2IL/Il2CppBinary.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using System.Globalization; using System.IO; using System.Linq; @@ -459,6 +460,11 @@ public ulong GetMethodPointer(int methodIndex, int methodDefinitionIndex, int im public abstract byte[] GetRawBinaryContent(); public abstract ulong GetVirtualAddressOfExportedFunctionByName(string toFind); public virtual bool IsExportedFunction(ulong addr) => false; + public virtual bool TryGetExportedFunctionName(ulong addr, [NotNullWhen(true)] out string? name) + { + name = null; + return false; + } public abstract byte[] GetEntirePrimaryExecutableSection(); diff --git a/LibCpp2IL/PE/PE.cs b/LibCpp2IL/PE/PE.cs index 4787a2c0..60ebbbbe 100644 --- a/LibCpp2IL/PE/PE.cs +++ b/LibCpp2IL/PE/PE.cs @@ -158,7 +158,7 @@ public override ulong GetVirtualAddressOfExportedFunctionByName(string toFind) var index = Array.FindIndex(peExportedFunctionNamePtrs, stringAddress => { var rawStringAddress = MapVirtualAddressToRaw(stringAddress + peImageBase); - string exportName = ReadStringToNull(rawStringAddress); + var exportName = ReadStringToNull(rawStringAddress); return exportName == toFind; }); @@ -176,7 +176,7 @@ public override bool IsExportedFunction(ulong addr) if (addr <= peImageBase) return false; - var rva = addr - peImageBase; + var rva = GetRva(addr); if (rva > uint.MaxValue) return false; @@ -186,6 +186,35 @@ public override bool IsExportedFunction(ulong addr) return Array.IndexOf(peExportedFunctionPointers, (uint)rva) >= 0; } + public override bool TryGetExportedFunctionName(ulong addr, [NotNullWhen(true)] out string? name) + { + if (addr <= peImageBase) + { + return base.TryGetExportedFunctionName(addr, out name); + } + + var rva = GetRva(addr); + if (rva > uint.MaxValue) + { + return base.TryGetExportedFunctionName(addr, out name); + } + + if (peExportedFunctionPointers == null) + LoadPeExportTable(); + + var index = Array.IndexOf(peExportedFunctionPointers, (uint)rva); + if (index < 0) + { + return base.TryGetExportedFunctionName(addr, out name); + } + else + { + var rawStringAddress = MapVirtualAddressToRaw(peExportedFunctionNamePtrs[index] + peImageBase); + name = ReadStringToNull(rawStringAddress); + return true; + } + } + public override ulong GetRva(ulong pointer) { return pointer - peImageBase; From ebe78e8cbef5bc9ae6f0c841a59211af5d221680 Mon Sep 17 00:00:00 2001 From: Jeremy Pritts <49847914+ds5678@users.noreply.github.com> Date: Sun, 31 Dec 2023 11:35:56 -0500 Subject: [PATCH 2/3] Support MachO exports --- LibCpp2IL/MachO/MachOFile.cs | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/LibCpp2IL/MachO/MachOFile.cs b/LibCpp2IL/MachO/MachOFile.cs index 55b7ccd1..e2cdd721 100644 --- a/LibCpp2IL/MachO/MachOFile.cs +++ b/LibCpp2IL/MachO/MachOFile.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Linq; using LibCpp2IL.Logging; +using System.Diagnostics.CodeAnalysis; namespace LibCpp2IL.MachO { @@ -15,7 +16,8 @@ public class MachOFile : Il2CppBinary private readonly MachOSegmentCommand[] Segments64; private readonly MachOSection[] Sections64; - private Dictionary _exportsDict; + private readonly Dictionary _exportAddressesDict; + private readonly Dictionary _exportNamesDict; public MachOFile(MemoryStream input) : base(input) { @@ -74,9 +76,10 @@ public MachOFile(MemoryStream input) : base(input) var dyldData = _loadCommands.FirstOrDefault(c => c.Command is LoadCommandId.LC_DYLD_INFO or LoadCommandId.LC_DYLD_INFO_ONLY)?.CommandData as MachODynamicLinkerCommand; var exports = dyldData?.Exports ?? Array.Empty(); - _exportsDict = exports.ToDictionary(e => e.Name[1..], e => e.Address); //Skip the first character, which is a leading underscore inserted by the compiler + _exportAddressesDict = exports.ToDictionary(e => e.Name[1..], e => e.Address); //Skip the first character, which is a leading underscore inserted by the compiler + _exportNamesDict = _exportAddressesDict.ToDictionary(kvp => kvp.Value, kvp => kvp.Key); - LibLogger.VerboseNewline($"Found {_exportsDict.Count} exports in the DYLD info load command."); + LibLogger.VerboseNewline($"Found {_exportAddressesDict.Count} exports in the DYLD info load command."); LibLogger.VerboseNewline($"\tMach-O contains {Segments64.Length} segments, split into {Sections64.Length} sections."); } @@ -116,12 +119,19 @@ public override ulong GetRva(ulong pointer) public override ulong GetVirtualAddressOfExportedFunctionByName(string toFind) { - if (!_exportsDict.TryGetValue(toFind, out var addr)) + if (!_exportAddressesDict.TryGetValue(toFind, out var addr)) return 0; return (ulong) addr; } + public override bool IsExportedFunction(ulong addr) => _exportNamesDict.ContainsKey((long) addr); + + public override bool TryGetExportedFunctionName(ulong addr, [NotNullWhen(true)] out string? name) + { + return _exportNamesDict.TryGetValue((long) addr, out name); + } + private MachOSection GetTextSection64() { var textSection = Sections64.FirstOrDefault(s => s.SectionName == "__text"); From 98c425d72bcf8beb996c90d8fc4576362a9fdd57 Mon Sep 17 00:00:00 2001 From: Jeremy Pritts <49847914+ds5678@users.noreply.github.com> Date: Sun, 31 Dec 2023 11:43:11 -0500 Subject: [PATCH 3/3] Support Elf exports --- LibCpp2IL/Elf/ElfFile.cs | 31 ++++++++++++++++++++++++++----- 1 file changed, 26 insertions(+), 5 deletions(-) diff --git a/LibCpp2IL/Elf/ElfFile.cs b/LibCpp2IL/Elf/ElfFile.cs index 905d0001..ffa9f9d8 100644 --- a/LibCpp2IL/Elf/ElfFile.cs +++ b/LibCpp2IL/Elf/ElfFile.cs @@ -1,5 +1,6 @@ -using System; +using System; using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using System.IO; using System.Linq; using LibCpp2IL.Logging; @@ -16,7 +17,8 @@ public sealed class ElfFile : Il2CppBinary private ElfFileHeader? _elfHeader; private readonly List _dynamicSection = new(); private readonly List _symbolTable = new(); - private readonly Dictionary _exportTable = new(); + private readonly Dictionary _exportNameTable = new(); + private readonly Dictionary _exportAddressTable = new(); private List? _initializerPointers; private readonly List<(ulong start, ulong end)> relocationBlocks = new(); @@ -405,7 +407,8 @@ private void ProcessSymbols() } _symbolTable.Clear(); - _exportTable.Clear(); + _exportNameTable.Clear(); + _exportAddressTable.Clear(); //Unify symbol tables foreach (var (offset, count, stringTable) in symbolTables) @@ -440,7 +443,10 @@ private void ProcessSymbols() _symbolTable.Add(entry); if (symbol.Shndx != 0) - _exportTable.TryAdd(name, entry); + { + _exportNameTable.TryAdd(name, entry); + _exportAddressTable.TryAdd(virtualAddress, entry); + } } } } @@ -707,12 +713,27 @@ public override ulong MapRawAddressToVirtual(uint offset) public override ulong GetVirtualAddressOfExportedFunctionByName(string toFind) { - if (!_exportTable.TryGetValue(toFind, out var exportedSymbol)) + if (!_exportNameTable.TryGetValue(toFind, out var exportedSymbol)) return 0; return exportedSymbol.VirtualAddress; } + public override bool IsExportedFunction(ulong addr) => _exportAddressTable.ContainsKey(addr); + + public override bool TryGetExportedFunctionName(ulong addr, [NotNullWhen(true)] out string? name) + { + if (_exportAddressTable.TryGetValue(addr, out var symbol)) + { + name = symbol.Name; + return true; + } + else + { + return base.TryGetExportedFunctionName(addr, out name); + } + } + public override ulong GetVirtualAddressOfPrimaryExecutableSection() => _elfSectionHeaderEntries.FirstOrDefault(s => s.Name == ".text")?.VirtualAddress ?? 0; public override byte[] GetEntirePrimaryExecutableSection()