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.Reflection;
using System.Text;
using NoisyCowStudios.Bin2Object;
namespace Il2CppInspector
{
@@ -112,7 +113,7 @@ namespace Il2CppInspector
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 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<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);
@@ -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)) {
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<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
// 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<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?)
var rels = new HashSet<ElfReloc>();
@@ -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<string, Symbol> GetSymbolTable() => symbolTable;
public override IEnumerable<Export> 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<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() {
StatusUpdate("Processing symbols");
@@ -604,9 +646,14 @@ namespace Il2CppInspector
exports = exportTable.Values.ToList();
}
public override Dictionary<string, Symbol> GetSymbolTable() => symbolTable;
public override IEnumerable<Export> 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<uint>();
var init = MapVATR(conv.ULong(getDynamic(Elf.DT_INIT_ARRAY).d_un));
var size = getDynamic(Elf.DT_INIT_ARRAYSZ).d_un;

View File

@@ -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<TWord> 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<uint>
{
internal class elf_32_phdr : Ielf_phdr<uint> {
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<ulong>
{
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;

View File

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