Skip to content

Commit 1d54f12

Browse files
committed
Implement ArmV7 using CapstoneSharp
1 parent d219946 commit 1d54f12

File tree

8 files changed

+358
-1
lines changed

8 files changed

+358
-1
lines changed

Cpp2IL.Core/Il2CppApiFunctions/BaseKeyFunctionAddresses.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -146,7 +146,7 @@ private void FindThunks()
146146

147147
if (il2cpp_type_get_object != 0)
148148
{
149-
Logger.Verbose("\t\tMapping il2cpp_resolve_icall to Reflection::GetTypeObject...");
149+
Logger.Verbose("\t\tMapping il2cpp_type_get_object to Reflection::GetTypeObject...");
150150
il2cpp_vm_reflection_get_type_object = FindFunctionThisIsAThunkOf(il2cpp_type_get_object);
151151
Logger.VerboseNewline($"Found at 0x{il2cpp_vm_reflection_get_type_object:X}");
152152
}

Cpp2IL.InstructionSets.All/AllInstructionSets.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
using Cpp2IL.Core.Api;
2+
using Cpp2IL.InstructionSets.ArmV7;
23
using Cpp2IL.InstructionSets.ArmV8;
34
using Cpp2IL.InstructionSets.Wasm;
45
using Cpp2IL.InstructionSets.X86;
@@ -10,6 +11,7 @@ public static class AllInstructionSets
1011
public static void Register()
1112
{
1213
X86InstructionSet.RegisterInstructionSet();
14+
ArmV7InstructionSet.RegisterInstructionSet();
1315
ArmV8InstructionSet.RegisterInstructionSet();
1416
WasmInstructionSet.RegisterInstructionSet();
1517
OutputFormatRegistry.Register<WasmMappingOutputFormat>();

Cpp2IL.InstructionSets.All/Cpp2IL.InstructionSets.All.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66

77
<ItemGroup>
88
<ProjectReference Include="..\Cpp2IL.Core\Cpp2IL.Core.csproj" />
9+
<ProjectReference Include="..\Cpp2IL.InstructionSets.ArmV7\Cpp2IL.InstructionSets.ArmV7.csproj" />
910
<ProjectReference Include="..\Cpp2IL.InstructionSets.ArmV8\Cpp2IL.InstructionSets.ArmV8.csproj" />
1011
<ProjectReference Include="..\Cpp2IL.InstructionSets.Wasm\Cpp2IL.InstructionSets.Wasm.csproj" />
1112
<ProjectReference Include="..\Cpp2IL.InstructionSets.X86\Cpp2IL.InstructionSets.X86.csproj" />
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
using System.Text;
2+
using Cpp2IL.Core.Api;
3+
using Cpp2IL.Core.Il2CppApiFunctions;
4+
using Cpp2IL.Core.ISIL;
5+
using Cpp2IL.Core.Model.Contexts;
6+
using LibCpp2IL;
7+
8+
namespace Cpp2IL.InstructionSets.ArmV7;
9+
10+
public class ArmV7InstructionSet : Cpp2IlInstructionSet
11+
{
12+
public static void RegisterInstructionSet()
13+
{
14+
InstructionSetRegistry.RegisterInstructionSet<ArmV7InstructionSet>(DefaultInstructionSets.ARM_V7);
15+
}
16+
17+
public override Memory<byte> GetRawBytesForMethod(MethodAnalysisContext context, bool isAttributeGenerator)
18+
{
19+
if (ArmV7Utils.TryGetMethodBodyBytesFast(context.UnderlyingPointer, context is AttributeGeneratorMethodAnalysisContext) is { } ret)
20+
return ret;
21+
22+
ArmV7Utils.DisassembleManagedMethod(context.UnderlyingPointer, out var endVirtualAddress);
23+
24+
var start = (int)context.AppContext.Binary.MapVirtualAddressToRaw(context.UnderlyingPointer);
25+
var end = (int)context.AppContext.Binary.MapVirtualAddressToRaw(endVirtualAddress);
26+
27+
return context.AppContext.Binary.GetRawBinaryContent().AsMemory(start, end - start);
28+
}
29+
30+
public override List<InstructionSetIndependentInstruction> GetIsilFromMethod(MethodAnalysisContext context)
31+
{
32+
throw new NotImplementedException();
33+
}
34+
35+
public override BaseKeyFunctionAddresses CreateKeyFunctionAddressesInstance()
36+
{
37+
return new ArmV7KeyFunctionAddresses();
38+
}
39+
40+
public override unsafe string PrintAssembly(MethodAnalysisContext context)
41+
{
42+
var sb = new StringBuilder();
43+
var first = true;
44+
45+
using (ArmV7Utils.Disassembler.AllocInstruction(out var instruction))
46+
{
47+
fixed (byte* code = context.RawBytes.Span)
48+
{
49+
var size = (nuint)context.RawBytes.Length;
50+
var address = context.UnderlyingPointer;
51+
while (ArmV7Utils.Disassembler.UnsafeIterate(&code, &size, &address, instruction))
52+
{
53+
if (!first)
54+
{
55+
sb.AppendLine();
56+
first = false;
57+
}
58+
59+
sb.Append("0x").Append(address.ToString("X")).Append(" ").AppendLine(instruction->ToString());
60+
}
61+
}
62+
}
63+
64+
return sb.ToString();
65+
}
66+
}
Lines changed: 144 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,144 @@
1+
using CapstoneSharp.Arm;
2+
using Cpp2IL.Core.Il2CppApiFunctions;
3+
using Cpp2IL.Core.Logging;
4+
using LibCpp2IL;
5+
using LibCpp2IL.Reflection;
6+
7+
namespace Cpp2IL.InstructionSets.ArmV7;
8+
9+
public class ArmV7KeyFunctionAddresses : BaseKeyFunctionAddresses
10+
{
11+
private List<CapstoneArmInstruction>? _cachedDisassembledBytes;
12+
13+
private List<CapstoneArmInstruction> DisassembleTextSection()
14+
{
15+
if (_cachedDisassembledBytes == null)
16+
{
17+
var toDisasm = LibCpp2IlMain.Binary!.GetEntirePrimaryExecutableSection();
18+
_cachedDisassembledBytes = ArmV7Utils.Disassembler.Iterate(toDisasm, LibCpp2IlMain.Binary.GetVirtualAddressOfPrimaryExecutableSection()).ToList();
19+
}
20+
21+
return _cachedDisassembledBytes;
22+
}
23+
24+
protected override IEnumerable<ulong> FindAllThunkFunctions(ulong addr, uint maxBytesBack = 0, params ulong[] addressesToIgnore)
25+
{
26+
//Disassemble .text
27+
var disassembly = DisassembleTextSection();
28+
29+
//Find all jumps to the target address
30+
var matchingJmps = disassembly.Where(i => i.IsBranchingTo((int)addr)).ToList();
31+
32+
foreach (var matchingJmp in matchingJmps)
33+
{
34+
if (addressesToIgnore.Contains(matchingJmp.Address)) continue;
35+
36+
var backtrack = 0;
37+
var idx = disassembly.IndexOf(matchingJmp);
38+
39+
do
40+
{
41+
//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
42+
//Or a ret, but that's less reliable.
43+
//if so, this is probably a thunk.
44+
if (idx - backtrack > 0)
45+
{
46+
var prevInstruction = disassembly[idx - backtrack - 1];
47+
48+
if (addressesToIgnore.Contains(prevInstruction.Address))
49+
{
50+
backtrack++;
51+
continue;
52+
}
53+
54+
if (prevInstruction.IsSkippedData && prevInstruction.Bytes.IsAllZero())
55+
{
56+
//All-zero instructions are a match
57+
yield return matchingJmp.Address - (ulong)(backtrack * 4);
58+
break;
59+
}
60+
61+
if (prevInstruction.Id is CapstoneArmInstructionId.STR)
62+
{
63+
//ADRP instructions are a deal breaker - this means we're loading something from memory, so it's not a simple thunk
64+
break;
65+
}
66+
67+
if (prevInstruction.Id is CapstoneArmInstructionId.B or CapstoneArmInstructionId.BL)
68+
{
69+
//Previous branches are a match
70+
yield return matchingJmp.Address - (ulong)(backtrack * 4);
71+
break;
72+
}
73+
}
74+
75+
//We're working in the .text section here so we have few symbols, so there's no point looking for the previous one.
76+
77+
backtrack++;
78+
} while (backtrack * 4 < maxBytesBack);
79+
}
80+
}
81+
82+
protected override ulong GetObjectIsInstFromSystemType()
83+
{
84+
Logger.Verbose("\tTrying to use System.Type::IsInstanceOfType to find il2cpp::vm::Object::IsInst...");
85+
var typeIsInstanceOfType = LibCpp2IlReflection.GetType("Type", "System")?.Methods?.FirstOrDefault(m => m.Name == "IsInstanceOfType");
86+
if (typeIsInstanceOfType == null)
87+
{
88+
Logger.VerboseNewline("Type or method not found, aborting.");
89+
return 0;
90+
}
91+
92+
//IsInstanceOfType is a very simple ICall, that looks like this:
93+
// Il2CppClass* klass = vm::Class::FromIl2CppType(type->type.type);
94+
// return il2cpp::vm::Object::IsInst(obj, klass) != NULL;
95+
//The last call is to Object::IsInst
96+
97+
Logger.Verbose($"IsInstanceOfType found at 0x{typeIsInstanceOfType.MethodPointer:X}...");
98+
var instructions = ArmV7Utils.DisassembleManagedMethod(typeIsInstanceOfType.MethodPointer);
99+
100+
var lastCall = instructions.LastOrDefault(i => i.Id == CapstoneArmInstructionId.BL);
101+
102+
if (lastCall == null)
103+
{
104+
Logger.VerboseNewline("Method does not match expected signature. Aborting.");
105+
return 0;
106+
}
107+
108+
var target = lastCall.GetBranchTarget();
109+
Logger.VerboseNewline($"Success. IsInst found at 0x{target:X}");
110+
return (ulong)target;
111+
}
112+
113+
protected override ulong FindFunctionThisIsAThunkOf(ulong thunkPtr, bool prioritiseCall = false)
114+
{
115+
var instructions = ArmV7Utils.DisassembleFunction(thunkPtr);
116+
117+
try
118+
{
119+
var target = prioritiseCall ? CapstoneArmInstructionId.BL : CapstoneArmInstructionId.B;
120+
var matchingCall = instructions.FirstOrDefault(i => i.Id == target);
121+
122+
if (matchingCall == null)
123+
{
124+
target = target == CapstoneArmInstructionId.BL ? CapstoneArmInstructionId.B : CapstoneArmInstructionId.BL;
125+
matchingCall = instructions.First(i => i.Id == target);
126+
}
127+
128+
return (ulong)matchingCall.GetBranchTarget();
129+
}
130+
catch (Exception)
131+
{
132+
return 0;
133+
}
134+
}
135+
136+
protected override int GetCallerCount(ulong toWhere)
137+
{
138+
//Disassemble .text
139+
var disassembly = DisassembleTextSection();
140+
141+
//Find all jumps to the target address
142+
return disassembly.Count(i => i.IsBranchingTo((int)toWhere));
143+
}
144+
}
Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
using System.Runtime.InteropServices;
2+
using CapstoneSharp.Arm;
3+
using Cpp2IL.Core.Utils;
4+
using LibCpp2IL;
5+
6+
namespace Cpp2IL.InstructionSets.ArmV7;
7+
8+
internal static class ArmV7Utils
9+
{
10+
private static CapstoneArmDisassembler? _disassembler;
11+
12+
// TODO dispose this
13+
public static CapstoneArmDisassembler Disassembler => _disassembler ??= new CapstoneArmDisassembler
14+
{
15+
EnableInstructionDetails = true, EnableSkipData = true,
16+
};
17+
18+
public static bool IsAllZero(this ReadOnlySpan<byte> span)
19+
{
20+
if (MemoryMarshal.TryRead<int>(span, out var value))
21+
{
22+
return value == 0;
23+
}
24+
25+
foreach (var b in span)
26+
{
27+
if (b != 0)
28+
{
29+
return true;
30+
}
31+
}
32+
33+
return true;
34+
}
35+
36+
public static int GetBranchTarget(this CapstoneArmInstruction instruction)
37+
{
38+
if (instruction.Id is not (CapstoneArmInstructionId.B or CapstoneArmInstructionId.BL))
39+
throw new InvalidOperationException("Branch target not available for this instruction, must be a B or BL");
40+
41+
return instruction.Details.ArchDetails.Operands[0].Immediate;
42+
}
43+
44+
public static bool IsBranchingTo(this CapstoneArmInstruction instruction, int toWhere)
45+
{
46+
if (instruction.Id is not (CapstoneArmInstructionId.B or CapstoneArmInstructionId.BL))
47+
return false;
48+
49+
return instruction.GetBranchTarget() == toWhere;
50+
}
51+
52+
public static Memory<byte>? TryGetMethodBodyBytesFast(ulong virtualAddress, bool isCAGen)
53+
{
54+
var startOfNext = MiscUtils.GetAddressOfNextFunctionStart(virtualAddress);
55+
56+
var length = (startOfNext - virtualAddress);
57+
if (isCAGen && length > 50_000)
58+
return null;
59+
60+
if (startOfNext <= 0)
61+
//We have to fall through to default behavior for the last method because we cannot accurately pinpoint its end
62+
return null;
63+
64+
var rawStartOfNextMethod = LibCpp2IlMain.Binary!.MapVirtualAddressToRaw(startOfNext);
65+
66+
var rawStart = LibCpp2IlMain.Binary.MapVirtualAddressToRaw(virtualAddress);
67+
if (rawStartOfNextMethod < rawStart)
68+
rawStartOfNextMethod = LibCpp2IlMain.Binary.RawLength;
69+
70+
return LibCpp2IlMain.Binary.GetRawBinaryContent().AsMemory((int)rawStart, (int)(rawStartOfNextMethod - rawStart));
71+
}
72+
73+
public static List<CapstoneArmInstruction> DisassembleFunction(ulong virtualAddress, int count = -1)
74+
{
75+
return DisassembleFunction(virtualAddress, out _, count);
76+
}
77+
78+
public static List<CapstoneArmInstruction> DisassembleFunction(ulong virtualAddress, out ulong endVirtualAddress, int count = -1)
79+
{
80+
// Unmanaged function, look for first b
81+
var pos = (int)LibCpp2IlMain.Binary!.MapVirtualAddressToRaw(virtualAddress);
82+
var allBytes = LibCpp2IlMain.Binary.GetRawBinaryContent();
83+
84+
var instructions = new List<CapstoneArmInstruction>();
85+
86+
endVirtualAddress = virtualAddress;
87+
foreach (var instruction in Disassembler.Iterate(allBytes.AsSpan(pos), virtualAddress))
88+
{
89+
instructions.Add(instruction);
90+
endVirtualAddress = instruction.Address;
91+
if (instruction.Id == CapstoneArmInstructionId.B) break;
92+
if (count != -1 && instructions.Count >= count) break;
93+
}
94+
95+
return instructions;
96+
}
97+
98+
public static IEnumerable<CapstoneArmInstruction> DisassembleManagedMethod(ulong virtualAddress, int count = -1)
99+
{
100+
return DisassembleManagedMethod(virtualAddress, out _, count);
101+
}
102+
103+
public static IEnumerable<CapstoneArmInstruction> DisassembleManagedMethod(ulong virtualAddress, out ulong endVirtualAddress, int count = -1)
104+
{
105+
var startOfNext = MiscUtils.GetAddressOfNextFunctionStart(virtualAddress);
106+
107+
// We have to fall through to default behavior for the last method because we cannot accurately pinpoint its end
108+
if (startOfNext > 0)
109+
{
110+
var rawStartOfNextMethod = LibCpp2IlMain.Binary!.MapVirtualAddressToRaw(startOfNext);
111+
112+
var rawStart = LibCpp2IlMain.Binary.MapVirtualAddressToRaw(virtualAddress);
113+
if (rawStartOfNextMethod < rawStart)
114+
rawStartOfNextMethod = LibCpp2IlMain.Binary.RawLength;
115+
116+
var bytes = LibCpp2IlMain.Binary.GetRawBinaryContent().AsMemory((int)rawStart, (int)(rawStartOfNextMethod - rawStart));
117+
118+
endVirtualAddress = virtualAddress + (ulong)bytes.Length;
119+
var instructions = Disassembler.Iterate(bytes, virtualAddress);
120+
return count == -1 ? instructions : instructions.Take(count);
121+
}
122+
123+
return DisassembleFunction(virtualAddress, out endVirtualAddress, count);
124+
}
125+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
<Project Sdk="Microsoft.NET.Sdk">
2+
<PropertyGroup>
3+
<TargetFramework>netstandard2.0</TargetFramework>
4+
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
5+
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
6+
</PropertyGroup>
7+
8+
<ItemGroup>
9+
<ProjectReference Include="..\Cpp2IL.Core\Cpp2IL.Core.csproj" />
10+
<PackageReference Include="CapstoneSharp" Version="0.1.0" />
11+
</ItemGroup>
12+
</Project>

0 commit comments

Comments
 (0)