ELF: Enable rebasing (for dumped memory images)
This commit is contained in:
@@ -11,6 +11,7 @@ using System.IO;
|
|||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
|
using NoisyCowStudios.Bin2Object;
|
||||||
|
|
||||||
namespace Il2CppInspector
|
namespace Il2CppInspector
|
||||||
{
|
{
|
||||||
@@ -112,7 +113,7 @@ namespace Il2CppInspector
|
|||||||
private Dictionary<string, elf_shdr<TWord>> sectionByName = new Dictionary<string, elf_shdr<TWord>>();
|
private Dictionary<string, elf_shdr<TWord>> sectionByName = new Dictionary<string, elf_shdr<TWord>>();
|
||||||
private List<(uint Start, uint End)> reverseMapExclusions = new List<(uint Start, uint End)>();
|
private List<(uint Start, uint End)> reverseMapExclusions = new List<(uint Start, uint End)>();
|
||||||
private bool preferPHT = false;
|
private bool preferPHT = false;
|
||||||
private bool isDumpedImage = false;
|
private bool isMemoryImage = false;
|
||||||
|
|
||||||
public ElfReader(Stream stream) : base(stream) { }
|
public ElfReader(Stream stream) : base(stream) { }
|
||||||
|
|
||||||
@@ -153,6 +154,10 @@ namespace Il2CppInspector
|
|||||||
if ((Elf) elf_header.m_arch != ArchClass)
|
if ((Elf) elf_header.m_arch != ArchClass)
|
||||||
return false;
|
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
|
// Get PHT and SHT
|
||||||
program_header_table = ReadArray<TPHdr>(conv.Long(elf_header.e_phoff), elf_header.e_phnum);
|
program_header_table = ReadArray<TPHdr>(conv.Long(elf_header.e_phoff), elf_header.e_phnum);
|
||||||
section_header_table = ReadArray<elf_shdr<TWord>>(conv.Long(elf_header.e_shoff), elf_header.e_shnum);
|
section_header_table = ReadArray<elf_shdr<TWord>>(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
|
// No sections that map into memory - this is probably a dumped image
|
||||||
if (!shtShouldBeOrdered.Any()) {
|
if (!shtShouldBeOrdered.Any()) {
|
||||||
Console.WriteLine("ELF binary appears to be a dumped memory image");
|
Console.WriteLine("ELF binary appears to be a dumped memory image");
|
||||||
isDumpedImage = true;
|
isMemoryImage = true;
|
||||||
preferPHT = true;
|
preferPHT = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -197,10 +202,25 @@ namespace Il2CppInspector
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Dumped images must be rebased
|
// Dumped images must be rebased
|
||||||
if (isDumpedImage && !(LoadOptions?.ImageBase is ulong newImageBase)) {
|
if (isMemoryImage) {
|
||||||
throw new InvalidOperationException("To load a dumped ELF image, you must specify the image base virtual address");
|
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<elf_dynamic<TWord>>(conv.Long(PT_DYNAMIC.p_offset), (int) (conv.Long(PT_DYNAMIC.p_filesz) / Sizeof(typeof(elf_dynamic<TWord>))));
|
||||||
|
|
||||||
|
// 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
|
// Get section name mappings if there are any
|
||||||
// This is currently only used to defeat the XOR obfuscation handled below
|
// 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
|
// 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<elf_dynamic<TWord>>(conv.Long(PT_DYNAMIC.p_offset), (int) (conv.Long(PT_DYNAMIC.p_filesz) / Sizeof(typeof(elf_dynamic<TWord>))));
|
|
||||||
|
|
||||||
// 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?)
|
// Find all relocations; target address => (rela header (rels are converted to rela), symbol table base address, is rela?)
|
||||||
var rels = new HashSet<ElfReloc>();
|
var rels = new HashSet<ElfReloc>();
|
||||||
|
|
||||||
@@ -262,10 +274,6 @@ namespace Il2CppInspector
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Process relocations
|
// 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);
|
using var writer = new BinaryWriter(BaseStream, Encoding.Default, true);
|
||||||
var relsz = Sizeof(typeof(TSym));
|
var relsz = Sizeof(typeof(TSym));
|
||||||
|
|
||||||
@@ -536,8 +544,42 @@ namespace Il2CppInspector
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public override Dictionary<string, Symbol> GetSymbolTable() => symbolTable;
|
// Rebase the image to a new virtual address
|
||||||
public override IEnumerable<Export> GetExports() => exports;
|
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<elf_dynamic<TWord>>(conv.Long(PT_DYNAMIC.p_offset), (int) (conv.Long(PT_DYNAMIC.p_filesz) / Sizeof(typeof(elf_dynamic<TWord>))));
|
||||||
|
|
||||||
|
// 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() {
|
private void processSymbols() {
|
||||||
StatusUpdate("Processing symbols");
|
StatusUpdate("Processing symbols");
|
||||||
@@ -604,9 +646,14 @@ namespace Il2CppInspector
|
|||||||
exports = exportTable.Values.ToList();
|
exports = exportTable.Values.ToList();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public override Dictionary<string, Symbol> GetSymbolTable() => symbolTable;
|
||||||
|
public override IEnumerable<Export> GetExports() => exports;
|
||||||
|
|
||||||
public override uint[] GetFunctionTable() {
|
public override uint[] GetFunctionTable() {
|
||||||
// INIT_ARRAY contains a list of pointers to initialization functions (not all functions in the binary)
|
// INIT_ARRAY contains a list of pointers to initialization functions (not all functions in the binary)
|
||||||
// INIT_ARRAYSZ contains the size of INIT_ARRAY
|
// INIT_ARRAYSZ contains the size of INIT_ARRAY
|
||||||
|
if (getDynamic(Elf.DT_INIT_ARRAY) == null || getDynamic(Elf.DT_INIT_ARRAYSZ) == null)
|
||||||
|
return Array.Empty<uint>();
|
||||||
|
|
||||||
var init = MapVATR(conv.ULong(getDynamic(Elf.DT_INIT_ARRAY).d_un));
|
var init = MapVATR(conv.ULong(getDynamic(Elf.DT_INIT_ARRAY).d_un));
|
||||||
var size = getDynamic(Elf.DT_INIT_ARRAYSZ).d_un;
|
var size = getDynamic(Elf.DT_INIT_ARRAYSZ).d_un;
|
||||||
|
|||||||
@@ -32,7 +32,6 @@ namespace Il2CppInspector
|
|||||||
// PHTs
|
// PHTs
|
||||||
PT_LOAD = 1,
|
PT_LOAD = 1,
|
||||||
PT_DYNAMIC = 2,
|
PT_DYNAMIC = 2,
|
||||||
DT_PLTGOT = 3,
|
|
||||||
|
|
||||||
PF_X = 1,
|
PF_X = 1,
|
||||||
PF_W = 2,
|
PF_W = 2,
|
||||||
@@ -68,17 +67,27 @@ namespace Il2CppInspector
|
|||||||
SHF_EXECINSTR = 4,
|
SHF_EXECINSTR = 4,
|
||||||
|
|
||||||
// dynamic sections
|
// dynamic sections
|
||||||
|
DT_PLTGOT = 3,
|
||||||
|
DT_HASH = 4,
|
||||||
DT_STRTAB = 5,
|
DT_STRTAB = 5,
|
||||||
DT_SYMTAB = 6,
|
DT_SYMTAB = 6,
|
||||||
DT_RELA = 7,
|
DT_RELA = 7,
|
||||||
DT_RELASZ = 8,
|
DT_RELASZ = 8,
|
||||||
DT_RELAENT = 9,
|
DT_RELAENT = 9,
|
||||||
DT_INIT = 12,
|
DT_INIT = 12,
|
||||||
|
DT_FINI = 13,
|
||||||
DT_REL = 17,
|
DT_REL = 17,
|
||||||
DT_RELSZ = 18,
|
DT_RELSZ = 18,
|
||||||
DT_RELENT = 19,
|
DT_RELENT = 19,
|
||||||
|
DT_JMPREL = 23,
|
||||||
DT_INIT_ARRAY = 25,
|
DT_INIT_ARRAY = 25,
|
||||||
|
DT_FINI_ARRAY = 26,
|
||||||
DT_INIT_ARRAYSZ = 27,
|
DT_INIT_ARRAYSZ = 27,
|
||||||
|
DT_PREINIT_ARRAY = 32,
|
||||||
|
DT_MOVETAB = 0x6ffffefe,
|
||||||
|
DT_VERDEF = 0x6ffffffc,
|
||||||
|
DT_VERNEED = 0x6ffffffe,
|
||||||
|
DT_SYMINFO = 0x6ffffeff,
|
||||||
|
|
||||||
// relocation types
|
// relocation types
|
||||||
R_ARM_ABS32 = 2,
|
R_ARM_ABS32 = 2,
|
||||||
@@ -152,19 +161,18 @@ namespace Il2CppInspector
|
|||||||
internal interface Ielf_phdr<TWord> where TWord : struct
|
internal interface Ielf_phdr<TWord> where TWord : struct
|
||||||
{
|
{
|
||||||
uint p_type { get; }
|
uint p_type { get; }
|
||||||
TWord p_offset { get; }
|
TWord p_offset { get; set; }
|
||||||
TWord p_filesz { get; }
|
TWord p_filesz { get; set; }
|
||||||
TWord p_memsz { get; }
|
TWord p_memsz { get; }
|
||||||
TWord p_vaddr { get; }
|
TWord p_vaddr { get; set; }
|
||||||
uint p_flags { get; }
|
uint p_flags { get; }
|
||||||
}
|
}
|
||||||
|
|
||||||
internal class elf_32_phdr : Ielf_phdr<uint>
|
internal class elf_32_phdr : Ielf_phdr<uint> {
|
||||||
{
|
|
||||||
public uint p_type => f_p_type;
|
public uint p_type => f_p_type;
|
||||||
public uint p_offset => f_p_offset;
|
public uint p_offset { get => f_p_offset; set => f_p_offset = value; }
|
||||||
public uint p_filesz => f_p_filesz;
|
public uint p_filesz { get => f_p_filesz; set => f_p_filesz = value; }
|
||||||
public uint p_vaddr => f_p_vaddr;
|
public uint p_vaddr { get => f_p_vaddr; set => f_p_vaddr = value; }
|
||||||
public uint p_flags => f_p_flags;
|
public uint p_flags => f_p_flags;
|
||||||
public uint p_memsz => f_p_memsz;
|
public uint p_memsz => f_p_memsz;
|
||||||
|
|
||||||
@@ -181,10 +189,10 @@ namespace Il2CppInspector
|
|||||||
internal class elf_64_phdr : Ielf_phdr<ulong>
|
internal class elf_64_phdr : Ielf_phdr<ulong>
|
||||||
{
|
{
|
||||||
public uint p_type => f_p_type;
|
public uint p_type => f_p_type;
|
||||||
public ulong p_offset => f_p_offset;
|
public ulong p_offset { get => f_p_offset; set => f_p_offset = value; }
|
||||||
public ulong p_filesz => f_p_filesz;
|
public ulong p_filesz { get => f_p_filesz; set => f_p_filesz = value; }
|
||||||
public ulong p_memsz => f_p_memsz;
|
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 p_flags => f_p_flags;
|
||||||
|
|
||||||
public uint f_p_type;
|
public uint f_p_type;
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ namespace Il2CppInspector
|
|||||||
TWord Div(TWord a, TWord b);
|
TWord Div(TWord a, TWord b);
|
||||||
TWord Div(TWord a, int b);
|
TWord Div(TWord a, int b);
|
||||||
TWord FromUInt(uint a);
|
TWord FromUInt(uint a);
|
||||||
|
TWord FromULong(ulong a);
|
||||||
int Int(TWord a);
|
int Int(TWord a);
|
||||||
long Long(TWord a);
|
long Long(TWord a);
|
||||||
ulong ULong(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, uint b) => a / b;
|
||||||
public uint Div(uint a, int b) => a / (uint)b;
|
public uint Div(uint a, int b) => a / (uint)b;
|
||||||
public uint FromUInt(uint a) => a;
|
public uint FromUInt(uint a) => a;
|
||||||
|
public uint FromULong(ulong a) => (uint) a;
|
||||||
public int Int(uint a) => (int)a;
|
public int Int(uint a) => (int)a;
|
||||||
public long Long(uint a) => a;
|
public long Long(uint a) => a;
|
||||||
public ulong ULong(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, ulong b) => a / b;
|
||||||
public ulong Div(ulong a, int b) => a / (uint)b;
|
public ulong Div(ulong a, int b) => a / (uint)b;
|
||||||
public ulong FromUInt(uint a) => a;
|
public ulong FromUInt(uint a) => a;
|
||||||
|
public ulong FromULong(ulong a) => a;
|
||||||
public int Int(ulong a) => (int)a;
|
public int Int(ulong a) => (int)a;
|
||||||
public long Long(ulong a) => (long)a;
|
public long Long(ulong a) => (long)a;
|
||||||
public ulong ULong(ulong a) => a;
|
public ulong ULong(ulong a) => a;
|
||||||
|
|||||||
Reference in New Issue
Block a user