From 58968c237a267726536fde70f19389963aeac40d Mon Sep 17 00:00:00 2001 From: Katy Coe Date: Sun, 22 Oct 2017 02:24:10 +0200 Subject: [PATCH] Add support for Mach-O files --- Il2CppInspector/FileFormatReader.cs | 12 ++- Il2CppInspector/Il2CppProcessor.cs | 5 +- Il2CppInspector/Il2CppReader.cs | 1 + Il2CppInspector/MachOHeaders.cs | 123 +++++++++++++++++++++++ Il2CppInspector/MachOReader.cs | 150 ++++++++++++++++++++++++++++ 5 files changed, 286 insertions(+), 5 deletions(-) create mode 100644 Il2CppInspector/MachOHeaders.cs create mode 100644 Il2CppInspector/MachOReader.cs diff --git a/Il2CppInspector/FileFormatReader.cs b/Il2CppInspector/FileFormatReader.cs index 02a524b..9329beb 100644 --- a/Il2CppInspector/FileFormatReader.cs +++ b/Il2CppInspector/FileFormatReader.cs @@ -20,6 +20,7 @@ namespace Il2CppInspector U ReadMappedObject(uint uiAddr) where U : new(); U[] ReadMappedArray(uint uiAddr, int count) where U : new(); uint MapVATR(uint uiAddr); + void FinalizeInit(Il2CppReader il2cpp); byte[] ReadBytes(int count); ulong ReadUInt64(); @@ -38,13 +39,13 @@ namespace Il2CppInspector public virtual string Arch => throw new NotImplementedException(); - public static T Load(string filename) { + public static T Load(string filename, uint offset = 0) { using (var stream = new FileStream(filename, FileMode.Open)) - return Load(stream); + return Load(stream, offset); } - public static T Load(Stream stream) { - stream.Position = 0; + public static T Load(Stream stream, uint offset = 0) { + stream.Position = offset; var pe = (T) Activator.CreateInstance(typeof(T), stream); return pe.Init() ? pe : null; } @@ -66,5 +67,8 @@ namespace Il2CppInspector public U[] ReadMappedArray(uint uiAddr, int count) where U : new() { return ReadArray(MapVATR(uiAddr), count); } + + // Perform file format-based post-load manipulations to the IL2Cpp data + public virtual void FinalizeInit(Il2CppReader il2cpp) { } } } \ No newline at end of file diff --git a/Il2CppInspector/Il2CppProcessor.cs b/Il2CppInspector/Il2CppProcessor.cs index 7a5b7f2..9e5cb15 100644 --- a/Il2CppInspector/Il2CppProcessor.cs +++ b/Il2CppInspector/Il2CppProcessor.cs @@ -33,7 +33,10 @@ 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); + IFileFormatReader stream = + ((IFileFormatReader) ElfReader.Load(memoryStream) ?? + PEReader.Load(memoryStream)) ?? + MachOReader.Load(memoryStream); if (stream == null) { Console.Error.WriteLine("Unsupported executable file format"); return null; diff --git a/Il2CppInspector/Il2CppReader.cs b/Il2CppInspector/Il2CppReader.cs index 2a153b1..31a5210 100644 --- a/Il2CppInspector/Il2CppReader.cs +++ b/Il2CppInspector/Il2CppReader.cs @@ -35,6 +35,7 @@ namespace Il2CppInspector var (code, metadata) = Search(loc, Image.GlobalOffset); if (code != 0) { Configure(code, metadata); + Image.FinalizeInit(this); return true; } } diff --git a/Il2CppInspector/MachOHeaders.cs b/Il2CppInspector/MachOHeaders.cs new file mode 100644 index 0000000..64f310d --- /dev/null +++ b/Il2CppInspector/MachOHeaders.cs @@ -0,0 +1,123 @@ +/* + 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://opensource.apple.com/source/cctools/cctools-870/include/mach-o/loader.h + + public enum MachO : uint + { + MH_MAGIC = 0xfeedface, + MH_CIGAM = 0xcefaedfe, + MH_MAGIC_64 = 0xfeedfacf, + MH_CIGAM_64 = 0xcffaedfe, + + MH_EXECUTE = 0x2, + + LC_SEGMENT = 0x1, + LC_SEGMENT_64 = 0x19, + LC_FUNCTION_STARTS = 0x26, + + CPU_TYPE_X86 = 7, + CPU_TYPE_X86_64 = 0x01000000 + CPU_TYPE_X86, + CPU_TYPE_ARM = 12, + CPU_TYPE_ARM64 = 0x01000000 + CPU_TYPE_ARM + } + + public class MachOHeader + { + public uint Magic; + public uint CPUType; + public uint CPUSubType; + public uint FileType; + public uint NumCommands; + public uint SizeOfCommands; + public uint Flags; + // 64-bit header has an extra 32-bit Reserved field + } + + public class MachOLoadCommand + { + public uint Command; + public uint Size; + } + + public class MachOSegmentCommand + { + // MachOLoadCommand + [String(FixedSize = 16)] + public string Name; + public uint VirtualAddress; + public uint VirtualSize; + public uint ImageOffset; + public uint ImageSize; + public uint VMMaxProt; + public uint VMInitProt; + public uint NumSections; + public uint Flags; + } + + public class MachOSegmentCommand64 + { + // MachOLoadCommand + [String(FixedSize = 16)] + public string Name; + public ulong VirtualAddress; + public ulong VirtualSize; + public ulong ImageOffset; + public ulong ImageSize; + public uint VMMaxProt; + public uint VMInitProt; + public uint NumSections; + public uint Flags; + } + + public class MachOSection + { + [String(FixedSize = 16)] + public string Name; + [String(FixedSize = 16)] + public string SegmentName; + public uint Address; + public uint Size; + public uint ImageOffset; + public uint Align; + public uint ImageRelocOffset; + public uint NumRelocEntries; + public uint Flags; + public uint Reserved1; + public uint Reserved2; + } + + public class MachOSection64 + { + [String(FixedSize = 16)] + public string Name; + [String(FixedSize = 16)] + public string SegmentName; + public ulong Address; + public ulong Size; + public uint ImageOffset; + public uint Align; + public uint ImageRelocOffset; + public uint NumRelocEntries; + public uint Flags; + public uint Reserved1; + public uint Reserved2; + public uint Reserved3; + } + + public class MachOLinkEditDataCommand + { + // MachOLoadCommand + public uint Offset; + public uint Size; + } +} diff --git a/Il2CppInspector/MachOReader.cs b/Il2CppInspector/MachOReader.cs new file mode 100644 index 0000000..29f843e --- /dev/null +++ b/Il2CppInspector/MachOReader.cs @@ -0,0 +1,150 @@ +/* + 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 MachOReader : FileFormatReader + { + private MachOHeader header; + private uint pFuncTable; + private uint sFuncTable; + private uint fatIndex; + + public MachOReader(Stream stream) : base(stream) { } + + public override string Arch { + get { + switch ((MachO)header.CPUType) { + case MachO.CPU_TYPE_ARM: + case MachO.CPU_TYPE_ARM64: + return "ARM"; + case MachO.CPU_TYPE_X86: + case MachO.CPU_TYPE_X86_64: + return "x86"; + default: + return "Unsupported"; + } + } + } + + 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) { + Endianness = Endianness.Big; + } + else if (magic != MachO.MH_MAGIC && magic != MachO.MH_MAGIC_64) { + return false; + } + + Console.WriteLine("Endianness: {0}", Endianness); + + Position = fatIndex; + header = ReadObject(); + + // 64-bit files have an extra 4 bytes after the header + bool is64 = false; + if (magic == MachO.MH_MAGIC_64) { + is64 = true; + ReadUInt32(); + } + Console.WriteLine("Architecture: {0}-bit", is64 ? 64 : 32); + + // Must be executable file + if ((MachO) header.FileType != MachO.MH_EXECUTE) + return false; + + Console.WriteLine("CPU Type: " + (MachO)header.CPUType); + + MachOLinkEditDataCommand functionStarts = null; + + for (var c = 0; c < header.NumCommands; c++) { + var startPos = Position; + var loadCommand = ReadObject(); + + if ((MachO)loadCommand.Command == MachO.LC_SEGMENT) { + var segment = ReadObject(); + if (segment.Name == "__TEXT") { + for (int s = 0; s < segment.NumSections; s++) { + var section = ReadObject(); + if (section.Name == "__text") + GlobalOffset = section.ImageOffset - section.Address + fatIndex; + } + } + } + else if ((MachO)loadCommand.Command == MachO.LC_SEGMENT_64) { + var segment = ReadObject(); + if (segment.Name == "__TEXT") { + for (int s = 0; s < segment.NumSections; s++) { + var section = ReadObject(); + if (section.Name == "__text") + GlobalOffset = section.ImageOffset - (uint)section.Address + fatIndex; + } + } + } + + if ((MachO)loadCommand.Command == MachO.LC_FUNCTION_STARTS) { + functionStarts = ReadObject(); + } + + // There might be other data after the load command so always use the specified total size to step forwards + Position = startPos + loadCommand.Size; + } + + // Must find LC_FUNCTION_STARTS load command + if (functionStarts == null) + return false; + + pFuncTable = functionStarts.Offset + fatIndex; + sFuncTable = functionStarts.Size; + return true; + } + + public override uint[] GetSearchLocations() { + Position = pFuncTable; + var functionPointers = new List(); + + // Decompress ELB128 list of function offsets + // https://en.wikipedia.org/wiki/LEB128 + uint previous = 0; + while (Position < pFuncTable + sFuncTable) { + uint result = 0; + int shift = 0; + byte b; + do { + b = ReadByte(); + result |= (uint)((b & 0x7f) << shift); + shift += 7; + } while ((b & 0x80) != 0); + if (result > 0) { + if (previous == 0) + result &= 0xffffffc; + previous += result; + functionPointers.Add(previous + fatIndex); + } + } + 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(); + } + } +}