Skip to content

Commit

Permalink
Implement ArmV7 using CapstoneSharp
Browse files Browse the repository at this point in the history
  • Loading branch information
js6pak committed Jul 6, 2023
1 parent d219946 commit 1d54f12
Show file tree
Hide file tree
Showing 8 changed files with 358 additions and 1 deletion.
2 changes: 1 addition & 1 deletion Cpp2IL.Core/Il2CppApiFunctions/BaseKeyFunctionAddresses.cs
Original file line number Diff line number Diff line change
Expand Up @@ -146,7 +146,7 @@ private void FindThunks()

if (il2cpp_type_get_object != 0)
{
Logger.Verbose("\t\tMapping il2cpp_resolve_icall to Reflection::GetTypeObject...");
Logger.Verbose("\t\tMapping il2cpp_type_get_object to Reflection::GetTypeObject...");
il2cpp_vm_reflection_get_type_object = FindFunctionThisIsAThunkOf(il2cpp_type_get_object);
Logger.VerboseNewline($"Found at 0x{il2cpp_vm_reflection_get_type_object:X}");
}
Expand Down
2 changes: 2 additions & 0 deletions Cpp2IL.InstructionSets.All/AllInstructionSets.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using Cpp2IL.Core.Api;
using Cpp2IL.InstructionSets.ArmV7;
using Cpp2IL.InstructionSets.ArmV8;
using Cpp2IL.InstructionSets.Wasm;
using Cpp2IL.InstructionSets.X86;
Expand All @@ -10,6 +11,7 @@ public static class AllInstructionSets
public static void Register()
{
X86InstructionSet.RegisterInstructionSet();
ArmV7InstructionSet.RegisterInstructionSet();
ArmV8InstructionSet.RegisterInstructionSet();
WasmInstructionSet.RegisterInstructionSet();
OutputFormatRegistry.Register<WasmMappingOutputFormat>();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

<ItemGroup>
<ProjectReference Include="..\Cpp2IL.Core\Cpp2IL.Core.csproj" />
<ProjectReference Include="..\Cpp2IL.InstructionSets.ArmV7\Cpp2IL.InstructionSets.ArmV7.csproj" />
<ProjectReference Include="..\Cpp2IL.InstructionSets.ArmV8\Cpp2IL.InstructionSets.ArmV8.csproj" />
<ProjectReference Include="..\Cpp2IL.InstructionSets.Wasm\Cpp2IL.InstructionSets.Wasm.csproj" />
<ProjectReference Include="..\Cpp2IL.InstructionSets.X86\Cpp2IL.InstructionSets.X86.csproj" />
Expand Down
66 changes: 66 additions & 0 deletions Cpp2IL.InstructionSets.ArmV7/ArmV7InstructionSet.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
using System.Text;
using Cpp2IL.Core.Api;
using Cpp2IL.Core.Il2CppApiFunctions;
using Cpp2IL.Core.ISIL;
using Cpp2IL.Core.Model.Contexts;
using LibCpp2IL;

namespace Cpp2IL.InstructionSets.ArmV7;

public class ArmV7InstructionSet : Cpp2IlInstructionSet
{
public static void RegisterInstructionSet()
{
InstructionSetRegistry.RegisterInstructionSet<ArmV7InstructionSet>(DefaultInstructionSets.ARM_V7);
}

public override Memory<byte> GetRawBytesForMethod(MethodAnalysisContext context, bool isAttributeGenerator)
{
if (ArmV7Utils.TryGetMethodBodyBytesFast(context.UnderlyingPointer, context is AttributeGeneratorMethodAnalysisContext) is { } ret)
return ret;

ArmV7Utils.DisassembleManagedMethod(context.UnderlyingPointer, out var endVirtualAddress);

var start = (int)context.AppContext.Binary.MapVirtualAddressToRaw(context.UnderlyingPointer);
var end = (int)context.AppContext.Binary.MapVirtualAddressToRaw(endVirtualAddress);

return context.AppContext.Binary.GetRawBinaryContent().AsMemory(start, end - start);
}

public override List<InstructionSetIndependentInstruction> GetIsilFromMethod(MethodAnalysisContext context)
{
throw new NotImplementedException();
}

public override BaseKeyFunctionAddresses CreateKeyFunctionAddressesInstance()
{
return new ArmV7KeyFunctionAddresses();
}

public override unsafe string PrintAssembly(MethodAnalysisContext context)
{
var sb = new StringBuilder();
var first = true;

using (ArmV7Utils.Disassembler.AllocInstruction(out var instruction))
{
fixed (byte* code = context.RawBytes.Span)
{
var size = (nuint)context.RawBytes.Length;
var address = context.UnderlyingPointer;
while (ArmV7Utils.Disassembler.UnsafeIterate(&code, &size, &address, instruction))
{
if (!first)
{
sb.AppendLine();
first = false;
}

sb.Append("0x").Append(address.ToString("X")).Append(" ").AppendLine(instruction->ToString());
}
}
}

return sb.ToString();
}
}
144 changes: 144 additions & 0 deletions Cpp2IL.InstructionSets.ArmV7/ArmV7KeyFunctionAddresses.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
using CapstoneSharp.Arm;
using Cpp2IL.Core.Il2CppApiFunctions;
using Cpp2IL.Core.Logging;
using LibCpp2IL;
using LibCpp2IL.Reflection;

namespace Cpp2IL.InstructionSets.ArmV7;

public class ArmV7KeyFunctionAddresses : BaseKeyFunctionAddresses
{
private List<CapstoneArmInstruction>? _cachedDisassembledBytes;

private List<CapstoneArmInstruction> DisassembleTextSection()
{
if (_cachedDisassembledBytes == null)
{
var toDisasm = LibCpp2IlMain.Binary!.GetEntirePrimaryExecutableSection();
_cachedDisassembledBytes = ArmV7Utils.Disassembler.Iterate(toDisasm, LibCpp2IlMain.Binary.GetVirtualAddressOfPrimaryExecutableSection()).ToList();
}

return _cachedDisassembledBytes;
}

protected override IEnumerable<ulong> FindAllThunkFunctions(ulong addr, uint maxBytesBack = 0, params ulong[] addressesToIgnore)
{
//Disassemble .text
var disassembly = DisassembleTextSection();

//Find all jumps to the target address
var matchingJmps = disassembly.Where(i => i.IsBranchingTo((int)addr)).ToList();

foreach (var matchingJmp in matchingJmps)
{
if (addressesToIgnore.Contains(matchingJmp.Address)) continue;

var backtrack = 0;
var idx = disassembly.IndexOf(matchingJmp);

do
{
//About the only way we have of checking for a thunk is if there is an all-zero instruction or another unconditional branch just before this
//Or a ret, but that's less reliable.
//if so, this is probably a thunk.
if (idx - backtrack > 0)
{
var prevInstruction = disassembly[idx - backtrack - 1];

if (addressesToIgnore.Contains(prevInstruction.Address))
{
backtrack++;
continue;
}

if (prevInstruction.IsSkippedData && prevInstruction.Bytes.IsAllZero())
{
//All-zero instructions are a match
yield return matchingJmp.Address - (ulong)(backtrack * 4);
break;
}

if (prevInstruction.Id is CapstoneArmInstructionId.STR)
{
//ADRP instructions are a deal breaker - this means we're loading something from memory, so it's not a simple thunk
break;
}

if (prevInstruction.Id is CapstoneArmInstructionId.B or CapstoneArmInstructionId.BL)
{
//Previous branches are a match
yield return matchingJmp.Address - (ulong)(backtrack * 4);
break;
}
}

//We're working in the .text section here so we have few symbols, so there's no point looking for the previous one.

backtrack++;
} while (backtrack * 4 < maxBytesBack);
}
}

protected override ulong GetObjectIsInstFromSystemType()
{
Logger.Verbose("\tTrying to use System.Type::IsInstanceOfType to find il2cpp::vm::Object::IsInst...");
var typeIsInstanceOfType = LibCpp2IlReflection.GetType("Type", "System")?.Methods?.FirstOrDefault(m => m.Name == "IsInstanceOfType");
if (typeIsInstanceOfType == null)
{
Logger.VerboseNewline("Type or method not found, aborting.");
return 0;
}

//IsInstanceOfType is a very simple ICall, that looks like this:
// Il2CppClass* klass = vm::Class::FromIl2CppType(type->type.type);
// return il2cpp::vm::Object::IsInst(obj, klass) != NULL;
//The last call is to Object::IsInst

Logger.Verbose($"IsInstanceOfType found at 0x{typeIsInstanceOfType.MethodPointer:X}...");
var instructions = ArmV7Utils.DisassembleManagedMethod(typeIsInstanceOfType.MethodPointer);

var lastCall = instructions.LastOrDefault(i => i.Id == CapstoneArmInstructionId.BL);

if (lastCall == null)
{
Logger.VerboseNewline("Method does not match expected signature. Aborting.");
return 0;
}

var target = lastCall.GetBranchTarget();
Logger.VerboseNewline($"Success. IsInst found at 0x{target:X}");
return (ulong)target;
}

protected override ulong FindFunctionThisIsAThunkOf(ulong thunkPtr, bool prioritiseCall = false)
{
var instructions = ArmV7Utils.DisassembleFunction(thunkPtr);

try
{
var target = prioritiseCall ? CapstoneArmInstructionId.BL : CapstoneArmInstructionId.B;
var matchingCall = instructions.FirstOrDefault(i => i.Id == target);

if (matchingCall == null)
{
target = target == CapstoneArmInstructionId.BL ? CapstoneArmInstructionId.B : CapstoneArmInstructionId.BL;
matchingCall = instructions.First(i => i.Id == target);
}

return (ulong)matchingCall.GetBranchTarget();
}
catch (Exception)
{
return 0;
}
}

protected override int GetCallerCount(ulong toWhere)
{
//Disassemble .text
var disassembly = DisassembleTextSection();

//Find all jumps to the target address
return disassembly.Count(i => i.IsBranchingTo((int)toWhere));
}
}
125 changes: 125 additions & 0 deletions Cpp2IL.InstructionSets.ArmV7/ArmV7Utils.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
using System.Runtime.InteropServices;
using CapstoneSharp.Arm;
using Cpp2IL.Core.Utils;
using LibCpp2IL;

namespace Cpp2IL.InstructionSets.ArmV7;

internal static class ArmV7Utils
{
private static CapstoneArmDisassembler? _disassembler;

// TODO dispose this
public static CapstoneArmDisassembler Disassembler => _disassembler ??= new CapstoneArmDisassembler
{
EnableInstructionDetails = true, EnableSkipData = true,
};

public static bool IsAllZero(this ReadOnlySpan<byte> span)
{
if (MemoryMarshal.TryRead<int>(span, out var value))
{
return value == 0;
}

foreach (var b in span)
{
if (b != 0)
{
return true;
}
}

return true;
}

public static int GetBranchTarget(this CapstoneArmInstruction instruction)
{
if (instruction.Id is not (CapstoneArmInstructionId.B or CapstoneArmInstructionId.BL))
throw new InvalidOperationException("Branch target not available for this instruction, must be a B or BL");

return instruction.Details.ArchDetails.Operands[0].Immediate;
}

public static bool IsBranchingTo(this CapstoneArmInstruction instruction, int toWhere)
{
if (instruction.Id is not (CapstoneArmInstructionId.B or CapstoneArmInstructionId.BL))
return false;

return instruction.GetBranchTarget() == toWhere;
}

public static Memory<byte>? TryGetMethodBodyBytesFast(ulong virtualAddress, bool isCAGen)
{
var startOfNext = MiscUtils.GetAddressOfNextFunctionStart(virtualAddress);

var length = (startOfNext - virtualAddress);
if (isCAGen && length > 50_000)
return null;

if (startOfNext <= 0)
//We have to fall through to default behavior for the last method because we cannot accurately pinpoint its end
return null;

var rawStartOfNextMethod = LibCpp2IlMain.Binary!.MapVirtualAddressToRaw(startOfNext);

var rawStart = LibCpp2IlMain.Binary.MapVirtualAddressToRaw(virtualAddress);
if (rawStartOfNextMethod < rawStart)
rawStartOfNextMethod = LibCpp2IlMain.Binary.RawLength;

return LibCpp2IlMain.Binary.GetRawBinaryContent().AsMemory((int)rawStart, (int)(rawStartOfNextMethod - rawStart));
}

public static List<CapstoneArmInstruction> DisassembleFunction(ulong virtualAddress, int count = -1)
{
return DisassembleFunction(virtualAddress, out _, count);
}

public static List<CapstoneArmInstruction> DisassembleFunction(ulong virtualAddress, out ulong endVirtualAddress, int count = -1)
{
// Unmanaged function, look for first b
var pos = (int)LibCpp2IlMain.Binary!.MapVirtualAddressToRaw(virtualAddress);
var allBytes = LibCpp2IlMain.Binary.GetRawBinaryContent();

var instructions = new List<CapstoneArmInstruction>();

endVirtualAddress = virtualAddress;
foreach (var instruction in Disassembler.Iterate(allBytes.AsSpan(pos), virtualAddress))
{
instructions.Add(instruction);
endVirtualAddress = instruction.Address;
if (instruction.Id == CapstoneArmInstructionId.B) break;
if (count != -1 && instructions.Count >= count) break;
}

return instructions;
}

public static IEnumerable<CapstoneArmInstruction> DisassembleManagedMethod(ulong virtualAddress, int count = -1)
{
return DisassembleManagedMethod(virtualAddress, out _, count);
}

public static IEnumerable<CapstoneArmInstruction> DisassembleManagedMethod(ulong virtualAddress, out ulong endVirtualAddress, int count = -1)
{
var startOfNext = MiscUtils.GetAddressOfNextFunctionStart(virtualAddress);

// We have to fall through to default behavior for the last method because we cannot accurately pinpoint its end
if (startOfNext > 0)
{
var rawStartOfNextMethod = LibCpp2IlMain.Binary!.MapVirtualAddressToRaw(startOfNext);

var rawStart = LibCpp2IlMain.Binary.MapVirtualAddressToRaw(virtualAddress);
if (rawStartOfNextMethod < rawStart)
rawStartOfNextMethod = LibCpp2IlMain.Binary.RawLength;

var bytes = LibCpp2IlMain.Binary.GetRawBinaryContent().AsMemory((int)rawStart, (int)(rawStartOfNextMethod - rawStart));

endVirtualAddress = virtualAddress + (ulong)bytes.Length;
var instructions = Disassembler.Iterate(bytes, virtualAddress);
return count == -1 ? instructions : instructions.Take(count);
}

return DisassembleFunction(virtualAddress, out endVirtualAddress, count);
}
}
12 changes: 12 additions & 0 deletions Cpp2IL.InstructionSets.ArmV7/Cpp2IL.InstructionSets.ArmV7.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup>

<ItemGroup>
<ProjectReference Include="..\Cpp2IL.Core\Cpp2IL.Core.csproj" />
<PackageReference Include="CapstoneSharp" Version="0.1.0" />
</ItemGroup>
</Project>
Loading

0 comments on commit 1d54f12

Please sign in to comment.