Skip to content

Commit

Permalink
Native Method Detection (#246)
Browse files Browse the repository at this point in the history
  • Loading branch information
ds5678 authored Dec 31, 2023
1 parent 9b50c20 commit 3cacee9
Show file tree
Hide file tree
Showing 7 changed files with 211 additions and 11 deletions.
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 (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 / 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 (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

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
{
#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

0 comments on commit 3cacee9

Please sign in to comment.