Add Fat Mach-O (Universal Binary) support

Fix fieldOffsets bug in some metadata versions
Add support for generic multi-architecture binaries
Add Mach-O section RVA mapping
This commit is contained in:
Katy Coe
2017-10-23 12:35:59 +02:00
parent fef4d3d8f3
commit 16b56e271b
9 changed files with 218 additions and 82 deletions

View File

@@ -43,7 +43,7 @@ namespace Il2CppInspector
var fieldEnd = typeDef.fieldStart + typeDef.field_count;
for (int i = typeDef.fieldStart; i < fieldEnd; ++i) {
var pField = metadata.Fields[i];
var pType = il2cpp.Code.GetTypeFromTypeIndex(pField.typeIndex);
var pType = il2cpp.GetTypeFromTypeIndex(pField.typeIndex);
var pDefault = metadata.GetFieldDefaultFromIndex(i);
writer.Write("\t");
if ((pType.attrs & DefineConstants.FIELD_ATTRIBUTE_PRIVATE) ==
@@ -59,7 +59,7 @@ namespace Il2CppInspector
writer.Write($"{il2cpp.GetTypeName(pType)} {metadata.GetString(pField.nameIndex)}");
if (pDefault != null && pDefault.dataIndex != -1) {
var pointer = metadata.GetDefaultValueFromIndex(pDefault.dataIndex);
Il2CppType pTypeToUse = il2cpp.Code.GetTypeFromTypeIndex(pDefault.typeIndex);
Il2CppType pTypeToUse = il2cpp.GetTypeFromTypeIndex(pDefault.typeIndex);
if (pointer > 0) {
metadata.Position = pointer;
object multi = null;
@@ -110,14 +110,14 @@ namespace Il2CppInspector
}
}
writer.Write("; // 0x{0:x}\n",
il2cpp.Code.GetFieldOffsetFromIndex(idx, i - typeDef.fieldStart));
il2cpp.GetFieldOffsetFromIndex(idx, i - typeDef.fieldStart));
}
writer.Write("\t// Methods\n");
var methodEnd = typeDef.methodStart + typeDef.method_count;
for (int i = typeDef.methodStart; i < methodEnd; ++i) {
var methodDef = metadata.Methods[i];
writer.Write("\t");
Il2CppType pReturnType = il2cpp.Code.GetTypeFromTypeIndex(methodDef.returnType);
Il2CppType pReturnType = il2cpp.GetTypeFromTypeIndex(methodDef.returnType);
if ((methodDef.flags & DefineConstants.METHOD_ATTRIBUTE_MEMBER_ACCESS_MASK) ==
DefineConstants.METHOD_ATTRIBUTE_PRIVATE)
writer.Write("private ");
@@ -133,7 +133,7 @@ namespace Il2CppInspector
for (int j = 0; j < methodDef.parameterCount; ++j) {
Il2CppParameterDefinition pParam = metadata.parameterDefs[methodDef.parameterStart + j];
string szParamName = metadata.GetString(pParam.nameIndex);
Il2CppType pType = il2cpp.Code.GetTypeFromTypeIndex(pParam.typeIndex);
Il2CppType pType = il2cpp.GetTypeFromTypeIndex(pParam.typeIndex);
string szTypeName = il2cpp.GetTypeName(pType);
if ((pType.attrs & DefineConstants.PARAM_ATTRIBUTE_OPTIONAL) != 0)
writer.Write("optional ");

View File

@@ -40,13 +40,14 @@ namespace Il2CppInspector
}
// Analyze data
var il2cpp = Il2CppProcessor.LoadFromFile(imageFile, metaFile);
if (il2cpp == null)
var il2cppProcessors = Il2CppProcessor.LoadFromFile(imageFile, metaFile);
if (il2cppProcessors == null)
Environment.Exit(1);
// Write output file
var dumper = new Il2CppDumper(il2cpp);
dumper.WriteFile(outFile);
int i = 0;
foreach (var il2cpp in il2cppProcessors)
new Il2CppDumper(il2cpp).WriteFile(outFile + "-" + (i++));
}
}
}

View File

@@ -5,7 +5,9 @@
*/
using System;
using System.Collections.Generic;
using System.IO;
using System.Runtime.CompilerServices;
using NoisyCowStudios.Bin2Object;
namespace Il2CppInspector
@@ -13,6 +15,9 @@ namespace Il2CppInspector
public interface IFileFormatReader
{
BinaryObjectReader Stream { get; }
uint NumImages { get; }
IEnumerable<IFileFormatReader> Images { get; }
IFileFormatReader this[uint index] { get; }
long Position { get; set; }
string Arch { get; }
uint GlobalOffset { get; }
@@ -35,17 +40,26 @@ namespace Il2CppInspector
public BinaryObjectReader Stream => this;
public uint NumImages { get; protected set; } = 1;
public uint GlobalOffset { get; protected set; }
public virtual string Arch => throw new NotImplementedException();
public static T Load(string filename, uint offset = 0) {
using (var stream = new FileStream(filename, FileMode.Open))
return Load(stream, offset);
public IEnumerable<IFileFormatReader> Images {
get {
for (uint i = 0; i < NumImages; i++)
yield return this[i];
}
}
public static T Load(Stream stream, uint offset = 0) {
stream.Position = offset;
public static T Load(string filename) {
using (var stream = new FileStream(filename, FileMode.Open))
return Load(stream);
}
public static T Load(Stream stream) {
stream.Position = 0;
var pe = (T) Activator.CreateInstance(typeof(T), stream);
return pe.Init() ? pe : null;
}
@@ -53,11 +67,21 @@ namespace Il2CppInspector
// Confirm file is valid and set up RVA mappings
protected virtual bool Init() => throw new NotImplementedException();
// Choose a sub-binary within the image for multi-architecture binaries
public virtual IFileFormatReader this[uint index] {
get {
if (index == 0)
return this;
throw new IndexOutOfRangeException("Binary image index out of bounds");
}
}
// Find search locations in the machine code for Il2Cpp data
public virtual uint[] GetSearchLocations() => throw new NotImplementedException();
// Map an RVA to an offset into the file image
public virtual uint MapVATR(uint uiAddr) => throw new NotImplementedException();
// No mapping by default
public virtual uint MapVATR(uint uiAddr) => uiAddr;
// Retrieve object(s) from specified RVA(s)
public U ReadMappedObject<U>(uint uiAddr) where U : new() {

View File

@@ -20,7 +20,7 @@ namespace Il2CppInspector
Metadata = metadata;
}
public static Il2CppProcessor LoadFromFile(string codeFile, string metadataFile) {
public static List<Il2CppProcessor> LoadFromFile(string codeFile, string metadataFile) {
// Load the metadata file
Metadata metadata;
try {
@@ -34,36 +34,41 @@ namespace Il2CppInspector
// Load the il2cpp code file (try ELF and PE)
var memoryStream = new MemoryStream(File.ReadAllBytes(codeFile));
IFileFormatReader stream =
((IFileFormatReader) ElfReader.Load(memoryStream) ??
PEReader.Load(memoryStream)) ??
MachOReader.Load(memoryStream);
(((IFileFormatReader) ElfReader.Load(memoryStream) ??
PEReader.Load(memoryStream)) ??
MachOReader.Load(memoryStream)) ??
UBReader.Load(memoryStream);
if (stream == null) {
Console.Error.WriteLine("Unsupported executable file format");
return null;
}
Il2CppReader il2cpp;
var processors = new List<Il2CppProcessor>();
foreach (var image in stream.Images) {
Il2CppReader il2cpp;
// We are currently supporting x86 and ARM architectures
switch (stream.Arch) {
case "x86":
il2cpp = new Il2CppReaderX86(stream);
break;
case "ARM":
il2cpp = new Il2CppReaderARM(stream);
break;
default:
Console.Error.WriteLine("Unsupported architecture");
return null;
// We are currently supporting x86 and ARM architectures
switch (image.Arch) {
case "x86":
il2cpp = new Il2CppReaderX86(image);
break;
case "ARM":
il2cpp = new Il2CppReaderARM(image);
break;
default:
Console.Error.WriteLine("Unsupported architecture");
return null;
}
// Find code and metadata regions
if (!il2cpp.Load(metadata.Version)) {
Console.Error.WriteLine("Could not process IL2CPP image");
}
else {
processors.Add(new Il2CppProcessor(il2cpp, metadata));
}
}
// Find code and metadata regions
if (!il2cpp.Load(metadata.Version)) {
Console.Error.WriteLine("Could not process IL2CPP image");
return null;
}
return new Il2CppProcessor(il2cpp, metadata);
return processors;
}
public string GetTypeName(Il2CppType pType) {
@@ -106,6 +111,31 @@ namespace Il2CppInspector
return ret;
}
public Il2CppType GetTypeFromTypeIndex(int idx) {
return Code.PtrMetadataRegistration.types[idx];
}
public int GetFieldOffsetFromIndex(int typeIndex, int fieldIndexInType) {
// Versions from 22 onwards use an array of pointers in fieldOffsets
bool fieldOffsetsArePointers = (Metadata.Version >= 22);
// Some variants of 21 also use an array of pointers
if (Metadata.Version == 21) {
var f = Code.PtrMetadataRegistration.fieldOffsets;
fieldOffsetsArePointers = (f[0] == 0 && f[1] == 0 && f[2] == 0 && f[3] == 0 && f[4] == 0 && f[5] > 0);
}
// All older versions use values directly in the array
if (!fieldOffsetsArePointers) {
var typeDef = Metadata.Types[typeIndex];
return Code.PtrMetadataRegistration.fieldOffsets[typeDef.fieldStart + fieldIndexInType];
}
var ptr = Code.PtrMetadataRegistration.fieldOffsets[typeIndex];
Code.Image.Stream.Position = Code.Image.MapVATR((uint)ptr) + 4 * fieldIndexInType;
return Code.Image.Stream.ReadInt32();
}
private readonly string[] szTypeString =
{
"END",

View File

@@ -17,7 +17,7 @@ namespace Il2CppInspector
protected Il2CppReader(IFileFormatReader stream, uint codeRegistration, uint metadataRegistration) {
Image = stream;
Configure(codeRegistration, metadataRegistration);
Configure(Image, codeRegistration, metadataRegistration);
}
public Il2CppCodeRegistration PtrCodeRegistration { get; protected set; }
@@ -27,44 +27,35 @@ namespace Il2CppInspector
protected abstract (uint, uint) Search(uint loc, uint globalOffset);
// Check all search locations
public bool Load(int version) {
public bool Load(int version, uint imageIndex = 0) {
var subImage = Image[imageIndex];
Image.Stream.Version = version;
var addrs = Image.GetSearchLocations();
var addrs = subImage.GetSearchLocations();
foreach (var loc in addrs)
if (loc != 0) {
var (code, metadata) = Search(loc, Image.GlobalOffset);
if (code != 0) {
Configure(code, metadata);
Image.FinalizeInit(this);
Configure(subImage, code, metadata);
subImage.FinalizeInit(this);
return true;
}
}
return false;
}
private void Configure(uint codeRegistration, uint metadataRegistration) {
PtrCodeRegistration = Image.ReadMappedObject<Il2CppCodeRegistration>(codeRegistration);
PtrMetadataRegistration = Image.ReadMappedObject<Il2CppMetadataRegistration>(metadataRegistration);
PtrCodeRegistration.methodPointers = Image.ReadMappedArray<uint>(PtrCodeRegistration.pmethodPointers,
private void Configure(IFileFormatReader image, uint codeRegistration, uint metadataRegistration) {
PtrCodeRegistration = image.ReadMappedObject<Il2CppCodeRegistration>(codeRegistration);
PtrMetadataRegistration = image.ReadMappedObject<Il2CppMetadataRegistration>(metadataRegistration);
PtrCodeRegistration.methodPointers = image.ReadMappedArray<uint>(PtrCodeRegistration.pmethodPointers,
(int) PtrCodeRegistration.methodPointersCount);
PtrMetadataRegistration.fieldOffsets = Image.ReadMappedArray<int>(PtrMetadataRegistration.pfieldOffsets,
PtrMetadataRegistration.fieldOffsets = image.ReadMappedArray<int>(PtrMetadataRegistration.pfieldOffsets,
PtrMetadataRegistration.fieldOffsetsCount);
var types = Image.ReadMappedArray<uint>(PtrMetadataRegistration.ptypes, PtrMetadataRegistration.typesCount);
var types = image.ReadMappedArray<uint>(PtrMetadataRegistration.ptypes, PtrMetadataRegistration.typesCount);
PtrMetadataRegistration.types = new Il2CppType[PtrMetadataRegistration.typesCount];
for (int i = 0; i < PtrMetadataRegistration.typesCount; ++i) {
PtrMetadataRegistration.types[i] = Image.ReadMappedObject<Il2CppType>(types[i]);
PtrMetadataRegistration.types[i] = image.ReadMappedObject<Il2CppType>(types[i]);
PtrMetadataRegistration.types[i].Init();
}
}
public Il2CppType GetTypeFromTypeIndex(int idx) {
return PtrMetadataRegistration.types[idx];
}
public int GetFieldOffsetFromIndex(int typeIndex, int fieldIndexInType) {
var ptr = PtrMetadataRegistration.fieldOffsets[typeIndex];
Image.Stream.Position = Image.MapVATR((uint) ptr) + 4 * fieldIndexInType;
return Image.Stream.ReadInt32();
}
}
}

View File

@@ -19,7 +19,7 @@ namespace Il2CppInspector
// Assembly bytes to search for at start of each function
uint metadataRegistration, codeRegistration;
// ARM
// ARMv7
var bytes = new byte[] { 0x1c, 0x0, 0x9f, 0xe5, 0x1c, 0x10, 0x9f, 0xe5, 0x1c, 0x20, 0x9f, 0xe5 };
Image.Position = loc;
var buff = Image.ReadBytes(12);
@@ -35,7 +35,7 @@ namespace Il2CppInspector
return (codeRegistration, metadataRegistration);
}
// ARM metadata v23
// ARMv7 metadata v23
Image.Position = loc;
// Check for ADD Rx, PC in relevant parts of function
@@ -51,7 +51,7 @@ namespace Il2CppInspector
// Follow path to code pointer
var pCode = decodeMovImm32(func.Skip(8).Take(4).Concat(func.Skip(14).Take(4)).ToArray());
codeRegistration = pCode + loc + 0x1A - globalOffset;
codeRegistration = pCode + loc + 0x1A + globalOffset;
return (codeRegistration, metadataRegistration);
}

View File

@@ -17,7 +17,9 @@ namespace Il2CppInspector
private MachOHeader header;
private uint pFuncTable;
private uint sFuncTable;
private uint fatIndex;
private bool is64;
private List<MachOSection> sections = new List<MachOSection>();
private List<MachOSection64> sections64 = new List<MachOSection64>();
public MachOReader(Stream stream) : base(stream) { }
@@ -37,8 +39,6 @@ namespace Il2CppInspector
}
protected override bool Init() {
fatIndex = (uint)Position;
// Detect endianness - default is little-endianness
MachO magic = (MachO)ReadUInt32();
if (magic == MachO.MH_CIGAM || magic == MachO.MH_CIGAM_64) {
@@ -50,11 +50,11 @@ namespace Il2CppInspector
Console.WriteLine("Endianness: {0}", Endianness);
Position = fatIndex;
Position -= sizeof(uint);
header = ReadObject<MachOHeader>();
// 64-bit files have an extra 4 bytes after the header
bool is64 = false;
is64 = false;
if (magic == MachO.MH_MAGIC_64) {
is64 = true;
ReadUInt32();
@@ -75,21 +75,23 @@ namespace Il2CppInspector
if ((MachO)loadCommand.Command == MachO.LC_SEGMENT) {
var segment = ReadObject<MachOSegmentCommand>();
if (segment.Name == "__TEXT") {
if (segment.Name == "__TEXT" || segment.Name == "__DATA") {
for (int s = 0; s < segment.NumSections; s++) {
var section = ReadObject<MachOSection>();
sections.Add(section);
if (section.Name == "__text")
GlobalOffset = section.ImageOffset - section.Address + fatIndex;
GlobalOffset = section.Address - section.ImageOffset;
}
}
}
else if ((MachO)loadCommand.Command == MachO.LC_SEGMENT_64) {
var segment = ReadObject<MachOSegmentCommand64>();
if (segment.Name == "__TEXT") {
if (segment.Name == "__TEXT" || segment.Name == "__DATA") {
for (int s = 0; s < segment.NumSections; s++) {
var section = ReadObject<MachOSection64>();
if (section.Name == "__text")
GlobalOffset = section.ImageOffset - (uint)section.Address + fatIndex;
var section64 = ReadObject<MachOSection64>();
sections64.Add(section64);
if (section64.Name == "__text")
GlobalOffset = (uint)section64.Address - section64.ImageOffset;
}
}
}
@@ -106,7 +108,7 @@ namespace Il2CppInspector
if (functionStarts == null)
return false;
pFuncTable = functionStarts.Offset + fatIndex;
pFuncTable = functionStarts.Offset;
sFuncTable = functionStarts.Size;
return true;
}
@@ -131,20 +133,25 @@ namespace Il2CppInspector
if (previous == 0)
result &= 0xffffffc;
previous += result;
functionPointers.Add(previous + fatIndex);
functionPointers.Add(previous);
}
}
return functionPointers.ToArray();
}
public override uint MapVATR(uint uiAddr) {
return uiAddr + GlobalOffset;
}
public override void FinalizeInit(Il2CppReader il2cpp) {
// Mach-O function pointers have an annoying habit of being 1-off
il2cpp.PtrCodeRegistration.methodPointers =
il2cpp.PtrCodeRegistration.methodPointers.Select(x => x - 1).ToArray();
}
public override uint MapVATR(uint uiAddr) {
if (!is64) {
var section = sections.First(x => uiAddr >= x.Address && uiAddr <= (x.Address + x.Size));
return uiAddr - (section.Address - section.ImageOffset);
}
var section64 = sections64.First(x => uiAddr >= x.Address && uiAddr <= (x.Address + x.Size));
return uiAddr - ((uint)section64.Address - section64.ImageOffset);
}
}
}

View File

@@ -0,0 +1,36 @@
/*
Copyright 2017 Katy Coe - http://www.hearthcode.org - http://www.djkaty.com
All rights reserved.
*/
using System;
using NoisyCowStudios.Bin2Object;
namespace Il2CppInspector
{
#pragma warning disable CS0649
// Structures and enums: https://cocoaintheshell.whine.fr/2009/07/universal-binary-mach-o-format/
public enum UB : uint
{
FAT_MAGIC = 0xcafebabe
}
// Big-endian
internal class FatHeader
{
public uint Magic;
public uint NumArch;
}
// Big-endian
internal class FatArch
{
public uint CPUType;
public uint CPUSubType;
public uint Offset;
public uint Size;
public uint Align;
}
}

View File

@@ -0,0 +1,47 @@
/*
Copyright 2017 Katy Coe - http://www.hearthcode.org - http://www.djkaty.com
All rights reserved.
*/
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using NoisyCowStudios.Bin2Object;
namespace Il2CppInspector
{
internal class UBReader : FileFormatReader<UBReader>
{
private FatHeader header;
public UBReader(Stream stream) : base(stream) { }
protected override bool Init() {
// Fat headers are always big-endian regardless of architectures
Endianness = Endianness.Big;
header = ReadObject<FatHeader>();
if ((UB) header.Magic != UB.FAT_MAGIC)
return false;
NumImages = header.NumArch;
return true;
}
public override IFileFormatReader this[uint index] {
get {
Position = 0x8 + 0x14 * index; // sizeof(FatHeader), sizeof(FatArch)
Endianness = Endianness.Big;
var arch = ReadObject<FatArch>();
Position = arch.Offset;
Endianness = Endianness.Little;
return MachOReader.Load(new MemoryStream(ReadBytes((int)arch.Size)));
}
}
}
}