ELF: Enable rebasing (for dumped memory images)

This commit is contained in:
Katy Coe
2020-12-12 05:25:00 +01:00
parent 8cdc8c8850
commit 6a46b76af2
3 changed files with 88 additions and 30 deletions

View File

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

View File

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

View File

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