diff --git a/LibCpp2IL/ClassReadingBinaryReader.cs b/LibCpp2IL/ClassReadingBinaryReader.cs index c1fb15b8..1cd11425 100644 --- a/LibCpp2IL/ClassReadingBinaryReader.cs +++ b/LibCpp2IL/ClassReadingBinaryReader.cs @@ -248,8 +248,6 @@ private object ReadAndConvertPrimitive(bool overrideArchCheck, Type type) public virtual string ReadStringToNull(long offset) { - var builder = new List(); - GetLockOrThrow(); try @@ -284,6 +282,9 @@ internal string ReadStringToNullNoLock(long offset) } } + public string ReadStringToNullAtCurrentPos() + => ReadStringToNullNoLock(-1); + public byte[] ReadByteArrayAtRawAddress(long offset, int count) { GetLockOrThrow(); diff --git a/LibCpp2IL/MachO/MachODynamicLinkerCommand.cs b/LibCpp2IL/MachO/MachODynamicLinkerCommand.cs new file mode 100644 index 00000000..46140f3b --- /dev/null +++ b/LibCpp2IL/MachO/MachODynamicLinkerCommand.cs @@ -0,0 +1,43 @@ +using System; + +namespace LibCpp2IL.MachO +{ + public class MachODynamicLinkerCommand : ReadableClass + { + public int RebaseOffset; + public int RebaseSize; + public int BindOffset; + public int BindSize; + public int WeakBindOffset; + public int WeakBindSize; + public int LazyBindOffset; + public int LazyBindSize; + public int ExportOffset; + public int ExportSize; + + public MachOExportEntry[] Exports = Array.Empty(); + + public override void Read(ClassReadingBinaryReader reader) + { + RebaseOffset = reader.ReadInt32(); + RebaseSize = reader.ReadInt32(); + BindOffset = reader.ReadInt32(); + BindSize = reader.ReadInt32(); + WeakBindOffset = reader.ReadInt32(); + WeakBindSize = reader.ReadInt32(); + LazyBindOffset = reader.ReadInt32(); + LazyBindSize = reader.ReadInt32(); + ExportOffset = reader.ReadInt32(); + ExportSize = reader.ReadInt32(); + + var returnTo = reader.BaseStream.Position; + + reader.BaseStream.Position = ExportOffset; + + var exports = new MachOExportTrie(reader); + Exports = exports.Entries.ToArray(); + + reader.BaseStream.Position = returnTo; + } + } +} diff --git a/LibCpp2IL/MachO/MachOExportEntry.cs b/LibCpp2IL/MachO/MachOExportEntry.cs new file mode 100644 index 00000000..872bce8e --- /dev/null +++ b/LibCpp2IL/MachO/MachOExportEntry.cs @@ -0,0 +1,20 @@ +namespace LibCpp2IL.MachO +{ + public class MachOExportEntry + { + public string Name; + public long Address; + public long Flags; + public long Other; + public string? ImportName; + + public MachOExportEntry(string name, long address, long flags, long other, string? importName) + { + Name = name; + Address = address; + Flags = flags; + Other = other; + ImportName = importName; + } + } +} \ No newline at end of file diff --git a/LibCpp2IL/MachO/MachOExportTrie.cs b/LibCpp2IL/MachO/MachOExportTrie.cs new file mode 100644 index 00000000..97ac3e6a --- /dev/null +++ b/LibCpp2IL/MachO/MachOExportTrie.cs @@ -0,0 +1,85 @@ +using System; +using System.Collections.Generic; + +namespace LibCpp2IL.MachO +{ + public class MachOExportTrie + { + public List Entries = new(); + + private ClassReadingBinaryReader _reader; + private long _basePtr; + + public MachOExportTrie(ClassReadingBinaryReader reader) + { + _reader = reader; + _basePtr = reader.BaseStream.Position; + + var children = ParseNode("", 0); + while (children.Count > 0) + { + var current = children[0]; + children.RemoveAt(0); + children.AddRange(ParseNode(current.Name, current.Offset)); + } + } + + private List ParseNode(string name, int offset) + { + var children = new List(); + _reader.BaseStream.Position = _basePtr + offset; + + var terminalSize = _reader.BaseStream.ReadLEB128Unsigned(); + var childrenIndex = _reader.BaseStream.Position + (long)terminalSize; + if (terminalSize != 0) + { + var flags = (ExportFlags) _reader.BaseStream.ReadLEB128Unsigned(); + var address = 0L; + var other = 0L; + string? importName = null; + + if ((flags & ExportFlags.ReExport) != 0) + { + other = _reader.BaseStream.ReadLEB128Signed(); + importName = _reader.ReadStringToNullAtCurrentPos(); + } + else + { + address = _reader.BaseStream.ReadLEB128Signed(); + if ((flags & ExportFlags.StubAndResolver) != 0) + other = _reader.BaseStream.ReadLEB128Signed(); + } + + Entries.Add(new(name, address, (long) flags, other, importName)); + } + + _reader.BaseStream.Position = childrenIndex; + var numChildren = _reader.BaseStream.ReadLEB128Unsigned(); + for (var i = 0ul; i < numChildren; i++) + { + var childName = _reader.ReadStringToNullAtCurrentPos(); + var childOffset = _reader.BaseStream.ReadLEB128Unsigned(); + children.Add(new Node {Name = name + childName, Offset = (int) childOffset}); + } + + return children; + } + + [Flags] + private enum ExportFlags + { + KindRegular = 0, + KindThreadLocal = 1, + KindAbsolute = 2, + WeakDefinition = 4, + ReExport = 8, + StubAndResolver = 0x10 + } + + private struct Node + { + public string Name; + public int Offset; + } + } +} \ No newline at end of file diff --git a/LibCpp2IL/MachO/MachOFile.cs b/LibCpp2IL/MachO/MachOFile.cs index 99e004a9..55b7ccd1 100644 --- a/LibCpp2IL/MachO/MachOFile.cs +++ b/LibCpp2IL/MachO/MachOFile.cs @@ -1,4 +1,6 @@ -using System.IO; +using System.IO; +using System; +using System.Collections.Generic; using System.Linq; using LibCpp2IL.Logging; @@ -13,6 +15,7 @@ public class MachOFile : Il2CppBinary private readonly MachOSegmentCommand[] Segments64; private readonly MachOSection[] Sections64; + private Dictionary _exportsDict; public MachOFile(MemoryStream input) : base(input) { @@ -69,6 +72,12 @@ public MachOFile(MemoryStream input) : base(input) Segments64 = _loadCommands.Where(c => c.Command == LoadCommandId.LC_SEGMENT_64).Select(c => c.CommandData).Cast().ToArray(); Sections64 = Segments64.SelectMany(s => s.Sections).ToArray(); + var dyldData = _loadCommands.FirstOrDefault(c => c.Command is LoadCommandId.LC_DYLD_INFO or LoadCommandId.LC_DYLD_INFO_ONLY)?.CommandData as MachODynamicLinkerCommand; + var exports = dyldData?.Exports ?? Array.Empty(); + _exportsDict = exports.ToDictionary(e => e.Name[1..], e => e.Address); //Skip the first character, which is a leading underscore inserted by the compiler + + LibLogger.VerboseNewline($"Found {_exportsDict.Count} exports in the DYLD info load command."); + LibLogger.VerboseNewline($"\tMach-O contains {Segments64.Length} segments, split into {Sections64.Length} sections."); } @@ -107,7 +116,10 @@ public override ulong GetRva(ulong pointer) public override ulong GetVirtualAddressOfExportedFunctionByName(string toFind) { - return 0; //TODO? + if (!_exportsDict.TryGetValue(toFind, out var addr)) + return 0; + + return (ulong) addr; } private MachOSection GetTextSection64() @@ -129,4 +141,4 @@ public override byte[] GetEntirePrimaryExecutableSection() public override ulong GetVirtualAddressOfPrimaryExecutableSection() => GetTextSection64().Address; } -} \ No newline at end of file +} diff --git a/LibCpp2IL/MachO/MachOLoadCommand.cs b/LibCpp2IL/MachO/MachOLoadCommand.cs index ffa77142..26c17178 100644 --- a/LibCpp2IL/MachO/MachOLoadCommand.cs +++ b/LibCpp2IL/MachO/MachOLoadCommand.cs @@ -23,12 +23,25 @@ public override void Read(ClassReadingBinaryReader reader) { case LoadCommandId.LC_SEGMENT: case LoadCommandId.LC_SEGMENT_64: + { CommandData = reader.ReadReadableHereNoLock(); break; + } + case LoadCommandId.LC_SYMTAB: + { + CommandData = reader.ReadReadableHereNoLock(); + break; + } + case LoadCommandId.LC_DYLD_INFO: + case LoadCommandId.LC_DYLD_INFO_ONLY: + { + CommandData = reader.ReadReadableHereNoLock(); + break; + } default: UnknownCommandData = reader.ReadByteArrayAtRawAddressNoLock(-1, (int) CommandSize - 8); // -8 because we've already read the 8 bytes of the header break; } } } -} \ No newline at end of file +} diff --git a/LibCpp2IL/MachO/MachOSymtabCommand.cs b/LibCpp2IL/MachO/MachOSymtabCommand.cs new file mode 100644 index 00000000..e4917ab7 --- /dev/null +++ b/LibCpp2IL/MachO/MachOSymtabCommand.cs @@ -0,0 +1,36 @@ +using System; +using System.Collections.Generic; + +namespace LibCpp2IL.MachO +{ + public class MachOSymtabCommand : ReadableClass + { + public uint SymbolTableOffset; + public uint NumSymbols; + public uint StringTableOffset; + public uint StringTableSize; + + public MachOSymtabEntry[] Symbols = Array.Empty(); + + public override void Read(ClassReadingBinaryReader reader) + { + SymbolTableOffset = reader.ReadUInt32(); + NumSymbols = reader.ReadUInt32(); + StringTableOffset = reader.ReadUInt32(); + StringTableSize = reader.ReadUInt32(); + + var returnTo = reader.BaseStream.Position; + + reader.BaseStream.Position = SymbolTableOffset; + + Symbols = new MachOSymtabEntry[NumSymbols]; + for (var i = 0; i < NumSymbols; i++) + { + Symbols[i] = new(); + Symbols[i].Read(reader, this); + } + + reader.BaseStream.Position = returnTo; + } + } +} diff --git a/LibCpp2IL/MachO/MachOSymtabEntry.cs b/LibCpp2IL/MachO/MachOSymtabEntry.cs new file mode 100644 index 00000000..2ab2d8f0 --- /dev/null +++ b/LibCpp2IL/MachO/MachOSymtabEntry.cs @@ -0,0 +1,38 @@ +namespace LibCpp2IL.MachO +{ + public class MachOSymtabEntry + { + public uint NameOffset; + public byte Type; + public byte Section; + public ushort Description; + public ulong Value; // Architecture sized + + public string Name; + + public bool IsExternal => (Type & 0b1) == 0b1; + public bool IsSymbolicDebugging => (Type & 0b1110_0000) != 0; + public bool IsPrivateExternal => (Type & 0b0001_0000) == 0b0001_0000; + + private byte TypeBits => (byte) (Type & 0b1110); + + public bool IsTypeUndefined => Section == 0 && TypeBits == 0b0000; + public bool IsTypeAbsolute => Section == 0 && TypeBits == 0b0010; + public bool IsTypePreboundUndefined => Section == 0 && TypeBits == 0b1100; + public bool IsTypeIndirect => Section == 0 && TypeBits == 0b1010; + public bool IsTypeSection => TypeBits == 0b1110; + + public void Read(ClassReadingBinaryReader reader, MachOSymtabCommand machOSymtabCommand) + { + NameOffset = reader.ReadUInt32(); + Type = reader.ReadByte(); + Section = reader.ReadByte(); + Description = reader.ReadUInt16(); + Value = reader.ReadNUint(); + + var returnTo = reader.BaseStream.Position; + Name = reader.ReadStringToNullNoLock(machOSymtabCommand.StringTableOffset + NameOffset); + reader.BaseStream.Position = returnTo; + } + } +}