Refactor solution layout
This commit is contained in:
282
Il2CppInspector.Common/Architectures/Il2CppBinaryARM.cs
Normal file
282
Il2CppInspector.Common/Architectures/Il2CppBinaryARM.cs
Normal file
@@ -0,0 +1,282 @@
|
||||
/*
|
||||
Copyright 2017 Perfare - https://github.com/Perfare/Il2CppDumper
|
||||
Copyright 2017-2019 Katy Coe - http://www.hearthcode.org - http://www.djkaty.com
|
||||
|
||||
All rights reserved.
|
||||
*/
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace Il2CppInspector
|
||||
{
|
||||
internal class Il2CppBinaryARM : Il2CppBinary
|
||||
{
|
||||
public Il2CppBinaryARM(IFileFormatReader stream) : base(stream) { }
|
||||
|
||||
public Il2CppBinaryARM(IFileFormatReader stream, uint codeRegistration, uint metadataRegistration) : base(stream, codeRegistration, metadataRegistration) { }
|
||||
|
||||
// Thumb 2 Supplement Reference Manual: http://class.ece.iastate.edu/cpre288/resources/docs/Thumb-2SupplementReferenceManual.pdf
|
||||
|
||||
// Section 3.1
|
||||
private uint getNextThumbInstruction(IFileFormatReader image) {
|
||||
// Assume 16-bit
|
||||
uint inst = image.ReadUInt16();
|
||||
|
||||
// Is 32-bit?
|
||||
if (inst.Bits(13, 15) == 0b111)
|
||||
if (inst.Bits(11, 2) != 0b00)
|
||||
inst = (inst << 16) + image.ReadUInt16();
|
||||
|
||||
return inst;
|
||||
}
|
||||
|
||||
// Page 4-166 (MOVS encoding T1, MOVW encoding T3), Page 4-171 (MOVT)
|
||||
// In Thumb, an 8-byte MOV instruction is MOVW followed by MOVT
|
||||
private enum Thumb : uint
|
||||
{
|
||||
MovW = 0b100100,
|
||||
MovT = 0b101100
|
||||
}
|
||||
|
||||
private (uint reg_d, ushort imm)? getMovImm(uint inst, Thumb movType) {
|
||||
uint reg_d, imm;
|
||||
|
||||
// Encoding T1
|
||||
if (inst.Bits(11, 21) == 0b00100 && movType == Thumb.MovW) {
|
||||
imm = inst.Bits(0, 8);
|
||||
reg_d = inst.Bits(8, 3);
|
||||
return (reg_d, (ushort) imm);
|
||||
}
|
||||
|
||||
// Encoding T3
|
||||
if (inst.Bits(20, 6) != (uint) movType || inst.Bits(27, 5) != 0b11110 || inst.Bits(15, 1) != 0)
|
||||
return null;
|
||||
|
||||
imm = (inst.Bits(16, 4) << 12) + (inst.Bits(26, 1) << 11) + (inst.Bits(12, 3) << 8) + inst.Bits(0, 8);
|
||||
reg_d = inst.Bits(8, 4);
|
||||
return (reg_d, (ushort) imm);
|
||||
}
|
||||
|
||||
// Section 4.6.4 (ADD encoding T2)
|
||||
private (uint reg_dn, uint reg_m)? getAddReg(uint inst) {
|
||||
if (inst.Bits(8, 8) != 0b_0100_0100)
|
||||
return null;
|
||||
|
||||
var reg_dn = (inst.Bits(7, 1) << 3) + inst.Bits(0, 3);
|
||||
var reg_m = inst.Bits(3, 4);
|
||||
return (reg_dn, reg_m);
|
||||
}
|
||||
|
||||
// Section 4.6.43 (LDR encoding T1)
|
||||
private (uint reg_n, uint reg_t, ushort imm)? getLdrImm(uint inst) {
|
||||
if (inst.Bits(11, 5) != 0b_01101)
|
||||
return null;
|
||||
|
||||
var reg_n = inst.Bits(3, 3);
|
||||
var reg_t = inst.Bits(0, 3);
|
||||
var imm = inst.Bits(6, 5);
|
||||
return (reg_n, reg_t, (ushort) imm);
|
||||
}
|
||||
|
||||
// Section 4.6.12 (B.W encoding T4; for encoding T3, flip bit 12)
|
||||
private bool isBW(uint inst) => inst.Bits(27, 5) == 0b11110 && inst.Bits(14, 2) == 0b10 && inst.Bits(12, 1) == 1;
|
||||
|
||||
// Sweep a Thumb function and return the register values at the end (register number => value)
|
||||
private Dictionary<uint, uint> sweepThumbForAddressLoads(List<uint> func, uint baseAddress, IFileFormatReader image) {
|
||||
// List of registers and addresses loaded into them
|
||||
var regs = new Dictionary<uint, uint>();
|
||||
|
||||
// Program counter is R15 in ARM
|
||||
// https://www.scss.tcd.ie/~waldroj/3d1/arm_arm.pdf states:
|
||||
// For a Thumb instruction, the value read is the address of the instruction plus 4 bytes
|
||||
regs.Add(15, baseAddress + 4);
|
||||
|
||||
// Iterate each instruction
|
||||
foreach (var inst in func) {
|
||||
|
||||
var accepted = false;
|
||||
|
||||
// Is it a MOVW?
|
||||
if (getMovImm(inst, Thumb.MovW) is (uint movw_reg_d, ushort movw_imm)) {
|
||||
if (regs.ContainsKey(movw_reg_d))
|
||||
regs[movw_reg_d] = movw_imm; // Clears top 16 bits
|
||||
else
|
||||
regs.Add(movw_reg_d, movw_imm);
|
||||
|
||||
accepted = true;
|
||||
}
|
||||
|
||||
// Is it a MOVT?
|
||||
if (getMovImm(inst, Thumb.MovT) is (uint movt_reg_d, ushort movt_imm)) {
|
||||
if (regs.ContainsKey(movt_reg_d))
|
||||
regs[movt_reg_d] |= (uint) movt_imm << 16;
|
||||
else
|
||||
regs.Add(movt_reg_d, (uint) movt_imm << 16);
|
||||
|
||||
accepted = true;
|
||||
}
|
||||
|
||||
// Is it a pointer de-reference (LDR Rt, [Rn, #imm])?
|
||||
if (getLdrImm(inst) is (uint ldr_reg_n, uint ldr_reg_t, ushort ldr_imm)) {
|
||||
// The code below works in the generic case for all Rt, Rn and #imm,
|
||||
// but for our scan we want to restrict it such that Rt == Rn and #imm == 0
|
||||
// otherwise we might pick up functions we don't want
|
||||
if (ldr_reg_n == ldr_reg_t && ldr_imm == 0)
|
||||
|
||||
if (regs.ContainsKey(ldr_reg_n)) {
|
||||
var offset = (regs[ldr_reg_n] & 0xffff_fffe) + ldr_imm;
|
||||
var value = image.ReadUInt32(image.MapVATR(offset));
|
||||
if (regs.ContainsKey(ldr_reg_t))
|
||||
regs[ldr_reg_t] = value;
|
||||
else
|
||||
regs.Add(ldr_reg_t, value);
|
||||
|
||||
accepted = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Is it an ADD Rdn, Rm?
|
||||
if (getAddReg(inst) is (uint add_reg_dn, uint add_reg_m)) {
|
||||
if (regs.ContainsKey(add_reg_dn) && regs.ContainsKey(add_reg_m)) {
|
||||
regs[add_reg_dn] += regs[add_reg_m];
|
||||
|
||||
accepted = true;
|
||||
}
|
||||
}
|
||||
|
||||
// is it the end?
|
||||
if (isBW(inst))
|
||||
accepted = true;
|
||||
|
||||
// In our scan, we will ONLY accept one of the above instructions
|
||||
if (!accepted)
|
||||
return null;
|
||||
|
||||
// Advance program counter which we need to calculate ADDs with PC as operand correctly
|
||||
regs[15] += inst.Bits(29, 3) == 0b111 ? 4u : 2u;
|
||||
}
|
||||
return regs;
|
||||
}
|
||||
|
||||
// Get a Thumb function that ends in B.W
|
||||
private List<uint> getThumbFunctionAtFileOffset(IFileFormatReader image, uint loc, uint maxLength) {
|
||||
// Read a function that ends in a hard branch (B.W) or exceeds maxLength instructions
|
||||
var func = new List<uint>();
|
||||
uint inst;
|
||||
|
||||
image.Position = loc;
|
||||
|
||||
do {
|
||||
inst = getNextThumbInstruction(image);
|
||||
func.Add(inst);
|
||||
} while (!isBW(inst) && func.Count < maxLength);
|
||||
|
||||
return func;
|
||||
}
|
||||
|
||||
protected override (ulong, ulong) ConsiderCode(IFileFormatReader image, uint loc) {
|
||||
// Assembly bytes to search for at start of each function
|
||||
ulong metadataRegistration, codeRegistration;
|
||||
|
||||
// ARMv7 based on ELF GOT
|
||||
// Il2CppCodeRegistration.cpp initializer
|
||||
image.Position = loc;
|
||||
|
||||
var buff = image.ReadBytes(0xc);
|
||||
// LDR R0, [PC, #0x1C]; LDR R1, [PC, #0x1C]; LDR R2, [PC, #0x1C]
|
||||
if (new byte[] { 0x1c, 0x0, 0x9f, 0xe5, 0x1c, 0x10, 0x9f, 0xe5, 0x1c, 0x20, 0x9f, 0xe5 }.SequenceEqual(buff)) {
|
||||
|
||||
// Get offset to all addresses
|
||||
// The +8 is because in ARM, PC always contains the currently executing instruction + 8
|
||||
var offset = image.ReadUInt32(loc + 0x24) + loc + 0xc + 8;
|
||||
|
||||
// Get pointer to Il2CppCodegenRegistration(void)
|
||||
var pCgr = image.ReadUInt32(loc + 0x2C) + offset;
|
||||
|
||||
// Read pointer table at end of function
|
||||
codeRegistration = image.ReadUInt32(pCgr + 0x28) + offset;
|
||||
var pMetadataRegistration = image.ReadUInt32(pCgr + 0x2C) + offset;
|
||||
metadataRegistration = image.ReadUInt32(pMetadataRegistration);
|
||||
return (codeRegistration, metadataRegistration);
|
||||
}
|
||||
|
||||
// ARMv7
|
||||
// Il2CppCodeRegistration.cpp initializer
|
||||
image.Position = loc;
|
||||
|
||||
buff = image.ReadBytes(0x18);
|
||||
// Check for ADD R0, PC, R0; ADD R1, PC, R1 near the end of the function
|
||||
if (new byte[] {0x00, 0x00, 0x8F, 0xE0, 0x01, 0x10, 0x8F, 0xE0}.SequenceEqual(buff.Skip(0x10))
|
||||
|
||||
// Check for LDR R1, [PC, #x] where x is an offset to *Il2CppCodegenRegistration
|
||||
&& new byte[] {0x10, 0x9F, 0xE5}.SequenceEqual(buff.Skip(0x9).Take(3))) {
|
||||
|
||||
// Read offset in LDR operand plus pointer table at end of function to find pCgr
|
||||
var pCgr = buff[8] + loc + 0x10;
|
||||
image.Position = pCgr;
|
||||
pCgr = image.ReadUInt32() + loc + 0x1c;
|
||||
|
||||
// void Il2CppCodegenRegistration()
|
||||
// Read pointer table at end of function
|
||||
image.Position = pCgr + 0x1C;
|
||||
var pMetadata = image.ReadUInt32() + pCgr + 0x14;
|
||||
codeRegistration = image.ReadUInt32() + pCgr + 0x18;
|
||||
|
||||
image.Position = image.MapVATR(pMetadata);
|
||||
metadataRegistration = image.ReadUInt32();
|
||||
return (codeRegistration, metadataRegistration);
|
||||
}
|
||||
|
||||
// Thumb-2
|
||||
// We use a method similar to the linear sweep in Il2CppBinaryARM64; see the comments there for details
|
||||
loc &= 0xffff_fffe;
|
||||
image.Position = loc;
|
||||
|
||||
// Load function into memory
|
||||
// In practice, the longest function length we need is not generally longer than 11 instructions
|
||||
var func = getThumbFunctionAtFileOffset(image, loc, 11);
|
||||
|
||||
// Don't accept functions longer than 10 instructions (in this case, the last instruction won't be a B.W)
|
||||
if (!isBW(func[^1]))
|
||||
return (0, 0);
|
||||
|
||||
// Get a list of registers and values in them at the end of the function
|
||||
var regs = sweepThumbForAddressLoads(func, (uint) image.GlobalOffset + loc, image);
|
||||
if (regs == null)
|
||||
return (0, 0);
|
||||
|
||||
uint r0, r1;
|
||||
|
||||
// Is it the Il2CppCodeRegistration.cpp initializer?
|
||||
// R0-R3 + PC will be set and they will be the only registers set
|
||||
// R2 and R3 must be zero
|
||||
if (regs.Count() == 5 && regs.TryGetValue(0, out _) && regs.TryGetValue(1, out r1)
|
||||
&& regs.TryGetValue(2, out uint r2) && regs.TryGetValue(3, out uint r3)) {
|
||||
|
||||
if (r2 == 0 && r3 == 0) {
|
||||
// Load up the function whose address is in R1
|
||||
func = getThumbFunctionAtFileOffset(image, image.MapVATR(r1 & 0xffff_fffe), 11);
|
||||
|
||||
if (!isBW(func[^1]))
|
||||
return (0, 0);
|
||||
|
||||
regs = sweepThumbForAddressLoads(func, r1 & 0xffff_fffe, image);
|
||||
}
|
||||
}
|
||||
|
||||
// Is it Il2CppCodegenRegistration(void)?
|
||||
// In v21 and later, R0-R2 + PC will be set and they will be the only registers set
|
||||
// Pre-v21, R0-R1 + PC will be the only registers set
|
||||
|
||||
if (image.Version >= 21 && regs.Count == 4 && regs.TryGetValue(0, out r0) && regs.TryGetValue(1, out r1) && regs.TryGetValue(2, out uint _))
|
||||
return (r0 & 0xffff_fffe, r1 & 0xffff_fffe);
|
||||
|
||||
if (image.Version < 21 && regs.Count == 3 && regs.TryGetValue(0, out r0) && regs.TryGetValue(1, out r1))
|
||||
return (r0 & 0xffff_fffe, r1 & 0xffff_fffe);
|
||||
|
||||
return (0, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
185
Il2CppInspector.Common/Architectures/Il2CppBinaryARM64.cs
Normal file
185
Il2CppInspector.Common/Architectures/Il2CppBinaryARM64.cs
Normal file
@@ -0,0 +1,185 @@
|
||||
/*
|
||||
Copyright 2019 Katy Coe - http://www.hearthcode.org - http://www.djkaty.com
|
||||
|
||||
All rights reserved.
|
||||
*/
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Il2CppInspector
|
||||
{
|
||||
// A64 ISA reference: https://static.docs.arm.com/ddi0596/a/DDI_0596_ARM_a64_instruction_set_architecture.pdf
|
||||
internal class Il2CppBinaryARM64 : Il2CppBinary
|
||||
{
|
||||
public Il2CppBinaryARM64(IFileFormatReader stream) : base(stream) { }
|
||||
|
||||
public Il2CppBinaryARM64(IFileFormatReader stream, uint codeRegistration, uint metadataRegistration) : base(stream, codeRegistration, metadataRegistration) { }
|
||||
|
||||
private (uint reg, ulong page)? getAdrp(uint inst, ulong pc) {
|
||||
if ((inst.Bits(24, 8) & 0b_1000_1111) != 1 << 7)
|
||||
return null;
|
||||
|
||||
var addendLo = inst.Bits(29, 2);
|
||||
var addendHi = inst.Bits(5, 19);
|
||||
var addend = (addendHi << 14) + (addendLo << 12);
|
||||
var page = pc & ~((1Lu << 12) - 1);
|
||||
var reg = inst.Bits(0, 5);
|
||||
|
||||
return (reg, page + addend);
|
||||
}
|
||||
|
||||
// https://static.docs.arm.com/100878/0100/fundamentals_of_armv8_a_100878_0100_en.pdf states:
|
||||
// Unlike ARMv7-A, there is no implied offset of 4 or 8 bytes
|
||||
private (uint reg, ulong addr)? getAdr(uint inst, ulong pc) {
|
||||
if (inst.Bits(24, 5) != 0b10000 || inst.Bits(31, 1) != 0)
|
||||
return null;
|
||||
|
||||
ulong imm = (inst.Bits(5, 19) << 2) + inst.Bits(29, 2);
|
||||
|
||||
// Sign extend the 21-bit number to 64 bits
|
||||
imm = (imm & (1 << 20)) == 0 ? imm : imm | unchecked((ulong) -(1 << 21));
|
||||
|
||||
var reg = inst.Bits(0, 5);
|
||||
|
||||
return (reg, pc + imm);
|
||||
}
|
||||
|
||||
private (uint reg_n, uint reg_d, uint imm)? getAdd64(uint inst) {
|
||||
if (inst.Bits(22, 10) != 0b_1001_0001_00)
|
||||
return null;
|
||||
|
||||
var imm = inst.Bits(10, 12);
|
||||
var reg_n = inst.Bits(5, 5);
|
||||
var reg_d = inst.Bits(0, 5);
|
||||
|
||||
return (reg_n, reg_d, imm);
|
||||
}
|
||||
|
||||
private (uint reg_t, uint reg_n, uint simm)? getLdr64ImmOffset(uint inst) {
|
||||
if (inst.Bits(22, 10) != 0b_11_1110_0101)
|
||||
return null;
|
||||
|
||||
var imm = inst.Bits(10, 12);
|
||||
var reg_t = inst.Bits(0, 5);
|
||||
var reg_n = inst.Bits(5, 5);
|
||||
|
||||
return (reg_t, reg_n, imm);
|
||||
}
|
||||
|
||||
private bool isB(uint inst) => inst.Bits(26, 6) == 0b_000101;
|
||||
|
||||
private Dictionary<uint, ulong> sweepForAddressLoads(List<uint> func, ulong baseAddress, IFileFormatReader image) {
|
||||
// List of registers and addresses loaded into them
|
||||
var regs = new Dictionary<uint, ulong>();
|
||||
|
||||
// Iterate each instruction
|
||||
var pc = baseAddress;
|
||||
foreach (var inst in func) {
|
||||
|
||||
// Is it an ADRP Xn, #page?
|
||||
if (getAdrp(inst, pc) is (uint reg, ulong page)) {
|
||||
// If we've had an earlier ADRP for the same register, we'll discard the previous load
|
||||
if (regs.ContainsKey(reg))
|
||||
regs[reg] = page;
|
||||
else
|
||||
regs.Add(reg, page);
|
||||
}
|
||||
|
||||
if (getAdr(inst, pc) is (uint reg_adr, ulong addr)) {
|
||||
if (regs.ContainsKey(reg_adr))
|
||||
regs[reg_adr] = addr;
|
||||
else
|
||||
regs.Add(reg_adr, addr);
|
||||
}
|
||||
|
||||
// Is it an ADD Xm, Xn, #offset?
|
||||
if (getAdd64(inst) is (uint reg_n, uint reg_d, uint imm)) {
|
||||
// We are only interested in registers that have already had an ADRP, and the ADD must be to itself
|
||||
if (reg_n == reg_d && regs.ContainsKey(reg_d))
|
||||
regs[reg_d] += imm;
|
||||
}
|
||||
|
||||
// Is it an LDR Xm, [Xn, #offset]?
|
||||
if (getLdr64ImmOffset(inst) is (uint reg_t, uint reg_ldr_n, uint simm)) {
|
||||
// We are only interested in registers that have already had an ADRP, and the LDR must be to itself
|
||||
if (reg_t == reg_ldr_n && regs.ContainsKey(reg_ldr_n)) {
|
||||
regs[reg_ldr_n] += simm * 8; // simm is a byte offset in a multiple of 8
|
||||
|
||||
// Now we have a pointer address, dereference it
|
||||
regs[reg_ldr_n] = image.ReadUInt64(image.MapVATR(regs[reg_ldr_n]));
|
||||
}
|
||||
}
|
||||
|
||||
// Advance program counter which we need to calculate ADRP pages correctly
|
||||
pc += 4;
|
||||
}
|
||||
return regs;
|
||||
}
|
||||
|
||||
private List<uint> getFunctionAtFileOffset(IFileFormatReader image, uint loc, uint maxLength) {
|
||||
// Read a function that ends in a hard branch (B) or exceeds maxLength instructions
|
||||
var func = new List<uint>();
|
||||
uint inst;
|
||||
|
||||
image.Position = loc;
|
||||
|
||||
do {
|
||||
inst = image.ReadUInt32();
|
||||
func.Add(inst);
|
||||
} while (!isB(inst) && func.Count < maxLength);
|
||||
|
||||
return func;
|
||||
}
|
||||
|
||||
// The method for ARM64:
|
||||
// - We want to extract values for CodeRegistration and MetadataRegistration from Il2CppCodegenRegistration(void)
|
||||
// - One of the functions supplied will be either Il2CppCodeGenRegistration or an initializer for Il2CppCodeGenRegistration.cpp
|
||||
// - The initializer (if present) loads a pointer to Il2CppCodegenRegistration in X1, if the function isn't in the function table
|
||||
// - Il2CppCodegenRegistration loads CodeRegistration into X0, MetadataRegistration into X1 and Il2CppCodeGenOptions into X2
|
||||
// - Loads can be done either with ADRP+ADD (loads the address of the wanted struct) or ADRP+LDR (loads a pointer to the address which must be de-referenced)
|
||||
// - Loads do not need to be pairs of sequential instructions
|
||||
// - We need to sweep the whole function from the ADRP to the next B to find an ADD or LDR with a corresponding register
|
||||
protected override (ulong, ulong) ConsiderCode(IFileFormatReader image, uint loc) {
|
||||
// Load function into memory
|
||||
// In practice, the longest function length we need is not generally longer than 7 instructions (0x1C bytes)
|
||||
var func = getFunctionAtFileOffset(image, loc, 7);
|
||||
|
||||
// Don't accept functions longer than 7 instructions (in this case, the last instruction won't be a B)
|
||||
if (!isB(func[^1]))
|
||||
return (0, 0);
|
||||
|
||||
// Get a list of registers and values in them at the end of the function
|
||||
var regs = sweepForAddressLoads(func, image.GlobalOffset + loc, image);
|
||||
|
||||
// Is it the Il2CppCodeRegistration.cpp initializer?
|
||||
// X0-X1 will be set and they will be the only registers set
|
||||
if (regs.Count == 2 && regs.TryGetValue(0, out _) && regs.TryGetValue(1, out ulong x1)) {
|
||||
// Load up the function whose address is in X1
|
||||
func = getFunctionAtFileOffset(image, (uint) image.MapVATR(x1), 7);
|
||||
|
||||
if (!isB(func[^1]))
|
||||
return (0, 0);
|
||||
|
||||
regs = sweepForAddressLoads(func, x1, image);
|
||||
}
|
||||
|
||||
// Is it Il2CppCodegenRegistration(void)?
|
||||
// In v21 and later, X0-X2 will be set and they will be the only registers set
|
||||
// Pre-v21, X0-X1 will be the only registers set
|
||||
if (image.Version >= 21 && regs.Count == 3 && regs.TryGetValue(0, out ulong x0) && regs.TryGetValue(1, out x1) && regs.TryGetValue(2, out ulong _))
|
||||
return (x0, x1);
|
||||
|
||||
if (image.Version < 21 && regs.Count == 2 && regs.TryGetValue(0, out x0) && regs.TryGetValue(1, out x1))
|
||||
return (x0, x1);
|
||||
|
||||
return (0, 0);
|
||||
}
|
||||
}
|
||||
|
||||
internal static class UIntExtensions
|
||||
{
|
||||
// Return count bits starting at bit low of integer x
|
||||
public static uint Bits(this uint x, int low, int count) => (x >> low) & (uint) ((1 << count) - 1);
|
||||
}
|
||||
}
|
||||
163
Il2CppInspector.Common/Architectures/Il2CppBinaryX64.cs
Normal file
163
Il2CppInspector.Common/Architectures/Il2CppBinaryX64.cs
Normal file
@@ -0,0 +1,163 @@
|
||||
/*
|
||||
Copyright 2019 Katy Coe - http://www.hearthcode.org - http://www.djkaty.com
|
||||
|
||||
All rights reserved.
|
||||
*/
|
||||
|
||||
using System;
|
||||
using System.Linq;
|
||||
|
||||
namespace Il2CppInspector
|
||||
{
|
||||
internal class Il2CppBinaryX64 : Il2CppBinary
|
||||
{
|
||||
public Il2CppBinaryX64(IFileFormatReader stream) : base(stream) { }
|
||||
public Il2CppBinaryX64(IFileFormatReader stream, uint codeRegistration, uint metadataRegistration) : base(stream, codeRegistration, metadataRegistration) { }
|
||||
|
||||
// Format of 64-bit LEA:
|
||||
// 0x48 - REX prefix signifying 64-bit mode with 64-bit operand size (REX prefix bits: Volume 2A, page 2-9)
|
||||
// 8x8D - LEA opcode (8D /r, LEA r64, m)
|
||||
// 0xX5 - bottom 3 bits = 101 to indicate subsequent operand is a 32-bit displacement; middle 3 bits = register number; top 2 bits = 00
|
||||
// Bytes 03-06 - 32-bit displacement
|
||||
// Register numbers: 00 = RAX, 01 = RCX, 10 = RDX, 11 = RBX
|
||||
// See: https://software.intel.com/sites/default/files/managed/39/c5/325462-sdm-vol-1-2abcd-3abcd.pdf
|
||||
// Chapter 2.1, 2.1.3, 2.1.5 table 2-2, page 3-537
|
||||
// NOTE: There is a chance of false positives because of x86's variable instruction length architecture
|
||||
private (int foundOffset, int reg, uint operand)? findLea(byte[] buff, int offset, int searchDistance) {
|
||||
|
||||
// Find first LEA but don't search too far
|
||||
var opcode = new byte[] { 0x48, 0x8D };
|
||||
int i, index;
|
||||
|
||||
for (i = offset, index = 0; i < offset + searchDistance && i < buff.Length && index < opcode.Length; i++)
|
||||
if (buff[i] != opcode[index++]) {
|
||||
index = 0;
|
||||
|
||||
// Maybe we're starting a new match
|
||||
if (buff[i] != opcode[index++])
|
||||
index = 0;
|
||||
}
|
||||
|
||||
if (index < opcode.Length)
|
||||
return null;
|
||||
|
||||
var lea = getLea(buff, (int) i - 2);
|
||||
|
||||
return (i - 2, lea.Value.reg, lea.Value.operand);
|
||||
}
|
||||
|
||||
private (int reg, uint operand)? getLea(byte[] buff, int offset) {
|
||||
if (buff[offset] != 0x48 || buff[offset + 1] != 0x8D)
|
||||
return null;
|
||||
|
||||
// Found LEA RnX, [RIP + disp32]
|
||||
var reg = (buff[offset + 2] >> 3) & 7;
|
||||
var operand = BitConverter.ToUInt32(buff, offset + 3);
|
||||
|
||||
return (reg, operand);
|
||||
}
|
||||
|
||||
// 0x40 to set 64-bit mode with 32-bit register size, 0x50+rd to push specified register number
|
||||
// Volume 2B, page 4-511
|
||||
private bool isPushR32(byte[] buff, int offset) => buff[offset] == 0x40 && buff[offset + 1] >= 0x50 && buff[offset + 1] < 0x58;
|
||||
|
||||
// 0b0100_0X0Y to set 64-bit mode, 0x33 for XOR, 0b11_XXX_YYY for register numbers
|
||||
// Volume 2C, page 5-278
|
||||
private (int reg_op1, int reg_op2)? getXorR64R64(byte[] buff, int offset) {
|
||||
if ((buff[offset] & 0b1111_1010) != 0b_0100_0000 || buff[offset + 1] != 0x33 || (buff[offset + 2] & 0b1100_0000) != 0b1100_0000)
|
||||
return null;
|
||||
return (((buff[offset] & 0b0000_0100) << 1) + ((buff[offset + 2] & 0b0011_1000) >> 3),
|
||||
((buff[offset] & 0b0000_0001) << 3) + (buff[offset + 2] & 0b0000_0111));
|
||||
}
|
||||
|
||||
protected override (ulong, ulong) ConsiderCode(IFileFormatReader image, uint loc) {
|
||||
|
||||
// We have seen two versions of the initializer:
|
||||
// 1. Regular version
|
||||
// 2. Inlined version with il2cpp::utils::RegisterRuntimeInitializeAndCleanup(CallbackFunction, CallbackFunction, order)
|
||||
|
||||
// Version 1 passes "this" in rcx and the arguments in rdx (our wanted pointer), r8d (always zero) and r9d (always zero)
|
||||
// Version 2 has a standard prologue and loads the wanted pointer into rax (lea rax)
|
||||
|
||||
(int reg, uint operand)? lea;
|
||||
ulong pCgr = 0;
|
||||
|
||||
image.Position = loc;
|
||||
var buff = image.ReadBytes(0x20); // arbitrary number of bytes, but enough to process the function
|
||||
|
||||
// Check for regular version
|
||||
var xor = getXorR64R64(buff, 0); // 3 bytes
|
||||
if (xor != null && xor.Value.reg_op1 == xor.Value.reg_op2) {
|
||||
lea = getLea(buff, 3); // 7 bytes
|
||||
|
||||
if (lea != null) {
|
||||
xor = getXorR64R64(buff, 10);
|
||||
if (xor != null && xor.Value.reg_op1 == xor.Value.reg_op2) {
|
||||
// We found Il2CppCodegenRegistration(void)
|
||||
pCgr = image.GlobalOffset + loc + 10 + lea.Value.operand;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Check for inlined version
|
||||
if (pCgr == 0) {
|
||||
// Check for prologue
|
||||
if (isPushR32(buff, 0)) {
|
||||
// Linear sweep for LEA
|
||||
var leaInlined = findLea(buff, 2, 0x1E); // 0x20 - 2
|
||||
if (leaInlined == null)
|
||||
return (0, 0);
|
||||
// LEA is 7 bytes long
|
||||
pCgr = image.GlobalOffset + loc + (uint) leaInlined.Value.foundOffset + 7 + leaInlined.Value.operand;
|
||||
}
|
||||
}
|
||||
|
||||
if (pCgr == 0)
|
||||
return (0, 0);
|
||||
|
||||
// Assume we've found the pointer to Il2CppCodegenRegistration(void) and jump there
|
||||
try {
|
||||
Image.Position = Image.MapVATR(pCgr);
|
||||
}
|
||||
|
||||
// Couldn't map virtual address to data in file, so it's not this function
|
||||
catch (InvalidOperationException) {
|
||||
return (0, 0);
|
||||
}
|
||||
|
||||
// Find the first 2 LEAs which we'll hope contain pointers to CodeRegistration and MetadataRegistration
|
||||
|
||||
// There are two options here:
|
||||
// 1. il2cpp::vm::MetadataCache::Register is called directly with arguments in rcx, rdx and r8 (lea, lea, lea, jmp)
|
||||
// 2. The two functions being inlined. The arguments are loaded sequentially into rax after the prologue
|
||||
|
||||
// By ignoring the REX R flag (bit 2 of the instruction prefix) which specifies an extension bit to the register operand,
|
||||
// we skip over "lea r8". This will leave us with two LEAs containing our desired pointers.
|
||||
|
||||
buff = image.ReadBytes(0x40);
|
||||
|
||||
// LEA is 7 bytes long
|
||||
var lea1 = findLea(buff, 0, 0x40 - 7);
|
||||
if (lea1 == null)
|
||||
return (0, 0);
|
||||
|
||||
var lea2 = findLea(buff, lea1.Value.foundOffset + 7, 0x40 - lea1.Value.foundOffset - 7);
|
||||
if (lea2 == null)
|
||||
return (0, 0);
|
||||
|
||||
// Use the original pointer found, not the file location + GlobalOffset because the data may be in a different section
|
||||
var ptr1 = pCgr + (uint) lea1.Value.foundOffset + 7 + lea1.Value.operand;
|
||||
var ptr2 = pCgr + (uint) lea2.Value.foundOffset + 7 + lea2.Value.operand;
|
||||
|
||||
// RCX and RDX argument passing?
|
||||
if (lea1.Value.reg == 2 /* RDX */ && lea2.Value.reg == 1 /* RCX */)
|
||||
return (ptr2, ptr1);
|
||||
|
||||
// RAX sequential loading?
|
||||
if (lea1.Value.reg == 0 /* RAX */ && lea2.Value.reg == 0 /* RAX */)
|
||||
return (ptr1, ptr2);
|
||||
|
||||
return (0, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
103
Il2CppInspector.Common/Architectures/Il2CppBinaryX86.cs
Normal file
103
Il2CppInspector.Common/Architectures/Il2CppBinaryX86.cs
Normal file
@@ -0,0 +1,103 @@
|
||||
/*
|
||||
Copyright 2017-2019 Katy Coe - http://www.hearthcode.org - http://www.djkaty.com
|
||||
|
||||
All rights reserved.
|
||||
*/
|
||||
|
||||
using System;
|
||||
using System.Linq;
|
||||
|
||||
namespace Il2CppInspector
|
||||
{
|
||||
internal class Il2CppBinaryX86 : Il2CppBinary
|
||||
{
|
||||
public Il2CppBinaryX86(IFileFormatReader stream) : base(stream) { }
|
||||
public Il2CppBinaryX86(IFileFormatReader stream, uint codeRegistration, uint metadataRegistration) : base(stream, codeRegistration, metadataRegistration) { }
|
||||
|
||||
protected override (ulong, ulong) ConsiderCode(IFileFormatReader image, uint loc) {
|
||||
ulong metadata, code;
|
||||
long pCgr;
|
||||
|
||||
// x86
|
||||
// Assembly bytes to search for at start of each function
|
||||
var bytes = new byte[] {0x6A, 0x00, 0x6A, 0x00, 0x68};
|
||||
image.Position = loc;
|
||||
var buff = image.ReadBytes(5);
|
||||
if (bytes.SequenceEqual(buff)) {
|
||||
// Next 4 bytes are the function pointer being pushed onto the stack
|
||||
pCgr = image.ReadUInt32();
|
||||
|
||||
// Start of next instruction
|
||||
if (image.ReadByte() != 0xB9)
|
||||
return (0, 0);
|
||||
|
||||
// Jump to Il2CppCodegenRegistration
|
||||
image.Position = image.MapVATR((ulong) pCgr + 6);
|
||||
metadata = image.ReadUInt32();
|
||||
image.Position = image.MapVATR((ulong) pCgr + 11);
|
||||
code = image.ReadUInt32();
|
||||
return (code, metadata);
|
||||
}
|
||||
|
||||
// x86 based on ELF PLT
|
||||
if (image is IElfReader elf) {
|
||||
var plt = elf.GetPLTAddress();
|
||||
|
||||
// push ebp; mov ebp, esp; push ebx; and esp, 0FFFFFFF0h; sub esp, 20h; call $+5; pop ebx
|
||||
bytes = new byte[]
|
||||
{0x55, 0x89, 0xE5, 0x53, 0x83, 0xE4, 0xF0, 0x83, 0xEC, 0x20, 0xE8, 0x00, 0x00, 0x00, 0x00, 0x5B};
|
||||
image.Position = loc;
|
||||
buff = image.ReadBytes(16);
|
||||
if (!bytes.SequenceEqual(buff))
|
||||
return (0, 0);
|
||||
|
||||
// lea eax, (pCgr - offset)[ebx] (Position + 6 is the opcode lea eax; Position + 8 is the operand)
|
||||
image.Position += 6;
|
||||
|
||||
// Ensure it's lea eax, #address
|
||||
if (image.ReadUInt16() != 0x838D)
|
||||
return (0, 0);
|
||||
|
||||
try {
|
||||
pCgr = image.MapVATR(image.ReadUInt32() + plt);
|
||||
}
|
||||
// Could not find a mapping in the section table
|
||||
catch (InvalidOperationException) {
|
||||
return (0, 0);
|
||||
}
|
||||
|
||||
// Extract Metadata pointer
|
||||
// An 0x838D opcode indicates LEA (no indirection)
|
||||
image.Position = pCgr + 0x20;
|
||||
var opcode = image.ReadUInt16();
|
||||
metadata = image.ReadUInt32() + plt;
|
||||
|
||||
// An 8x838B opcode indicates MOV (pointer indirection)
|
||||
if (opcode == 0x838B) {
|
||||
image.Position = image.MapVATR(metadata);
|
||||
metadata = image.ReadUInt32();
|
||||
}
|
||||
|
||||
if (opcode != 0x838B && opcode != 0x838D)
|
||||
return (0, 0);
|
||||
|
||||
// Repeat the same logic for extracting the Code pointer
|
||||
image.Position = pCgr + 0x2A;
|
||||
opcode = image.ReadUInt16();
|
||||
code = image.ReadUInt32() + plt;
|
||||
|
||||
if (opcode == 0x838B) {
|
||||
image.Position = image.MapVATR(code);
|
||||
code = image.ReadUInt32();
|
||||
}
|
||||
|
||||
if (opcode != 0x838B && opcode != 0x838D)
|
||||
return (0, 0);
|
||||
|
||||
return (code, metadata);
|
||||
}
|
||||
|
||||
return (0, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user