diff --git a/Il2CppInspector.Common/FileFormatReaders/ElfReader.cs b/Il2CppInspector.Common/FileFormatReaders/ElfReader.cs index 7c0d831..28ea422 100644 --- a/Il2CppInspector.Common/FileFormatReaders/ElfReader.cs +++ b/Il2CppInspector.Common/FileFormatReaders/ElfReader.cs @@ -11,6 +11,7 @@ using System.IO; using System.Linq; using System.Reflection; using System.Text; +using NoisyCowStudios.Bin2Object; namespace Il2CppInspector { @@ -112,7 +113,7 @@ namespace Il2CppInspector private Dictionary> sectionByName = new Dictionary>(); private List<(uint Start, uint End)> reverseMapExclusions = new List<(uint Start, uint End)>(); private bool preferPHT = false; - private bool isDumpedImage = false; + private bool isMemoryImage = false; public ElfReader(Stream stream) : base(stream) { } @@ -153,6 +154,10 @@ namespace Il2CppInspector if ((Elf) elf_header.m_arch != ArchClass) return false; + // Relocations and rebasing will modify the stream - ensure it is non-destructive + if (!(BaseStream is MemoryStream)) + throw new InvalidOperationException("Input stream to ElfReader must be a MemoryStream."); + // Get PHT and SHT program_header_table = ReadArray(conv.Long(elf_header.e_phoff), elf_header.e_phnum); section_header_table = ReadArray>(conv.Long(elf_header.e_shoff), elf_header.e_shnum); @@ -182,7 +187,7 @@ namespace Il2CppInspector // No sections that map into memory - this is probably a dumped image if (!shtShouldBeOrdered.Any()) { Console.WriteLine("ELF binary appears to be a dumped memory image"); - isDumpedImage = true; + isMemoryImage = true; preferPHT = true; } @@ -197,10 +202,25 @@ namespace Il2CppInspector } // Dumped images must be rebased - if (isDumpedImage && !(LoadOptions?.ImageBase is ulong newImageBase)) { - throw new InvalidOperationException("To load a dumped ELF image, you must specify the image base virtual address"); + if (isMemoryImage) { + if (!(LoadOptions?.ImageBase is ulong newImageBase)) + throw new InvalidOperationException("To load a dumped ELF image, you must specify the image base virtual address"); + + rebase(conv.FromULong(newImageBase)); } + // Get dynamic table if it exists (must be done after rebasing) + if (getProgramHeader(Elf.PT_DYNAMIC) is TPHdr PT_DYNAMIC) + dynamic_table = ReadArray>(conv.Long(PT_DYNAMIC.p_offset), (int) (conv.Long(PT_DYNAMIC.p_filesz) / Sizeof(typeof(elf_dynamic)))); + + // Get offset of code section + var codeSegment = program_header_table.First(x => ((Elf) x.p_flags & Elf.PF_X) == Elf.PF_X); + GlobalOffset = conv.ULong(conv.Sub(codeSegment.p_vaddr, codeSegment.p_offset)); + + // Nothing more to do if the image is a memory dump (no section names, relocations or decryption) + if (isMemoryImage) + return true; + // Get section name mappings if there are any // This is currently only used to defeat the XOR obfuscation handled below // Note: There can be more than one section with the same name, or unnamed; we take the first section with a given name @@ -212,14 +232,6 @@ namespace Il2CppInspector } } - // Get dynamic table if it exists - if (getProgramHeader(Elf.PT_DYNAMIC) is TPHdr PT_DYNAMIC) - dynamic_table = ReadArray>(conv.Long(PT_DYNAMIC.p_offset), (int) (conv.Long(PT_DYNAMIC.p_filesz) / Sizeof(typeof(elf_dynamic)))); - - // Get offset of code section - var codeSegment = program_header_table.First(x => ((Elf) x.p_flags & Elf.PF_X) == Elf.PF_X); - GlobalOffset = conv.ULong(conv.Sub(codeSegment.p_vaddr, codeSegment.p_offset)); - // Find all relocations; target address => (rela header (rels are converted to rela), symbol table base address, is rela?) var rels = new HashSet(); @@ -262,10 +274,6 @@ namespace Il2CppInspector } // Process relocations - // WARNING: This modifies the stream passed in the constructor - if (BaseStream is FileStream) - throw new InvalidOperationException("Input stream to ElfReader is a file. Please supply a mutable stream source."); - using var writer = new BinaryWriter(BaseStream, Encoding.Default, true); var relsz = Sizeof(typeof(TSym)); @@ -536,8 +544,42 @@ namespace Il2CppInspector } } - public override Dictionary GetSymbolTable() => symbolTable; - public override IEnumerable GetExports() => exports; + // Rebase the image to a new virtual address + private void rebase(TWord imageBase) { + // Rebase PHT + foreach (var segment in program_header_table) { + segment.p_offset = segment.p_vaddr; + segment.p_vaddr = conv.Add(segment.p_vaddr, imageBase); + segment.p_filesz = segment.p_memsz; + } + + // Rewrite to stream + using var writer = new BinaryObjectWriter(BaseStream, Endianness, true); + writer.WriteArray(conv.Long(elf_header.e_phoff), program_header_table); + IsModified = true; + + // Rebase dynamic table if it exists + // Note we have to rebase the PHT first to get the correct location to read this + if (!(getProgramHeader(Elf.PT_DYNAMIC) is TPHdr PT_DYNAMIC)) + return; + + var dt = ReadArray>(conv.Long(PT_DYNAMIC.p_offset), (int) (conv.Long(PT_DYNAMIC.p_filesz) / Sizeof(typeof(elf_dynamic)))); + + // Every table containing virtual address pointers + // https://docs.oracle.com/cd/E19683-01/817-3677/chapter6-42444/index.html + var tablesToRebase = new [] { + Elf.DT_PLTGOT, Elf.DT_HASH, Elf.DT_STRTAB, Elf.DT_SYMTAB, Elf.DT_RELA, + Elf.DT_INIT, Elf.DT_FINI, Elf.DT_REL, Elf.DT_JMPREL, Elf.DT_INIT_ARRAY, Elf.DT_FINI_ARRAY, + Elf.DT_PREINIT_ARRAY, Elf.DT_MOVETAB, Elf.DT_VERDEF, Elf.DT_VERNEED, Elf.DT_SYMINFO + }; + + // Rebase dynamic tables + foreach (var section in dt.Where(x => tablesToRebase.Contains((Elf) conv.ULong(x.d_tag)))) + section.d_un = conv.Add(section.d_un, imageBase); + + // Rewrite to stream + writer.WriteArray(conv.Long(PT_DYNAMIC.p_offset), dt); + } private void processSymbols() { StatusUpdate("Processing symbols"); @@ -604,9 +646,14 @@ namespace Il2CppInspector exports = exportTable.Values.ToList(); } + public override Dictionary GetSymbolTable() => symbolTable; + public override IEnumerable GetExports() => exports; + public override uint[] GetFunctionTable() { // INIT_ARRAY contains a list of pointers to initialization functions (not all functions in the binary) // INIT_ARRAYSZ contains the size of INIT_ARRAY + if (getDynamic(Elf.DT_INIT_ARRAY) == null || getDynamic(Elf.DT_INIT_ARRAYSZ) == null) + return Array.Empty(); var init = MapVATR(conv.ULong(getDynamic(Elf.DT_INIT_ARRAY).d_un)); var size = getDynamic(Elf.DT_INIT_ARRAYSZ).d_un; diff --git a/Il2CppInspector.Common/FileFormatReaders/FormatLayouts/Elf.cs b/Il2CppInspector.Common/FileFormatReaders/FormatLayouts/Elf.cs index 8134e9d..94965ed 100644 --- a/Il2CppInspector.Common/FileFormatReaders/FormatLayouts/Elf.cs +++ b/Il2CppInspector.Common/FileFormatReaders/FormatLayouts/Elf.cs @@ -32,7 +32,6 @@ namespace Il2CppInspector // PHTs PT_LOAD = 1, PT_DYNAMIC = 2, - DT_PLTGOT = 3, PF_X = 1, PF_W = 2, @@ -68,17 +67,27 @@ namespace Il2CppInspector SHF_EXECINSTR = 4, // dynamic sections + DT_PLTGOT = 3, + DT_HASH = 4, DT_STRTAB = 5, DT_SYMTAB = 6, DT_RELA = 7, DT_RELASZ = 8, DT_RELAENT = 9, DT_INIT = 12, + DT_FINI = 13, DT_REL = 17, DT_RELSZ = 18, DT_RELENT = 19, + DT_JMPREL = 23, DT_INIT_ARRAY = 25, + DT_FINI_ARRAY = 26, DT_INIT_ARRAYSZ = 27, + DT_PREINIT_ARRAY = 32, + DT_MOVETAB = 0x6ffffefe, + DT_VERDEF = 0x6ffffffc, + DT_VERNEED = 0x6ffffffe, + DT_SYMINFO = 0x6ffffeff, // relocation types R_ARM_ABS32 = 2, @@ -152,19 +161,18 @@ namespace Il2CppInspector internal interface Ielf_phdr where TWord : struct { uint p_type { get; } - TWord p_offset { get; } - TWord p_filesz { get; } + TWord p_offset { get; set; } + TWord p_filesz { get; set; } TWord p_memsz { get; } - TWord p_vaddr { get; } + TWord p_vaddr { get; set; } uint p_flags { get; } } - internal class elf_32_phdr : Ielf_phdr - { + internal class elf_32_phdr : Ielf_phdr { public uint p_type => f_p_type; - public uint p_offset => f_p_offset; - public uint p_filesz => f_p_filesz; - public uint p_vaddr => f_p_vaddr; + public uint p_offset { get => f_p_offset; set => f_p_offset = value; } + public uint p_filesz { get => f_p_filesz; set => f_p_filesz = value; } + public uint p_vaddr { get => f_p_vaddr; set => f_p_vaddr = value; } public uint p_flags => f_p_flags; public uint p_memsz => f_p_memsz; @@ -181,10 +189,10 @@ namespace Il2CppInspector internal class elf_64_phdr : Ielf_phdr { public uint p_type => f_p_type; - public ulong p_offset => f_p_offset; - public ulong p_filesz => f_p_filesz; + public ulong p_offset { get => f_p_offset; set => f_p_offset = value; } + public ulong p_filesz { get => f_p_filesz; set => f_p_filesz = value; } public ulong p_memsz => f_p_memsz; - public ulong p_vaddr => f_p_vaddr; + public ulong p_vaddr { get => f_p_vaddr; set => f_p_vaddr = value; } public uint p_flags => f_p_flags; public uint f_p_type; diff --git a/Il2CppInspector.Common/FileFormatReaders/WordConversions.cs b/Il2CppInspector.Common/FileFormatReaders/WordConversions.cs index f56f3f2..21089bb 100644 --- a/Il2CppInspector.Common/FileFormatReaders/WordConversions.cs +++ b/Il2CppInspector.Common/FileFormatReaders/WordConversions.cs @@ -17,6 +17,7 @@ namespace Il2CppInspector TWord Div(TWord a, TWord b); TWord Div(TWord a, int b); TWord FromUInt(uint a); + TWord FromULong(ulong a); int Int(TWord a); long Long(TWord a); ulong ULong(TWord a); @@ -31,6 +32,7 @@ namespace Il2CppInspector public uint Div(uint a, uint b) => a / b; public uint Div(uint a, int b) => a / (uint)b; public uint FromUInt(uint a) => a; + public uint FromULong(ulong a) => (uint) a; public int Int(uint a) => (int)a; public long Long(uint a) => a; public ulong ULong(uint a) => a; @@ -45,6 +47,7 @@ namespace Il2CppInspector public ulong Div(ulong a, ulong b) => a / b; public ulong Div(ulong a, int b) => a / (uint)b; public ulong FromUInt(uint a) => a; + public ulong FromULong(ulong a) => a; public int Int(ulong a) => (int)a; public long Long(ulong a) => (long)a; public ulong ULong(ulong a) => a;