Add support for Mach-O files

This commit is contained in:
Katy Coe
2017-10-22 02:24:10 +02:00
parent 16ae3ed108
commit 58968c237a
5 changed files with 286 additions and 5 deletions

View File

@@ -20,6 +20,7 @@ namespace Il2CppInspector
U ReadMappedObject<U>(uint uiAddr) where U : new(); U ReadMappedObject<U>(uint uiAddr) where U : new();
U[] ReadMappedArray<U>(uint uiAddr, int count) where U : new(); U[] ReadMappedArray<U>(uint uiAddr, int count) where U : new();
uint MapVATR(uint uiAddr); uint MapVATR(uint uiAddr);
void FinalizeInit(Il2CppReader il2cpp);
byte[] ReadBytes(int count); byte[] ReadBytes(int count);
ulong ReadUInt64(); ulong ReadUInt64();
@@ -38,13 +39,13 @@ namespace Il2CppInspector
public virtual string Arch => throw new NotImplementedException(); 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)) using (var stream = new FileStream(filename, FileMode.Open))
return Load(stream); return Load(stream, offset);
} }
public static T Load(Stream stream) { public static T Load(Stream stream, uint offset = 0) {
stream.Position = 0; stream.Position = offset;
var pe = (T) Activator.CreateInstance(typeof(T), stream); var pe = (T) Activator.CreateInstance(typeof(T), stream);
return pe.Init() ? pe : null; return pe.Init() ? pe : null;
} }
@@ -66,5 +67,8 @@ namespace Il2CppInspector
public U[] ReadMappedArray<U>(uint uiAddr, int count) where U : new() { public U[] ReadMappedArray<U>(uint uiAddr, int count) where U : new() {
return ReadArray<U>(MapVATR(uiAddr), count); return ReadArray<U>(MapVATR(uiAddr), count);
} }
// Perform file format-based post-load manipulations to the IL2Cpp data
public virtual void FinalizeInit(Il2CppReader il2cpp) { }
} }
} }

View File

@@ -33,7 +33,10 @@ namespace Il2CppInspector
// Load the il2cpp code file (try ELF and PE) // Load the il2cpp code file (try ELF and PE)
var memoryStream = new MemoryStream(File.ReadAllBytes(codeFile)); 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) { if (stream == null) {
Console.Error.WriteLine("Unsupported executable file format"); Console.Error.WriteLine("Unsupported executable file format");
return null; return null;

View File

@@ -35,6 +35,7 @@ namespace Il2CppInspector
var (code, metadata) = Search(loc, Image.GlobalOffset); var (code, metadata) = Search(loc, Image.GlobalOffset);
if (code != 0) { if (code != 0) {
Configure(code, metadata); Configure(code, metadata);
Image.FinalizeInit(this);
return true; return true;
} }
} }

View File

@@ -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;
}
}

View File

@@ -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<MachOReader>
{
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<MachOHeader>();
// 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<MachOLoadCommand>();
if ((MachO)loadCommand.Command == MachO.LC_SEGMENT) {
var segment = ReadObject<MachOSegmentCommand>();
if (segment.Name == "__TEXT") {
for (int s = 0; s < segment.NumSections; s++) {
var section = ReadObject<MachOSection>();
if (section.Name == "__text")
GlobalOffset = section.ImageOffset - section.Address + fatIndex;
}
}
}
else if ((MachO)loadCommand.Command == MachO.LC_SEGMENT_64) {
var segment = ReadObject<MachOSegmentCommand64>();
if (segment.Name == "__TEXT") {
for (int s = 0; s < segment.NumSections; s++) {
var section = ReadObject<MachOSection64>();
if (section.Name == "__text")
GlobalOffset = section.ImageOffset - (uint)section.Address + fatIndex;
}
}
}
if ((MachO)loadCommand.Command == MachO.LC_FUNCTION_STARTS) {
functionStarts = ReadObject<MachOLinkEditDataCommand>();
}
// 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<uint>();
// 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();
}
}
}