diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..ee39077 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "Bin2Object"] + path = Bin2Object + url = https://github.com/djkaty/Bin2Object diff --git a/Bin2Object b/Bin2Object new file mode 160000 index 0000000..8cb9cc9 --- /dev/null +++ b/Bin2Object @@ -0,0 +1 @@ +Subproject commit 8cb9cc95df417f09e460dc027ff39e24f8284ee3 diff --git a/Il2CppDumper/Il2CppDumper.cs b/Il2CppDumper/Il2CppDumper.cs new file mode 100644 index 0000000..397af3d --- /dev/null +++ b/Il2CppDumper/Il2CppDumper.cs @@ -0,0 +1,161 @@ +// Copyright (c) 2017 Katy Coe - https://www.djkaty.com - https://github.com/djlaty +// All rights reserved + +using System.IO; +using System.Text; + +namespace Il2CppInspector +{ + public class Il2CppDumper + { + private readonly Il2CppProcessor il2cpp; + + public Il2CppDumper(Il2CppProcessor proc) { + il2cpp = proc; + } + + public void WriteFile(string outFile) { + using (var writer = new StreamWriter(new FileStream(outFile, FileMode.Create))) { + var metadata = il2cpp.Metadata; + + for (int imageIndex = 0; imageIndex < metadata.Images.Length; imageIndex++) { + var imageDef = metadata.Images[imageIndex]; + writer.Write($"// Image {imageIndex}: {metadata.GetImageName(imageDef)} - {imageDef.typeStart}\n"); + } + for (int idx = 0; idx < metadata.Types.Length; ++idx) { + var typeDef = metadata.Types[idx]; + writer.Write($"// Namespace: {metadata.GetTypeNamespace(typeDef)}\n"); + if ((typeDef.flags & DefineConstants.TYPE_ATTRIBUTE_SERIALIZABLE) != 0) + writer.Write("[Serializable]\n"); + if ((typeDef.flags & DefineConstants.TYPE_ATTRIBUTE_VISIBILITY_MASK) == + DefineConstants.TYPE_ATTRIBUTE_PUBLIC) + writer.Write("public "); + if ((typeDef.flags & DefineConstants.TYPE_ATTRIBUTE_ABSTRACT) != 0) + writer.Write("abstract "); + if ((typeDef.flags & DefineConstants.TYPE_ATTRIBUTE_SEALED) != 0) + writer.Write("sealed "); + if ((typeDef.flags & DefineConstants.TYPE_ATTRIBUTE_INTERFACE) != 0) + writer.Write("interface "); + else + writer.Write("class "); + writer.Write($"{metadata.GetTypeName(typeDef)} // TypeDefIndex: {idx}\n{{\n"); + writer.Write("\t// Fields\n"); + 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 pDefault = metadata.GetFieldDefaultFromIndex(i); + writer.Write("\t"); + if ((pType.attrs & DefineConstants.FIELD_ATTRIBUTE_PRIVATE) == + DefineConstants.FIELD_ATTRIBUTE_PRIVATE) + writer.Write("private "); + if ((pType.attrs & DefineConstants.FIELD_ATTRIBUTE_PUBLIC) == + DefineConstants.FIELD_ATTRIBUTE_PUBLIC) + writer.Write("public "); + if ((pType.attrs & DefineConstants.FIELD_ATTRIBUTE_STATIC) != 0) + writer.Write("static "); + if ((pType.attrs & DefineConstants.FIELD_ATTRIBUTE_INIT_ONLY) != 0) + writer.Write("readonly "); + 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); + if (pointer > 0) { + metadata.Position = pointer; + object multi = null; + switch (pTypeToUse.type) { + case Il2CppTypeEnum.IL2CPP_TYPE_BOOLEAN: + multi = metadata.ReadBoolean(); + break; + case Il2CppTypeEnum.IL2CPP_TYPE_U1: + case Il2CppTypeEnum.IL2CPP_TYPE_I1: + multi = metadata.ReadByte(); + break; + case Il2CppTypeEnum.IL2CPP_TYPE_CHAR: + multi = metadata.ReadChar(); + break; + case Il2CppTypeEnum.IL2CPP_TYPE_U2: + multi = metadata.ReadUInt16(); + break; + case Il2CppTypeEnum.IL2CPP_TYPE_I2: + multi = metadata.ReadInt16(); + break; + case Il2CppTypeEnum.IL2CPP_TYPE_U4: + multi = metadata.ReadUInt32(); + break; + case Il2CppTypeEnum.IL2CPP_TYPE_I4: + multi = metadata.ReadInt32(); + break; + case Il2CppTypeEnum.IL2CPP_TYPE_U8: + multi = metadata.ReadUInt64(); + break; + case Il2CppTypeEnum.IL2CPP_TYPE_I8: + multi = metadata.ReadInt64(); + break; + case Il2CppTypeEnum.IL2CPP_TYPE_R4: + multi = metadata.ReadSingle(); + break; + case Il2CppTypeEnum.IL2CPP_TYPE_R8: + multi = metadata.ReadDouble(); + break; + case Il2CppTypeEnum.IL2CPP_TYPE_STRING: + var uiLen = metadata.ReadInt32(); + multi = Encoding.UTF8.GetString(metadata.ReadBytes(uiLen)); + break; + } + if (multi is string) + writer.Write($" = \"{multi}\""); + else if (multi != null) + writer.Write($" = {multi}"); + } + } + writer.Write("; // 0x{0:x}\n", + il2cpp.Code.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); + if ((methodDef.flags & DefineConstants.METHOD_ATTRIBUTE_MEMBER_ACCESS_MASK) == + DefineConstants.METHOD_ATTRIBUTE_PRIVATE) + writer.Write("private "); + if ((methodDef.flags & DefineConstants.METHOD_ATTRIBUTE_MEMBER_ACCESS_MASK) == + DefineConstants.METHOD_ATTRIBUTE_PUBLIC) + writer.Write("public "); + if ((methodDef.flags & DefineConstants.METHOD_ATTRIBUTE_VIRTUAL) != 0) + writer.Write("virtual "); + if ((methodDef.flags & DefineConstants.METHOD_ATTRIBUTE_STATIC) != 0) + writer.Write("static "); + + writer.Write($"{il2cpp.GetTypeName(pReturnType)} {metadata.GetString(methodDef.nameIndex)}("); + 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); + string szTypeName = il2cpp.GetTypeName(pType); + if ((pType.attrs & DefineConstants.PARAM_ATTRIBUTE_OPTIONAL) != 0) + writer.Write("optional "); + if ((pType.attrs & DefineConstants.PARAM_ATTRIBUTE_OUT) != 0) + writer.Write("out "); + if (j != methodDef.parameterCount - 1) { + writer.Write($"{szTypeName} {szParamName}, "); + } + else { + writer.Write($"{szTypeName} {szParamName}"); + } + } + if (methodDef.methodIndex >= 0) + writer.Write("); // {0:x} - {1}\n", + il2cpp.Code.PtrCodeRegistration.methodPointers[methodDef.methodIndex], + methodDef.methodIndex); + else + writer.Write("); // 0 - -1\n"); + } + writer.Write("}\n"); + } + } + } + } +} diff --git a/Il2CppDumper/Il2CppDumper.csproj b/Il2CppDumper/Il2CppDumper.csproj new file mode 100644 index 0000000..40bbc13 --- /dev/null +++ b/Il2CppDumper/Il2CppDumper.csproj @@ -0,0 +1,12 @@ + + + + Exe + netcoreapp1.1 + + + + + + + \ No newline at end of file diff --git a/Il2CppDumper/Program.cs b/Il2CppDumper/Program.cs new file mode 100644 index 0000000..b9d2b17 --- /dev/null +++ b/Il2CppDumper/Program.cs @@ -0,0 +1,52 @@ +// Copyright (c) 2017 Katy Coe - https://www.djkaty.com - https://github.com/djlaty +// All rights reserved + +using System; +using System.IO; + +namespace Il2CppInspector +{ + public class App + { + static void Main(string[] args) { + + // Command-line usage: dotnet run [ [ []]] + // Defaults to libil2cpp.so or GameAssembly.dll if binary file not specified + string imageFile = "libil2cpp.so"; + string metaFile = "global-metadata.dat"; + string outFile = "types.cs"; + + if (args.Length == 0) + if (!File.Exists(imageFile)) + imageFile = "GameAssembly.dll"; + + if (args.Length >= 1) + imageFile = args[0]; + + if (args.Length >= 2) + metaFile = args[1]; + + if (args.Length >= 3) + outFile = args[2]; + + // Check files + if (!File.Exists(imageFile)) { + Console.Error.WriteLine($"File {imageFile} does not exist"); + Environment.Exit(1); + } + if (!File.Exists(metaFile)) { + Console.Error.WriteLine($"File {metaFile} does not exist"); + Environment.Exit(1); + } + + // Analyze data + var il2cpp = Il2CppProcessor.LoadFromFile(imageFile, metaFile); + if (il2cpp == null) + Environment.Exit(1); + + // Write output file + var dumper = new Il2CppDumper(il2cpp); + dumper.WriteFile(outFile); + } + } +} diff --git a/Il2CppInspector.sln b/Il2CppInspector.sln new file mode 100644 index 0000000..c3236dd --- /dev/null +++ b/Il2CppInspector.sln @@ -0,0 +1,34 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 15 +VisualStudioVersion = 15.0.26228.9 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Bin2Object", "Bin2Object\Bin2Object\Bin2Object.csproj", "{55382D6C-01B6-4AFD-850C-7A79DAB6F270}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Il2CppInspector", "Il2CppInspector\Il2CppInspector.csproj", "{E4721466-CC6F-47EB-AD48-F4DE70D77E5C}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Il2CppDumper", "Il2CppDumper\Il2CppDumper.csproj", "{EA4C27DF-4640-48DF-8CAF-5587884CAF30}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {55382D6C-01B6-4AFD-850C-7A79DAB6F270}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {55382D6C-01B6-4AFD-850C-7A79DAB6F270}.Debug|Any CPU.Build.0 = Debug|Any CPU + {55382D6C-01B6-4AFD-850C-7A79DAB6F270}.Release|Any CPU.ActiveCfg = Release|Any CPU + {55382D6C-01B6-4AFD-850C-7A79DAB6F270}.Release|Any CPU.Build.0 = Release|Any CPU + {E4721466-CC6F-47EB-AD48-F4DE70D77E5C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {E4721466-CC6F-47EB-AD48-F4DE70D77E5C}.Debug|Any CPU.Build.0 = Debug|Any CPU + {E4721466-CC6F-47EB-AD48-F4DE70D77E5C}.Release|Any CPU.ActiveCfg = Release|Any CPU + {E4721466-CC6F-47EB-AD48-F4DE70D77E5C}.Release|Any CPU.Build.0 = Release|Any CPU + {EA4C27DF-4640-48DF-8CAF-5587884CAF30}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {EA4C27DF-4640-48DF-8CAF-5587884CAF30}.Debug|Any CPU.Build.0 = Debug|Any CPU + {EA4C27DF-4640-48DF-8CAF-5587884CAF30}.Release|Any CPU.ActiveCfg = Release|Any CPU + {EA4C27DF-4640-48DF-8CAF-5587884CAF30}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/Il2CppInspector/DefineConstants.cs b/Il2CppInspector/DefineConstants.cs new file mode 100644 index 0000000..01bea56 --- /dev/null +++ b/Il2CppInspector/DefineConstants.cs @@ -0,0 +1,20 @@ +public static class DefineConstants +{ + public const int FIELD_ATTRIBUTE_PRIVATE = 0x0001; + public const int FIELD_ATTRIBUTE_PUBLIC = 0x0006; + public const int FIELD_ATTRIBUTE_STATIC = 0x0010; + public const int FIELD_ATTRIBUTE_INIT_ONLY = 0x0020; + public const int METHOD_ATTRIBUTE_MEMBER_ACCESS_MASK = 0x0007; + public const int METHOD_ATTRIBUTE_PRIVATE = 0x0001; + public const int METHOD_ATTRIBUTE_PUBLIC = 0x0006; + public const int METHOD_ATTRIBUTE_STATIC = 0x0010; + public const int METHOD_ATTRIBUTE_VIRTUAL = 0x0040; + public const int TYPE_ATTRIBUTE_VISIBILITY_MASK = 0x00000007; + public const int TYPE_ATTRIBUTE_PUBLIC = 0x00000001; + public const int TYPE_ATTRIBUTE_INTERFACE = 0x00000020; + public const int TYPE_ATTRIBUTE_ABSTRACT = 0x00000080; + public const int TYPE_ATTRIBUTE_SEALED = 0x00000100; + public const int TYPE_ATTRIBUTE_SERIALIZABLE = 0x00002000; + public const int PARAM_ATTRIBUTE_OUT = 0x0002; + public const int PARAM_ATTRIBUTE_OPTIONAL = 0x0010; +} \ No newline at end of file diff --git a/Il2CppInspector/ElfHeaders.cs b/Il2CppInspector/ElfHeaders.cs new file mode 100644 index 0000000..e0f50a4 --- /dev/null +++ b/Il2CppInspector/ElfHeaders.cs @@ -0,0 +1,89 @@ +/* + Copyright 2017 Perfare - https://github.com/Perfare/Il2CppDumper + Copyright 2017 Katy Coe - http://www.hearthcode.org - http://www.djkaty.com + + All rights reserved. +*/ + +using NoisyCowStudios.Bin2Object; + +namespace Il2CppInspector +{ +#pragma warning disable CS0649 + internal class elf_header + { + // 0x7f followed by ELF in ascii + public uint m_dwFormat; + + // 1 - 32 bit + // 2 - 64 bit + public byte m_arch; + + // 1 - little endian + // 2 - big endian + public byte m_endian; + + // 1 is original elf format + public byte m_version; + + // set based on OS, refer to OSABI enum + public byte m_osabi; + + // refer to elf documentation + public byte m_osabi_ver; + + // unused + [ArrayLength(FixedSize=7)] + public byte[] e_pad;//byte[7] + + // 1 - relocatable + // 2 - executable + // 3 - shared + // 4 - core + public ushort e_type; + + // refer to isa enum + public ushort e_machine; + + public uint e_version; + + public uint e_entry; + public uint e_phoff; + public uint e_shoff; + public uint e_flags; + public ushort e_ehsize; + public ushort e_phentsize; + public ushort e_phnum; + public ushort e_shentsize; + public ushort e_shnum; + public ushort e_shtrndx; + } + + internal class program_header_table + { + public uint p_type; + public uint p_offset; + public uint p_vaddr; + public uint p_paddr; + public uint p_filesz; + public uint p_memsz; + public uint p_flags; + public uint p_align; + //public byte[] p_data;忽略 + } + + internal class elf_32_shdr + { + public uint sh_name; + public uint sh_type; + public uint sh_flags; + public uint sh_addr; + public uint sh_offset; + public uint sh_size; + public uint sh_link; + public uint sh_info; + public uint sh_addralign; + public uint sh_entsize; + } +#pragma warning restore CS0649 +} diff --git a/Il2CppInspector/ElfReader.cs b/Il2CppInspector/ElfReader.cs new file mode 100644 index 0000000..c0af446 --- /dev/null +++ b/Il2CppInspector/ElfReader.cs @@ -0,0 +1,93 @@ +/* + Copyright 2017 Perfare - https://github.com/Perfare/Il2CppDumper + Copyright 2017 Katy Coe - http://www.hearthcode.org - http://www.djkaty.com + + All rights reserved. +*/ + +using System; +using System.IO; +using System.Linq; + +namespace Il2CppInspector +{ + internal class ElfReader : FileFormatReader + { + private program_header_table[] program_table_element; + private elf_header elf_header; + + public ElfReader(Stream stream) : base(stream) { } + + public override string Arch { + get { + switch (elf_header.e_machine) { + case 0x03: + return "x86"; + case 0x28: + return "ARM"; + default: + return "Unsupported"; + } + } + } + + protected override bool Init() { + elf_header = ReadObject(); + + if (elf_header.m_dwFormat != 0x464c457f) { + // Not an ELF file + return false; + } + if (elf_header.m_arch == 2)//64 + { + // 64-bit not supported + return false; + } + program_table_element = ReadArray(elf_header.e_phoff, elf_header.e_phnum); + return true; + } + + public override uint[] GetSearchLocations() { + // Find dynamic section + var dynamic = new elf_32_shdr(); + var PT_DYNAMIC = program_table_element.First(x => x.p_type == 2u); + dynamic.sh_offset = PT_DYNAMIC.p_offset; + dynamic.sh_size = PT_DYNAMIC.p_filesz; + + // We need GOT, INIT_ARRAY and INIT_ARRAYSZ + uint _GLOBAL_OFFSET_TABLE_ = 0; + var init_array = new elf_32_shdr(); + Position = dynamic.sh_offset; + var dynamicend = dynamic.sh_offset + dynamic.sh_size; + while (Position < dynamicend) { + var tag = ReadInt32(); + if (tag == 3) //DT_PLTGOT + { + _GLOBAL_OFFSET_TABLE_ = ReadUInt32(); + continue; + } + else if (tag == 25) //DT_INIT_ARRAY + { + init_array.sh_offset = MapVATR(ReadUInt32()); + continue; + } + else if (tag == 27) //DT_INIT_ARRAYSZ + { + init_array.sh_size = ReadUInt32(); + continue; + } + Position += 4; + } + if (_GLOBAL_OFFSET_TABLE_ == 0) + throw new InvalidOperationException("Unable to get GLOBAL_OFFSET_TABLE from PT_DYNAMIC"); + GlobalOffset = _GLOBAL_OFFSET_TABLE_; + return ReadArray(init_array.sh_offset, (int) init_array.sh_size / 4); + } + + public override uint MapVATR(uint uiAddr) + { + var program_header_table = program_table_element.First(x => uiAddr >= x.p_vaddr && uiAddr <= (x.p_vaddr + x.p_memsz)); + return uiAddr - (program_header_table.p_vaddr - program_header_table.p_offset); + } + } +} diff --git a/Il2CppInspector/FileFormatReader.cs b/Il2CppInspector/FileFormatReader.cs new file mode 100644 index 0000000..02a524b --- /dev/null +++ b/Il2CppInspector/FileFormatReader.cs @@ -0,0 +1,70 @@ +/* + Copyright 2017 Katy Coe - http://www.hearthcode.org - http://www.djkaty.com + + All rights reserved. +*/ + +using System; +using System.IO; +using NoisyCowStudios.Bin2Object; + +namespace Il2CppInspector +{ + public interface IFileFormatReader + { + BinaryObjectReader Stream { get; } + long Position { get; set; } + string Arch { get; } + uint GlobalOffset { get; } + uint[] GetSearchLocations(); + U ReadMappedObject(uint uiAddr) where U : new(); + U[] ReadMappedArray(uint uiAddr, int count) where U : new(); + uint MapVATR(uint uiAddr); + + byte[] ReadBytes(int count); + ulong ReadUInt64(); + uint ReadUInt32(); + ushort ReadUInt16(); + byte ReadByte(); + } + + internal class FileFormatReader : BinaryObjectReader, IFileFormatReader where T : FileFormatReader + { + public FileFormatReader(Stream stream) : base(stream) { } + + public BinaryObjectReader Stream => this; + + public uint GlobalOffset { get; protected set; } + + public virtual string Arch => throw new NotImplementedException(); + + 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; + } + + // Confirm file is valid and set up RVA mappings + protected virtual bool Init() => throw new NotImplementedException(); + + // 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(); + + // Retrieve object(s) from specified RVA(s) + public U ReadMappedObject(uint uiAddr) where U : new() { + return ReadObject(MapVATR(uiAddr)); + } + + public U[] ReadMappedArray(uint uiAddr, int count) where U : new() { + return ReadArray(MapVATR(uiAddr), count); + } + } +} \ No newline at end of file diff --git a/Il2CppInspector/Il2CppClasses.cs b/Il2CppInspector/Il2CppClasses.cs new file mode 100644 index 0000000..575f643 --- /dev/null +++ b/Il2CppInspector/Il2CppClasses.cs @@ -0,0 +1,207 @@ +using System; +using System.Linq; + +namespace Il2CppInspector +{ + public class Il2CppCodeRegistration + { + public uint methodPointersCount; + public uint pmethodPointers; + public uint delegateWrappersFromNativeToManagedCount; + public uint delegateWrappersFromNativeToManaged; // note the double indirection to handle different calling conventions + public uint delegateWrappersFromManagedToNativeCount; + public uint delegateWrappersFromManagedToNative; + public uint marshalingFunctionsCount; + public uint marshalingFunctions; + public uint ccwMarshalingFunctionsCount; + public uint ccwMarshalingFunctions; + public uint genericMethodPointersCount; + public uint genericMethodPointers; + public uint invokerPointersCount; + public uint invokerPointers; + public int customAttributeCount; + public uint customAttributeGenerators; + public int guidCount; + public uint guids; // Il2CppGuid + + public uint[] methodPointers + { + get; set; + } + } + +#pragma warning disable CS0649 + public class Il2CppMetadataRegistration + { + public int genericClassesCount; + public uint genericClasses; + public int genericInstsCount; + public uint genericInsts; + public int genericMethodTableCount; + public uint genericMethodTable; // Il2CppGenericMethodFunctionsDefinitions + public int typesCount; + public uint ptypes; + public int methodSpecsCount; + public uint methodSpecs; + + public int fieldOffsetsCount; + public uint pfieldOffsets; + + public int typeDefinitionsSizesCount; + public uint typeDefinitionsSizes; + public uint metadataUsagesCount; + public uint metadataUsages; + + public int[] fieldOffsets + { + get; set; + } + + public Il2CppType[] types + { + get; set; + } + } +#pragma warning restore CS0649 + + public enum Il2CppTypeEnum + { + IL2CPP_TYPE_END = 0x00, /* End of List */ + IL2CPP_TYPE_VOID = 0x01, + IL2CPP_TYPE_BOOLEAN = 0x02, + IL2CPP_TYPE_CHAR = 0x03, + IL2CPP_TYPE_I1 = 0x04, + IL2CPP_TYPE_U1 = 0x05, + IL2CPP_TYPE_I2 = 0x06, + IL2CPP_TYPE_U2 = 0x07, + IL2CPP_TYPE_I4 = 0x08, + IL2CPP_TYPE_U4 = 0x09, + IL2CPP_TYPE_I8 = 0x0a, + IL2CPP_TYPE_U8 = 0x0b, + IL2CPP_TYPE_R4 = 0x0c, + IL2CPP_TYPE_R8 = 0x0d, + IL2CPP_TYPE_STRING = 0x0e, + IL2CPP_TYPE_PTR = 0x0f, /* arg: token */ + IL2CPP_TYPE_BYREF = 0x10, /* arg: token */ + IL2CPP_TYPE_VALUETYPE = 0x11, /* arg: token */ + IL2CPP_TYPE_CLASS = 0x12, /* arg: token */ + IL2CPP_TYPE_VAR = 0x13, /* Generic parameter in a generic type definition, represented as number (compressed unsigned integer) number */ + IL2CPP_TYPE_ARRAY = 0x14, /* type, rank, boundsCount, bound1, loCount, lo1 */ + IL2CPP_TYPE_GENERICINST = 0x15, /* \x{2026} */ + IL2CPP_TYPE_TYPEDBYREF = 0x16, + IL2CPP_TYPE_I = 0x18, + IL2CPP_TYPE_U = 0x19, + IL2CPP_TYPE_FNPTR = 0x1b, /* arg: full method signature */ + IL2CPP_TYPE_OBJECT = 0x1c, + IL2CPP_TYPE_SZARRAY = 0x1d, /* 0-based one-dim-array */ + IL2CPP_TYPE_MVAR = 0x1e, /* Generic parameter in a generic method definition, represented as number (compressed unsigned integer) */ + IL2CPP_TYPE_CMOD_REQD = 0x1f, /* arg: typedef or typeref token */ + IL2CPP_TYPE_CMOD_OPT = 0x20, /* optional arg: typedef or typref token */ + IL2CPP_TYPE_INTERNAL = 0x21, /* CLR internal type */ + + IL2CPP_TYPE_MODIFIER = 0x40, /* Or with the following types */ + IL2CPP_TYPE_SENTINEL = 0x41, /* Sentinel for varargs method signature */ + IL2CPP_TYPE_PINNED = 0x45, /* Local var that points to pinned object */ + + IL2CPP_TYPE_ENUM = 0x55 /* an enumeration */ + } + + public class Il2CppType + { + public uint datapoint; + public Anonymous data { get; set; } + public uint bits; + public uint attrs { get; set; } + public Il2CppTypeEnum type { get; set; } + public uint num_mods { get; set; } + public uint byref { get; set; } + public uint pinned { get; set; } + + public void Init() + { + var str = Convert.ToString(bits, 2); + if (str.Length != 32) + { + str = new string(Enumerable.Repeat('0', 32 - str.Length).Concat(str.ToCharArray()).ToArray()); + } + attrs = Convert.ToUInt32(str.Substring(16, 16), 2); + type = (Il2CppTypeEnum)Convert.ToInt32(str.Substring(8, 8), 2); + num_mods = Convert.ToUInt32(str.Substring(2, 6), 2); + byref = Convert.ToUInt32(str.Substring(1, 1), 2); + pinned = Convert.ToUInt32(str.Substring(0, 1), 2); + data = new Anonymous() { dummy = datapoint }; + } + + public class Anonymous + { + public uint dummy; + public int klassIndex + { + get + { + return (int)dummy; + } + } + public uint type + { + get + { + return dummy; + } + } + public uint array + { + get + { + return dummy; + } + } + public int genericParameterIndex + { + get + { + return (int)dummy; + } + } + public uint generic_class + { + get + { + return dummy; + } + } + } + } + + public class Il2CppGenericClass + { + public int typeDefinitionIndex; /* the generic type definition */ + public Il2CppGenericContext context; /* a context that contains the type instantiation doesn't contain any method instantiation */ + public uint cached_class; /* if present, the Il2CppClass corresponding to the instantiation. */ + } + + public class Il2CppGenericContext + { + /* The instantiation corresponding to the class generic parameters */ + public uint class_inst; + /* The instantiation corresponding to the method generic parameters */ + public uint method_inst; + } + + + public class Il2CppGenericInst + { + public uint type_argc; + public uint type_argv; + } + + public class Il2CppArrayType + { + public uint etype; + public byte rank; + public byte numsizes; + public byte numlobounds; + public uint sizes; + public uint lobounds; + } +} diff --git a/Il2CppInspector/Il2CppInspector.csproj b/Il2CppInspector/Il2CppInspector.csproj new file mode 100644 index 0000000..0690e34 --- /dev/null +++ b/Il2CppInspector/Il2CppInspector.csproj @@ -0,0 +1,15 @@ + + + + netstandard1.5 + + + + + + + + + + + \ No newline at end of file diff --git a/Il2CppInspector/Il2CppProcessor.cs b/Il2CppInspector/Il2CppProcessor.cs new file mode 100644 index 0000000..eab48f4 --- /dev/null +++ b/Il2CppInspector/Il2CppProcessor.cs @@ -0,0 +1,137 @@ +/* + Copyright 2017 Katy Coe - http://www.hearthcode.org - http://www.djkaty.com + + All rights reserved. +*/ + +using System; +using System.Collections.Generic; +using System.IO; + +namespace Il2CppInspector +{ + public class Il2CppProcessor + { + public Il2CppReader Code { get; } + public Metadata Metadata { get; } + + public Il2CppProcessor(Il2CppReader code, Metadata metadata) { + Code = code; + Metadata = metadata; + } + + public static Il2CppProcessor LoadFromFile(string codeFile, string metadataFile) { + // Load the metadata file + var metadata = new Metadata(new MemoryStream(File.ReadAllBytes(metadataFile))); + + // 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); + if (stream == null) { + Console.Error.WriteLine("Unsupported executable file format"); + return null; + } + + 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; + } + + // Find code and metadata regions + if (!il2cpp.Load()) { + Console.Error.WriteLine("Could not process IL2CPP image"); + return null; + } + + return new Il2CppProcessor(il2cpp, metadata); + } + + public string GetTypeName(Il2CppType pType) { + string ret; + if (pType.type == Il2CppTypeEnum.IL2CPP_TYPE_CLASS || pType.type == Il2CppTypeEnum.IL2CPP_TYPE_VALUETYPE) { + Il2CppTypeDefinition klass = Metadata.Types[pType.data.klassIndex]; + ret = Metadata.GetString(klass.nameIndex); + } + else if (pType.type == Il2CppTypeEnum.IL2CPP_TYPE_GENERICINST) { + Il2CppGenericClass generic_class = Code.Image.ReadMappedObject(pType.data.generic_class); + Il2CppTypeDefinition pMainDef = Metadata.Types[generic_class.typeDefinitionIndex]; + ret = Metadata.GetString(pMainDef.nameIndex); + var typeNames = new List(); + Il2CppGenericInst pInst = Code.Image.ReadMappedObject(generic_class.context.class_inst); + var pointers = Code.Image.ReadMappedArray(pInst.type_argv, (int)pInst.type_argc); + for (int i = 0; i < pInst.type_argc; ++i) { + var pOriType = Code.Image.ReadMappedObject(pointers[i]); + pOriType.Init(); + typeNames.Add(GetTypeName(pOriType)); + } + ret += $"<{string.Join(", ", typeNames)}>"; + } + else if (pType.type == Il2CppTypeEnum.IL2CPP_TYPE_ARRAY) { + Il2CppArrayType arrayType = Code.Image.ReadMappedObject(pType.data.array); + var type = Code.Image.ReadMappedObject(arrayType.etype); + type.Init(); + ret = $"{GetTypeName(type)}[]"; + } + else if (pType.type == Il2CppTypeEnum.IL2CPP_TYPE_SZARRAY) { + var type = Code.Image.ReadMappedObject(pType.data.type); + type.Init(); + ret = $"{GetTypeName(type)}[]"; + } + else { + if ((int)pType.type >= szTypeString.Length) + ret = "unknow"; + else + ret = szTypeString[(int)pType.type]; + } + return ret; + } + + private readonly string[] szTypeString = + { + "END", + "void", + "bool", + "char", + "sbyte", + "byte", + "short", + "ushort", + "int", + "uint", + "long", + "ulong", + "float", + "double", + "string", + "PTR",//eg. void* + "BYREF", + "VALUETYPE", + "CLASS", + "T", + "ARRAY", + "GENERICINST", + "TYPEDBYREF", + "None", + "IntPtr", + "UIntPtr", + "None", + "FNPTR", + "object", + "SZARRAY", + "T", + "CMOD_REQD", + "CMOD_OPT", + "INTERNAL", + }; + } +} diff --git a/Il2CppInspector/Il2CppReader.cs b/Il2CppInspector/Il2CppReader.cs new file mode 100644 index 0000000..ee2c1db --- /dev/null +++ b/Il2CppInspector/Il2CppReader.cs @@ -0,0 +1,68 @@ +/* + Copyright 2017 Perfare - https://github.com/Perfare/Il2CppDumper + Copyright 2017 Katy Coe - http://www.hearthcode.org - http://www.djkaty.com + + All rights reserved. +*/ + +namespace Il2CppInspector +{ + public abstract class Il2CppReader + { + public IFileFormatReader Image { get; } + + protected Il2CppReader(IFileFormatReader stream) { + Image = stream; + } + + protected Il2CppReader(IFileFormatReader stream, uint codeRegistration, uint metadataRegistration) { + Image = stream; + Configure(codeRegistration, metadataRegistration); + } + + public Il2CppCodeRegistration PtrCodeRegistration { get; protected set; } + public Il2CppMetadataRegistration PtrMetadataRegistration { get; protected set; } + + // Architecture-specific search function + protected abstract (uint, uint) Search(uint loc, uint globalOffset); + + // Check all search locations + public bool Load() { + var addrs = Image.GetSearchLocations(); + foreach (var loc in addrs) + if (loc != 0) { + var (code, metadata) = Search(loc, Image.GlobalOffset); + if (code != 0) { + Configure(code, metadata); + 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, + (int) PtrCodeRegistration.methodPointersCount); + PtrMetadataRegistration.fieldOffsets = Image.ReadMappedArray(PtrMetadataRegistration.pfieldOffsets, + PtrMetadataRegistration.fieldOffsetsCount); + 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].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 new file mode 100644 index 0000000..196b3f4 --- /dev/null +++ b/Il2CppInspector/Il2CppReaderARM.cs @@ -0,0 +1,64 @@ +/* + Copyright 2017 Perfare - https://github.com/Perfare/Il2CppDumper + Copyright 2017 Katy Coe - http://www.hearthcode.org - http://www.djkaty.com + + All rights reserved. +*/ + +using System.Linq; + +namespace Il2CppInspector +{ + internal class Il2CppReaderARM : Il2CppReader + { + public Il2CppReaderARM(IFileFormatReader stream) : base(stream) { } + + public Il2CppReaderARM(IFileFormatReader stream, uint codeRegistration, uint metadataRegistration) : base(stream, codeRegistration, metadataRegistration) { } + + protected override (uint, uint) Search(uint loc, uint globalOffset) { + // Assembly bytes to search for at start of each function + uint metadataRegistration, codeRegistration; + + // ARM + var bytes = new byte[] { 0x1c, 0x0, 0x9f, 0xe5, 0x1c, 0x10, 0x9f, 0xe5, 0x1c, 0x20, 0x9f, 0xe5 }; + Image.Position = loc; + var buff = Image.ReadBytes(12); + if (bytes.SequenceEqual(buff)) { + Image.Position = loc + 0x2c; + var subaddr = Image.ReadUInt32() + globalOffset; + Image.Position = subaddr + 0x28; + codeRegistration = Image.ReadUInt32() + globalOffset; + Image.Position = subaddr + 0x2C; + var ptr = Image.ReadUInt32() + globalOffset; + Image.Position = Image.MapVATR(ptr); + metadataRegistration = Image.ReadUInt32(); + return (codeRegistration, metadataRegistration); + } + + // ARMv7 Thumb (T1) + // http://liris.cnrs.fr/~mmrissa/lib/exe/fetch.php?media=armv7-a-r-manual.pdf - A8.8.106 + // http://armconverter.com/hextoarm/ + bytes = new byte[] { 0x2d, 0xe9, 0x00, 0x48, 0xeb, 0x46 }; + Image.Position = loc; + buff = Image.ReadBytes(6); + if (!bytes.SequenceEqual(buff)) + return (0, 0); + bytes = new byte[] { 0x00, 0x23, 0x00, 0x22, 0xbd, 0xe8, 0x00, 0x48 }; + Image.Position += 0x10; + buff = Image.ReadBytes(8); + if (!bytes.SequenceEqual(buff)) + return (0, 0); + Image.Position = loc + 6; + Image.Position = (Image.MapVATR(decodeMovImm32(Image.ReadBytes(8))) & 0xfffffffc) + 0x0e; + metadataRegistration = decodeMovImm32(Image.ReadBytes(8)); + codeRegistration = decodeMovImm32(Image.ReadBytes(8)); + return (codeRegistration, metadataRegistration); + } + + private uint decodeMovImm32(byte[] asm) { + ushort low = (ushort) (asm[2] + ((asm[3] & 0x70) << 4) + ((asm[1] & 0x04) << 9) + ((asm[0] & 0x0f) << 12)); + ushort high = (ushort) (asm[6] + ((asm[7] & 0x70) << 4) + ((asm[5] & 0x04) << 9) + ((asm[4] & 0x0f) << 12)); + return (uint) ((high << 16) + low); + } + } +} diff --git a/Il2CppInspector/Il2CppReaderX86.cs b/Il2CppInspector/Il2CppReaderX86.cs new file mode 100644 index 0000000..cc5848b --- /dev/null +++ b/Il2CppInspector/Il2CppReaderX86.cs @@ -0,0 +1,58 @@ +/* + Copyright 2017 Katy Coe - http://www.hearthcode.org - http://www.djkaty.com + + All rights reserved. +*/ + +using System.Linq; + +namespace Il2CppInspector +{ + internal class Il2CppReaderX86 : Il2CppReader + { + public Il2CppReaderX86(IFileFormatReader stream) : base(stream) { } + public Il2CppReaderX86(IFileFormatReader stream, uint codeRegistration, uint metadataRegistration) : base(stream, codeRegistration, metadataRegistration) { } + protected override (uint, uint) Search(uint loc, uint globalOffset) { + uint funcPtr, metadata, code; + + // Variant 1 + + // 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 + funcPtr = Image.ReadUInt32(); + + // Start of next instruction + if (Image.ReadByte() != 0xB9) + return (0, 0); + + // Jump to Il2CppCodegenRegistration + Image.Position = Image.MapVATR(funcPtr) + 6; + metadata = Image.ReadUInt32(); + Image.Position = Image.MapVATR(funcPtr) + 11; + code = Image.ReadUInt32(); + return (code, metadata); + } + + // Variant 2 + 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); + + Image.Position += 8; + funcPtr = Image.MapVATR(Image.ReadUInt32() + globalOffset); + if (funcPtr > Image.Stream.BaseStream.Length) + return (0, 0); + Image.Position = funcPtr + 0x22; + metadata = Image.ReadUInt32() + globalOffset; + Image.Position = funcPtr + 0x2C; + code = Image.ReadUInt32() + globalOffset; + return (code, metadata); + } + } +} diff --git a/Il2CppInspector/Metadata.cs b/Il2CppInspector/Metadata.cs new file mode 100644 index 0000000..2c81591 --- /dev/null +++ b/Il2CppInspector/Metadata.cs @@ -0,0 +1,99 @@ +/* + Copyright 2017 Perfare - https://github.com/Perfare/Il2CppDumper + Copyright 2017 Katy Coe - http://www.hearthcode.org - http://www.djkaty.com + + All rights reserved. +*/ + +using System; +using System.IO; +using System.Linq; +using System.Reflection; +using NoisyCowStudios.Bin2Object; + +namespace Il2CppInspector +{ + public class Metadata : BinaryObjectReader + { + private Il2CppGlobalMetadataHeader pMetadataHdr; + + public Il2CppImageDefinition[] Images { get; } + public Il2CppTypeDefinition[] Types { get; } + public Il2CppMethodDefinition[] Methods { get; } + public Il2CppParameterDefinition[] parameterDefs; + public Il2CppFieldDefinition[] Fields { get; } + public Il2CppFieldDefaultValue[] fieldDefaultValues; + + public string GetImageName(Il2CppImageDefinition image) => GetString(image.nameIndex); + public string GetTypeNamespace(Il2CppTypeDefinition type) => GetString(type.namespaceIndex); + public string GetTypeName(Il2CppTypeDefinition type) => GetString(type.nameIndex); + + + + public Metadata(Stream stream) : base(stream) + { + pMetadataHdr = ReadObject(); + if (pMetadataHdr.sanity != 0xFAB11BAF) + { + throw new Exception("ERROR: Metadata file supplied is not valid metadata file."); + } + if (pMetadataHdr.version != 21 && pMetadataHdr.version != 22) + { + throw new Exception($"ERROR: Metadata file supplied is not a supported version[{pMetadataHdr.version}]."); + } + var uiImageCount = pMetadataHdr.imagesCount / MySizeOf(typeof(Il2CppImageDefinition)); + var uiNumTypes = pMetadataHdr.typeDefinitionsCount / MySizeOf(typeof(Il2CppTypeDefinition)); + Images = ReadArray(pMetadataHdr.imagesOffset, uiImageCount); + //GetTypeDefFromIndex + Types = ReadArray(pMetadataHdr.typeDefinitionsOffset, uiNumTypes); + //GetMethodDefinition + Methods = ReadArray(pMetadataHdr.methodsOffset, pMetadataHdr.methodsCount / MySizeOf(typeof(Il2CppMethodDefinition))); + //GetParameterFromIndex + parameterDefs = ReadArray(pMetadataHdr.parametersOffset, pMetadataHdr.parametersCount / MySizeOf(typeof(Il2CppParameterDefinition))); + //GetFieldDefFromIndex + Fields = ReadArray(pMetadataHdr.fieldsOffset, pMetadataHdr.fieldsCount / MySizeOf(typeof(Il2CppFieldDefinition))); + //GetFieldDefaultFromIndex + fieldDefaultValues = ReadArray(pMetadataHdr.fieldDefaultValuesOffset, pMetadataHdr.fieldDefaultValuesCount / MySizeOf(typeof(Il2CppFieldDefaultValue))); + } + + public Il2CppFieldDefaultValue GetFieldDefaultFromIndex(int idx) + { + return fieldDefaultValues.FirstOrDefault(x => x.fieldIndex == idx); + } + + public int GetDefaultValueFromIndex(int idx) + { + return pMetadataHdr.fieldAndParameterDefaultValueDataOffset + idx; + } + + public string GetString(int idx) + { + return ReadNullTerminatedString(pMetadataHdr.stringOffset + idx); + } + + private int MySizeOf(Type type) + { + int size = 0; + foreach (var i in type.GetTypeInfo().GetFields()) + { + if (i.FieldType == typeof(int)) + { + size += 4; + } + else if (i.FieldType == typeof(uint)) + { + size += 4; + } + else if (i.FieldType == typeof(short)) + { + size += 2; + } + else if (i.FieldType == typeof(ushort)) + { + size += 2; + } + } + return size; + } + } +} diff --git a/Il2CppInspector/MetadataClass.cs b/Il2CppInspector/MetadataClass.cs new file mode 100644 index 0000000..a840f92 --- /dev/null +++ b/Il2CppInspector/MetadataClass.cs @@ -0,0 +1,182 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace Il2CppInspector +{ +#pragma warning disable CS0649 + public class Il2CppGlobalMetadataHeader + { + public uint sanity; + public int version; + public int stringLiteralOffset; // string data for managed code + public int stringLiteralCount; + public int stringLiteralDataOffset; + public int stringLiteralDataCount; + public int stringOffset; // string data for metadata + public int stringCount; + public int eventsOffset; // Il2CppEventDefinition + public int eventsCount; + public int propertiesOffset; // Il2CppPropertyDefinition + public int propertiesCount; + public int methodsOffset; // Il2CppMethodDefinition + public int methodsCount; + public int parameterDefaultValuesOffset; // Il2CppParameterDefaultValue + public int parameterDefaultValuesCount; + public int fieldDefaultValuesOffset; // Il2CppFieldDefaultValue + public int fieldDefaultValuesCount; + public int fieldAndParameterDefaultValueDataOffset; // uint8_t + public int fieldAndParameterDefaultValueDataCount; + public int fieldMarshaledSizesOffset; // Il2CppFieldMarshaledSize + public int fieldMarshaledSizesCount; + public int parametersOffset; // Il2CppParameterDefinition + public int parametersCount; + public int fieldsOffset; // Il2CppFieldDefinition + public int fieldsCount; + public int genericParametersOffset; // Il2CppGenericParameter + public int genericParametersCount; + public int genericParameterConstraintsOffset; // TypeIndex + public int genericParameterConstraintsCount; + public int genericContainersOffset; // Il2CppGenericContainer + public int genericContainersCount; + public int nestedTypesOffset; // TypeDefinitionIndex + public int nestedTypesCount; + public int interfacesOffset; // TypeIndex + public int interfacesCount; + public int vtableMethodsOffset; // EncodedMethodIndex + public int vtableMethodsCount; + public int interfaceOffsetsOffset; // Il2CppInterfaceOffsetPair + public int interfaceOffsetsCount; + public int typeDefinitionsOffset; // Il2CppTypeDefinition + public int typeDefinitionsCount; + public int rgctxEntriesOffset; // Il2CppRGCTXDefinition + public int rgctxEntriesCount; + public int imagesOffset; // Il2CppImageDefinition + public int imagesCount; + public int assembliesOffset; // Il2CppAssemblyDefinition + public int assembliesCount; + public int metadataUsageListsOffset; // Il2CppMetadataUsageList + public int metadataUsageListsCount; + public int metadataUsagePairsOffset; // Il2CppMetadataUsagePair + public int metadataUsagePairsCount; + public int fieldRefsOffset; // Il2CppFieldRef + public int fieldRefsCount; + public int referencedAssembliesOffset; // int + public int referencedAssembliesCount; + public int attributesInfoOffset; // Il2CppCustomAttributeTypeRange + public int attributesInfoCount; + public int attributeTypesOffset; // TypeIndex + public int attributeTypesCount; + } + + public class Il2CppImageDefinition + { + public int nameIndex; + public int assemblyIndex; + + public int typeStart; + public uint typeCount; + + public int entryPointIndex; + public uint token; + } +#pragma warning restore CS0649 + + public class Il2CppTypeDefinition + { + public int nameIndex; + public int namespaceIndex; + public int customAttributeIndex; + public int byvalTypeIndex; + public int byrefTypeIndex; + + public int declaringTypeIndex; + public int parentIndex; + public int elementTypeIndex; // we can probably remove this one. Only used for enums + + public int rgctxStartIndex; + public int rgctxCount; + + public int genericContainerIndex; + + public int delegateWrapperFromManagedToNativeIndex; + public int marshalingFunctionsIndex; + public int ccwFunctionIndex; + public int guidIndex; + + public uint flags; + + public int fieldStart; + public int methodStart; + public int eventStart; + public int propertyStart; + public int nestedTypesStart; + public int interfacesStart; + public int vtableStart; + public int interfaceOffsetsStart; + + public ushort method_count; + public ushort property_count; + public ushort field_count; + public ushort event_count; + public ushort nested_type_count; + public ushort vtable_count; + public ushort interfaces_count; + public ushort interface_offsets_count; + + // bitfield to portably encode boolean values as single bits + // 01 - valuetype; + // 02 - enumtype; + // 03 - has_finalize; + // 04 - has_cctor; + // 05 - is_blittable; + // 06 - is_import; + // 07-10 - One of nine possible PackingSize values (0, 1, 2, 4, 8, 16, 32, 64, or 128) + public uint bitfield; + public uint token; + } + + public class Il2CppMethodDefinition + { + public int nameIndex; + public int declaringType; + public int returnType; + public int parameterStart; + public int customAttributeIndex; + public int genericContainerIndex; + public int methodIndex; + public int invokerIndex; + public int delegateWrapperIndex; + public int rgctxStartIndex; + public int rgctxCount; + public uint token; + public ushort flags; + public ushort iflags; + public ushort slot; + public ushort parameterCount; + } + + public class Il2CppParameterDefinition + { + public int nameIndex; + public uint token; + public int customAttributeIndex; + public int typeIndex; + } + + public class Il2CppFieldDefinition + { + public int nameIndex; + public int typeIndex; + public int customAttributeIndex; + public uint token; + } + + public class Il2CppFieldDefaultValue + { + public int fieldIndex; + public int typeIndex; + public int dataIndex; + } +} diff --git a/Il2CppInspector/PEHeaders.cs b/Il2CppInspector/PEHeaders.cs new file mode 100644 index 0000000..06adcc5 --- /dev/null +++ b/Il2CppInspector/PEHeaders.cs @@ -0,0 +1,78 @@ +/* + Copyright 2017 Katy Coe - http://www.hearthcode.org - http://www.djkaty.com + + All rights reserved. +*/ + +using NoisyCowStudios.Bin2Object; + +namespace Il2CppInspector +{ +#pragma warning disable CS0649 + internal class COFFHeader + { + public ushort Machine; + public ushort NumberOfSections; + public uint TimeDateStamp; + public uint PointerToSymbolTable; + public uint NumberOfSymbols; + public ushort SizeOfOptionalHeader; + public ushort Characteristics; + } + + internal class PEOptHeader + { + public ushort signature; + public byte MajorLinkerVersion; + public byte MinorLinkerVersion; + public uint SizeOfCode; + public uint SizeOfInitializedData; + public uint SizeOfUninitializedData; + public uint AddressOfEntryPoint; + public uint BaseOfCode; + public uint BaseOfData; + public uint ImageBase; + public uint SectionAlignment; + public uint FileAlignment; + public ushort MajorOSVersion; + public ushort MinorOSVersion; + public ushort MajorImageVersion; + public ushort MinorImageVersion; + public ushort MajorSubsystemVersion; + public ushort MinorSubsystemVersion; + public uint Win32VersionValue; + public uint SizeOfImage; + public uint SizeOfHeaders; + public uint Checksum; + public ushort Subsystem; + public ushort DLLCharacteristics; + public uint SizeOfStackReserve; + public uint SizeOfStackCommit; + public uint SizeOfHeapReserve; + public uint SizeOfHeapCommit; + public uint LoaderFlags; + public uint NumberOfRvaAndSizes; + [ArrayLength(FieldName = "NumberOfRvaAndSizes")] + public RvaEntry[] DataDirectory; + } + + internal class RvaEntry + { + public uint VirtualAddress; + public uint Size; + } + + internal class PESection + { + [String(FixedSize=8)] + public string Name; + public uint SizeMemory; + public uint BaseMemory; // RVA + public uint SizeImage; // Size in file + public uint BaseImage; // Base address in file + [ArrayLength(FixedSize=12)] + public byte[] Reserved; + public uint Flags; + } +#pragma warning restore CS0649 +} diff --git a/Il2CppInspector/PEReader.cs b/Il2CppInspector/PEReader.cs new file mode 100644 index 0000000..f820bce --- /dev/null +++ b/Il2CppInspector/PEReader.cs @@ -0,0 +1,101 @@ +/* + 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; + +namespace Il2CppInspector +{ + internal class PEReader : FileFormatReader + { + private COFFHeader coff; + private PEOptHeader pe; + private PESection[] sections; + private uint pFuncTable; + + public PEReader(Stream stream) : base(stream) {} + + public override string Arch { + get { + switch (coff.Machine) { + case 0x14C: + return "x86"; + case 0x1C0: // ARMv7 + case 0x1C4: // ARMv7 Thumb (T1) + return "ARM"; + default: + return "Unsupported"; + } + } + } + + protected override bool Init() { + // Check for MZ signature "MZ" + if (ReadUInt16() != 0x5A4D) + return false; + + // Get offset to PE header from DOS header + Position = 0x3C; + Position = ReadUInt32(); + + // Check PE signature "PE\0\0" + if (ReadUInt32() != 0x00004550) + return false; + + // Read COFF Header + coff = ReadObject(); + + // Ensure presence of PE Optional header + // Size will always be 0x60 + (0x10 ' 0x8) for 16 RVA entries @ 8 bytes each + if (coff.SizeOfOptionalHeader != 0xE0) + return false; + + // Read PE optional header + pe = ReadObject(); + + // Ensure IMAGE_NT_OPTIONAL_HDR32_MAGIC (32-bit) + if (pe.signature != 0x10B) + return false; + + // Get IAT + var IATStart = pe.DataDirectory[12].VirtualAddress; + var IATSize = pe.DataDirectory[12].Size; + + // Get sections table + sections = ReadArray(coff.NumberOfSections); + + // Confirm that .rdata section begins at same place as IAT + var rData = sections.First(x => x.Name == ".rdata"); + if (rData.BaseMemory != IATStart) + return false; + + // Calculate start of function pointer table + pFuncTable = rData.BaseImage + IATSize + 8; + GlobalOffset = pe.ImageBase; + return true; + } + + public override uint[] GetSearchLocations() { + Position = pFuncTable; + var addrs = new List(); + uint addr; + while ((addr = ReadUInt32()) != 0) + addrs.Add(MapVATR(addr) & 0xfffffffc); + return addrs.ToArray(); + } + + public override uint MapVATR(uint uiAddr) { + if (uiAddr == 0) + return 0; + + var section = sections.First(x => uiAddr - GlobalOffset >= x.BaseMemory && + uiAddr - GlobalOffset < x.BaseMemory + x.SizeMemory); + return uiAddr - section.BaseMemory - GlobalOffset + section.BaseImage; + } + } +} diff --git a/README.md b/README.md index 4a18c0a..f3d6b19 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,45 @@ # Il2CppInspector -Extract types, methods, properties and fields from Unity IL2CPP binaries +Extract types, methods, properties and fields from Unity IL2CPP binaries. + +* Supports ELF (Android .so) and PE (Windows .exe) file formats +* Supports ARM, ARMv7 Thumb (T1) and x86 architectures regardless of file format +* Supports metadata versions 21 and 22 +* No manual reverse-engineering required; all data is calculated automatically +* **Il2CppInspector** re-usable class library + +Targets .NET Standard 1.5 / .NET Core 1.1. Built with Visual Studio 2017. + +### Usage + +``` +dotnet run [ [ []]] +``` + +Defaults if not specified: + +- _binary-file_ - searches for `libil2cpp.so` and `GameAssembly.dll` +- _metadata-file_ - `global-metadata.dat` +- _output-file_ - `types.cs` + +File format and architecture are automatically detected. + +### Help with iOS support + +Mach-O (iOS) file format is not currently supported. Please contact me via the contact form at http://www.djkaty.com if you have a rooted iOS device and can produce cracked IPA files. + +### Acknowledgements + +Thanks to the following individuals whose code and research helped me develop this tool: + +- Perfare - https://github.com/Perfare/Il2CppDumper +- Jumboperson - https://github.com/Jumboperson/Il2CppDumper +- nevermoe - https://github.com/nevermoe/unity_metadata_loader +- branw - https://github.com/branw/pogo-proto-dumper +- fry - https://github.com/fry/d3 +- ARMConvertor - http://armconverter.com + +This tool uses Perfare's Il2CppDumper code as a base. + +### License + +All rights reserved. Unauthorized use, re-use or the creation of derivative works of this code for commercial purposes whether directly or indirectly is strictly prohibited. Use, re-use or the creation of derivative works for non-commercial purposes is expressly permitted.