Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Native Method Detection #246

Merged
merged 3 commits into from
Dec 31, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Cpp2IL.Core/Cpp2IlCorePlugin.cs
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ public override void OnLoad()
ProcessingLayerRegistry.Register<AttributeAnalysisProcessingLayer>();
ProcessingLayerRegistry.Register<AttributeInjectorProcessingLayer>();
ProcessingLayerRegistry.Register<CallAnalysisProcessingLayer>();
ProcessingLayerRegistry.Register<NativeMethodDetectionProcessingLayer>();
ProcessingLayerRegistry.Register<StableRenamingProcessingLayer>();
ProcessingLayerRegistry.Register<DeobfuscationMapProcessingLayer>();

Expand Down
38 changes: 38 additions & 0 deletions Cpp2IL.Core/Model/Contexts/NativeMethodAnalysisContext.cs
Original file line number Diff line number Diff line change
@@ -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);
}
}
Original file line number Diff line number Diff line change
@@ -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<int, int>? 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)

Check warning on line 50 in Cpp2IL.Core/ProcessingLayers/NativeMethodDetectionProcessingLayer.cs

View workflow job for this annotation

GitHub Actions / Build - Windows .NET Framework Zip

The variable 'ex' is declared but never used

Check warning on line 50 in Cpp2IL.Core/ProcessingLayers/NativeMethodDetectionProcessingLayer.cs

View workflow job for this annotation

GitHub Actions / Build Single-File Artifact (osx-x64, Cpp2IL)

The variable 'ex' is declared but never used

Check warning on line 50 in Cpp2IL.Core/ProcessingLayers/NativeMethodDetectionProcessingLayer.cs

View workflow job for this annotation

GitHub Actions / Build Single-File Artifact (linux-x64, Cpp2IL)

The variable 'ex' is declared but never used

Check warning on line 50 in Cpp2IL.Core/ProcessingLayers/NativeMethodDetectionProcessingLayer.cs

View workflow job for this annotation

GitHub Actions / Build Single-File Artifact (win-x64, Cpp2IL)

The variable 'ex' is declared but never used

Check warning on line 50 in Cpp2IL.Core/ProcessingLayers/NativeMethodDetectionProcessingLayer.cs

View workflow job for this annotation

GitHub Actions / Build Single-File Artifact (osx-x64, Cpp2IL.Gui)

The variable 'ex' is declared but never used

Check warning on line 50 in Cpp2IL.Core/ProcessingLayers/NativeMethodDetectionProcessingLayer.cs

View workflow job for this annotation

GitHub Actions / Run Tests & Publish Dev Package

The variable 'ex' is declared but never used

Check warning on line 50 in Cpp2IL.Core/ProcessingLayers/NativeMethodDetectionProcessingLayer.cs

View workflow job for this annotation

GitHub Actions / Run Tests & Publish Dev Package

The variable 'ex' is declared but never used

Check warning on line 50 in Cpp2IL.Core/ProcessingLayers/NativeMethodDetectionProcessingLayer.cs

View workflow job for this annotation

GitHub Actions / Run Tests & Publish Dev Package

The variable 'ex' is declared but never used

Check warning on line 50 in Cpp2IL.Core/ProcessingLayers/NativeMethodDetectionProcessingLayer.cs

View workflow job for this annotation

GitHub Actions / Build Single-File Artifact (win-x64, Cpp2IL.Gui)

The variable 'ex' is declared but never used

Check warning on line 50 in Cpp2IL.Core/ProcessingLayers/NativeMethodDetectionProcessingLayer.cs

View workflow job for this annotation

GitHub Actions / Build Single-File Artifact (linux-x64, Cpp2IL.Gui)

The variable 'ex' is declared but never used
{
#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;
}
}
31 changes: 26 additions & 5 deletions LibCpp2IL/Elf/ElfFile.cs
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -16,7 +17,8 @@ public sealed class ElfFile : Il2CppBinary
private ElfFileHeader? _elfHeader;
private readonly List<ElfDynamicEntry> _dynamicSection = new();
private readonly List<ElfSymbolTableEntry> _symbolTable = new();
private readonly Dictionary<string, ElfSymbolTableEntry> _exportTable = new();
private readonly Dictionary<string, ElfSymbolTableEntry> _exportNameTable = new();
private readonly Dictionary<ulong, ElfSymbolTableEntry> _exportAddressTable = new();
private List<long>? _initializerPointers;

private readonly List<(ulong start, ulong end)> relocationBlocks = new();
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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);
}
}
}
}
Expand Down Expand Up @@ -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()
Expand Down
6 changes: 6 additions & 0 deletions LibCpp2IL/Il2CppBinary.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using System.IO;
using System.Linq;
Expand Down Expand Up @@ -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();

Expand Down
18 changes: 14 additions & 4 deletions LibCpp2IL/MachO/MachOFile.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
using System.Collections.Generic;
using System.Linq;
using LibCpp2IL.Logging;
using System.Diagnostics.CodeAnalysis;

namespace LibCpp2IL.MachO
{
Expand All @@ -15,7 +16,8 @@ public class MachOFile : Il2CppBinary

private readonly MachOSegmentCommand[] Segments64;
private readonly MachOSection[] Sections64;
private Dictionary<string, long> _exportsDict;
private readonly Dictionary<string, long> _exportAddressesDict;
private readonly Dictionary<long, string> _exportNamesDict;

public MachOFile(MemoryStream input) : base(input)
{
Expand Down Expand Up @@ -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<MachOExportEntry>();
_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.");
}
Expand Down Expand Up @@ -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");
Expand Down
33 changes: 31 additions & 2 deletions LibCpp2IL/PE/PE.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
});

Expand All @@ -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;

Expand All @@ -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;
Expand Down
Loading