diff --git a/Il2CppDumper/Il2CppDumper.cs b/Il2CppDumper/Il2CppDumper.cs index 397af3d..930e396 100644 --- a/Il2CppDumper/Il2CppDumper.cs +++ b/Il2CppDumper/Il2CppDumper.cs @@ -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 "); diff --git a/Il2CppDumper/Program.cs b/Il2CppDumper/Program.cs index b9d2b17..722ff46 100644 --- a/Il2CppDumper/Program.cs +++ b/Il2CppDumper/Program.cs @@ -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++)); } } } diff --git a/Il2CppInspector/FileFormatReader.cs b/Il2CppInspector/FileFormatReader.cs index 9329beb..2e71032 100644 --- a/Il2CppInspector/FileFormatReader.cs +++ b/Il2CppInspector/FileFormatReader.cs @@ -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 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 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(uint uiAddr) where U : new() { diff --git a/Il2CppInspector/Il2CppProcessor.cs b/Il2CppInspector/Il2CppProcessor.cs index 9e5cb15..6b68355 100644 --- a/Il2CppInspector/Il2CppProcessor.cs +++ b/Il2CppInspector/Il2CppProcessor.cs @@ -20,7 +20,7 @@ namespace Il2CppInspector Metadata = metadata; } - public static Il2CppProcessor LoadFromFile(string codeFile, string metadataFile) { + public static List 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(); + 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", diff --git a/Il2CppInspector/Il2CppReader.cs b/Il2CppInspector/Il2CppReader.cs index 31a5210..e014d1a 100644 --- a/Il2CppInspector/Il2CppReader.cs +++ b/Il2CppInspector/Il2CppReader.cs @@ -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(codeRegistration); - PtrMetadataRegistration = Image.ReadMappedObject(metadataRegistration); - PtrCodeRegistration.methodPointers = Image.ReadMappedArray(PtrCodeRegistration.pmethodPointers, + private void Configure(IFileFormatReader image, uint codeRegistration, uint metadataRegistration) { + PtrCodeRegistration = image.ReadMappedObject(codeRegistration); + PtrMetadataRegistration = image.ReadMappedObject(metadataRegistration); + PtrCodeRegistration.methodPointers = image.ReadMappedArray(PtrCodeRegistration.pmethodPointers, (int) PtrCodeRegistration.methodPointersCount); - PtrMetadataRegistration.fieldOffsets = Image.ReadMappedArray(PtrMetadataRegistration.pfieldOffsets, + PtrMetadataRegistration.fieldOffsets = image.ReadMappedArray(PtrMetadataRegistration.pfieldOffsets, PtrMetadataRegistration.fieldOffsetsCount); - var types = Image.ReadMappedArray(PtrMetadataRegistration.ptypes, PtrMetadataRegistration.typesCount); + var types = image.ReadMappedArray(PtrMetadataRegistration.ptypes, PtrMetadataRegistration.typesCount); PtrMetadataRegistration.types = new Il2CppType[PtrMetadataRegistration.typesCount]; for (int i = 0; i < PtrMetadataRegistration.typesCount; ++i) { - PtrMetadataRegistration.types[i] = Image.ReadMappedObject(types[i]); + PtrMetadataRegistration.types[i] = image.ReadMappedObject(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(); - } } } diff --git a/Il2CppInspector/Il2CppReaderARM.cs b/Il2CppInspector/Il2CppReaderARM.cs index 81db6e9..909252f 100644 --- a/Il2CppInspector/Il2CppReaderARM.cs +++ b/Il2CppInspector/Il2CppReaderARM.cs @@ -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); } diff --git a/Il2CppInspector/MachOReader.cs b/Il2CppInspector/MachOReader.cs index 29f843e..bad7863 100644 --- a/Il2CppInspector/MachOReader.cs +++ b/Il2CppInspector/MachOReader.cs @@ -17,7 +17,9 @@ namespace Il2CppInspector private MachOHeader header; private uint pFuncTable; private uint sFuncTable; - private uint fatIndex; + private bool is64; + private List sections = new List(); + private List sections64 = new List(); 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(); // 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(); - if (segment.Name == "__TEXT") { + if (segment.Name == "__TEXT" || segment.Name == "__DATA") { for (int s = 0; s < segment.NumSections; s++) { var section = ReadObject(); + 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(); - if (segment.Name == "__TEXT") { + if (segment.Name == "__TEXT" || segment.Name == "__DATA") { for (int s = 0; s < segment.NumSections; s++) { - var section = ReadObject(); - if (section.Name == "__text") - GlobalOffset = section.ImageOffset - (uint)section.Address + fatIndex; + var section64 = ReadObject(); + 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); + } } } diff --git a/Il2CppInspector/UBHeaders.cs b/Il2CppInspector/UBHeaders.cs new file mode 100644 index 0000000..622aefa --- /dev/null +++ b/Il2CppInspector/UBHeaders.cs @@ -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; + } +} diff --git a/Il2CppInspector/UBReader.cs b/Il2CppInspector/UBReader.cs new file mode 100644 index 0000000..b418590 --- /dev/null +++ b/Il2CppInspector/UBReader.cs @@ -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 + { + 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(); + + 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(); + + Position = arch.Offset; + Endianness = Endianness.Little; + return MachOReader.Load(new MemoryStream(ReadBytes((int)arch.Size))); + } + } + } +}