From 9ee316e8b2183c56e671a9405c093d7940d3513d Mon Sep 17 00:00:00 2001 From: Katy Coe Date: Fri, 7 Aug 2020 19:09:25 +0200 Subject: [PATCH] APK: Add multi-architecture (multiple binaries) support --- .../FileFormatReaders/APKReader.cs | 74 +++++++++++++++++++ .../IL2CPP/Il2CppInspector.cs | 35 ++++++--- 2 files changed, 97 insertions(+), 12 deletions(-) create mode 100644 Il2CppInspector.Common/FileFormatReaders/APKReader.cs diff --git a/Il2CppInspector.Common/FileFormatReaders/APKReader.cs b/Il2CppInspector.Common/FileFormatReaders/APKReader.cs new file mode 100644 index 0000000..ba74323 --- /dev/null +++ b/Il2CppInspector.Common/FileFormatReaders/APKReader.cs @@ -0,0 +1,74 @@ +/* + 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 : FileFormatReader + { + private ZipArchive zip; + private ZipArchiveEntry[] binaryFiles; + + public APKReader(Stream stream) : base(stream) { } + + protected override bool Init() { + + // Check if it's a zip file first because ZipFile.OpenRead is extremely slow if it isn't + if (ReadUInt32() != 0x04034B50) + return false; + + try { + zip = new ZipArchive(BaseStream); + + // Check for existence of global-metadata.dat + if (!zip.Entries.Any(f => f.FullName == "assets/bin/Data/Managed/Metadata/global-metadata.dat")) + return false; + + // 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 application + if (!binaryFiles.Any()) + return false; + } + + // Not an archive + catch (InvalidDataException) { + return false; + } + + NumImages = (uint) binaryFiles.Count(); + return true; + } + + public override IFileFormatReader this[uint index] { + get { + Console.WriteLine($"Extracting binary from {binaryFiles[index].FullName}"); + IFileFormatReader 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, OnStatusUpdate); + binary.Close(); + + if (loaded != null) + return loaded; + + binary = binaryFiles[index].Open(); + loaded = ElfReader64.Load(binary, OnStatusUpdate); + binary.Close(); + + return loaded; + } + } + } +} diff --git a/Il2CppInspector.Common/IL2CPP/Il2CppInspector.cs b/Il2CppInspector.Common/IL2CPP/Il2CppInspector.cs index 14526d2..81112b9 100644 --- a/Il2CppInspector.Common/IL2CPP/Il2CppInspector.cs +++ b/Il2CppInspector.Common/IL2CPP/Il2CppInspector.cs @@ -1,5 +1,5 @@ /* - Copyright 2017-2020 Katy Coe - http://www.hearthcode.org - http://www.djkaty.com + Copyright 2017-2020 Katy Coe - http://www.djkaty.com - https://github.com/djkaty All rights reserved. */ @@ -315,7 +315,7 @@ namespace Il2CppInspector // Check for Android APK var metadataFile = zip.Entries.FirstOrDefault(f => f.FullName == "assets/bin/Data/Managed/Metadata/global-metadata.dat"); - var binaryFile = zip.Entries.FirstOrDefault(f => f.FullName.StartsWith("lib/") && f.Name == "libil2cpp.so"); + var binaryFiles = zip.Entries.Where(f => f.FullName.StartsWith("lib/") && f.Name == "libil2cpp.so"); // Check for iOS IPA var ipaBinaryFolder = zip.Entries.FirstOrDefault(f => f.FullName.StartsWith("Payload/") && f.FullName.EndsWith(".app/") && f.FullName.Count(x => x == '/') == 2); @@ -323,30 +323,41 @@ namespace Il2CppInspector if (ipaBinaryFolder != null) { var ipaBinaryName = ipaBinaryFolder.FullName[8..^5]; metadataFile = zip.Entries.FirstOrDefault(f => f.FullName == $"Payload/{ipaBinaryName}.app/Data/Managed/Metadata/global-metadata.dat"); - binaryFile = zip.Entries.FirstOrDefault(f => f.FullName == $"Payload/{ipaBinaryName}.app/{ipaBinaryName}"); + binaryFiles = zip.Entries.Where(f => f.FullName == $"Payload/{ipaBinaryName}.app/{ipaBinaryName}"); } // This package doesn't contain an IL2CPP application - if (metadataFile == null || binaryFile == null) { + if (metadataFile == null || !binaryFiles.Any()) { Console.WriteLine($"Package {packageFile} does not contain an IL2CPP application"); return null; } - // Extract the files to memory + // Extract the metadata file to memory Console.WriteLine($"Extracting metadata from {packageFile}{Path.DirectorySeparatorChar}{metadataFile.FullName}"); - Console.WriteLine($"Extracting binary from {packageFile}{Path.DirectorySeparatorChar}{binaryFile.FullName}"); - var binaryMemoryStream = new MemoryStream(); var metadataMemoryStream = new MemoryStream(); - metadataStream = metadataFile.Open(); - binaryStream = binaryFile.Open(); - - binaryStream.CopyTo(binaryMemoryStream); metadataStream.CopyTo(metadataMemoryStream); - binaryMemoryStream.Position = 0; metadataMemoryStream.Position = 0; + // Extract the binary file or package to memory + var binaryMemoryStream = new MemoryStream(); + + // IPAs will only have one binary (which may or may not be a UB covering multiple architectures) + if (ipaBinaryFolder != null) { + Console.WriteLine($"Extracting binary from {packageFile}{Path.DirectorySeparatorChar}{binaryFiles.First().FullName}"); + + binaryStream = binaryFiles.First().Open(); + binaryStream.CopyTo(binaryMemoryStream); + binaryMemoryStream.Position = 0; + } + + // APKs may have one or more binaries, one per architecture + // We'll read the entire APK and load those via APKReader + else { + binaryMemoryStream = new MemoryStream(File.ReadAllBytes(packageFile)); + } + return (metadataMemoryStream, binaryMemoryStream); }