IL2CPP: Change metadata and binary to derive from BinaryObjectStream
This commit is contained in:
73
Il2CppInspector.Common/FileFormatStreams/AABReader.cs
Normal file
73
Il2CppInspector.Common/FileFormatStreams/AABReader.cs
Normal file
@@ -0,0 +1,73 @@
|
||||
/*
|
||||
Copyright 2020 Katy Coe - http://www.djkaty.com - https://github.com/djkaty
|
||||
|
||||
All rights reserved.
|
||||
*/
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.IO.Compression;
|
||||
using System.Linq;
|
||||
|
||||
namespace Il2CppInspector
|
||||
{
|
||||
// This is a wrapper for multiple binary files of different architectures within a single AAB
|
||||
internal class AABReader : FileFormatStream<AABReader>
|
||||
{
|
||||
private ZipArchive zip;
|
||||
private ZipArchiveEntry[] binaryFiles;
|
||||
|
||||
public override string DefaultFilename => "Package.aab";
|
||||
|
||||
protected override bool Init() {
|
||||
|
||||
// Check if it's a zip file first because ZipFile.OpenRead is extremely slow if it isn't
|
||||
// 0x04034B50 = magic file header
|
||||
// 0x02014B50 = central directory file header (will appear if we merged a split AAB in memory)
|
||||
var magic = ReadUInt32();
|
||||
if (magic != 0x04034B50 && magic != 0x02014B50)
|
||||
return false;
|
||||
|
||||
try {
|
||||
zip = new ZipArchive(this);
|
||||
|
||||
// Get list of binary files
|
||||
binaryFiles = zip.Entries.Where(f => f.FullName.StartsWith("base/lib/") && f.Name == "libil2cpp.so").ToArray();
|
||||
|
||||
// This package doesn't contain an IL2CPP binary
|
||||
if (!binaryFiles.Any())
|
||||
return false;
|
||||
}
|
||||
|
||||
// Not an archive
|
||||
catch (InvalidDataException) {
|
||||
return false;
|
||||
}
|
||||
|
||||
NumImages = (uint) binaryFiles.Count();
|
||||
return true;
|
||||
}
|
||||
|
||||
public override IFileFormatStream this[uint index] {
|
||||
get {
|
||||
Console.WriteLine($"Extracting binary from {binaryFiles[index].FullName}");
|
||||
IFileFormatStream loaded = null;
|
||||
|
||||
// ZipArchiveEntry does not support seeking so we have to close and re-open for each possible load format
|
||||
var binary = binaryFiles[index].Open();
|
||||
loaded = ElfReader32.Load(binary, LoadOptions, OnStatusUpdate);
|
||||
binary.Close();
|
||||
|
||||
if (loaded != null)
|
||||
return loaded;
|
||||
|
||||
binary = binaryFiles[index].Open();
|
||||
loaded = ElfReader64.Load(binary, LoadOptions, OnStatusUpdate);
|
||||
binary.Close();
|
||||
|
||||
return loaded;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
73
Il2CppInspector.Common/FileFormatStreams/APKReader.cs
Normal file
73
Il2CppInspector.Common/FileFormatStreams/APKReader.cs
Normal file
@@ -0,0 +1,73 @@
|
||||
/*
|
||||
Copyright 2020 Katy Coe - http://www.djkaty.com - https://github.com/djkaty
|
||||
|
||||
All rights reserved.
|
||||
*/
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.IO.Compression;
|
||||
using System.Linq;
|
||||
|
||||
namespace Il2CppInspector
|
||||
{
|
||||
// This is a wrapper for multiple binary files of different architectures within a single APK
|
||||
internal class APKReader : FileFormatStream<APKReader>
|
||||
{
|
||||
private ZipArchive zip;
|
||||
private ZipArchiveEntry[] binaryFiles;
|
||||
|
||||
public override string DefaultFilename => "Package.apk";
|
||||
|
||||
protected override bool Init() {
|
||||
|
||||
// Check if it's a zip file first because ZipFile.OpenRead is extremely slow if it isn't
|
||||
// 0x04034B50 = magic file header
|
||||
// 0x02014B50 = central directory file header (will appear if we merged a split APK in memory)
|
||||
var magic = ReadUInt32();
|
||||
if (magic != 0x04034B50 && magic != 0x02014B50)
|
||||
return false;
|
||||
|
||||
try {
|
||||
zip = new ZipArchive(this);
|
||||
|
||||
// Get list of binary files
|
||||
binaryFiles = zip.Entries.Where(f => f.FullName.StartsWith("lib/") && f.Name == "libil2cpp.so").ToArray();
|
||||
|
||||
// This package doesn't contain an IL2CPP binary
|
||||
if (!binaryFiles.Any())
|
||||
return false;
|
||||
}
|
||||
|
||||
// Not an archive
|
||||
catch (InvalidDataException) {
|
||||
return false;
|
||||
}
|
||||
|
||||
NumImages = (uint) binaryFiles.Count();
|
||||
return true;
|
||||
}
|
||||
|
||||
public override IFileFormatStream this[uint index] {
|
||||
get {
|
||||
Console.WriteLine($"Extracting binary from {binaryFiles[index].FullName}");
|
||||
IFileFormatStream loaded = null;
|
||||
|
||||
// ZipArchiveEntry does not support seeking so we have to close and re-open for each possible load format
|
||||
var binary = binaryFiles[index].Open();
|
||||
loaded = ElfReader32.Load(binary, LoadOptions, OnStatusUpdate);
|
||||
binary.Close();
|
||||
|
||||
if (loaded != null)
|
||||
return loaded;
|
||||
|
||||
binary = binaryFiles[index].Open();
|
||||
loaded = ElfReader64.Load(binary, LoadOptions, OnStatusUpdate);
|
||||
binary.Close();
|
||||
|
||||
return loaded;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
738
Il2CppInspector.Common/FileFormatStreams/ElfReader.cs
Normal file
738
Il2CppInspector.Common/FileFormatStreams/ElfReader.cs
Normal file
@@ -0,0 +1,738 @@
|
||||
/*
|
||||
Copyright 2017 Perfare - https://github.com/Perfare/Il2CppDumper
|
||||
Copyright 2017-2020 Katy Coe - http://www.djkaty.com - https://github.com/djkaty
|
||||
|
||||
All rights reserved.
|
||||
*/
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Text;
|
||||
using NoisyCowStudios.Bin2Object;
|
||||
|
||||
namespace Il2CppInspector
|
||||
{
|
||||
internal class ElfReader32 : ElfReader<uint, elf_32_phdr, elf_32_sym, ElfReader32, Convert32>
|
||||
{
|
||||
public ElfReader32() : base() {
|
||||
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 WriteWord(uint value) => Write(value);
|
||||
}
|
||||
|
||||
internal class ElfReader64 : ElfReader<ulong, elf_64_phdr, elf_64_sym, ElfReader64, Convert64>
|
||||
{
|
||||
public ElfReader64() : base() {
|
||||
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 WriteWord(ulong value) => Write(value);
|
||||
}
|
||||
|
||||
interface IElfReader
|
||||
{
|
||||
uint GetPLTAddress();
|
||||
}
|
||||
|
||||
internal abstract class ElfReader<TWord, TPHdr, TSym, TReader, TConvert> : FileFormatStream<TReader>, IElfReader
|
||||
where TWord : struct
|
||||
where TPHdr : Ielf_phdr<TWord>, new()
|
||||
where TSym : Ielf_sym<TWord>, new()
|
||||
where TConvert : IWordConverter<TWord>, new()
|
||||
where TReader : FileFormatStream<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;
|
||||
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 isMemoryImage = false;
|
||||
|
||||
public override string DefaultFilename => "libil2cpp.so";
|
||||
|
||||
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);
|
||||
|
||||
private Dictionary<string, Symbol> symbolTable = new Dictionary<string, Symbol>();
|
||||
private List<Export> exports = new List<Export>();
|
||||
|
||||
protected abstract Elf ArchClass { get; }
|
||||
|
||||
protected abstract void WriteWord(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;
|
||||
|
||||
// Ensure supported architecture
|
||||
if ((Elf) elf_header.m_arch != ArchClass)
|
||||
return false;
|
||||
|
||||
// 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);
|
||||
|
||||
// Determine if SHT is valid
|
||||
|
||||
// These can happen as a result of conversions from other formats to ELF,
|
||||
// or if the SHT has been deliberately stripped
|
||||
if (!section_header_table.Any()) {
|
||||
Console.WriteLine("ELF binary has no SHT - reverting to PHT");
|
||||
preferPHT = true;
|
||||
}
|
||||
|
||||
else if (section_header_table.All(s => conv.ULong(s.sh_addr) == 0ul)) {
|
||||
Console.WriteLine("ELF binary SHT is all-zero - reverting to PHT");
|
||||
preferPHT = true;
|
||||
}
|
||||
|
||||
// Check for overlaps in sections that are memory-allocated on load
|
||||
else {
|
||||
var shtShouldBeOrdered = section_header_table
|
||||
.Where(s => ((Elf) conv.Int(s.sh_flags) & Elf.SHF_ALLOC) == Elf.SHF_ALLOC)
|
||||
.OrderBy(s => s.sh_addr)
|
||||
.Select(s => new[] { conv.ULong(s.sh_addr), conv.ULong(s.sh_addr) + conv.ULong(s.sh_size) })
|
||||
.SelectMany(s => s);
|
||||
|
||||
// No sections that map into memory - this is probably a dumped image
|
||||
if (!shtShouldBeOrdered.Any()) {
|
||||
|
||||
// If the first file offset of the first PHT is zero, assume a dumped image
|
||||
if (program_header_table.Any(t => conv.ULong(t.p_vaddr) == 0ul)) {
|
||||
Console.WriteLine("ELF binary appears to be a dumped memory image");
|
||||
isMemoryImage = true;
|
||||
}
|
||||
preferPHT = true;
|
||||
}
|
||||
|
||||
// Sections overlap - this can happen if the ELF has been improperly generated or processed by another tool
|
||||
else {
|
||||
var shtOverlap = shtShouldBeOrdered.Aggregate((x, y) => x <= y? y : ulong.MaxValue) == ulong.MaxValue;
|
||||
if (shtOverlap) {
|
||||
Console.WriteLine("ELF binary SHT contains invalid ranges - reverting to PHT");
|
||||
preferPHT = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Dumped images must be rebased
|
||||
if (isMemoryImage) {
|
||||
if (LoadOptions.ImageBase == 0xffffffff_ffffffff)
|
||||
throw new InvalidOperationException("To load a dumped ELF image, you must specify the image base virtual address");
|
||||
|
||||
rebase(conv.FromULong(LoadOptions.ImageBase));
|
||||
}
|
||||
|
||||
// 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
|
||||
if (elf_header.e_shtrndx < section_header_table.Length) {
|
||||
var pStrtab = section_header_table[elf_header.e_shtrndx].sh_offset;
|
||||
foreach (var section in section_header_table) {
|
||||
try {
|
||||
var name = ReadNullTerminatedString(conv.Long(pStrtab) + section.sh_name);
|
||||
sectionByName.TryAdd(name, section);
|
||||
} catch (ArgumentOutOfRangeException) {
|
||||
// Names have been stripped, maybe previously dumped image
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Find all relocations; target address => (rela header (rels are converted to rela), symbol table base address, is rela?)
|
||||
var rels = new HashSet<ElfReloc>();
|
||||
|
||||
StatusUpdate("Finding relocations");
|
||||
|
||||
// Two types: add value from offset in image, and add value from specified addend
|
||||
foreach (var relSection in getSections(Elf.SHT_REL)) {
|
||||
reverseMapExclusions.Add(((uint) conv.Int(relSection.sh_offset), (uint) (conv.Int(relSection.sh_offset) + conv.Int(relSection.sh_size) - 1)));
|
||||
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)) {
|
||||
reverseMapExclusions.Add(((uint) conv.Int(relaSection.sh_offset), (uint) (conv.Int(relaSection.sh_offset) + conv.Int(relaSection.sh_size) - 1)));
|
||||
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.Int(conv.Div(getDynamic(Elf.DT_RELSZ).d_un, getDynamic(Elf.DT_RELENT).d_un));
|
||||
var dt_item_size = Sizeof(typeof(elf_rel<TWord>));
|
||||
var dt_start = MapVATR(conv.ULong(dt_rel.d_un));
|
||||
var dt_rel_list = ReadArray<elf_rel<TWord>>(dt_start, dt_rel_count);
|
||||
var dt_symtab = getDynamic(Elf.DT_SYMTAB).d_un;
|
||||
reverseMapExclusions.Add((dt_start, (uint) (dt_start + dt_rel_count * dt_item_size - 1)));
|
||||
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.Int(conv.Div(getDynamic(Elf.DT_RELASZ).d_un, getDynamic(Elf.DT_RELAENT).d_un));
|
||||
var dt_item_size = Sizeof(typeof(elf_rela<TWord>));
|
||||
var dt_start = MapVATR(conv.ULong(dt_rela.d_un));
|
||||
var dt_rela_list = ReadArray<elf_rela<TWord>>(dt_start, dt_rela_count);
|
||||
var dt_symtab = getDynamic(Elf.DT_SYMTAB).d_un;
|
||||
reverseMapExclusions.Add((dt_start, (uint) (dt_start + dt_rela_count * dt_item_size - 1)));
|
||||
rels.UnionWith(from rela in dt_rela_list select new ElfReloc(rela, dt_symtab));
|
||||
}
|
||||
|
||||
// Process relocations
|
||||
var relsz = Sizeof(typeof(TSym));
|
||||
|
||||
var currentRel = 0;
|
||||
var totalRel = rels.Count();
|
||||
|
||||
foreach (var rel in rels) {
|
||||
currentRel++;
|
||||
if (currentRel % 1000 == 0)
|
||||
StatusUpdate($"Processing relocations ({currentRel * 100 / totalRel:F0}%)");
|
||||
|
||||
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));
|
||||
WriteWord(result.newValue);
|
||||
}
|
||||
}
|
||||
Console.WriteLine($"Processed {rels.Count} relocations");
|
||||
|
||||
// Detect and defeat various kinds of XOR encryption
|
||||
StatusUpdate("Detecting encryption");
|
||||
|
||||
if (getDynamic(Elf.DT_INIT) != null && sectionByName.ContainsKey(".rodata")) {
|
||||
// Use the data section to determine some possible keys
|
||||
// If the data section uses striped encryption, bucketing the whole section will not give the correct key
|
||||
var roDataBytes = ReadBytes(conv.Long(sectionByName[".rodata"].sh_offset), conv.Int(sectionByName[".rodata"].sh_size));
|
||||
var xorKeyCandidateStriped = roDataBytes.Take(1024).GroupBy(b => b).OrderByDescending(f => f.Count()).First().Key;
|
||||
var xorKeyCandidateFull = roDataBytes.GroupBy(b => b).OrderByDescending(f => f.Count()).First().Key;
|
||||
|
||||
// Select test nibbles and values for ARM instructions depending on architecture (ARMv7 / AArch64)
|
||||
var testValues = new Dictionary<int, (int, int, int, int)> {
|
||||
[32] = (8, 28, 0x0, 0xE),
|
||||
[64] = (4, 28, 0xE, 0xF)
|
||||
};
|
||||
|
||||
var (armNibbleB, armNibbleT, armValueB, armValueT) = testValues[Bits];
|
||||
|
||||
var instructionsToTest = 256;
|
||||
|
||||
// This gives us an idea of whether the code might be encrypted
|
||||
var textFirstDWords = ReadArray<uint>(conv.Long(sectionByName[".text"].sh_offset), instructionsToTest);
|
||||
var bottom = textFirstDWords.Select(w => (w >> armNibbleB) & 0xF).GroupBy(n => n).OrderByDescending(f => f.Count()).First().Key;
|
||||
var top = textFirstDWords.Select(w => w >> armNibbleT).GroupBy(n => n).OrderByDescending(f => f.Count()).First().Key;
|
||||
var xorKeyCandidateFromCode = (byte) (((top ^ armValueT) << 4) | (bottom ^ armValueB));
|
||||
|
||||
// If the first part of the data section is encrypted, proceed
|
||||
if (xorKeyCandidateStriped != 0x00) {
|
||||
|
||||
// Some files may use a striped encryption whereby alternate blocks are encrypted and un-encrypted
|
||||
// The first part of each section is always encrypted.
|
||||
|
||||
// We refer to issue #96 where the code uses striped encryption in 4KB blocks
|
||||
// We perform heuristics for block of size blockSize below
|
||||
const int blockSize = 0x100;
|
||||
const int maxBrokenRun = 4;
|
||||
const int minMultiplierInValid = 6;
|
||||
const int minTotalValidInBucket = 0x10;
|
||||
|
||||
// Take all of the instructions from the code section starting on a VA block boundary and determine which are valid
|
||||
var startSkip = 0;
|
||||
if (conv.Int(sectionByName[".text"].sh_addr) % blockSize != 0)
|
||||
startSkip = blockSize - conv.Int(sectionByName[".text"].sh_addr) % blockSize;
|
||||
|
||||
var insts = ReadArray<uint>(conv.Long(sectionByName[".text"].sh_offset) + startSkip, (conv.Int(sectionByName[".text"].sh_size) - startSkip) / 4);
|
||||
var instsValid = insts.Select(i => Bits == 32? isCommonARMv7(i) : isCommonARMv8A(i)).ToList();
|
||||
|
||||
// Use RLE to produce frequency distribution of number of consecutive valid and invalid instructions,
|
||||
// allowing for maxBrokenRun breaks in valid instructions in a row before considering a run to have ended
|
||||
var freqValid = new SortedDictionary<int, int>();
|
||||
var runLength = 0;
|
||||
var brokenRun = 0;
|
||||
foreach (var i in instsValid) {
|
||||
if (i) {
|
||||
runLength = runLength + brokenRun + 1;
|
||||
brokenRun = 0;
|
||||
} else if (runLength > 0) {
|
||||
brokenRun++;
|
||||
|
||||
if (brokenRun > maxBrokenRun) {
|
||||
if (freqValid.ContainsKey(runLength))
|
||||
freqValid[runLength]++;
|
||||
else
|
||||
freqValid[runLength] = 1;
|
||||
runLength = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Create a histogram of how often each range of valid instruction counts occurred
|
||||
// The uses of 4 refer to the size of an ARM instruction
|
||||
var histValid = freqValid.GroupBy(f => f.Key - (f.Key % (blockSize / 4)))
|
||||
.Select(f => new {
|
||||
Key = f.Key * 4,
|
||||
Value = f.Sum(x => x.Value)
|
||||
}).ToDictionary(x => x.Key, x => x.Value);
|
||||
|
||||
// Find first point in the histogram where the number of valid instructions suddenly spikes
|
||||
var stripeSize = (uint) histValid.Zip(histValid.Skip(1), (p,c) => (p,c))
|
||||
.FirstOrDefault(x => x.c.Value >= x.p.Value * minMultiplierInValid && x.c.Value >= minTotalValidInBucket).c.Key;
|
||||
|
||||
// Select the key
|
||||
|
||||
// If more than one key candidates are the same, select the most common candidate
|
||||
var keys = new [] { xorKeyCandidateFromCode, xorKeyCandidateStriped, xorKeyCandidateFull };
|
||||
var bestKey = keys.GroupBy(k => k).OrderByDescending(k => k.Count()).First();
|
||||
var xorKey = bestKey.Key;
|
||||
|
||||
// Otherwise choose according to striped/full encryption
|
||||
if (bestKey.Count() == 1) {
|
||||
xorKey = keys.OrderByDescending(k => textFirstDWords.Select(w => w ^ (k << 24) ^ (k << 16) ^ (k << 8) ^ k)
|
||||
.Count(w => Bits == 32? isCommonARMv7((uint) w) : isCommonARMv8A((uint) w))).First();
|
||||
}
|
||||
|
||||
StatusUpdate("Decrypting");
|
||||
Console.WriteLine($"Performing XOR decryption (key: 0x{xorKey:X2}, stripe size: 0x{stripeSize:X4})");
|
||||
|
||||
xorSection(".text", xorKey, stripeSize);
|
||||
xorSection(".rodata", xorKey, stripeSize);
|
||||
|
||||
IsModified = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Detect more sophisticated packing
|
||||
// We have seen several examples (eg. #14 and #26) where most of the file is zeroed
|
||||
// and packed data is found in the latter third. So far these files always have zeroed .rodata sections
|
||||
if (sectionByName.ContainsKey(".rodata")) {
|
||||
var rodataBytes = ReadBytes(conv.Long(sectionByName[".rodata"].sh_offset), conv.Int(sectionByName[".rodata"].sh_size));
|
||||
if (rodataBytes.All(b => b == 0x00))
|
||||
throw new InvalidOperationException("This IL2CPP binary is packed in a way not currently supported by Il2CppInspector and cannot be loaded.");
|
||||
}
|
||||
|
||||
// Build symbol and export tables
|
||||
processSymbols();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// https://developer.arm.com/documentation/ddi0406/cb/Application-Level-Architecture/ARM-Instruction-Set-Encoding/ARM-instruction-set-encoding
|
||||
private bool isCommonARMv7(uint inst) {
|
||||
var cond = inst >> 28; // We'll allow 0x1111 (for BL/BLX), AL, EQ, NE, GE, LT, GT, LE only
|
||||
|
||||
if (cond != 0b1111 && cond != 0b1110 && cond != 0b0000 && cond != 0b0001 && cond != 0b1010 && cond != 0b1011 && cond != 0b1100 && cond != 0b1101)
|
||||
return false;
|
||||
|
||||
var op1 = (inst >> 25) & 7;
|
||||
|
||||
// Disallow media instructions
|
||||
var op = (inst >> 4) & 1;
|
||||
if (op1 == 0b011 && op == 1)
|
||||
return false;
|
||||
|
||||
// Disallow co-processor instructions
|
||||
if (op1 == 0b110 || op1 == 0b111)
|
||||
return false;
|
||||
|
||||
// Disallow 0b1111 cond except for BL and BLX
|
||||
if (cond == 0b1111) {
|
||||
var op1_1 = (inst >> 20) & 0b11111111;
|
||||
|
||||
if ((op1_1 >> 5) != 0b101)
|
||||
return false;
|
||||
}
|
||||
|
||||
// Disallow MSR and other miscellaneous
|
||||
if (op == 1) {
|
||||
var op1_1 = (inst >> 20) & 0b11111;
|
||||
var op2 = (inst >> 4) & 0b1111;
|
||||
|
||||
if (op1_1 == 0b10010 || op1_1 == 0b10110 || op1_1 == 0b10000 || op1_1 == 0b10100)
|
||||
return false;
|
||||
|
||||
// Disallow synchronization primitives
|
||||
if ((op1_1 >> 4) == 1)
|
||||
return false;
|
||||
}
|
||||
|
||||
// Probably a common instruction
|
||||
return true;
|
||||
}
|
||||
|
||||
// https://montcs.bloomu.edu/Information/ARMv8/ARMv8-A_Architecture_Reference_Manual_(Issue_A.a).pdf
|
||||
private bool isCommonARMv8A(uint inst) {
|
||||
var op = (inst >> 24) & 0b11111;
|
||||
|
||||
// Disallow unexpected, SIMD and FP
|
||||
if ((op >> 3) == 0 || (op >> 1) == 0b0111 || (op >> 1) == 0b1111)
|
||||
return false;
|
||||
|
||||
// Disallow exception generation and system instructions
|
||||
if ((inst >> 24) == 0b11010100 || (inst >> 22) == 0b1101010100)
|
||||
return false;
|
||||
|
||||
// Disallow bitfield and extract
|
||||
if (op == 0b10011)
|
||||
return false;
|
||||
|
||||
// Disallow conditional compare and data processing
|
||||
if ((op >> 1) == 0b1101)
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private void xorRange(int offset, int length, byte xorValue) {
|
||||
var bytes = ReadBytes(offset, length);
|
||||
bytes = bytes.Select(b => (byte) (b ^ xorValue)).ToArray();
|
||||
Write(offset, bytes);
|
||||
}
|
||||
|
||||
private void xorSection(string sectionName, byte xorValue, uint stripeSize) {
|
||||
var section = sectionByName[sectionName];
|
||||
|
||||
// First part up to stripe size boundary is always encrypted, first full block is always encrypted
|
||||
var start = conv.Int(section.sh_offset);
|
||||
var length = conv.Int(section.sh_size);
|
||||
|
||||
// Non-striped
|
||||
if (stripeSize == 0) {
|
||||
xorRange(start, length, xorValue);
|
||||
return;
|
||||
}
|
||||
|
||||
// Striped
|
||||
// The first block's length is the distance to the boundary to the first stripe size + one stripe
|
||||
var firstBlockLength = stripeSize;
|
||||
if (start % stripeSize != 0)
|
||||
firstBlockLength += stripeSize - (uint) (start % stripeSize);
|
||||
|
||||
xorRange(start, (int) firstBlockLength, xorValue);
|
||||
|
||||
// Step forward two stripe sizes at a time, decrypting the first and ignoring the second
|
||||
for (var pos = start + firstBlockLength + stripeSize; pos < start + length; pos += stripeSize * 2) {
|
||||
var size = Math.Min(stripeSize, start + length - pos);
|
||||
xorRange((int) pos, (int) size, xorValue);
|
||||
}
|
||||
}
|
||||
|
||||
// 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
|
||||
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
|
||||
WriteArray(conv.Long(PT_DYNAMIC.p_offset), dt);
|
||||
}
|
||||
|
||||
private void processSymbols() {
|
||||
StatusUpdate("Processing symbols");
|
||||
|
||||
// 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
|
||||
symbolTable.Clear();
|
||||
var exportTable = new Dictionary<string, Export>();
|
||||
|
||||
foreach (var pTab in pTables) {
|
||||
var symbol_table = ReadArray<TSym>(conv.Long(pTab.offset), conv.Int(pTab.count));
|
||||
|
||||
foreach (var symbol in symbol_table) {
|
||||
string name = string.Empty;
|
||||
try {
|
||||
name = ReadNullTerminatedString(conv.Long(pTab.strings) + symbol.st_name);
|
||||
} catch (ArgumentOutOfRangeException) {
|
||||
// Name has been stripped, maybe previously dumped image
|
||||
continue;
|
||||
}
|
||||
|
||||
var type = symbol.type == Elf.STT_FUNC? SymbolType.Function
|
||||
: symbol.type == Elf.STT_OBJECT || symbol.type == Elf.STT_COMMON? SymbolType.Name
|
||||
: SymbolType.Unknown;
|
||||
|
||||
if (symbol.st_shndx == (ushort) Elf.SHN_UNDEF)
|
||||
type = SymbolType.Import;
|
||||
|
||||
// Avoid duplicates
|
||||
var symbolItem = new Symbol {Name = name, Type = type, VirtualAddress = conv.ULong(symbol.st_value) };
|
||||
symbolTable.TryAdd(name, symbolItem);
|
||||
if (symbol.st_shndx != (ushort) Elf.SHN_UNDEF)
|
||||
exportTable.TryAdd(name, new Export {Name = symbolItem.DemangledName, VirtualAddress = conv.ULong(symbol.st_value)});
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
// INIT_ARRAY is probably broken in dumped images and resaved dumped images
|
||||
if (getDynamic(Elf.DT_INIT_ARRAY) == null || getDynamic(Elf.DT_INIT_ARRAYSZ) == null || isMemoryImage)
|
||||
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;
|
||||
|
||||
var init_array = conv.UIntArray(ReadArray<TWord>(init, conv.Int(size) / (Bits / 8)));
|
||||
|
||||
// Additionally, check if there is an old-style DT_INIT function and include it in the list if so
|
||||
if (getDynamic(Elf.DT_INIT) != null)
|
||||
init_array = init_array.Concat(conv.UIntArray(new[] { getDynamic(Elf.DT_INIT).d_un })).ToArray();
|
||||
|
||||
return init_array.Select(x => MapVATR(x)).ToArray();
|
||||
}
|
||||
|
||||
public override IEnumerable<Section> GetSections() {
|
||||
// If the sections have been stripped, use the segment list from the PHT instead
|
||||
if (preferPHT)
|
||||
return program_header_table.Select(p => new Section {
|
||||
VirtualStart = conv.ULong(p.p_vaddr),
|
||||
VirtualEnd = conv.ULong(p.p_vaddr) + conv.ULong(p.p_memsz) - 1,
|
||||
ImageStart = (uint) conv.Int(p.p_offset),
|
||||
ImageEnd = (uint) conv.Int(p.p_offset) + (uint) conv.Int(p.p_filesz) - 1,
|
||||
|
||||
// Not correct but probably the best we can do
|
||||
IsData = ((Elf) p.p_flags & Elf.PF_R) != 0,
|
||||
IsExec = ((Elf) p.p_flags & Elf.PF_X) != 0,
|
||||
IsBSS = conv.Int(p.p_filesz) == 0,
|
||||
|
||||
Name = string.Empty
|
||||
});
|
||||
|
||||
// Return sections list
|
||||
return section_header_table.Select(s => new Section {
|
||||
VirtualStart = conv.ULong(s.sh_addr),
|
||||
VirtualEnd = conv.ULong(s.sh_addr) + conv.ULong(s.sh_size) - 1,
|
||||
ImageStart = (uint) conv.Int(s.sh_offset),
|
||||
ImageEnd = (uint) conv.Int(s.sh_offset) + (uint) conv.Int(s.sh_size) - 1,
|
||||
|
||||
IsData = ((Elf) conv.Int(s.sh_flags) & Elf.SHF_ALLOC) == Elf.SHF_ALLOC && ((Elf) conv.Int(s.sh_flags) & Elf.SHF_EXECINSTR) == 0 && (Elf) s.sh_type == Elf.SHT_PROGBITS,
|
||||
IsExec = ((Elf) conv.Int(s.sh_flags) & Elf.SHF_EXECINSTR) == Elf.SHF_EXECINSTR && (Elf) s.sh_type == Elf.SHT_PROGBITS,
|
||||
IsBSS = (Elf) s.sh_type == Elf.SHT_NOBITS,
|
||||
|
||||
Name = sectionByName.First(sbn => conv.Int(sbn.Value.sh_offset) == conv.Int(s.sh_offset)).Key
|
||||
});
|
||||
}
|
||||
|
||||
// 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)));
|
||||
}
|
||||
|
||||
public override ulong MapFileOffsetToVA(uint offset) {
|
||||
// Exclude relocation areas
|
||||
if (reverseMapExclusions.Any(r => offset >= r.Start && offset <= r.End))
|
||||
throw new InvalidOperationException("Attempt to map to a relocation address");
|
||||
|
||||
var section = program_header_table.First(x => offset >= conv.Int(x.p_offset) && offset < conv.Int(x.p_offset) + conv.Int(x.p_filesz));
|
||||
return conv.ULong(section.p_vaddr) + offset - conv.ULong(section.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);
|
||||
}
|
||||
}
|
||||
16
Il2CppInspector.Common/FileFormatStreams/Export.cs
Normal file
16
Il2CppInspector.Common/FileFormatStreams/Export.cs
Normal file
@@ -0,0 +1,16 @@
|
||||
/*
|
||||
Copyright 2020 Katy Coe - http://www.djkaty.com - https://github.com/djkaty
|
||||
|
||||
All rights reserved.
|
||||
*/
|
||||
|
||||
namespace Il2CppInspector
|
||||
{
|
||||
// A code file function export
|
||||
public class Export
|
||||
{
|
||||
public int Ordinal;
|
||||
public string Name;
|
||||
public ulong VirtualAddress;
|
||||
}
|
||||
}
|
||||
302
Il2CppInspector.Common/FileFormatStreams/FileFormatStream.cs
Normal file
302
Il2CppInspector.Common/FileFormatStreams/FileFormatStream.cs
Normal file
@@ -0,0 +1,302 @@
|
||||
/*
|
||||
Copyright 2017-2020 Katy Coe - http://www.djkaty.com - https://github.com/djkaty
|
||||
|
||||
All rights reserved.
|
||||
*/
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Text;
|
||||
using NoisyCowStudios.Bin2Object;
|
||||
|
||||
namespace Il2CppInspector
|
||||
{
|
||||
public interface IFileFormatStream
|
||||
{
|
||||
double Version { get; set; }
|
||||
long Length { get; }
|
||||
uint NumImages { get; }
|
||||
string DefaultFilename { get; }
|
||||
bool IsModified { get; }
|
||||
IEnumerable<IFileFormatStream> Images { get; } // Each child image of this object (eg. 32/64-bit versions in Fat MachO file)
|
||||
IFileFormatStream this[uint index] { get; } // With no additional override, one object = one file, this[0] == this
|
||||
long Position { get; set; }
|
||||
string Format { get; }
|
||||
string Arch { get; }
|
||||
int Bits { get; }
|
||||
ulong GlobalOffset { get; } // The virtual address where the code section (.text) would be loaded in memory
|
||||
ulong ImageBase { get; } // The virtual address of where the image would be loaded in memory (same as GlobalOffset except for PE)
|
||||
IEnumerable<IFileFormatStream> TryNextLoadStrategy(); // Some images can be loaded multiple ways, eg. default, packed
|
||||
Dictionary<string, Symbol> GetSymbolTable();
|
||||
uint[] GetFunctionTable();
|
||||
IEnumerable<Export> GetExports();
|
||||
IEnumerable<Section> GetSections();
|
||||
bool TryGetSections(out IEnumerable<Section> sections);
|
||||
|
||||
uint MapVATR(ulong uiAddr);
|
||||
bool TryMapVATR(ulong uiAddr, out uint fileOffset);
|
||||
ulong MapFileOffsetToVA(uint offset);
|
||||
bool TryMapFileOffsetToVA(uint offset, out ulong va);
|
||||
|
||||
byte ReadByte();
|
||||
byte ReadByte(long uiAddr);
|
||||
byte[] ReadBytes(int count);
|
||||
byte[] ReadBytes(long uiAddr, int count);
|
||||
bool ReadBoolean();
|
||||
bool ReadBoolean(long uiAddr);
|
||||
long ReadInt64();
|
||||
long ReadInt64(long uiAddr);
|
||||
int ReadInt32();
|
||||
int ReadInt32(long uiAddr);
|
||||
short ReadInt16();
|
||||
short ReadInt16(long uiAddr);
|
||||
ulong ReadUInt64();
|
||||
ulong ReadUInt64(long uiAddr);
|
||||
uint ReadUInt32();
|
||||
uint ReadUInt32(long uiAddr);
|
||||
ushort ReadUInt16();
|
||||
ushort ReadUInt16(long uiAddr);
|
||||
U ReadObject<U>() where U : new();
|
||||
U ReadObject<U>(long uiAddr) where U : new();
|
||||
U[] ReadArray<U>(int count) where U : new();
|
||||
U[] ReadArray<U>(long uiAddr, int count) where U : new();
|
||||
string ReadNullTerminatedString(Encoding encoding = null);
|
||||
string ReadNullTerminatedString(long uiAddr, Encoding encoding = null);
|
||||
string ReadFixedLengthString(int length, Encoding encoding = null);
|
||||
string ReadFixedLengthString(long uiAddr, int length, Encoding encoding = null);
|
||||
|
||||
long ReadWord();
|
||||
long ReadWord(long uiAddr);
|
||||
long[] ReadWordArray(int count);
|
||||
long[] ReadWordArray(long uiAddr, int count);
|
||||
|
||||
byte ReadMappedByte(ulong uiAddr);
|
||||
byte[] ReadMappedBytes(ulong uiAddr, int count);
|
||||
bool ReadMappedBoolean(ulong uiAddr);
|
||||
long ReadMappedInt64(ulong uiAddr);
|
||||
int ReadMappedInt32(ulong uiAddr);
|
||||
short ReadMappedInt16(ulong uiAddr);
|
||||
ulong ReadMappedUInt64(ulong uiAddr);
|
||||
uint ReadMappedUInt32(ulong uiAddr);
|
||||
ushort ReadMappedUInt16(ulong uiAddr);
|
||||
U ReadMappedObject<U>(ulong uiAddr) where U : new();
|
||||
U[] ReadMappedArray<U>(ulong uiAddr, int count) where U : new();
|
||||
string ReadMappedNullTerminatedString(ulong uiAddr, Encoding encoding = null);
|
||||
string ReadMappedFixedLengthString(ulong uiAddr, int length, Encoding encoding = null);
|
||||
|
||||
long ReadMappedWord(ulong uiAddr);
|
||||
long[] ReadMappedWordArray(ulong uiAddr, int count);
|
||||
List<U> ReadMappedObjectPointerArray<U>(ulong uiAddr, int count) where U : new();
|
||||
|
||||
void WriteEndianBytes(byte[] bytes);
|
||||
void Write(long int64);
|
||||
void Write(ulong uint64);
|
||||
void Write(int int32);
|
||||
void Write(uint uint32);
|
||||
void Write(short int16);
|
||||
void Write(ushort uint16);
|
||||
void Write(long addr, byte[] bytes);
|
||||
void Write(long addr, long int64);
|
||||
void Write(long addr, ulong uint64);
|
||||
void Write(long addr, int int32);
|
||||
void Write(long addr, uint uint32);
|
||||
void Write(long addr, short int16);
|
||||
void Write(long addr, ushort uint16);
|
||||
void Write(long addr, byte value);
|
||||
void Write(long addr, bool value);
|
||||
void WriteObject<T>(long addr, T obj);
|
||||
void WriteObject<T>(T obj);
|
||||
void WriteArray<T>(long addr, T[] array);
|
||||
void WriteArray<T>(T[] array);
|
||||
void WriteNullTerminatedString(long addr, string str, Encoding encoding = null);
|
||||
void WriteNullTerminatedString(string str, Encoding encoding = null);
|
||||
void WriteFixedLengthString(long addr, string str, int size = -1, Encoding encoding = null);
|
||||
void WriteFixedLengthString(string str, int size = -1, Encoding encoding = null);
|
||||
|
||||
EventHandler<string> OnStatusUpdate { get; set; }
|
||||
|
||||
public void AddPrimitiveMapping(Type objType, Type streamType);
|
||||
public void CopyTo(Stream stream);
|
||||
}
|
||||
|
||||
public class FileFormatStream
|
||||
{
|
||||
// Helper method to try all defined file formats when the contents of the binary is unknown
|
||||
public static IFileFormatStream Load(string filename, LoadOptions loadOptions = null, EventHandler<string> statusCallback = null)
|
||||
=> Load(new FileStream(filename, FileMode.Open, FileAccess.Read), loadOptions, statusCallback);
|
||||
|
||||
public static IFileFormatStream Load(Stream stream, LoadOptions loadOptions = null, EventHandler<string> statusCallback = null) {
|
||||
var types = Assembly.GetExecutingAssembly().DefinedTypes
|
||||
.Where(x => x.ImplementedInterfaces.Contains(typeof(IFileFormatStream)) && !x.IsGenericTypeDefinition);
|
||||
|
||||
foreach (var type in types) {
|
||||
try {
|
||||
if (type.GetMethod("Load", BindingFlags.FlattenHierarchy | BindingFlags.Static | BindingFlags.Public,
|
||||
null, new[] {typeof(Stream), typeof(LoadOptions), typeof(EventHandler<string>)}, null)
|
||||
.Invoke(null, new object[] {stream, loadOptions, statusCallback}) is IFileFormatStream loaded)
|
||||
return loaded;
|
||||
}
|
||||
catch (TargetInvocationException ex) {
|
||||
throw ex.InnerException;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public abstract class FileFormatStream<T> : BinaryObjectStream, IFileFormatStream where T : FileFormatStream<T>
|
||||
{
|
||||
public abstract string DefaultFilename { get; }
|
||||
|
||||
public bool IsModified { get; protected set; } = false;
|
||||
|
||||
public uint NumImages { get; protected set; } = 1;
|
||||
|
||||
public ulong GlobalOffset { get; protected set; }
|
||||
|
||||
public virtual ulong ImageBase => GlobalOffset;
|
||||
|
||||
public virtual string Format => throw new NotImplementedException();
|
||||
|
||||
public virtual string Arch => throw new NotImplementedException();
|
||||
|
||||
public virtual int Bits => throw new NotImplementedException();
|
||||
|
||||
// Extra parameters to be passed to a loader
|
||||
protected LoadOptions LoadOptions;
|
||||
|
||||
public EventHandler<string> OnStatusUpdate { get; set; }
|
||||
|
||||
protected void StatusUpdate(string status) => OnStatusUpdate?.Invoke(this, status);
|
||||
|
||||
public IEnumerable<IFileFormatStream> Images {
|
||||
get {
|
||||
for (uint i = 0; i < NumImages; i++)
|
||||
yield return this[i];
|
||||
}
|
||||
}
|
||||
|
||||
public static T Load(string filename, LoadOptions loadOptions = null, EventHandler<string> statusCallback = null) {
|
||||
using var stream = new FileStream(filename, FileMode.Open, FileAccess.Read, FileShare.Read);
|
||||
return Load(stream, loadOptions, statusCallback);
|
||||
}
|
||||
|
||||
public static T Load(Stream stream, LoadOptions loadOptions = null, EventHandler<string> statusCallback = null) {
|
||||
// Copy the original stream in case we modify it
|
||||
if (stream.CanSeek)
|
||||
stream.Position = 0;
|
||||
var binary = (T) Activator.CreateInstance(typeof(T));
|
||||
stream.CopyTo(binary);
|
||||
binary.Position = 0;
|
||||
return binary.InitImpl(loadOptions, statusCallback) ? binary : null;
|
||||
}
|
||||
|
||||
private bool InitImpl(LoadOptions loadOptions, EventHandler<string> statusCallback) {
|
||||
LoadOptions = loadOptions;
|
||||
OnStatusUpdate = statusCallback;
|
||||
return Init();
|
||||
}
|
||||
|
||||
// Confirm file is valid and set up RVA mappings
|
||||
protected virtual bool Init() => throw new NotImplementedException();
|
||||
|
||||
// Choose an image within the file for multi-architecture binaries
|
||||
public virtual IFileFormatStream this[uint index] => (index == 0)? this : throw new IndexOutOfRangeException("Binary image index out of bounds");
|
||||
|
||||
// For images that can be loaded and then tested with Il2CppBinary in multiple ways, get the next possible version of the image
|
||||
public virtual IEnumerable<IFileFormatStream> TryNextLoadStrategy() { yield return this; }
|
||||
|
||||
// Find search locations in the symbol table for Il2Cpp data
|
||||
public virtual Dictionary<string, Symbol> GetSymbolTable() => new Dictionary<string, Symbol>();
|
||||
|
||||
// Find search locations in the machine code for Il2Cpp data
|
||||
public virtual uint[] GetFunctionTable() => throw new NotImplementedException();
|
||||
|
||||
// Find all symbol exports for the image
|
||||
public virtual IEnumerable<Export> GetExports() => null;
|
||||
|
||||
// Get all sections for the image in a universal format
|
||||
public virtual IEnumerable<Section> GetSections() => throw new NotImplementedException();
|
||||
|
||||
public bool TryGetSections(out IEnumerable<Section> sections) {
|
||||
try {
|
||||
sections = GetSections();
|
||||
return true;
|
||||
} catch (NotImplementedException) {
|
||||
sections = null;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Map an RVA to an offset into the file image
|
||||
// No mapping by default
|
||||
public virtual uint MapVATR(ulong uiAddr) => (uint) uiAddr;
|
||||
|
||||
// Try to map an RVA to an offset in the file image
|
||||
public bool TryMapVATR(ulong uiAddr, out uint fileOffset) {
|
||||
try {
|
||||
fileOffset = MapVATR(uiAddr);
|
||||
return true;
|
||||
} catch (InvalidOperationException) {
|
||||
fileOffset = 0;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Map an offset into the file image to an RVA
|
||||
// No mapping by default
|
||||
public virtual ulong MapFileOffsetToVA(uint offset) => offset;
|
||||
|
||||
// Try to map an offset into the file image to an RVA
|
||||
public bool TryMapFileOffsetToVA(uint offset, out ulong va) {
|
||||
try {
|
||||
va = MapFileOffsetToVA(offset);
|
||||
return true;
|
||||
}
|
||||
catch (InvalidOperationException) {
|
||||
va = 0;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// 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);
|
||||
public long[] ReadWordArray(int count) => ReadArray<long>(count);
|
||||
public long[] ReadWordArray(long uiAddr, int count) => ReadArray<long>(uiAddr, count);
|
||||
|
||||
// Retrieve items from specified RVA(s)
|
||||
public byte ReadMappedByte(ulong uiAddr) => ReadByte(MapVATR(uiAddr));
|
||||
public byte[] ReadMappedBytes(ulong uiAddr, int count) => count > 0? ReadBytes(MapVATR(uiAddr), count) : new byte[0];
|
||||
public bool ReadMappedBoolean(ulong uiAddr) => ReadBoolean(MapVATR(uiAddr));
|
||||
public long ReadMappedInt64(ulong uiAddr) => ReadInt64(MapVATR(uiAddr));
|
||||
public int ReadMappedInt32(ulong uiAddr) => ReadInt32(MapVATR(uiAddr));
|
||||
public short ReadMappedInt16(ulong uiAddr) => ReadInt16(MapVATR(uiAddr));
|
||||
public ulong ReadMappedUInt64(ulong uiAddr) => ReadUInt64(MapVATR(uiAddr));
|
||||
public uint ReadMappedUInt32(ulong uiAddr) => ReadUInt32(MapVATR(uiAddr));
|
||||
public ushort ReadMappedUInt16(ulong uiAddr) => ReadUInt16(MapVATR(uiAddr));
|
||||
|
||||
public U ReadMappedObject<U>(ulong uiAddr) where U : new() => ReadObject<U>(MapVATR(uiAddr));
|
||||
public U[] ReadMappedArray<U>(ulong uiAddr, int count) where U : new() => count > 0 ? ReadArray<U>(MapVATR(uiAddr), count) : new U[0];
|
||||
public string ReadMappedNullTerminatedString(ulong uiAddr, Encoding encoding = null) => ReadNullTerminatedString(MapVATR(uiAddr), encoding);
|
||||
public string ReadMappedFixedLengthString(ulong uiAddr, int length, Encoding encoding = null) => ReadFixedLengthString(MapVATR(uiAddr), length, encoding);
|
||||
|
||||
// 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 ReadMappedWord(ulong uiAddr) => ReadWord(MapVATR(uiAddr));
|
||||
public long[] ReadMappedWordArray(ulong uiAddr, int count) => count > 0 ? ReadArray<long>(MapVATR(uiAddr), count) : new long[0];
|
||||
|
||||
// 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
282
Il2CppInspector.Common/FileFormatStreams/FormatLayouts/Elf.cs
Normal file
282
Il2CppInspector.Common/FileFormatStreams/FormatLayouts/Elf.cs
Normal file
@@ -0,0 +1,282 @@
|
||||
/*
|
||||
Copyright 2017 Perfare - https://github.com/Perfare/Il2CppDumper
|
||||
Copyright 2017-2020 Katy Coe - http://www.djkaty.com - https://github.com/djkaty
|
||||
|
||||
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,
|
||||
|
||||
// elf_header.e_Type
|
||||
ET_EXEC = 2,
|
||||
|
||||
// PHTs
|
||||
PT_LOAD = 1,
|
||||
PT_DYNAMIC = 2,
|
||||
|
||||
PF_X = 1,
|
||||
PF_W = 2,
|
||||
PF_R = 4,
|
||||
|
||||
// SHTs
|
||||
SHT_PROGBITS = 1,
|
||||
SHT_SYMTAB = 2,
|
||||
SHT_STRTAB = 3,
|
||||
SHT_RELA = 4,
|
||||
SHT_NOBITS = 8,
|
||||
SHT_REL = 9,
|
||||
SHT_DYNSYM = 11,
|
||||
|
||||
// SHNs
|
||||
SHN_UNDEF = 0,
|
||||
|
||||
// STTs
|
||||
STT_NOTYPE = 0,
|
||||
STT_OBJECT = 1,
|
||||
STT_FUNC = 2,
|
||||
STT_SECTION = 3,
|
||||
STT_FILE = 4,
|
||||
STT_COMMON = 5,
|
||||
STT_LOOS = 10,
|
||||
STT_HIOS = 12,
|
||||
STT_LOPROC = 13,
|
||||
STT_SPARC_REGISTER = 13,
|
||||
STT_HIPROC = 15,
|
||||
|
||||
// SHFs
|
||||
SHF_ALLOC = 2,
|
||||
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,
|
||||
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; set; }
|
||||
TWord p_filesz { get; set; }
|
||||
TWord p_memsz { get; }
|
||||
TWord p_vaddr { get; set; }
|
||||
uint p_flags { get; }
|
||||
}
|
||||
|
||||
internal class elf_32_phdr : Ielf_phdr<uint> {
|
||||
public uint p_type => f_p_type;
|
||||
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;
|
||||
|
||||
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 f_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 { 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 { get => f_p_vaddr; set => f_p_vaddr = value; }
|
||||
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 f_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; }
|
||||
ushort st_shndx { get; }
|
||||
Elf st_info { get; }
|
||||
Elf type { get; }
|
||||
}
|
||||
|
||||
internal class elf_32_sym : Ielf_sym<uint>
|
||||
{
|
||||
public uint st_name => f_st_name;
|
||||
public uint st_value => f_st_value;
|
||||
public ushort st_shndx => f_st_shndx;
|
||||
public Elf st_info => (Elf) f_st_info;
|
||||
public Elf type => (Elf) (f_st_info & 0xf);
|
||||
|
||||
public uint f_st_name;
|
||||
public uint f_st_value;
|
||||
public uint st_size;
|
||||
public byte f_st_info;
|
||||
public byte st_other;
|
||||
public ushort f_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 ushort st_shndx => f_st_shndx;
|
||||
public Elf st_info => (Elf) f_st_info;
|
||||
public Elf type => (Elf) (f_st_info & 0xf);
|
||||
|
||||
public uint f_st_name;
|
||||
public byte f_st_info;
|
||||
public byte st_other;
|
||||
public ushort f_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
|
||||
}
|
||||
175
Il2CppInspector.Common/FileFormatStreams/FormatLayouts/MachO.cs
Normal file
175
Il2CppInspector.Common/FileFormatStreams/FormatLayouts/MachO.cs
Normal file
@@ -0,0 +1,175 @@
|
||||
/*
|
||||
Copyright 2017-2020 Katy Coe - http://www.djkaty.com - https://github.com/djkaty
|
||||
|
||||
All rights reserved.
|
||||
*/
|
||||
|
||||
using System;
|
||||
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_MIN_FILETYPE = 0x1,
|
||||
MH_MAX_FILETYPE = 0xB,
|
||||
|
||||
LC_SEGMENT = 0x1,
|
||||
LC_SYMTAB = 0x2,
|
||||
LC_DYSYMTAB = 0xb,
|
||||
LC_SEGMENT_64 = 0x19,
|
||||
LC_ENCRYPTION_INFO = 0x21,
|
||||
LC_DYLD_INFO = 0x22,
|
||||
LC_DYLD_INFO_ONLY = 0x80000022,
|
||||
LC_FUNCTION_STARTS = 0x26,
|
||||
LC_ENCRYPTION_INFO_64 = 0x2C,
|
||||
|
||||
CPU_TYPE_X86 = 7,
|
||||
CPU_TYPE_X86_64 = 0x01000000 + CPU_TYPE_X86,
|
||||
CPU_TYPE_ARM = 12,
|
||||
CPU_TYPE_ARM64 = 0x01000000 + CPU_TYPE_ARM,
|
||||
}
|
||||
|
||||
[Flags]
|
||||
public enum MachO_NType : byte
|
||||
{
|
||||
// n_type masks
|
||||
N_STAB = 0xe0,
|
||||
N_PEXT = 0x10,
|
||||
N_TYPE = 0x0e,
|
||||
N_EXT = 0x01,
|
||||
|
||||
// n_stab bits
|
||||
N_UNDF = 0x00,
|
||||
N_ABS = 0x02,
|
||||
N_SECT = 0x0e,
|
||||
N_PBUD = 0x0c,
|
||||
N_INDR = 0x0a,
|
||||
|
||||
// n_type bits when N_STAB has some bits set
|
||||
N_GSYM = 0x20,
|
||||
N_FNAME = 0x22,
|
||||
N_FUN = 0x24,
|
||||
N_STSYM = 0x26,
|
||||
N_BNSYM = 0x2E,
|
||||
N_ENSYM = 0x4E,
|
||||
N_SO = 0x64,
|
||||
N_OSO = 0x66,
|
||||
}
|
||||
|
||||
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 MachODyldInfoCommand
|
||||
{
|
||||
public uint RebaseOffset;
|
||||
public uint RebaseSize;
|
||||
public uint BindOffset;
|
||||
public uint BindSize;
|
||||
public uint WeakBindOffset;
|
||||
public uint WeakBindSize;
|
||||
public uint LazyBindOffset;
|
||||
public uint LazyBindSize;
|
||||
public uint ExportOffset;
|
||||
public uint ExportSize;
|
||||
}
|
||||
|
||||
internal class MachOSymtabCommand
|
||||
{
|
||||
public uint SymOffset;
|
||||
public uint NumSyms;
|
||||
public uint StrOffset;
|
||||
public uint StrSize;
|
||||
}
|
||||
|
||||
internal class MachOEncryptionInfo
|
||||
{
|
||||
// MachOLoadCommand
|
||||
public uint CryptOffset;
|
||||
public uint CryptSize;
|
||||
public uint CryptID;
|
||||
}
|
||||
|
||||
internal class MachO_nlist<TWord> where TWord : struct
|
||||
{
|
||||
public MachO_NType n_type => (MachO_NType) f_n_type;
|
||||
public uint n_strx;
|
||||
public byte f_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;
|
||||
}
|
||||
}
|
||||
173
Il2CppInspector.Common/FileFormatStreams/FormatLayouts/PE.cs
Normal file
173
Il2CppInspector.Common/FileFormatStreams/FormatLayouts/PE.cs
Normal file
@@ -0,0 +1,173 @@
|
||||
/*
|
||||
Copyright 2017-2020 Katy Coe - http://www.djkaty.com - https://github.com/djkaty
|
||||
|
||||
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,
|
||||
|
||||
IMAGE_SCN_CNT_CODE = 0x00000020,
|
||||
IMAGE_SCN_CNT_INITIALIZED_DATA = 0x00000040,
|
||||
IMAGE_SCN_CNT_UNINITIALIZED_DATA = 0x00000080,
|
||||
IMAGE_SCN_MEM_EXECUTE = 0x20000000,
|
||||
IMAGE_SCN_MEM_READ = 0x40000000,
|
||||
IMAGE_SCN_MEM_WRITE = 0x80000000
|
||||
}
|
||||
|
||||
#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; set; }
|
||||
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 { get => f_ImageBase; set => f_ImageBase = (uint) value; }
|
||||
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 { get => f_ImageBase; set => f_ImageBase = value; }
|
||||
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
|
||||
{
|
||||
public PE Characteristics => (PE) f_Characteristics;
|
||||
|
||||
[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 f_Characteristics;
|
||||
}
|
||||
|
||||
// _IMAGE_EXPORT_DIRECTORY
|
||||
internal class PEExportDirectory
|
||||
{
|
||||
public uint Characteristics;
|
||||
public uint TimeDateStamp;
|
||||
public ushort MajorVersion;
|
||||
public ushort MinorVersion;
|
||||
public uint Name;
|
||||
public uint Base;
|
||||
public uint NumberOfFunctions;
|
||||
public uint NumberOfNames;
|
||||
public uint AddressOfFunctions;
|
||||
public uint AddressOfNames;
|
||||
public uint AddressOfNameOrdinals;
|
||||
}
|
||||
#pragma warning restore CS0649
|
||||
}
|
||||
117
Il2CppInspector.Common/FileFormatStreams/FormatLayouts/SElf.cs
Normal file
117
Il2CppInspector.Common/FileFormatStreams/FormatLayouts/SElf.cs
Normal file
@@ -0,0 +1,117 @@
|
||||
/*
|
||||
Copyright 2020 Katy Coe - http://www.hearthcode.org - http://www.djkaty.com
|
||||
|
||||
All rights reserved.
|
||||
*/
|
||||
|
||||
using System;
|
||||
using NoisyCowStudios.Bin2Object;
|
||||
|
||||
namespace Il2CppInspector
|
||||
{
|
||||
internal enum SElfConsts : uint
|
||||
{
|
||||
Magic = 0x1D3D154F
|
||||
}
|
||||
|
||||
[Flags]
|
||||
internal enum SElfEntryFlags : ulong
|
||||
{
|
||||
Ordered = 0x1,
|
||||
Encrypted = 0x2,
|
||||
Signed = 0x4,
|
||||
Deflated = 0x8,
|
||||
WindowMask = 0x700,
|
||||
Blocks = 0x800,
|
||||
BlockSizeMask = 0xF000,
|
||||
Digests = 0x10000,
|
||||
Extents = 0x20000,
|
||||
SegmentIndexMask = 0x_000F_FFF0_0000
|
||||
}
|
||||
|
||||
// SCE-specific definitions for e_type
|
||||
internal enum SElfETypes : ushort
|
||||
{
|
||||
ET_SCE_EXEC = 0xFE00,
|
||||
ET_SCE_RELEXEC = 0xFE04,
|
||||
ET_SCE_STUBLIB = 0xFE0C,
|
||||
ET_SCE_DYNEXEC = 0xFE10, // SCE EXEC_ASLR (PS4 Executable with ASLR)
|
||||
ET_SCE_DYNAMIC = 0xFE18,
|
||||
ET_SCE_IOPRELEXEC = 0xFF80,
|
||||
ET_SCE_IOPRELEXEC2 = 0xFF81,
|
||||
ET_SCE_EERELEXEC = 0xFF90,
|
||||
ET_SCE_EERELEXEC2 = 0xFF91,
|
||||
ET_SCE_PSPRELEXEC = 0xFFA0,
|
||||
ET_SCE_PPURELEXEC = 0xFFA4,
|
||||
ET_SCE_ARMRELEXEC = 0xFFA5,
|
||||
ET_SCE_PSPOVERLAY = 0xFFA8
|
||||
}
|
||||
|
||||
// SCE-specific definitions for program header type
|
||||
internal enum SElfPTypes : uint
|
||||
{
|
||||
PT_SCE_RELA = 0x60000000,
|
||||
PT_SCE_DYNLIBDATA = 0x61000000,
|
||||
PT_SCE_PROCPARAM = 0x61000001,
|
||||
PT_SCE_MODULE_PARAM = 0x61000002,
|
||||
PT_SCE_RELRO = 0x61000010,
|
||||
PT_SCE_COMMENT = 0x6FFFFF00,
|
||||
PT_SCE_VERSION = 0x6FFFFF01
|
||||
}
|
||||
|
||||
// Extended info types
|
||||
internal enum SElfExInfoTypes
|
||||
{
|
||||
PTYPE_FAKE = 0x1,
|
||||
PTYPE_NPDRM_EXEC = 0x4,
|
||||
PTYPE_NPDRM_DYNLIB = 0x5,
|
||||
PTYPE_SYSTEM_EXEC = 0x8,
|
||||
PTYPE_SYSTEM_DYNLIB = 0x9,
|
||||
PTYPE_HOST_KERNEL = 0xC,
|
||||
PTYPE_SECURE_MODULE = 0xE,
|
||||
PTYPE_SECURE_KERNEL = 0xF
|
||||
}
|
||||
|
||||
#pragma warning disable CS0649
|
||||
internal class SElfHeader
|
||||
{
|
||||
public uint Magic;
|
||||
public byte Version;
|
||||
public byte Mode;
|
||||
public byte Endian;
|
||||
public byte Attributes;
|
||||
public uint KeyType;
|
||||
public ushort HeaderSize;
|
||||
public ushort MetadataSize;
|
||||
public ulong FileSize;
|
||||
public ushort NumberOfEntries;
|
||||
public ushort Flags;
|
||||
public uint Padding;
|
||||
}
|
||||
|
||||
internal class SElfEntry
|
||||
{
|
||||
public ulong Flags;
|
||||
public ulong FileOffset;
|
||||
public ulong EncryptedCompressedSize;
|
||||
public ulong MemorySize;
|
||||
|
||||
public bool IsEncrypted => (Flags & (ulong) SElfEntryFlags.Encrypted) != 0;
|
||||
public bool IsDeflated => (Flags & (ulong) SElfEntryFlags.Deflated) != 0;
|
||||
public bool HasBlocks => (Flags & (ulong) SElfEntryFlags.Blocks) != 0;
|
||||
public ushort SegmentIndex => (ushort) ((Flags & (ulong) SElfEntryFlags.SegmentIndexMask) >> 20);
|
||||
}
|
||||
|
||||
internal class SElfSCEData
|
||||
{
|
||||
public ulong ProductAuthID;
|
||||
public ulong ProductType;
|
||||
public ulong AppVersion;
|
||||
public ulong FirmwareVersion;
|
||||
[ArrayLength(FixedSize = 0x20)]
|
||||
public byte[] ContentID; // Only if NPDRM
|
||||
[ArrayLength(FixedSize = 0x20)]
|
||||
public byte[] SHA256Digest;
|
||||
}
|
||||
#pragma warning restore CS0649
|
||||
}
|
||||
33
Il2CppInspector.Common/FileFormatStreams/FormatLayouts/UB.cs
Normal file
33
Il2CppInspector.Common/FileFormatStreams/FormatLayouts/UB.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
21
Il2CppInspector.Common/FileFormatStreams/LoadOptions.cs
Normal file
21
Il2CppInspector.Common/FileFormatStreams/LoadOptions.cs
Normal file
@@ -0,0 +1,21 @@
|
||||
/*
|
||||
Copyright 2017-2020 Katy Coe - http://www.djkaty.com - https://github.com/djkaty
|
||||
|
||||
All rights reserved.
|
||||
*/
|
||||
|
||||
namespace Il2CppInspector
|
||||
{
|
||||
// Modifiers for use when loading binary files
|
||||
public class LoadOptions
|
||||
{
|
||||
// For dumped ELF files, the virtual address to which we should rebase - ignored for other file types
|
||||
// Use 2^64-1 to prevent rebasing on a dumped file
|
||||
public ulong ImageBase { get; set; }
|
||||
|
||||
// For Linux process memory map inputs, we need the full path so we can find the .bin files
|
||||
// For packed PE files, we need the full path to reload the file via Win32 API
|
||||
// Ignored for all other cases
|
||||
public string BinaryFilePath { get; set; }
|
||||
}
|
||||
}
|
||||
293
Il2CppInspector.Common/FileFormatStreams/MachOReader.cs
Normal file
293
Il2CppInspector.Common/FileFormatStreams/MachOReader.cs
Normal file
@@ -0,0 +1,293 @@
|
||||
/*
|
||||
Copyright 2017-2020 Katy Coe - http://www.djkaty.com - https://github.com/djkaty
|
||||
|
||||
All rights reserved.
|
||||
*/
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using NoisyCowStudios.Bin2Object;
|
||||
|
||||
namespace Il2CppInspector
|
||||
{
|
||||
internal class MachOReader32 : MachOReader<uint, MachOReader32, Convert32>
|
||||
{
|
||||
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 = machoSections.First(x => uiAddr >= x.Address && uiAddr <= x.Address + x.Size && x.Name != "__bss" && x.Name != "__common");
|
||||
return (uint) uiAddr - (section.Address - section.ImageOffset);
|
||||
}
|
||||
|
||||
public override ulong MapFileOffsetToVA(uint offset) {
|
||||
var section = machoSections.First(x => offset >= x.ImageOffset && offset < x.ImageOffset + x.Size);
|
||||
return section.Address + offset - section.ImageOffset;
|
||||
}
|
||||
}
|
||||
|
||||
internal class MachOReader64 : MachOReader<ulong, MachOReader64, Convert64>
|
||||
{
|
||||
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 = machoSections.First(x => uiAddr >= x.Address && uiAddr <= x.Address + x.Size && x.Name != "__bss" && x.Name != "__common");
|
||||
return (uint) (uiAddr - (section.Address - section.ImageOffset));
|
||||
}
|
||||
|
||||
public override ulong MapFileOffsetToVA(uint offset) {
|
||||
var section = machoSections.First(x => offset >= x.ImageOffset && offset < x.ImageOffset + x.Size);
|
||||
return section.Address + offset - 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> : FileFormatStream<TReader>
|
||||
where TWord : struct
|
||||
where TReader : FileFormatStream<TReader>
|
||||
where TConvert : IWordConverter<TWord>, new()
|
||||
{
|
||||
private readonly TConvert conv = new TConvert();
|
||||
|
||||
private MachOHeader<TWord> header;
|
||||
protected readonly List<MachOSection<TWord>> machoSections = new List<MachOSection<TWord>>();
|
||||
private List<Section> sections = new List<Section>();
|
||||
|
||||
private MachOSection<TWord> funcTab;
|
||||
private MachOSymtabCommand symTab;
|
||||
|
||||
private List<Export> exports = new List<Export>();
|
||||
|
||||
public override string DefaultFilename => "app";
|
||||
|
||||
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 of a known file type
|
||||
if ((MachO)header.FileType < MachO.MH_MIN_FILETYPE || (MachO)header.FileType > MachO.MH_MAX_FILETYPE)
|
||||
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>>();
|
||||
|
||||
// Sections in each segment
|
||||
for (int s = 0; s < segment.NumSections; s++) {
|
||||
var section = ReadObject<MachOSection<TWord>>();
|
||||
machoSections.Add(section);
|
||||
|
||||
// Create universal section
|
||||
sections.Add(new Section {
|
||||
VirtualStart = conv.ULong(section.Address),
|
||||
VirtualEnd = conv.ULong(section.Address) + (uint) Math.Max(conv.Int(section.Size) - 1, 0),
|
||||
ImageStart = section.ImageOffset,
|
||||
ImageEnd = section.ImageOffset + (uint) Math.Max(conv.Int(section.Size) - 1, 0),
|
||||
|
||||
IsData = segment.Name == "__TEXT" || segment.Name == "__DATA",
|
||||
IsExec = segment.Name == "__TEXT",
|
||||
IsBSS = segment.Name == "__DATA" && (section.Name == "__bss" || section.Name == "__common"),
|
||||
|
||||
Name = section.Name
|
||||
});
|
||||
|
||||
// First code 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;
|
||||
|
||||
// Compressed dyld information
|
||||
case MachO.LC_DYLD_INFO:
|
||||
case MachO.LC_DYLD_INFO_ONLY:
|
||||
var dyld = ReadObject<MachODyldInfoCommand>();
|
||||
|
||||
loadExportTrie(dyld.ExportOffset);
|
||||
break;
|
||||
|
||||
// Encryption check
|
||||
// If cryptid == 1, this binary is encrypted with FairPlay DRM
|
||||
case MachO.LC_ENCRYPTION_INFO:
|
||||
case MachO.LC_ENCRYPTION_INFO_64:
|
||||
var encryptionInfo = ReadObject<MachOEncryptionInfo>();
|
||||
if (encryptionInfo.CryptID != 0)
|
||||
throw new NotImplementedException("This Mach-O executable is encrypted with FairPlay DRM and cannot be processed. Please provide a decrypted version of the executable.");
|
||||
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 machoSections) {
|
||||
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;
|
||||
}
|
||||
|
||||
// Handle export trie
|
||||
private void loadExportTrie(uint trieOffset, uint nodeOffset = 0, string partialSymbol = "") {
|
||||
Position = trieOffset + nodeOffset;
|
||||
|
||||
var size = ULEB128.Decode(this);
|
||||
|
||||
// Terminal information
|
||||
if (size != 0) {
|
||||
var flags = ReadByte();
|
||||
var symbolKind = flags & 0x03;
|
||||
var symbolType = (flags >> 2) & 0x03;
|
||||
|
||||
// 0 = regular, 1 = weak, 2 = re-export, 3 = stub
|
||||
switch (symbolType) {
|
||||
case 0:
|
||||
var address = ULEB128.Decode(this);
|
||||
exports.Add(new Export {Name = partialSymbol, VirtualAddress = GlobalOffset + address});
|
||||
break;
|
||||
case 1:
|
||||
var weakAddress = ULEB128.Decode(this);
|
||||
exports.Add(new Export {Name = partialSymbol, VirtualAddress = GlobalOffset + weakAddress});
|
||||
break;
|
||||
case 2:
|
||||
var ordinal = ULEB128.Decode(this);
|
||||
var name = ReadNullTerminatedString();
|
||||
break;
|
||||
case 3:
|
||||
var stubOffset = ULEB128.Decode(this);
|
||||
var resolverOffset = ULEB128.Decode(this);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
var branchCount = ReadByte();
|
||||
|
||||
for (int branch = 0; branch < branchCount; branch++) {
|
||||
var prefix = ReadNullTerminatedString();
|
||||
var childNodeOffset = (uint) ULEB128.Decode(this);
|
||||
|
||||
var currentPosition = Position;
|
||||
loadExportTrie(trieOffset, childNodeOffset, partialSymbol + prefix);
|
||||
Position = currentPosition;
|
||||
}
|
||||
}
|
||||
|
||||
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, Symbol> GetSymbolTable() {
|
||||
var symbols = new Dictionary<string, Symbol>();
|
||||
|
||||
// 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 symbols with no address or name
|
||||
if (value == 0 || name.Length == 0)
|
||||
continue;
|
||||
|
||||
// Mask out the N_EXT and N_PEXT bits because we don't care about it
|
||||
var ntype = (MachO_NType) ((byte)symbol.n_type & ~(byte)MachO_NType.N_EXT & ~(byte)MachO_NType.N_PEXT);
|
||||
|
||||
// For non-debugging symbols (no bits of N_STAB set), just leave the N_TYPE
|
||||
// Otherwise leave the whole n_type field (with N_EXT and N_PEXT removed)
|
||||
var dbg = (symbol.n_type & MachO_NType.N_STAB) != 0;
|
||||
|
||||
if (dbg)
|
||||
if (ntype == MachO_NType.N_BNSYM || ntype == MachO_NType.N_ENSYM
|
||||
|| ntype == MachO_NType.N_SO || ntype == MachO_NType.N_OSO)
|
||||
continue;
|
||||
|
||||
var type = ntype == MachO_NType.N_FUN? SymbolType.Function
|
||||
: ntype == MachO_NType.N_STSYM || ntype == MachO_NType.N_GSYM || ntype == MachO_NType.N_SECT? SymbolType.Name
|
||||
: SymbolType.Unknown;
|
||||
|
||||
if (type == SymbolType.Unknown) {
|
||||
Console.WriteLine($"Unknown symbol type: {((int) ntype):x2} {value:x16} " + CxxDemangler.CxxDemangler.Demangle(name));
|
||||
}
|
||||
|
||||
// Ignore duplicates
|
||||
symbols.TryAdd(name, new Symbol { Name = name, VirtualAddress = value, Type = type });
|
||||
}
|
||||
return symbols;
|
||||
}
|
||||
|
||||
public override IEnumerable<Export> GetExports() => exports;
|
||||
|
||||
public override IEnumerable<Section> GetSections() => sections;
|
||||
}
|
||||
}
|
||||
286
Il2CppInspector.Common/FileFormatStreams/PEReader.cs
Normal file
286
Il2CppInspector.Common/FileFormatStreams/PEReader.cs
Normal file
@@ -0,0 +1,286 @@
|
||||
/*
|
||||
Copyright 2017-2020 Katy Coe - http://www.djkaty.com - https://github.com/djkaty
|
||||
|
||||
All rights reserved.
|
||||
*/
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text.RegularExpressions;
|
||||
using NoisyCowStudios.Bin2Object;
|
||||
|
||||
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 : FileFormatStream<PEReader>
|
||||
{
|
||||
[DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Auto)]
|
||||
private extern static IntPtr LoadLibrary(string lpLibFileName);
|
||||
|
||||
[DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Auto)]
|
||||
private extern static bool FreeLibrary(IntPtr hLibModule);
|
||||
|
||||
private COFFHeader coff;
|
||||
private IPEOptHeader pe;
|
||||
private PESection[] sections;
|
||||
private uint pFuncTable;
|
||||
private bool mightBePacked;
|
||||
|
||||
// Section types we need to rename in obfuscated binaries
|
||||
private Dictionary<PE, string> wantedSectionTypes = new Dictionary<PE, string> {
|
||||
[PE.IMAGE_SCN_MEM_READ | PE.IMAGE_SCN_MEM_EXECUTE | PE.IMAGE_SCN_CNT_CODE] = ".text",
|
||||
[PE.IMAGE_SCN_MEM_READ | PE.IMAGE_SCN_CNT_INITIALIZED_DATA] = ".rdata",
|
||||
[PE.IMAGE_SCN_MEM_READ | PE.IMAGE_SCN_MEM_WRITE | PE.IMAGE_SCN_CNT_INITIALIZED_DATA] = ".data"
|
||||
};
|
||||
|
||||
public override string DefaultFilename => "GameAssembly.dll";
|
||||
|
||||
|
||||
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;
|
||||
|
||||
public override ulong ImageBase => pe.ImageBase;
|
||||
|
||||
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);
|
||||
|
||||
// Unpacking must be done starting here, one byte after the end of the headers
|
||||
// Packed or previously packed with Themida? This is purely for information
|
||||
if (sections.FirstOrDefault(x => x.Name == ".themida") is PESection _)
|
||||
Console.WriteLine("Themida protection detected");
|
||||
|
||||
// Packed with anything (including Themida)?
|
||||
mightBePacked = sections.FirstOrDefault(x => x.Name == ".rdata") is null;
|
||||
|
||||
// Rename sections if needed (before potentially searching them or rewriting them to the stream)
|
||||
foreach (var section in sections.Where(s => wantedSectionTypes.Keys.Contains(s.Characteristics)))
|
||||
// Replace section name if blank or all whitespace
|
||||
if (Regex.IsMatch(section.Name, @"^\s*$"))
|
||||
section.Name = wantedSectionTypes[section.Characteristics];
|
||||
|
||||
// Get base of code
|
||||
GlobalOffset = pe.ImageBase + pe.BaseOfCode - sections.First(x => x.Name == ".text").PointerToRawData;
|
||||
|
||||
// Confirm that .rdata section begins at same place as IAT
|
||||
var rData = sections.First(x => x.Name == ".rdata");
|
||||
mightBePacked |= rData.VirtualAddress != IATStart;
|
||||
|
||||
// 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;
|
||||
}
|
||||
|
||||
// In the fist go round, we signal that this is at least a valid PE file; we don't try to unpack yet
|
||||
return true;
|
||||
}
|
||||
|
||||
// Load DLL into memory and save it as a new PE stream
|
||||
private void load() {
|
||||
// Check that the process is running in the same word size as the DLL
|
||||
// One way round this in future would be to spawn a new process of the correct word size
|
||||
if ((Environment.Is64BitProcess && Bits == 32) || (!Environment.Is64BitProcess && Bits == 64))
|
||||
throw new InvalidOperationException($"Cannot unpack a {Bits}-bit DLL from within a {(Environment.Is64BitProcess ? 64 : 32)}-bit process. Use the {Bits}-version of Il2CppInspector to unpack this DLL.");
|
||||
|
||||
// Get file path
|
||||
// This error should never occur with the bundled CLI and GUI; only when used as a library by a 3rd party tool
|
||||
if (!(LoadOptions.BinaryFilePath is string dllPath))
|
||||
throw new InvalidOperationException("To load a packed PE file, you must specify the DLL file path in LoadOptions");
|
||||
|
||||
// Attempt to load DLL and run startup functions
|
||||
// NOTE: This can cause a CSE (AccessViolation) for certain types of protection
|
||||
// so only try to unpack as the final load strategy
|
||||
IntPtr hModule = LoadLibrary(dllPath);
|
||||
if (hModule == IntPtr.Zero) {
|
||||
var lastErrorCode = Marshal.GetLastWin32Error();
|
||||
throw new InvalidOperationException($"Unable to load the DLL for unpacking: error code {lastErrorCode}");
|
||||
}
|
||||
|
||||
// Maximum image size
|
||||
var size = sections.Last().VirtualAddress + sections.Last().VirtualSize;
|
||||
|
||||
// Allocate memory for unpacked image
|
||||
var peBytes = new byte[size];
|
||||
|
||||
// Copy relevant sections from unmanaged memory
|
||||
foreach (var section in sections.Where(s => wantedSectionTypes.Keys.Contains(s.Characteristics)))
|
||||
Marshal.Copy(IntPtr.Add(hModule, (int) section.VirtualAddress), peBytes, (int) section.VirtualAddress, (int) section.VirtualSize);
|
||||
|
||||
// Decrease reference count for unload
|
||||
FreeLibrary(hModule);
|
||||
|
||||
// Rebase
|
||||
pe.ImageBase = (ulong) hModule.ToInt64();
|
||||
|
||||
// Rewrite sections to match memory layout
|
||||
foreach (var section in sections) {
|
||||
section.PointerToRawData = section.VirtualAddress;
|
||||
section.SizeOfRawData = section.VirtualSize;
|
||||
}
|
||||
|
||||
// Truncate memory stream at start of COFF header
|
||||
var endOfSignature = ReadUInt32(0x3C) + 4; // DOS header + 4-byte PE signature
|
||||
SetLength(endOfSignature);
|
||||
|
||||
// Re-write the stream (the headers are only necessary in case the user wants to save)
|
||||
Position = endOfSignature;
|
||||
WriteObject(coff);
|
||||
if (Bits == 32) WriteObject((PEOptHeader32) pe);
|
||||
else WriteObject((PEOptHeader64) pe);
|
||||
WriteArray(sections);
|
||||
Write(peBytes, (int) Position, peBytes.Length - (int) Position);
|
||||
|
||||
IsModified = true;
|
||||
}
|
||||
|
||||
// Raw file / unpacked file load strategies
|
||||
public override IEnumerable<IFileFormatStream> TryNextLoadStrategy() {
|
||||
// First load strategy: the regular file
|
||||
yield return this;
|
||||
|
||||
// Second load strategy: load the DLL into memory to unpack it
|
||||
if (mightBePacked) {
|
||||
Console.WriteLine("IL2CPP binary appears to be packed - attempting to unpack and retrying");
|
||||
StatusUpdate("Unpacking binary");
|
||||
load();
|
||||
yield return this;
|
||||
}
|
||||
}
|
||||
|
||||
public override uint[] GetFunctionTable() {
|
||||
if (pFuncTable == 0)
|
||||
return Array.Empty<uint>();
|
||||
|
||||
Position = pFuncTable;
|
||||
var addrs = new List<uint>();
|
||||
ulong addr;
|
||||
|
||||
// Use TryMapVATR to avoid crash if function table is stripped or corrupted
|
||||
// Can happen with packed or previously packed files
|
||||
while ((addr = pe is PEOptHeader32? ReadUInt32() : ReadUInt64()) != 0 && TryMapVATR(addr, out uint fileOffset))
|
||||
addrs.Add(fileOffset & 0xfffffffc);
|
||||
return addrs.ToArray();
|
||||
}
|
||||
|
||||
public override IEnumerable<Export> GetExports() {
|
||||
// Get exports table
|
||||
var ETStart = pe.DataDirectory[0].VirtualAddress + pe.ImageBase;
|
||||
|
||||
// Get export RVAs
|
||||
var exportDirectoryTable = ReadObject<PEExportDirectory>(MapVATR(ETStart));
|
||||
var exportCount = (int) exportDirectoryTable.NumberOfFunctions;
|
||||
var exportAddresses = ReadArray<uint>(MapVATR(exportDirectoryTable.AddressOfFunctions + pe.ImageBase), exportCount);
|
||||
var exports = exportAddresses.Select((a, i) => new Export {
|
||||
Ordinal = (int) (exportDirectoryTable.Base + i),
|
||||
VirtualAddress = GlobalOffset + a
|
||||
}).ToDictionary(x => x.Ordinal, x => x);
|
||||
|
||||
// Get export names
|
||||
var nameCount = (int) exportDirectoryTable.NumberOfNames;
|
||||
var namePointers = ReadArray<uint>(MapVATR(exportDirectoryTable.AddressOfNames + pe.ImageBase), nameCount);
|
||||
var ordinals = ReadArray<ushort>(MapVATR(exportDirectoryTable.AddressOfNameOrdinals + pe.ImageBase), nameCount);
|
||||
for (int i = 0; i < nameCount; i++) {
|
||||
var name = ReadNullTerminatedString(MapVATR(namePointers[i] + pe.ImageBase));
|
||||
var ordinal = (int) exportDirectoryTable.Base + ordinals[i];
|
||||
exports[ordinal].Name = name;
|
||||
}
|
||||
|
||||
return exports.Values;
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
public override ulong MapFileOffsetToVA(uint offset) {
|
||||
var section = sections.First(x => offset >= x.PointerToRawData && offset < x.PointerToRawData + x.SizeOfRawData);
|
||||
|
||||
return pe.ImageBase + section.VirtualAddress + offset - section.PointerToRawData;
|
||||
}
|
||||
|
||||
public override IEnumerable<Section> GetSections() {
|
||||
return sections.Select(s => new Section {
|
||||
VirtualStart = pe.ImageBase + s.VirtualAddress,
|
||||
VirtualEnd = pe.ImageBase + s.VirtualAddress + s.VirtualSize - 1,
|
||||
ImageStart = s.PointerToRawData,
|
||||
ImageEnd = s.PointerToRawData + s.SizeOfRawData - 1,
|
||||
|
||||
IsData = (s.Characteristics & PE.IMAGE_SCN_CNT_INITIALIZED_DATA) != 0,
|
||||
IsExec = (s.Characteristics & PE.IMAGE_SCN_CNT_CODE) != 0,
|
||||
IsBSS = (s.Characteristics & PE.IMAGE_SCN_CNT_UNINITIALIZED_DATA) != 0 || s.PointerToRawData == 0u,
|
||||
|
||||
Name = s.Name
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
113
Il2CppInspector.Common/FileFormatStreams/ProcessMapReader.cs
Normal file
113
Il2CppInspector.Common/FileFormatStreams/ProcessMapReader.cs
Normal file
@@ -0,0 +1,113 @@
|
||||
/*
|
||||
Copyright 2020 Katy Coe - http://www.djkaty.com - https://github.com/djkaty
|
||||
|
||||
All rights reserved.
|
||||
*/
|
||||
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text.RegularExpressions;
|
||||
using NoisyCowStudios.Bin2Object;
|
||||
|
||||
namespace Il2CppInspector
|
||||
{
|
||||
// This is a wrapper for a Linux memory dump
|
||||
// The supplied file is a text file containing the output of "cat /proc/["self"|process-id]/maps"
|
||||
// We re-construct libil2cpp.so from the *.bin files and return it as the first image
|
||||
internal class ProcessMapReader : FileFormatStream<ProcessMapReader>
|
||||
{
|
||||
private MemoryStream il2cpp;
|
||||
|
||||
public override string DefaultFilename => "maps.txt";
|
||||
|
||||
protected override bool Init() {
|
||||
|
||||
// Maps.txt is extremely unlikely to be larger than this, so don't waste time loading many megabytes of binary data for no reason
|
||||
if (Length > 256 * 1024)
|
||||
return false;
|
||||
|
||||
// Get the entire stream as a string
|
||||
var text = System.Text.Encoding.ASCII.GetString(ToArray());
|
||||
|
||||
// Line format is: https://stackoverflow.com/questions/1401359/understanding-linux-proc-id-maps
|
||||
// xxxxxxxx-yyyyyyyy ffff zzzzzzzz aa:bb c [whitespace] [image path]
|
||||
// Where x = the start address
|
||||
// Where y = the end address
|
||||
// Where f = permission flags (rwxp or -)
|
||||
// Where z = offset in file that the region was mapped from (NOTE: we ignore this and assume it's a contiguous run)
|
||||
// Where aa:bb = device ID
|
||||
// Where c = inode
|
||||
|
||||
var rgxProc = new Regex(@"^(?<start>[0-9A-Fa-f]{8})-(?<end>[0-9A-Fa-f]{8}) [rwxp\-]{4} [0-9A-Fa-f]{8} [0-9A-Fa-f]{2}:[0-9A-Fa-f]{2} \d+\s+(?<path>\S+)$", RegexOptions.Multiline);
|
||||
|
||||
// Determine where libil2cpp.so was mapped into memory
|
||||
var il2cppMemory = rgxProc.Matches(text)
|
||||
.Where(m => m.Groups["path"].Value.EndsWith("libil2cpp.so"))
|
||||
.Select(m => new { Start = Convert.ToUInt32(m.Groups["start"].Value, 16),
|
||||
End = Convert.ToUInt32(m.Groups["end"].Value, 16) }).ToList();
|
||||
|
||||
if (il2cppMemory.Count == 0)
|
||||
return false;
|
||||
|
||||
// Get file path
|
||||
// This error should never occur with the bundled CLI and GUI; only when used as a library by a 3rd party tool
|
||||
if (!(LoadOptions.BinaryFilePath is string mapsPath))
|
||||
throw new InvalidOperationException("To load a Linux process map, you must specify the maps file path in LoadOptions");
|
||||
|
||||
if (!mapsPath.ToLower().EndsWith("-maps.txt"))
|
||||
throw new InvalidOperationException("To load a Linux process map, the map file must not be renamed");
|
||||
|
||||
var mapsDir = Path.GetDirectoryName(mapsPath);
|
||||
var mapsPrefix = Path.GetFileName(mapsPath[..^9]);
|
||||
|
||||
// Get memory dump filenames and mappings
|
||||
var rgxFile = new Regex(@"^\S+?-(?<start>[0-9A-Za-z]{8})-(?<end>[0-9A-Za-z]{8})\.bin$");
|
||||
|
||||
var files = Directory.GetFiles(mapsDir, mapsPrefix + "-*.bin")
|
||||
.Select(f => rgxFile.Match(f))
|
||||
.Where(m => m.Groups[0].Success)
|
||||
.Select(m => new {
|
||||
Start = Convert.ToUInt32(m.Groups["start"].Value, 16),
|
||||
End = Convert.ToUInt32(m.Groups["end"].Value, 16),
|
||||
Name = m.Groups[0].Value
|
||||
}).OrderBy(m => m.Start).ToList();
|
||||
|
||||
// Determine which files contain libil2cpp.so
|
||||
var neededFiles = files.Where(f => il2cppMemory.Any(m => f.Start < m.End && f.End > m.Start)).OrderBy(f => f.Start).ToList();
|
||||
|
||||
// Determine how much to trim from the start of the first file and the end of the last file
|
||||
var offsetFirst = il2cppMemory.First().Start - neededFiles.First().Start;
|
||||
var lengthLast = il2cppMemory.Last().End - neededFiles.Last().Start;
|
||||
|
||||
// Merge the files
|
||||
il2cpp = new MemoryStream();
|
||||
|
||||
for (var i = 0; i < neededFiles.Count; i++) {
|
||||
var offset = (i == 0)? offsetFirst : 0;
|
||||
var length = ((i == neededFiles.Count - 1)? lengthLast : neededFiles[i].End - neededFiles[i].Start) - offset;
|
||||
|
||||
using var source = File.Open(neededFiles[i].Name, FileMode.Open, FileAccess.Read, FileShare.Read);
|
||||
|
||||
// Can't use Stream.CopyTo as it doesn't support length parameter
|
||||
var buffer = new byte[length];
|
||||
source.Position = offset;
|
||||
source.Read(buffer, 0, (int) length);
|
||||
il2cpp.Write(buffer);
|
||||
}
|
||||
|
||||
// Set image base address for ELF loader
|
||||
// ELF loader will rebase the image and mark it as modified for saving
|
||||
LoadOptions.ImageBase = il2cppMemory.First().Start;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public override IFileFormatStream this[uint index] {
|
||||
get {
|
||||
// Get merged stream as ELF file
|
||||
return (IFileFormatStream) ElfReader32.Load(il2cpp, LoadOptions, OnStatusUpdate) ?? ElfReader64.Load(il2cpp, LoadOptions, OnStatusUpdate);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
130
Il2CppInspector.Common/FileFormatStreams/SElfReader.cs
Normal file
130
Il2CppInspector.Common/FileFormatStreams/SElfReader.cs
Normal file
@@ -0,0 +1,130 @@
|
||||
/*
|
||||
Copyright 2020 Katy Coe - http://www.djkaty.com - https://github.com/djkaty
|
||||
|
||||
All rights reserved.
|
||||
*/
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
using NoisyCowStudios.Bin2Object;
|
||||
|
||||
namespace Il2CppInspector
|
||||
{
|
||||
// Sony PlayStation 4 fake signed ELF reader
|
||||
// Not compatible with PlayStation 3, PSP or Vita
|
||||
// References:
|
||||
// http://hitmen.c02.at/files/yapspd/psp_doc/chap26.html
|
||||
// https://www.psdevwiki.com/ps3/SELF_-_SPRX#File_Format
|
||||
// https://www.psdevwiki.com/ps4/SELF_File_Format
|
||||
// https://www.psxhax.com/threads/ps4-self-spkg-file-format-documentation-detailed-for-scene-devs.6636/
|
||||
// https://wiki.henkaku.xyz/vita/images/a/a2/Vita_SDK_specifications.pdf
|
||||
// https://www.psxhax.com/threads/make-fself-gui-for-flat_zs-make_fself-py-script-by-cfwprophet.3494/
|
||||
internal class SElfReader : FileFormatStream<SElfReader>
|
||||
{
|
||||
public override string DefaultFilename => "libil2cpp.so";
|
||||
|
||||
public override string Format => sceData.ProductType == (ulong) SElfExInfoTypes.PTYPE_FAKE? "FSELF" : "SELF";
|
||||
|
||||
public override string Arch => "x64";
|
||||
|
||||
public override int Bits => 64;
|
||||
|
||||
private SElfHeader selfHeader;
|
||||
private SElfEntry[] entries;
|
||||
private SElfSCEData sceData;
|
||||
private elf_header<ulong> elfHeader;
|
||||
private elf_64_phdr[] pht;
|
||||
private elf_64_phdr getProgramHeader(Elf programIndex) => pht.FirstOrDefault(x => x.p_type == (uint) programIndex);
|
||||
|
||||
protected override bool Init() {
|
||||
selfHeader = ReadObject<SElfHeader>();
|
||||
|
||||
// Check for magic bytes
|
||||
if ((SElfConsts) selfHeader.Magic != SElfConsts.Magic)
|
||||
return false;
|
||||
|
||||
if (selfHeader.Endian != 0x1)
|
||||
Endianness = Endianness.Big;
|
||||
|
||||
// Read entries
|
||||
entries = ReadArray<SElfEntry>(selfHeader.NumberOfEntries);
|
||||
|
||||
// We can't deal with encrypted or compressed segments right now
|
||||
if (entries.Any(e => e.HasBlocks && e.IsEncrypted))
|
||||
throw new NotImplementedException("This file contains encrypted segments not currently supported by Il2CppInspector.");
|
||||
|
||||
if (entries.Any(e => e.HasBlocks && e.IsDeflated))
|
||||
throw new NotImplementedException("This file contains compressed segments not currently supported by Il2CppInspector.");
|
||||
|
||||
// Read ELF header
|
||||
// PS4 files as follows:
|
||||
// m_arch = 0x2 (64-bit)
|
||||
// m_endian = 0x1 (little endian)
|
||||
// m_version = 0x1 (ELF version 1)
|
||||
// m_osabi = 0x9 (FreeBSD)
|
||||
// e_type = special type, see psdevwiki documentation; probably 0xFE10 or 0xFE18
|
||||
// e_machine = 0x3E (x86-64)
|
||||
var startOfElf = Position;
|
||||
elfHeader = ReadObject<elf_header<ulong>>();
|
||||
|
||||
// Must be one of these supported binary types
|
||||
if (elfHeader.e_type != (ushort) Elf.ET_EXEC
|
||||
&& elfHeader.e_type != (ushort) SElfETypes.ET_SCE_EXEC
|
||||
&& elfHeader.e_type != (ushort) SElfETypes.ET_SCE_DYNEXEC
|
||||
&& elfHeader.e_type != (ushort) SElfETypes.ET_SCE_DYNAMIC)
|
||||
return false;
|
||||
|
||||
// There are no sections, but read all the program headers
|
||||
// Each segment of type PT_LOAD, PT_SCE_RELRO, PT_SCE_DYNLIBDATA and PT_SCE_COMMENT
|
||||
// generates two SELF entries above - one pointing to the ELF segment and one pointing to a digest.
|
||||
// Only p_vaddr is used for memory mapping; all other fields are ignored.
|
||||
// offset, memsz and filesz are taken from the SELF entries.
|
||||
// The digests are all-zero in FSELF files.
|
||||
// All other ELF segments are ignored completely.
|
||||
pht = ReadArray<elf_64_phdr>(startOfElf + (long) elfHeader.e_phoff, elfHeader.e_phnum);
|
||||
|
||||
// Read extended info
|
||||
sceData = ReadObject<SElfSCEData>(startOfElf + (long) elfHeader.e_phoff + elfHeader.e_phentsize * elfHeader.e_phnum);
|
||||
|
||||
// Get SELF entries which point to segments defined in phdrs
|
||||
var dataEntries = entries.Where(e => e.HasBlocks).ToList();
|
||||
|
||||
// Fixup the used phdr entries
|
||||
foreach (var entry in dataEntries) {
|
||||
pht[entry.SegmentIndex].f_p_filesz = entry.EncryptedCompressedSize;
|
||||
pht[entry.SegmentIndex].f_p_offset = entry.FileOffset;
|
||||
pht[entry.SegmentIndex].f_p_memsz = entry.MemorySize;
|
||||
}
|
||||
|
||||
// Filter out unused phdr entries
|
||||
var phdrIndices = dataEntries.Select(e => (int) e.SegmentIndex).ToList();
|
||||
pht = pht.Where((e, i) => phdrIndices.Contains(i)).ToArray();
|
||||
|
||||
// Get offset of code section
|
||||
var codeSegment = pht.First(x => ((Elf) x.p_flags & Elf.PF_X) == Elf.PF_X);
|
||||
GlobalOffset = codeSegment.p_vaddr - codeSegment.p_offset;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// Only the DT_INIT function equivalent
|
||||
public override uint[] GetFunctionTable() => new [] { MapVATR(elfHeader.e_entry) };
|
||||
|
||||
public override uint MapVATR(ulong uiAddr) {
|
||||
var program_header_table = pht.First(x => uiAddr >= x.p_vaddr && uiAddr <= x.p_vaddr + x.p_filesz);
|
||||
return (uint) (uiAddr - (program_header_table.p_vaddr - program_header_table.p_offset));
|
||||
}
|
||||
|
||||
public override ulong MapFileOffsetToVA(uint offset) {
|
||||
var segment = pht.First(x => offset >= x.p_offset && offset < x.p_offset + x.p_filesz);
|
||||
return segment.p_vaddr + offset - segment.p_offset;
|
||||
}
|
||||
|
||||
// TODO: SElfReader.GetSections()
|
||||
}
|
||||
}
|
||||
21
Il2CppInspector.Common/FileFormatStreams/Section.cs
Normal file
21
Il2CppInspector.Common/FileFormatStreams/Section.cs
Normal file
@@ -0,0 +1,21 @@
|
||||
/*
|
||||
Copyright 2020 Katy Coe - http://www.djkaty.com - https://github.com/djkaty
|
||||
|
||||
All rights reserved.
|
||||
*/
|
||||
|
||||
namespace Il2CppInspector
|
||||
{
|
||||
// A code file function export
|
||||
public class Section
|
||||
{
|
||||
public ulong VirtualStart;
|
||||
public ulong VirtualEnd;
|
||||
public uint ImageStart;
|
||||
public uint ImageEnd;
|
||||
public bool IsExec;
|
||||
public bool IsData;
|
||||
public bool IsBSS;
|
||||
public string Name;
|
||||
}
|
||||
}
|
||||
26
Il2CppInspector.Common/FileFormatStreams/Symbol.cs
Normal file
26
Il2CppInspector.Common/FileFormatStreams/Symbol.cs
Normal file
@@ -0,0 +1,26 @@
|
||||
/*
|
||||
Copyright 2020 Katy Coe - http://www.djkaty.com - https://github.com/djkaty
|
||||
|
||||
All rights reserved.
|
||||
*/
|
||||
|
||||
namespace Il2CppInspector
|
||||
{
|
||||
public enum SymbolType
|
||||
{
|
||||
Function,
|
||||
Name,
|
||||
Import,
|
||||
Unknown
|
||||
}
|
||||
|
||||
// A code file function export
|
||||
public class Symbol
|
||||
{
|
||||
public ulong VirtualAddress { get; set; }
|
||||
public string Name { get; set; }
|
||||
public SymbolType Type { get; set; }
|
||||
|
||||
public string DemangledName => CxxDemangler.CxxDemangler.Demangle(Name);
|
||||
}
|
||||
}
|
||||
46
Il2CppInspector.Common/FileFormatStreams/UBReader.cs
Normal file
46
Il2CppInspector.Common/FileFormatStreams/UBReader.cs
Normal file
@@ -0,0 +1,46 @@
|
||||
/*
|
||||
Copyright 2017-2020 Katy Coe - http://www.djkaty.com - https://github.com/djkaty
|
||||
|
||||
All rights reserved.
|
||||
*/
|
||||
|
||||
using System.IO;
|
||||
using NoisyCowStudios.Bin2Object;
|
||||
|
||||
namespace Il2CppInspector
|
||||
{
|
||||
internal class UBReader : FileFormatStream<UBReader>
|
||||
{
|
||||
private FatHeader header;
|
||||
|
||||
public override string DefaultFilename => "app";
|
||||
|
||||
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 IFileFormatStream 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 (IFileFormatStream) MachOReader32.Load(s, LoadOptions, OnStatusUpdate) ?? MachOReader64.Load(s, LoadOptions, OnStatusUpdate);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
57
Il2CppInspector.Common/FileFormatStreams/WordConversions.cs
Normal file
57
Il2CppInspector.Common/FileFormatStreams/WordConversions.cs
Normal file
@@ -0,0 +1,57 @@
|
||||
/*
|
||||
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);
|
||||
TWord FromULong(ulong 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 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;
|
||||
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 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;
|
||||
public bool Gt(ulong a, ulong b) => a > b;
|
||||
public uint[] UIntArray(ulong[] a) => Array.ConvertAll(a, x => (uint)x);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user