Refactor solution layout

This commit is contained in:
Katy Coe
2020-02-06 02:51:42 +01:00
parent 66b8e30586
commit e971cb8502
49 changed files with 72 additions and 50 deletions

View File

@@ -0,0 +1,318 @@
/*
Copyright 2017 Perfare - https://github.com/Perfare/Il2CppDumper
Copyright 2017-2020 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;
using System.Reflection;
namespace Il2CppInspector
{
internal class ElfReader32 : ElfReader<uint, elf_32_phdr, elf_32_sym, ElfReader32, Convert32>
{
public ElfReader32(Stream stream) : base(stream) {
ElfReloc.GetRelocType = info => (Elf) (info & 0xff);
ElfReloc.GetSymbolIndex = info => info >> 8;
}
public override int Bits => 32;
protected override Elf ArchClass => Elf.ELFCLASS32;
protected override void Write(BinaryWriter writer, uint value) => writer.Write(value);
}
internal class ElfReader64 : ElfReader<ulong, elf_64_phdr, elf_64_sym, ElfReader64, Convert64>
{
public ElfReader64(Stream stream) : base(stream) {
ElfReloc.GetRelocType = info => (Elf) (info & 0xffff_ffff);
ElfReloc.GetSymbolIndex = info => info >> 32;
}
public override int Bits => 64;
protected override Elf ArchClass => Elf.ELFCLASS64;
protected override void Write(BinaryWriter writer, ulong value) => writer.Write(value);
}
interface IElfReader
{
uint GetPLTAddress();
}
internal abstract class ElfReader<TWord, TPHdr, TSym, TReader, TConvert> : FileFormatReader<TReader>, IElfReader
where TWord : struct
where TPHdr : Ielf_phdr<TWord>, new()
where TSym : Ielf_sym<TWord>, new()
where TConvert : IWordConverter<TWord>, new()
where TReader : FileFormatReader<TReader>
{
private readonly TConvert conv = new TConvert();
// Internal relocation entry helper
protected class ElfReloc
{
public Elf Type;
public TWord Offset;
public TWord? Addend;
public TWord SymbolTable;
public TWord SymbolIndex;
// Equality based on target address
public override bool Equals(object obj) => obj is ElfReloc reloc && Equals(reloc);
public bool Equals(ElfReloc other) {
return Offset.Equals(other.Offset);
}
public override int GetHashCode() => Offset.GetHashCode();
// Cast operators (makes the below code MUCH easier to read)
public ElfReloc(elf_rel<TWord> rel, TWord symbolTable) {
Offset = rel.r_offset;
Addend = null;
Type = GetRelocType(rel.r_info);
SymbolIndex = GetSymbolIndex(rel.r_info);
SymbolTable = symbolTable;
}
public ElfReloc(elf_rela<TWord> rela, TWord symbolTable)
: this(new elf_rel<TWord> { r_info = rela.r_info, r_offset = rela.r_offset }, symbolTable) =>
Addend = rela.r_addend;
public static Func<TWord, Elf> GetRelocType;
public static Func<TWord, TWord> GetSymbolIndex;
}
// See also: https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/operators/sizeof
private int Sizeof(Type type) {
int size = 0;
foreach (var i in type.GetTypeInfo().GetFields()) {
if (i.FieldType == typeof(byte) || i.FieldType == typeof(sbyte))
size += sizeof(byte);
if (i.FieldType == typeof(long) || i.FieldType == typeof(ulong))
size += sizeof(ulong);
if (i.FieldType == typeof(int) || i.FieldType == typeof(uint))
size += sizeof(uint);
if (i.FieldType == typeof(short) || i.FieldType == typeof(ushort))
size += sizeof(ushort);
}
return size;
}
private TPHdr[] program_header_table;
private elf_shdr<TWord>[] section_header_table;
private elf_dynamic<TWord>[] dynamic_table;
private elf_header<TWord> elf_header;
public ElfReader(Stream stream) : base(stream) { }
public override string Format => Bits == 32 ? "ELF" : "ELF64";
public override string Arch => (Elf) elf_header.e_machine switch {
Elf.EM_386 => "x86",
Elf.EM_ARM => "ARM",
Elf.EM_X86_64 => "x64",
Elf.EM_AARCH64 => "ARM64",
_ => "Unsupported"
};
public override int Bits => (elf_header.m_arch == (uint) Elf.ELFCLASS64) ? 64 : 32;
private elf_shdr<TWord> getSection(Elf sectionIndex) => section_header_table.FirstOrDefault(x => x.sh_type == (uint) sectionIndex);
private IEnumerable<elf_shdr<TWord>> getSections(Elf sectionIndex) => section_header_table.Where(x => x.sh_type == (uint) sectionIndex);
private TPHdr getProgramHeader(Elf programIndex) => program_header_table.FirstOrDefault(x => x.p_type == (uint) programIndex);
private elf_dynamic<TWord> getDynamic(Elf dynamicIndex) => dynamic_table?.FirstOrDefault(x => (Elf) conv.ULong(x.d_tag) == dynamicIndex);
protected abstract Elf ArchClass { get; }
protected abstract void Write(BinaryWriter writer, TWord value);
protected override bool Init() {
elf_header = ReadObject<elf_header<TWord>>();
// Check for magic bytes
if ((Elf) elf_header.m_dwFormat != Elf.ELFMAG)
return false;
// 64-bit not supported
if ((Elf) elf_header.m_arch != ArchClass)
return false;
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);
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>();
// Two types: add value from offset in image, and add value from specified addend
foreach (var relSection in getSections(Elf.SHT_REL))
rels.UnionWith(
from rel in ReadArray<elf_rel<TWord>>(conv.Long(relSection.sh_offset), conv.Int(conv.Div(relSection.sh_size, relSection.sh_entsize)))
select new ElfReloc(rel, section_header_table[relSection.sh_link].sh_offset));
foreach (var relaSection in getSections(Elf.SHT_RELA))
rels.UnionWith(
from rela in ReadArray<elf_rela<TWord>>(conv.Long(relaSection.sh_offset), conv.Int(conv.Div(relaSection.sh_size, relaSection.sh_entsize)))
select new ElfReloc(rela, section_header_table[relaSection.sh_link].sh_offset));
// Relocations in dynamic section
if (getDynamic(Elf.DT_REL) is elf_dynamic<TWord> dt_rel) {
var dt_rel_count = conv.Div(getDynamic(Elf.DT_RELSZ).d_un, getDynamic(Elf.DT_RELENT).d_un);
var dt_rel_list = ReadArray<elf_rel<TWord>>(MapVATR(conv.ULong(dt_rel.d_un)), conv.Int(dt_rel_count));
var dt_symtab = getDynamic(Elf.DT_SYMTAB).d_un;
rels.UnionWith(from rel in dt_rel_list select new ElfReloc(rel, dt_symtab));
}
if (getDynamic(Elf.DT_RELA) is elf_dynamic<TWord> dt_rela) {
var dt_rela_count = conv.Div(getDynamic(Elf.DT_RELASZ).d_un, getDynamic(Elf.DT_RELAENT).d_un);
var dt_rela_list = ReadArray<elf_rela<TWord>>(MapVATR(conv.ULong(dt_rela.d_un)), conv.Int(dt_rela_count));
var dt_symtab = getDynamic(Elf.DT_SYMTAB).d_un;
rels.UnionWith(from rela in dt_rela_list select new ElfReloc(rela, dt_symtab));
}
// 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.");
var writer = new BinaryWriter(BaseStream);
var relsz = Sizeof(typeof(TSym));
foreach (var rel in rels) {
var symValue = ReadObject<TSym>(conv.Long(rel.SymbolTable) + conv.Long(rel.SymbolIndex) * relsz).st_value; // S
// Ignore relocations into memory addresses not mapped from the image
try {
Position = MapVATR(conv.ULong(rel.Offset));
}
catch (InvalidOperationException) {
continue;
}
// The addend is specified in the struct for rela, and comes from the target location for rel
var addend = rel.Addend ?? ReadObject<TWord>(); // A
// Only handle relocation types we understand, skip the rest
// Relocation types from https://docs.oracle.com/cd/E23824_01/html/819-0690/chapter6-54839.html#scrolltoc
// and https://studfiles.net/preview/429210/page:18/
// and http://infocenter.arm.com/help/topic/com.arm.doc.ihi0056b/IHI0056B_aaelf64.pdf (AArch64)
(TWord newValue, bool recognized) result = (rel.Type, (Elf) elf_header.e_machine) switch {
(Elf.R_ARM_ABS32, Elf.EM_ARM) => (conv.Add(symValue, addend), true), // S + A
(Elf.R_ARM_REL32, Elf.EM_ARM) => (conv.Add(conv.Sub(symValue, rel.Offset), addend), true), // S - P + A
(Elf.R_ARM_COPY, Elf.EM_ARM) => (symValue, true), // S
(Elf.R_AARCH64_ABS64, Elf.EM_AARCH64) => (conv.Add(symValue, addend), true), // S + A
(Elf.R_AARCH64_PREL64, Elf.EM_AARCH64) => (conv.Sub(conv.Add(symValue, addend), rel.Offset), true), // S + A - P
(Elf.R_AARCH64_GLOB_DAT, Elf.EM_AARCH64) => (conv.Add(symValue, addend), true), // S + A
(Elf.R_AARCH64_JUMP_SLOT, Elf.EM_AARCH64) => (conv.Add(symValue, addend), true), // S + A
(Elf.R_AARCH64_RELATIVE, Elf.EM_AARCH64) => (conv.Add(symValue, addend), true), // Delta(S) + A
(Elf.R_386_32, Elf.EM_386) => (conv.Add(symValue, addend), true), // S + A
(Elf.R_386_PC32, Elf.EM_386) => (conv.Sub(conv.Add(symValue, addend), rel.Offset), true), // S + A - P
(Elf.R_386_GLOB_DAT, Elf.EM_386) => (symValue, true), // S
(Elf.R_386_JMP_SLOT, Elf.EM_386) => (symValue, true), // S
(Elf.R_AMD64_64, Elf.EM_AARCH64) => (conv.Add(symValue, addend), true), // S + A
_ => (default(TWord), false)
};
if (result.recognized) {
Position = MapVATR(conv.ULong(rel.Offset));
Write(writer, result.newValue);
}
}
Console.WriteLine($"Processed {rels.Count} relocations");
return true;
}
public override Dictionary<string, ulong> GetSymbolTable() {
// Three possible symbol tables in ELF files
var pTables = new List<(TWord offset, TWord count, TWord strings)>();
// String table (a sequence of null-terminated strings, total length in sh_size
var SHT_STRTAB = getSection(Elf.SHT_STRTAB);
if (SHT_STRTAB != null) {
// Section header shared object symbol table (.symtab)
if (getSection(Elf.SHT_SYMTAB) is elf_shdr<TWord> SHT_SYMTAB)
pTables.Add((SHT_SYMTAB.sh_offset, conv.Div(SHT_SYMTAB.sh_size, SHT_SYMTAB.sh_entsize), SHT_STRTAB.sh_offset));
// Section header executable symbol table (.dynsym)
if (getSection(Elf.SHT_DYNSYM) is elf_shdr<TWord> SHT_DYNSYM)
pTables.Add((SHT_DYNSYM.sh_offset, conv.Div(SHT_DYNSYM.sh_size, SHT_DYNSYM.sh_entsize), SHT_STRTAB.sh_offset));
}
// Symbol table in dynamic section (DT_SYMTAB)
// Normally the same as .dynsym except that .dynsym may be removed in stripped binaries
// Dynamic string table
if (getDynamic(Elf.DT_STRTAB) is elf_dynamic<TWord> DT_STRTAB) {
if (getDynamic(Elf.DT_SYMTAB) is elf_dynamic<TWord> DT_SYMTAB) {
// Find the next pointer in the dynamic table to calculate the length of the symbol table
var end = (from x in dynamic_table where conv.Gt(x.d_un, DT_SYMTAB.d_un) orderby x.d_un select x).First().d_un;
// Dynamic symbol table
pTables.Add((
conv.FromUInt(MapVATR(conv.ULong(DT_SYMTAB.d_un))),
conv.Div(conv.Sub(end, DT_SYMTAB.d_un), Sizeof(typeof(TSym))),
DT_STRTAB.d_un
));
}
}
// Now iterate through all of the symbol and string tables we found to build a full list
var symbolTable = new Dictionary<string, ulong>();
foreach (var pTab in pTables) {
var symbol_table = ReadArray<TSym>(conv.Long(pTab.offset), conv.Int(pTab.count));
foreach (var symbol in symbol_table) {
var name = ReadNullTerminatedString(conv.Long(pTab.strings) + symbol.st_name);
// Avoid duplicates
symbolTable.TryAdd(name, conv.ULong(symbol.st_value));
}
}
return symbolTable;
}
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
var init = MapVATR(conv.ULong(getDynamic(Elf.DT_INIT_ARRAY).d_un));
var size = getDynamic(Elf.DT_INIT_ARRAYSZ).d_un;
return conv.UIntArray(ReadArray<TWord>(init, conv.Int(size) / (Bits / 8))).Select(x => MapVATR(x)).ToArray();
}
// Map a virtual address to an offset into the image file. Throws an exception if the virtual address is not mapped into the file.
// Note if uiAddr is a valid segment but filesz < memsz and the adjusted uiAddr falls between the range of filesz and memsz,
// an exception will be thrown. This area of memory is assumed to contain all zeroes.
public override uint MapVATR(ulong uiAddr) {
// Additions in the argument to MapVATR may cause an overflow which should be discarded for 32-bit files
if (Bits == 32)
uiAddr &= 0xffff_ffff;
var program_header_table = this.program_header_table.First(x => uiAddr >= conv.ULong(x.p_vaddr) && uiAddr <= conv.ULong(conv.Add(x.p_vaddr, x.p_filesz)));
return (uint) (uiAddr - conv.ULong(conv.Sub(program_header_table.p_vaddr, program_header_table.p_offset)));
}
// Get the address of the procedure linkage table (.got.plt) which is needed for some disassemblies
public uint GetPLTAddress() => (uint) conv.ULong(getDynamic(Elf.DT_PLTGOT).d_un);
}
}

View File

@@ -0,0 +1,153 @@
/*
Copyright 2017-2019 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;
using System.Reflection;
using NoisyCowStudios.Bin2Object;
namespace Il2CppInspector
{
public interface IFileFormatReader
{
BinaryObjectReader Stream { get; }
double Version { get; set; }
long Length { get; }
uint NumImages { get; }
IEnumerable<IFileFormatReader> Images { get; }
IFileFormatReader this[uint index] { get; }
long Position { get; set; }
string Format { get; }
string Arch { get; }
int Bits { get; }
ulong GlobalOffset { get; }
Dictionary<string, ulong> GetSymbolTable();
uint[] GetFunctionTable();
U ReadMappedObject<U>(ulong uiAddr) where U : new();
U[] ReadMappedArray<U>(ulong uiAddr, int count) where U : new();
long[] ReadMappedWordArray(ulong uiAddr, int count);
uint MapVATR(ulong uiAddr);
byte[] ReadBytes(int count);
ulong ReadUInt64();
ulong ReadUInt64(long uiAddr);
uint ReadUInt32();
uint ReadUInt32(long uiAddr);
ushort ReadUInt16();
ushort ReadUInt16(long uiAddr);
byte ReadByte();
byte ReadByte(long uiAddr);
long ReadWord();
long ReadWord(long uiAddr);
U ReadObject<U>() where U : new();
string ReadMappedNullTerminatedString(ulong uiAddr);
List<U> ReadMappedObjectPointerArray<U>(ulong uiAddr, int count) where U : new();
}
internal class FileFormatReader
{
// Helper method to try all defined file formats when the contents of the binary is unknown
public static IFileFormatReader Load(string filename) => Load(new FileStream(filename, FileMode.Open, FileAccess.Read));
public static IFileFormatReader Load(Stream stream) {
var types = Assembly.GetExecutingAssembly().DefinedTypes
.Where(x => x.ImplementedInterfaces.Contains(typeof(IFileFormatReader)) && !x.IsGenericTypeDefinition);
foreach (var type in types) {
if (type.GetMethod("Load", BindingFlags.FlattenHierarchy | BindingFlags.Static | BindingFlags.Public,
null, new [] {typeof(Stream)}, null)
.Invoke(null, new object[] { stream }) is IFileFormatReader loaded)
return loaded;
}
return null;
}
}
internal class FileFormatReader<T> : BinaryObjectReader, IFileFormatReader where T : FileFormatReader<T>
{
public FileFormatReader(Stream stream) : base(stream) { }
public BinaryObjectReader Stream => this;
public long Length => BaseStream.Length;
public uint NumImages { get; protected set; } = 1;
public ulong GlobalOffset { get; protected set; }
public virtual string Format => throw new NotImplementedException();
public virtual string Arch => throw new NotImplementedException();
public virtual int Bits => throw new NotImplementedException();
public IEnumerable<IFileFormatReader> Images {
get {
for (uint i = 0; i < NumImages; i++)
yield return this[i];
}
}
public static T Load(string filename) {
using var stream = new FileStream(filename, FileMode.Open, FileAccess.Read);
return Load(stream);
}
public static T Load(Stream stream) {
// Copy the original stream in case we modify it
var ms = new MemoryStream();
stream.Position = 0;
stream.CopyTo(ms);
ms.Position = 0;
var pe = (T) Activator.CreateInstance(typeof(T), ms);
return pe.Init() ? pe : null;
}
// Confirm file is valid and set up RVA mappings
protected virtual bool Init() => throw new NotImplementedException();
// Choose a sub-binary within the image for multi-architecture binaries
public virtual IFileFormatReader this[uint index] => (index == 0)? this : throw new IndexOutOfRangeException("Binary image index out of bounds");
// Find search locations in the symbol table for Il2Cpp data
public virtual Dictionary<string, ulong> GetSymbolTable() => null;
// Find search locations in the machine code for Il2Cpp data
public virtual uint[] GetFunctionTable() => throw new NotImplementedException();
// Map an RVA to an offset into the file image
// No mapping by default
public virtual uint MapVATR(ulong uiAddr) => (uint) uiAddr;
// Read a file format dependent word (32 or 64 bits)
// The primitive mappings in Bin2Object will automatically read a uint if the file is 32-bit
public long ReadWord() => ReadObject<long>();
public long ReadWord(long uiAddr) => ReadObject<long>(uiAddr);
// Retrieve object(s) from specified RVA(s)
public U ReadMappedObject<U>(ulong uiAddr) where U : new() => ReadObject<U>(MapVATR(uiAddr));
public U[] ReadMappedArray<U>(ulong uiAddr, int count) where U : new() => ReadArray<U>(MapVATR(uiAddr), count);
// Read a file format dependent array of words (32 or 64 bits)
// The primitive mappings in Bin2Object will automatically read a uint if the file is 32-bit
public long[] ReadMappedWordArray(ulong uiAddr, int count) => ReadArray<long>(MapVATR(uiAddr), count);
public string ReadMappedNullTerminatedString(ulong uiAddr) => ReadNullTerminatedString(MapVATR(uiAddr));
// Reads a list of pointers, then reads each object pointed to
public List<U> ReadMappedObjectPointerArray<U>(ulong uiAddr, int count) where U : new() {
var pointers = ReadMappedArray<ulong>(uiAddr, count);
var array = new List<U>();
for (int i = 0; i < count; i++)
array.Add(ReadMappedObject<U>(pointers[i]));
return array;
}
}
}

View File

@@ -0,0 +1,233 @@
/*
Copyright 2017 Perfare - https://github.com/Perfare/Il2CppDumper
Copyright 2017-2019 Katy Coe - http://www.hearthcode.org - http://www.djkaty.com
All rights reserved.
*/
using System;
using NoisyCowStudios.Bin2Object;
namespace Il2CppInspector
{
[Flags]
public enum Elf : uint
{
// elf_header.m_dwFormat
ELFMAG = 0x464c457f, // "\177ELF"
// elf_header.e_machine
EM_386 = 0x03,
EM_ARM = 0x28,
EM_X86_64 = 0x3E,
EM_AARCH64 = 0xB7,
// elf_header.m_arch
ELFCLASS32 = 1,
ELFCLASS64 = 2,
// PHTs
PT_DYNAMIC = 2,
DT_PLTGOT = 3,
PF_X = 1,
// SHTs
SHT_SYMTAB = 2,
SHT_STRTAB = 3,
SHT_RELA = 4,
SHT_REL = 9,
SHT_DYNSYM = 11,
// dynamic sections
DT_STRTAB = 5,
DT_SYMTAB = 6,
DT_RELA = 7,
DT_RELASZ = 8,
DT_RELAENT = 9,
DT_REL = 17,
DT_RELSZ = 18,
DT_RELENT = 19,
DT_INIT_ARRAY = 25,
DT_INIT_ARRAYSZ = 27,
// relocation types
R_ARM_ABS32 = 2,
R_ARM_REL32 = 3,
R_ARM_COPY = 20,
R_AARCH64_ABS64 = 0x101,
R_AARCH64_PREL64 = 0x104,
R_AARCH64_GLOB_DAT = 0x401,
R_AARCH64_JUMP_SLOT = 0x402,
R_AARCH64_RELATIVE = 0x403,
R_386_32 = 1,
R_386_PC32 = 2,
R_386_GLOB_DAT = 6,
R_386_JMP_SLOT = 7,
R_AMD64_64 = 1
}
#pragma warning disable CS0649
internal class elf_header<TWord> where TWord : struct
{
// 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 TWord e_entry;
public TWord e_phoff;
public TWord 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 interface Ielf_phdr<TWord> where TWord : struct
{
uint p_type { get; }
TWord p_offset { get; }
TWord p_filesz { get; }
TWord p_vaddr { get; }
uint p_flags { get; }
}
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_flags => f_p_flags;
public uint f_p_type;
public uint f_p_offset;
public uint f_p_vaddr;
public uint p_paddr;
public uint f_p_filesz;
public uint p_memsz;
public uint f_p_flags;
public uint p_align;
}
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_vaddr => f_p_vaddr;
public uint p_flags => f_p_flags;
public uint f_p_type;
public uint f_p_flags;
public ulong f_p_offset;
public ulong f_p_vaddr;
public ulong p_paddr;
public ulong f_p_filesz;
public ulong p_memsz;
public ulong p_align;
}
internal class elf_shdr<TWord> where TWord : struct
{
public uint sh_name;
public uint sh_type;
public TWord sh_flags;
public TWord sh_addr;
public TWord sh_offset;
public TWord sh_size;
public uint sh_link;
public uint sh_info;
public TWord sh_addralign;
public TWord sh_entsize;
}
internal interface Ielf_sym<TWord> where TWord : struct
{
uint st_name { get; }
TWord st_value { get; }
}
internal class elf_32_sym : Ielf_sym<uint>
{
public uint st_name => f_st_name;
public uint st_value => f_st_value;
public uint f_st_name;
public uint f_st_value;
public uint st_size;
public byte st_info;
public byte st_other;
public ushort st_shndx;
}
internal class elf_64_sym : Ielf_sym<ulong>
{
public uint st_name => f_st_name;
public ulong st_value => f_st_value;
public uint f_st_name;
public byte st_info;
public byte st_other;
public ushort st_shndx;
public ulong f_st_value;
public ulong st_size;
}
internal class elf_dynamic<TWord> where TWord : struct
{
public TWord d_tag;
public TWord d_un;
}
internal class elf_rel<TWord> where TWord : struct
{
public TWord r_offset;
public TWord r_info;
}
internal class elf_rela<TWord> where TWord : struct
{
public TWord r_offset;
public TWord r_info;
public TWord r_addend;
}
#pragma warning restore CS0649
}

View File

@@ -0,0 +1,119 @@
/*
Copyright 2017-2019 Katy Coe - http://www.hearthcode.org - http://www.djkaty.com
All rights reserved.
*/
using NoisyCowStudios.Bin2Object;
namespace Il2CppInspector
{
#pragma warning disable CS0649
// Structures and enums: https://opensource.apple.com/source/cctools/cctools-870/include/mach-o/loader.h
public enum MachO : uint
{
MH_MAGIC = 0xfeedface,
MH_CIGAM = 0xcefaedfe,
MH_MAGIC_64 = 0xfeedfacf,
MH_CIGAM_64 = 0xcffaedfe,
MH_EXECUTE = 0x2,
LC_SEGMENT = 0x1,
LC_SYMTAB = 0x2,
LC_DYSYMTAB = 0xb,
LC_SEGMENT_64 = 0x19,
LC_FUNCTION_STARTS = 0x26,
CPU_TYPE_X86 = 7,
CPU_TYPE_X86_64 = 0x01000000 + CPU_TYPE_X86,
CPU_TYPE_ARM = 12,
CPU_TYPE_ARM64 = 0x01000000 + CPU_TYPE_ARM
}
internal class MachOHeader<TWord> where TWord : struct
{
public uint Magic;
public uint CPUType;
public uint CPUSubType;
public uint FileType;
public uint NumCommands;
public uint SizeOfCommands;
public TWord Flags;
}
internal class MachOLoadCommand
{
public uint Command;
public uint Size;
}
internal class MachOSegmentCommand<TWord> where TWord : struct
{
// MachOLoadCommand
[String(FixedSize = 16)]
public string Name;
public TWord VirtualAddress;
public TWord VirtualSize;
public TWord ImageOffset;
public TWord ImageSize;
public uint VMMaxProt;
public uint VMInitProt;
public uint NumSections;
public uint Flags;
}
internal class MachOSection<TWord> where TWord : struct
{
[String(FixedSize = 16)]
public string Name;
[String(FixedSize = 16)]
public string SegmentName;
public TWord Address;
public TWord Size;
public uint ImageOffset;
public uint Align;
public uint ImageRelocOffset;
public int NumRelocEntries;
public uint Flags;
public uint Reserved1;
public TWord Reserved2;
}
internal class MachOLinkEditDataCommand
{
// MachOLoadCommand
public uint Offset;
public uint Size;
}
internal class MachOSymtabCommand
{
public uint SymOffset;
public uint NumSyms;
public uint StrOffset;
public uint StrSize;
}
internal class MachO_nlist<TWord> where TWord : struct
{
public uint n_strx;
public byte n_type;
public byte n_sect;
public ushort n_desc;
public TWord n_value;
}
internal class MachO_relocation_info
{
public int r_address;
public uint r_data;
public uint r_symbolnum => r_data & 0x00ffffff;
public bool r_pcrel => ((r_data >> 24) & 1) == 1;
public uint r_length => (r_data >> 25) & 3;
public bool r_extern => ((r_data >> 27) & 1) == 1;
public uint r_type => r_data >> 28;
}
}

View File

@@ -0,0 +1,148 @@
/*
Copyright 2017-2019 Katy Coe - http://www.hearthcode.org - http://www.djkaty.com
All rights reserved.
*/
using NoisyCowStudios.Bin2Object;
namespace Il2CppInspector
{
// Source: https://github.com/dotnet/llilc/blob/master/include/clr/ntimage.h
public enum PE : uint
{
IMAGE_NT_OPTIONAL_HDR32_MAGIC = 0x10b,
IMAGE_NT_OPTIONAL_HDR64_MAGIC = 0x20b
}
#pragma warning disable CS0649
// _IMAGE_FILE_HEADER
internal class COFFHeader
{
public ushort Machine;
public ushort NumberOfSections;
public uint TimeDateStamp;
public uint PointerToSymbolTable;
public uint NumberOfSymbols;
public ushort SizeOfOptionalHeader;
public ushort Characteristics;
}
// _IMAGE_OPTIONAL_HEADER
internal interface IPEOptHeader
{
PE ExpectedMagic { get; }
ushort Magic { get; }
ulong ImageBase { get; }
uint BaseOfCode { get; }
RvaEntry[] DataDirectory { get; }
}
internal class PEOptHeader32 : IPEOptHeader
{
public PE ExpectedMagic => PE.IMAGE_NT_OPTIONAL_HDR32_MAGIC;
public ushort Magic => f_Magic;
public ulong ImageBase => f_ImageBase;
public uint BaseOfCode => f_BaseOfCode;
public RvaEntry[] DataDirectory => f_DataDirectory;
public ushort f_Magic;
public byte MajorLinkerVersion;
public byte MinorLinkerVersion;
public uint SizeOfCode;
public uint SizeOfInitializedData;
public uint SizeOfUninitializedData;
public uint AddressOfEntryPoint;
public uint f_BaseOfCode;
public uint BaseOfData;
public uint f_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[] f_DataDirectory;
}
// _IMAGE_OPTIONAL_HEADER64
internal class PEOptHeader64 : IPEOptHeader
{
public PE ExpectedMagic => PE.IMAGE_NT_OPTIONAL_HDR64_MAGIC;
public ushort Magic => f_Magic;
public ulong ImageBase => f_ImageBase;
public uint BaseOfCode => f_BaseOfCode;
public RvaEntry[] DataDirectory => f_DataDirectory;
public ushort f_Magic;
public byte MajorLinkerVersion;
public byte MinorLinkerVersion;
public uint SizeOfCode;
public uint SizeOfInitializedData;
public uint SizeOfUninitializedData;
public uint AddressOfEntryPoint;
public uint f_BaseOfCode;
public ulong f_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 ulong SizeOfStackReserve;
public ulong SizeOfStackCommit;
public ulong SizeOfHeapReserve;
public ulong SizeOfHeapCommit;
public uint LoaderFlags;
public uint NumberOfRvaAndSizes;
[ArrayLength(FieldName = "NumberOfRvaAndSizes")]
public RvaEntry[] f_DataDirectory;
}
internal class RvaEntry
{
public uint VirtualAddress;
public uint Size;
}
// _IMAGE_SECTION_HEADER
internal class PESection
{
[String(FixedSize=8)]
public string Name;
public uint VirtualSize; // Size in memory
public uint VirtualAddress; // Base address in memory (RVA)
public uint SizeOfRawData; // Size in file
public uint PointerToRawData; // Base address in file
public uint PointerToRelocations;
public uint PointerToLinenumbers;
public ushort NumberOfRelocations;
public ushort NumberOfLinenumbers;
public uint Characteristics;
}
#pragma warning restore CS0649
}

View File

@@ -0,0 +1,33 @@
/*
Copyright 2017 Katy Coe - http://www.hearthcode.org - http://www.djkaty.com
All rights reserved.
*/
namespace Il2CppInspector
{
#pragma warning disable CS0649
// Structures and enums: https://cocoaintheshell.whine.fr/2009/07/universal-binary-mach-o-format/
public enum UB : uint
{
FAT_MAGIC = 0xcafebabe
}
// Big-endian
internal class FatHeader
{
public uint Magic;
public uint NumArch;
}
// Big-endian
internal class FatArch
{
public uint CPUType;
public uint CPUSubType;
public uint Offset;
public uint Size;
public uint Align;
}
}

View File

@@ -0,0 +1,176 @@
/*
Copyright 2017-2019 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;
using NoisyCowStudios.Bin2Object;
namespace Il2CppInspector
{
internal class MachOReader32 : MachOReader<uint, MachOReader32, Convert32>
{
public MachOReader32(Stream stream) : base(stream) { }
public override int Bits => 32;
protected override bool checkMagicLE(MachO magic) => magic == MachO.MH_MAGIC;
protected override bool checkMagicBE(MachO magic) => magic == MachO.MH_CIGAM;
protected override MachO lc_Segment => MachO.LC_SEGMENT;
public override uint MapVATR(ulong uiAddr) {
var section = sections.First(x => uiAddr >= x.Address && uiAddr <= x.Address + x.Size);
return (uint) uiAddr - (section.Address - section.ImageOffset);
}
}
internal class MachOReader64 : MachOReader<ulong, MachOReader64, Convert64>
{
public MachOReader64(Stream stream) : base(stream) { }
public override int Bits => 64;
protected override bool checkMagicLE(MachO magic) => magic == MachO.MH_MAGIC_64;
protected override bool checkMagicBE(MachO magic) => magic == MachO.MH_CIGAM_64;
protected override MachO lc_Segment => MachO.LC_SEGMENT_64;
public override uint MapVATR(ulong uiAddr) {
var section = sections.First(x => uiAddr >= x.Address && uiAddr <= x.Address + x.Size);
return (uint) (uiAddr - (section.Address - section.ImageOffset));
}
}
// We need this convoluted generic TReader declaration so that "static T FileFormatReader.Load(Stream)"
// is inherited to MachOReader32/64 with a correct definition of T
internal abstract class MachOReader<TWord, TReader, TConvert> : FileFormatReader<TReader>
where TWord : struct
where TReader : FileFormatReader<TReader>
where TConvert : IWordConverter<TWord>, new()
{
private readonly TConvert conv = new TConvert();
private MachOHeader<TWord> header;
protected readonly List<MachOSection<TWord>> sections = new List<MachOSection<TWord>>();
private MachOSection<TWord> funcTab;
private MachOSymtabCommand symTab;
protected MachOReader(Stream stream) : base(stream) { }
public override string Format => "Mach-O " + (Bits == 32 ? "32-bit" : "64-bit");
public override string Arch => (MachO)header.CPUType switch
{
MachO.CPU_TYPE_ARM => "ARM",
MachO.CPU_TYPE_ARM64 => "ARM64",
MachO.CPU_TYPE_X86 => "x86",
MachO.CPU_TYPE_X86_64 => "x64",
_ => "Unsupported"
};
protected abstract bool checkMagicLE(MachO magic);
protected abstract bool checkMagicBE(MachO magic);
protected abstract MachO lc_Segment { get; }
protected override bool Init() {
// Detect endianness - default is little-endianness
MachO magic = (MachO)ReadUInt32();
if (checkMagicBE(magic))
Endianness = Endianness.Big;
if (!checkMagicBE(magic) && !checkMagicLE(magic))
return false;
header = ReadObject<MachOHeader<TWord>>(0);
// Must be executable file
if ((MachO)header.FileType != MachO.MH_EXECUTE)
return false;
// Process load commands
for (var c = 0; c < header.NumCommands; c++) {
var startPos = Position;
var loadCommand = ReadObject<MachOLoadCommand>();
switch ((MachO) loadCommand.Command) {
// Segments
case MachO cmd when cmd == lc_Segment:
var segment = ReadObject<MachOSegmentCommand<TWord>>();
if (segment.Name == "__TEXT" || segment.Name == "__DATA") {
for (int s = 0; s < segment.NumSections; s++) {
var section = ReadObject<MachOSection<TWord>>();
sections.Add(section);
if (section.Name == "__text") {
GlobalOffset = (ulong) Convert.ChangeType(section.Address, typeof(ulong)) - section.ImageOffset;
}
// Initialization (pre-main) functions
if (section.Name == "__mod_init_func") {
funcTab = section;
}
}
}
break;
// Location of static symbol table
case MachO.LC_SYMTAB:
symTab = ReadObject<MachOSymtabCommand>();
break;
case MachO.LC_DYSYMTAB:
// TODO: Implement Mach-O dynamic symbol table
break;
}
// There might be other data after the load command so always use the specified total size to step forwards
Position = startPos + loadCommand.Size;
}
// Must find __mod_init_func
if (funcTab == null)
return false;
// Process relocations
foreach (var section in sections) {
var rels = ReadArray<MachO_relocation_info>(section.ImageRelocOffset, section.NumRelocEntries);
// TODO: Implement Mach-O relocations
if (rels.Any()) {
Console.WriteLine("Mach-O file contains relocations (feature not yet implemented)");
break;
}
}
return true;
}
public override uint[] GetFunctionTable() => ReadArray<TWord>(funcTab.ImageOffset, conv.Int(funcTab.Size) / (Bits / 8)).Select(x => MapVATR(conv.ULong(x)) & 0xffff_fffe).ToArray();
public override Dictionary<string, ulong> GetSymbolTable() {
var symbols = new Dictionary<string, ulong>();
// https://opensource.apple.com/source/cctools/cctools-795/include/mach-o/nlist.h
// n_sect: https://opensource.apple.com/source/cctools/cctools-795/include/mach-o/stab.h
var symbolList = ReadArray<MachO_nlist<TWord>>(symTab.SymOffset, (int) symTab.NumSyms);
// This is a really naive implementation that ignores the values of n_type and n_sect
// which may affect the interpretation of n_value
foreach (var symbol in symbolList) {
Position = symTab.StrOffset + symbol.n_strx;
var name = (symbol.n_strx != 0) ? ReadNullTerminatedString() : "";
var value = (ulong) Convert.ChangeType(symbol.n_value, typeof(ulong));
// Ignore duplicates for now, also ignore symbols with no address
if (value != 0)
symbols.TryAdd(name, value);
}
return symbols;
}
}
}

View File

@@ -0,0 +1,125 @@
/*
Copyright 2017-2019 Katy Coe - http://www.hearthcode.org - http://www.djkaty.com
All rights reserved.
*/
using System.Collections.Generic;
using System.IO;
using System.Linq;
namespace Il2CppInspector
{
// References:
// PE Header file: https://github.com/dotnet/llilc/blob/master/include/clr/ntimage.h
// PE format specification: https://docs.microsoft.com/en-us/windows/win32/debug/pe-format?redirectedfrom=MSDN
internal class PEReader : FileFormatReader<PEReader>
{
private COFFHeader coff;
private IPEOptHeader pe;
private PESection[] sections;
private uint pFuncTable;
public PEReader(Stream stream) : base(stream) {}
public override string Format => pe is PEOptHeader32 ? "PE32" : "PE32+";
public override string Arch => coff.Machine switch {
0x8664 => "x64", // IMAGE_FILE_MACHINE_AMD64
0x1C0 => "ARM", // IMAGE_FILE_MACHINE_ARM
0xAA64 => "ARM64", // IMAGE_FILE_MACHINE_ARM64
0x1C4 => "ARM", // IMAGE_FILE_MACHINE_ARMINT (Thumb-2)
0x14C => "x86", // IMAGE_FILE_MACHINE_I386
0x1C2 => "ARM", // IMAGE_FILE_MACHINE_THUMB (Thumb)
_ => "Unsupported"
};
// IMAGE_NT_OPTIONAL_HDR64_MAGIC = 0x20B
// IMAGE_NT_OPTIONAL_HDR32_MAGIC = 0x10B
// Could also use coff.Characteristics (IMAGE_FILE_32BIT_MACHINE) or coff.Machine
public override int Bits => (PE) pe.Magic == PE.IMAGE_NT_OPTIONAL_HDR64_MAGIC ? 64 : 32;
protected override bool Init() {
// Check for MZ signature "MZ"
if (ReadUInt16() != 0x5A4D)
return false;
// Get offset to PE header from DOS header
Position = ReadUInt32(0x3C);
// Check PE signature "PE\0\0"
if (ReadUInt32() != 0x00004550)
return false;
// Read COFF Header
coff = ReadObject<COFFHeader>();
// Ensure presence of PE Optional header
// Size will always be 0x60 (32-bit) or 0x70 (64-bit) + (0x10 ' 0x8) for 16 RVA entries @ 8 bytes each
if (!((coff.SizeOfOptionalHeader == 0xE0 ? 32 :
coff.SizeOfOptionalHeader == 0xF0 ? (int?) 64 : null) is var likelyWordSize))
return false;
// Read PE optional header
pe = likelyWordSize switch {
32 => ReadObject<PEOptHeader32>(),
64 => ReadObject<PEOptHeader64>(),
_ => null
};
// Confirm architecture magic number matches expected word size
if ((PE) pe.Magic != pe.ExpectedMagic)
return false;
// Get IAT
var IATStart = pe.DataDirectory[12].VirtualAddress;
var IATSize = pe.DataDirectory[12].Size;
// Get sections table
sections = ReadArray<PESection>(coff.NumberOfSections);
// Confirm that .rdata section begins at same place as IAT
var rData = sections.First(x => x.Name == ".rdata");
if (rData.VirtualAddress != IATStart)
return false;
// Calculate start of function pointer table
pFuncTable = rData.PointerToRawData + IATSize;
// Skip over __guard_check_icall_fptr and __guard_dispatch_icall_fptr if present, then the following zero offset
Position = pFuncTable;
if (pe is PEOptHeader32) {
while (ReadUInt32() != 0)
pFuncTable += 4;
pFuncTable += 4;
}
else {
while (ReadUInt64() != 0)
pFuncTable += 8;
pFuncTable += 8;
}
// Get base of code
GlobalOffset = pe.ImageBase + pe.BaseOfCode - sections.First(x => x.Name == ".text").PointerToRawData;
return true;
}
public override uint[] GetFunctionTable() {
Position = pFuncTable;
var addrs = new List<uint>();
ulong addr;
while ((addr = pe is PEOptHeader32? ReadUInt32() : ReadUInt64()) != 0)
addrs.Add(MapVATR(addr) & 0xfffffffc);
return addrs.ToArray();
}
public override uint MapVATR(ulong uiAddr) {
if (uiAddr == 0)
return 0;
var section = sections.First(x => uiAddr - pe.ImageBase >= x.VirtualAddress &&
uiAddr - pe.ImageBase < x.VirtualAddress + x.SizeOfRawData);
return (uint) (uiAddr - section.VirtualAddress - pe.ImageBase + section.PointerToRawData);
}
}
}

View File

@@ -0,0 +1,46 @@
/*
Copyright 2017-2019 Katy Coe - http://www.hearthcode.org - http://www.djkaty.com
All rights reserved.
*/
using System.IO;
using NoisyCowStudios.Bin2Object;
namespace Il2CppInspector
{
internal class UBReader : FileFormatReader<UBReader>
{
private FatHeader header;
public UBReader(Stream stream) : base(stream) { }
protected override bool Init() {
// Fat headers are always big-endian regardless of architectures
Endianness = Endianness.Big;
header = ReadObject<FatHeader>();
if ((UB) header.Magic != UB.FAT_MAGIC)
return false;
NumImages = header.NumArch;
return true;
}
public override IFileFormatReader this[uint index] {
get {
Position = 0x8 + 0x14 * index; // sizeof(FatHeader), sizeof(FatArch)
Endianness = Endianness.Big;
var arch = ReadObject<FatArch>();
Position = arch.Offset;
Endianness = Endianness.Little;
using var s = new MemoryStream(ReadBytes((int) arch.Size));
return (IFileFormatReader) MachOReader32.Load(s) ?? MachOReader64.Load(s);
}
}
}
}

View File

@@ -0,0 +1,54 @@
/*
Copyright 2019-2020 Katy Coe - http://www.hearthcode.org - http://www.djkaty.com
All rights reserved.
*/
using System;
namespace Il2CppInspector
{
// NOTE: What we really should have done here is add a TWord type parameter to FileFormatReader<T>
// then we could probably avoid most of this
interface IWordConverter<TWord> where TWord : struct
{
TWord Add(TWord a, TWord b);
TWord Sub(TWord a, TWord b);
TWord Div(TWord a, TWord b);
TWord Div(TWord a, int b);
TWord FromUInt(uint a);
int Int(TWord a);
long Long(TWord a);
ulong ULong(TWord a);
bool Gt(TWord a, TWord b);
uint[] UIntArray(TWord[] a);
}
internal class Convert32 : IWordConverter<uint>
{
public uint Add(uint a, uint b) => a + b;
public uint Sub(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 FromUInt(uint a) => a;
public int Int(uint a) => (int)a;
public long Long(uint a) => a;
public ulong ULong(uint a) => a;
public bool Gt(uint a, uint b) => a > b;
public uint[] UIntArray(uint[] a) => a;
}
internal class Convert64 : IWordConverter<ulong>
{
public ulong Add(ulong a, ulong b) => a + b;
public ulong Sub(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 FromUInt(uint a) => a;
public int Int(ulong a) => (int)a;
public long Long(ulong a) => (long)a;
public ulong ULong(ulong a) => a;
public bool Gt(ulong a, ulong b) => a > b;
public uint[] UIntArray(ulong[] a) => Array.ConvertAll(a, x => (uint)x);
}
}