Skip to content

Commit

Permalink
Core: *very* basic arm64 ISIL parsing.
Browse files Browse the repository at this point in the history
Enough to get call analysis working at least
  • Loading branch information
SamboyCoding committed Jun 11, 2024
1 parent 39698c5 commit 93ac84e
Show file tree
Hide file tree
Showing 10 changed files with 158 additions and 30 deletions.
2 changes: 1 addition & 1 deletion Cpp2IL.Core/Cpp2IL.Core.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@
<PackageReference Include="AssetRipper.CIL" Version="1.0.0" />

<!--For ARM64 dissassembly-->
<PackageReference Include="Disarm" Version="2022.1.0-master.26" />
<PackageReference Include="Disarm" Version="2022.1.0-master.34" />

<!--For X86/X64 disassembly-->
<PackageReference Include="Iced" Version="1.20.0" />
Expand Down
2 changes: 1 addition & 1 deletion Cpp2IL.Core/Extensions/MiscExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,7 @@ public static string Repeat(this string source, int count)
return res.ToString();
}

public static T[] SubArray<T>(this T[] source, Range range)
internal static T[] SubArray<T>(this T[] source, Range range)
{
if (!range.Start.IsFromEnd && !range.End.IsFromEnd)
if (range.Start.Value > range.End.Value)
Expand Down
10 changes: 5 additions & 5 deletions Cpp2IL.Core/ISIL/IsilMemoryOperand.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,14 @@ namespace Cpp2IL.Core.ISIL;
{
public readonly InstructionSetIndependentOperand? Base = null; //Must be literal
public readonly InstructionSetIndependentOperand? Index = null;
public readonly ulong Addend = 0;
public readonly long Addend = 0;
public readonly int Scale = 0;

/// <summary>
/// Create a new memory operand representing just a constant address
/// </summary>
/// <param name="addend">The constant address which will be represented as the addent</param>
public IsilMemoryOperand(ulong addend)
public IsilMemoryOperand(long addend)
{
Addend = addend;
}
Expand All @@ -40,7 +40,7 @@ public IsilMemoryOperand(InstructionSetIndependentOperand @base)
/// </summary>
/// <param name="base">The base. Should be an operand of type <see cref="InstructionSetIndependentOperand.OperandType.Register"/></param>
/// <param name="addend">The addend relative to the memory base.</param>
public IsilMemoryOperand(InstructionSetIndependentOperand @base, ulong addend)
public IsilMemoryOperand(InstructionSetIndependentOperand @base, long addend)
{
Debug.Assert(@base.Type == InstructionSetIndependentOperand.OperandType.Register);

Expand Down Expand Up @@ -72,7 +72,7 @@ public IsilMemoryOperand(InstructionSetIndependentOperand @base, InstructionSetI
/// <param name="index">The index. Should be an operand of type <see cref="InstructionSetIndependentOperand.OperandType.Register"/></param>
/// <param name="addend">A constant addend to be added to the memory address after adding the index multiplied by the scale.</param>
/// <param name="scale">The scale that the index is multiplied by. Should be a positive integer.</param>
public IsilMemoryOperand(InstructionSetIndependentOperand @base, InstructionSetIndependentOperand index, ulong addend, int scale)
public IsilMemoryOperand(InstructionSetIndependentOperand @base, InstructionSetIndependentOperand index, long addend, int scale)
{
Debug.Assert(@base.Type == InstructionSetIndependentOperand.OperandType.Register);
Debug.Assert(index.Type == InstructionSetIndependentOperand.OperandType.Register);
Expand Down Expand Up @@ -124,4 +124,4 @@ public override string ToString()

return ret.ToString();
}
}
}
12 changes: 6 additions & 6 deletions Cpp2IL.Core/Il2CppApiFunctions/NewArm64KeyFunctionAddresses.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,17 +12,17 @@ namespace Cpp2IL.Core.Il2CppApiFunctions;

public class NewArm64KeyFunctionAddresses : BaseKeyFunctionAddresses
{
private Arm64DisassemblyResult? _cachedDisassembledBytes;
private List<Arm64Instruction>? _cachedDisassembledBytes;

private Arm64DisassemblyResult DisassembleTextSection()
private List<Arm64Instruction> DisassembleTextSection()
{
if (_cachedDisassembledBytes == null)
{
var toDisasm = LibCpp2IlMain.Binary!.GetEntirePrimaryExecutableSection();
_cachedDisassembledBytes = Disassembler.Disassemble(toDisasm, LibCpp2IlMain.Binary.GetVirtualAddressOfPrimaryExecutableSection());
_cachedDisassembledBytes = Disassembler.Disassemble(toDisasm, LibCpp2IlMain.Binary.GetVirtualAddressOfPrimaryExecutableSection(), new(true, true, false)).ToList();
}

return _cachedDisassembledBytes.Value;
return _cachedDisassembledBytes;
}

protected override IEnumerable<ulong> FindAllThunkFunctions(ulong addr, uint maxBytesBack = 0, params ulong[] addressesToIgnore)
Expand All @@ -31,7 +31,7 @@ protected override IEnumerable<ulong> FindAllThunkFunctions(ulong addr, uint max
var disassembly = DisassembleTextSection();

//Find all jumps to the target address
var matchingJmps = disassembly.Instructions.Where(i => i.Mnemonic is Arm64Mnemonic.B or Arm64Mnemonic.BL && i.BranchTarget == addr).ToList();
var matchingJmps = disassembly.Where(i => i.Mnemonic is Arm64Mnemonic.B or Arm64Mnemonic.BL && i.BranchTarget == addr).ToList();

foreach (var matchingJmp in matchingJmps)
{
Expand Down Expand Up @@ -130,6 +130,6 @@ protected override int GetCallerCount(ulong toWhere)
var disassembly = DisassembleTextSection();

//Find all jumps to the target address
return disassembly.Instructions.Count(i => i.Mnemonic is Arm64Mnemonic.B or Arm64Mnemonic.BL && i.BranchTarget == toWhere);
return disassembly.Count(i => i.Mnemonic is Arm64Mnemonic.B or Arm64Mnemonic.BL && i.BranchTarget == toWhere);
}
}
99 changes: 92 additions & 7 deletions Cpp2IL.Core/InstructionSets/NewArmV8InstructionSet.cs
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Disarm;
using Cpp2IL.Core.Api;
using Cpp2IL.Core.Il2CppApiFunctions;
using Cpp2IL.Core.ISIL;
using Cpp2IL.Core.Model.Contexts;
using Cpp2IL.Core.Utils;
using Disarm.InternalDisassembly;
using LibCpp2IL;

namespace Cpp2IL.Core.InstructionSets;
Expand All @@ -26,26 +28,109 @@ public override Memory<byte> GetRawBytesForMethod(MethodAnalysisContext context,
}

var result = NewArm64Utils.GetArm64MethodBodyAtVirtualAddress(context.UnderlyingPointer);
var endVa = result.EndVirtualAddress;
var endVa = result.LastValid().Address + 4;

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

//Sanity check
if (start < 0 || end < 0 || start >= context.AppContext.Binary.RawLength || end >= context.AppContext.Binary.RawLength)
throw new Exception($"Failed to map virtual address 0x{context.UnderlyingPointer:X} to raw address for method {context!.DeclaringType?.FullName}/{context.Name} - start: 0x{start:X}, end: 0x{end:X} are out of bounds for length {context.AppContext.Binary.RawLength}.");

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

public override List<InstructionSetIndependentInstruction> GetIsilFromMethod(MethodAnalysisContext context)
{
var result = NewArm64Utils.GetArm64MethodBodyAtVirtualAddress(context.UnderlyingPointer);
var insns = NewArm64Utils.GetArm64MethodBodyAtVirtualAddress(context.UnderlyingPointer);

//TODO
return new();
var builder = new IsilBuilder();

foreach (var instruction in insns)
{
ConvertInstructionStatement(instruction, builder, context);
}

builder.FixJumps();

return builder.BackingStatementList;
}

public override BaseKeyFunctionAddresses CreateKeyFunctionAddressesInstance()
private void ConvertInstructionStatement(Arm64Instruction instruction, IsilBuilder builder, MethodAnalysisContext context)
{
return new NewArm64KeyFunctionAddresses();
switch (instruction.Mnemonic)
{
case Arm64Mnemonic.MOV:
builder.Move(instruction.Address, ConvertOperand(instruction, 0), ConvertOperand(instruction, 1));
break;
case Arm64Mnemonic.BL:
builder.Call(instruction.Address, (ulong) ((long) instruction.Address + instruction.Op0Imm));
break;
}
}

private InstructionSetIndependentOperand ConvertOperand(Arm64Instruction instruction, int operand)
{
var kind = operand switch
{
0 => instruction.Op0Kind,
1 => instruction.Op1Kind,
2 => instruction.Op2Kind,
3 => instruction.Op3Kind,
_ => throw new ArgumentOutOfRangeException(nameof(operand), $"Operand must be between 0 and 3, inclusive. Got {operand}")
};

if (kind is Arm64OperandKind.Immediate or Arm64OperandKind.ImmediatePcRelative)
{
var imm = operand switch
{
0 => instruction.Op0Imm,
1 => instruction.Op1Imm,
2 => instruction.Op2Imm,
3 => instruction.Op3Imm,
_ => throw new ArgumentOutOfRangeException(nameof(operand), $"Operand must be between 0 and 3, inclusive. Got {operand}")
};

if(kind == Arm64OperandKind.ImmediatePcRelative)
imm += (long) instruction.Address + 4; //Add 4 to the address to get the address of the next instruction (PC-relative addressing is relative to the address of the next instruction, not the current one

return InstructionSetIndependentOperand.MakeImmediate(imm);
}

if (kind == Arm64OperandKind.Register)
{
var reg = operand switch
{
0 => instruction.Op0Reg,
1 => instruction.Op1Reg,
2 => instruction.Op2Reg,
3 => instruction.Op3Reg,
_ => throw new ArgumentOutOfRangeException(nameof(operand), $"Operand must be between 0 and 3, inclusive. Got {operand}")
};

return InstructionSetIndependentOperand.MakeRegister(reg.ToString().ToLower());
}

if (kind == Arm64OperandKind.Memory)
{
var reg = instruction.MemBase;
var offset = instruction.MemOffset;
var isPreIndexed = instruction.MemIsPreIndexed;

if(reg == Arm64Register.INVALID)
//Offset only
return InstructionSetIndependentOperand.MakeMemory(new IsilMemoryOperand(offset));

//TODO Handle more stuff here
return InstructionSetIndependentOperand.MakeMemory(new IsilMemoryOperand(
InstructionSetIndependentOperand.MakeRegister(reg.ToString().ToLower()),
offset));
}

throw new NotImplementedException($"Operand kind {kind} not yet implemented.");
}

public override string PrintAssembly(MethodAnalysisContext context) => string.Join("\n", Disassembler.Disassemble(context.RawBytes.Span, context.UnderlyingPointer).Instructions);
public override BaseKeyFunctionAddresses CreateKeyFunctionAddressesInstance() => new NewArm64KeyFunctionAddresses();

public override string PrintAssembly(MethodAnalysisContext context) => string.Join("\n", Disassembler.Disassemble(context.RawBytes.Span, context.UnderlyingPointer).ToList());
}
6 changes: 3 additions & 3 deletions Cpp2IL.Core/InstructionSets/X86InstructionSet.cs
Original file line number Diff line number Diff line change
Expand Up @@ -317,7 +317,7 @@ private InstructionSetIndependentOperand ConvertOperand(Instruction instruction,
//Most complex to least complex

if (instruction.IsIPRelativeMemoryOperand)
return InstructionSetIndependentOperand.MakeMemory(new(instruction.IPRelativeMemoryAddress));
return InstructionSetIndependentOperand.MakeMemory(new((long)instruction.IPRelativeMemoryAddress));

//All four components
if (instruction.MemoryIndex != Register.None && instruction.MemoryBase != Register.None && instruction.MemoryDisplacement64 != 0)
Expand All @@ -339,7 +339,7 @@ private InstructionSetIndependentOperand ConvertOperand(Instruction instruction,
if (instruction.MemoryBase != Register.None && instruction.MemoryDisplacement64 > 0)
{
var mBase = InstructionSetIndependentOperand.MakeRegister(X86Utils.GetRegisterName(instruction.MemoryBase));
return InstructionSetIndependentOperand.MakeMemory(new(mBase, instruction.MemoryDisplacement64));
return InstructionSetIndependentOperand.MakeMemory(new(mBase, (long)instruction.MemoryDisplacement64));
}

//Only base
Expand All @@ -349,6 +349,6 @@ private InstructionSetIndependentOperand ConvertOperand(Instruction instruction,
}

//Only addend
return InstructionSetIndependentOperand.MakeMemory(new(instruction.MemoryDisplacement64));
return InstructionSetIndependentOperand.MakeMemory(new((long)instruction.MemoryDisplacement64));
}
}
4 changes: 4 additions & 0 deletions Cpp2IL.Core/Model/Contexts/ApplicationAnalysisContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -120,8 +120,10 @@ private void PopulateMethodsByAddressTable()
Logger.VerboseNewline("\tProcessing concrete generic methods...");
foreach (var methodRef in Binary.ConcreteGenericMethods.Values.SelectMany(v => v))
{
#if !DEBUG
try
{
#endif
var gm = new ConcreteGenericMethodAnalysisContext(methodRef, this);

var ptr = InstructionSet.GetPointerForMethod(gm);
Expand All @@ -131,11 +133,13 @@ private void PopulateMethodsByAddressTable()

MethodsByAddress[ptr].Add(gm);
ConcreteGenericMethodsByRef[methodRef] = gm;
#if !DEBUG
}
catch (Exception e)
{
throw new("Failed to process concrete generic method: " + methodRef, e);
}
#endif
}
}

Expand Down
33 changes: 28 additions & 5 deletions Cpp2IL.Core/Utils/NewArm64Utils.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Disarm;
using LibCpp2IL;
Expand All @@ -7,7 +8,7 @@ namespace Cpp2IL.Core.Utils;

public static class NewArm64Utils
{
public static Arm64DisassemblyResult GetArm64MethodBodyAtVirtualAddress(ulong virtAddress, bool managed = true, int count = -1)
public static List<Arm64Instruction> GetArm64MethodBodyAtVirtualAddress(ulong virtAddress, bool managed = true, int count = -1)
{
if (managed)
{
Expand All @@ -32,9 +33,9 @@ public static Arm64DisassemblyResult GetArm64MethodBodyAtVirtualAddress(ulong vi
var pos = (int)LibCpp2IlMain.Binary!.MapVirtualAddressToRaw(virtAddress);
var allBytes = LibCpp2IlMain.Binary.GetRawBinaryContent();
var span = allBytes.AsSpan(pos, 4);
Arm64DisassemblyResult ret = new();
List<Arm64Instruction> ret = new();

while ((count == -1 || ret.Instructions.Count < count) && !ret.Instructions.Any(i => i.Mnemonic is Arm64Mnemonic.B))
while ((count == -1 || ret.Count < count) && !ret.Any(i => i.Mnemonic is Arm64Mnemonic.B))
{
ret = Disassemble(span, virtAddress);

Expand All @@ -45,15 +46,37 @@ public static Arm64DisassemblyResult GetArm64MethodBodyAtVirtualAddress(ulong vi
return ret;
}

private static Arm64DisassemblyResult Disassemble(Span<byte> bytes, ulong virtAddress)
private static List<Arm64Instruction> Disassemble(Span<byte> bytes, ulong virtAddress)
{
try
{
return Disassembler.Disassemble(bytes, virtAddress);
return Disassembler.Disassemble(bytes, virtAddress, new Disassembler.Options(true, true, false)).ToList();
}
catch (Exception e)
{
throw new($"Failed to disassemble method body: {string.Join(", ", bytes.ToArray().Select(b => "0x" + b.ToString("X2")))}", e);
}
}

public static List<Arm64Instruction> ToList(this Disassembler.SpanEnumerator enumerator)
{
var ret = new List<Arm64Instruction>();
while (enumerator.MoveNext())
{
ret.Add(enumerator.Current);
}

return ret;
}

public static Arm64Instruction LastValid(this List<Arm64Instruction> list)
{
for (var i = list.Count - 1; i >= 0; i--)
{
if (list[i].Mnemonic is not (Arm64Mnemonic.INVALID or Arm64Mnemonic.UNIMPLEMENTED))
return list[i];
}

return list[^1];
}
}
4 changes: 4 additions & 0 deletions Cpp2IL/Cpp2IL.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,10 @@
<ItemGroup>
<PackageReference Include="CommandLineParser" Version="2.9.1" />
<PackageReference Include="Pastel" Version="4.2.0" />
<PackageReference Include="PolySharp" Version="1.14.1">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
</ItemGroup>

<ItemGroup>
Expand Down
16 changes: 14 additions & 2 deletions Cpp2IL/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -267,8 +267,20 @@ private static void HandleXapk(string gamePath, ref Cpp2IlRuntimeArgs args)
using var xapkStream = File.OpenRead(gamePath);
using var xapkZip = new ZipArchive(xapkStream);

var configApk = xapkZip.Entries.FirstOrDefault(e => e.FullName.Contains("config.") && e.FullName.EndsWith(".apk"));
var mainApk = xapkZip.Entries.FirstOrDefault(e => e != configApk && e.FullName.EndsWith(".apk"));
ZipArchiveEntry? configApk = null;
var configApks = xapkZip.Entries.Where(e => e.FullName.Contains("config.") && e.FullName.EndsWith(".apk")).ToList();

var instructionSetPreference = new string[] { "arm64_v8a", "arm64", "armeabi_v7a", "arm" };
foreach (var instructionSet in instructionSetPreference)
{
configApk = configApks.FirstOrDefault(e => e.FullName.Contains(instructionSet));
if (configApk != null)
break;
}

//Try for base.apk, else find any apk that isn't the config apk
var mainApk = xapkZip.Entries.FirstOrDefault(e => e.FullName.EndsWith(".apk") && e.FullName.Contains("base.apk"))
?? xapkZip.Entries.FirstOrDefault(e => e != configApk && e.FullName.EndsWith(".apk"));

Logger.InfoNewline($"Identified APKs inside XAPK - config: {configApk?.FullName}, mainPackage: {mainApk?.FullName}", "XAPK");

Expand Down

0 comments on commit 93ac84e

Please sign in to comment.