diff --git a/Il2CppInspector.Common/FileFormatReaders/ProcessMapReader.cs b/Il2CppInspector.Common/FileFormatReaders/ProcessMapReader.cs new file mode 100644 index 0000000..8854a24 --- /dev/null +++ b/Il2CppInspector.Common/FileFormatReaders/ProcessMapReader.cs @@ -0,0 +1,115 @@ +/* + 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 : FileFormatReader + { + private MemoryStream il2cpp; + + public ProcessMapReader(Stream stream) : base(stream) { } + + 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((BaseStream as MemoryStream).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(@"^(?[0-9A-Fa-f]{8})-(?[0-9A-Fa-f]{8}) [rwxp\-]{4} [0-9A-Fa-f]{8} [0-9A-Fa-f]{2}:[0-9A-Fa-f]{2} \d+\s+(?\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+?-(?[0-9A-Za-z]{8})-(?[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 IFileFormatReader this[uint index] { + get { + // Get merged stream as ELF file + return (IFileFormatReader) ElfReader32.Load(il2cpp, LoadOptions, OnStatusUpdate) ?? ElfReader64.Load(il2cpp, LoadOptions, OnStatusUpdate); + } + } + } +}