PE: Add LoadLibrary DLL unpacker load strategy

This commit is contained in:
Katy Coe
2020-12-14 02:53:26 +01:00
parent 7878193f74
commit 061886f10c
4 changed files with 128 additions and 23 deletions

View File

@@ -1,5 +1,5 @@
/*
Copyright 2017-2019 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.
*/
@@ -17,7 +17,10 @@ namespace Il2CppInspector
IMAGE_SCN_CNT_CODE = 0x00000020,
IMAGE_SCN_CNT_INITIALIZED_DATA = 0x00000040,
IMAGE_SCN_CNT_UNINITIALIZED_DATA = 0x00000080
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
@@ -38,7 +41,7 @@ namespace Il2CppInspector
{
PE ExpectedMagic { get; }
ushort Magic { get; }
ulong ImageBase { get; }
ulong ImageBase { get; set; }
uint BaseOfCode { get; }
RvaEntry[] DataDirectory { get; }
}
@@ -47,7 +50,7 @@ namespace Il2CppInspector
{
public PE ExpectedMagic => PE.IMAGE_NT_OPTIONAL_HDR32_MAGIC;
public ushort Magic => f_Magic;
public ulong ImageBase => f_ImageBase;
public ulong ImageBase { get => f_ImageBase; set => f_ImageBase = (uint) value; }
public uint BaseOfCode => f_BaseOfCode;
public RvaEntry[] DataDirectory => f_DataDirectory;
@@ -90,7 +93,7 @@ namespace Il2CppInspector
{
public PE ExpectedMagic => PE.IMAGE_NT_OPTIONAL_HDR64_MAGIC;
public ushort Magic => f_Magic;
public ulong ImageBase => f_ImageBase;
public ulong ImageBase { get => f_ImageBase; set => f_ImageBase = value; }
public uint BaseOfCode => f_BaseOfCode;
public RvaEntry[] DataDirectory => f_DataDirectory;

View File

@@ -14,6 +14,8 @@ namespace Il2CppInspector
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; }
}
}

View File

@@ -8,6 +8,9 @@ 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
{
@@ -16,10 +19,24 @@ namespace Il2CppInspector
// PE format specification: https://docs.microsoft.com/en-us/windows/win32/debug/pe-format?redirectedfrom=MSDN
internal class PEReader : FileFormatReader<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 PEReader(Stream stream) : base(stream) {}
@@ -84,21 +101,26 @@ namespace Il2CppInspector
// Get sections table
sections = ReadArray<PESection>(coff.NumberOfSections);
// Packed with Themida?
// TODO: Deal with Themida packing (issues #56, #95, #101)
if (sections.FirstOrDefault(x => x.Name == ".themida") is PESection _) {
throw new InvalidOperationException("This IL2CPP binary is packed with Themida and cannot be loaded directly. Unpack the binary first and try again. NOTE: Automatic unpacking of PE files will be included in a future update coming soon!");
}
// 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 something else?
if (sections.FirstOrDefault(x => x.Name == ".rdata") is null) {
throw new InvalidOperationException("This IL2CPP binary is packed in a way not currently supported by Il2CppInspector and cannot be loaded. NOTE: Automatic unpacking of PE files will be included in a future update coming soon!");
}
// 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");
if (rData.VirtualAddress != IATStart)
return false;
mightBePacked |= rData.VirtualAddress != IATStart;
// Calculate start of function pointer table
pFuncTable = rData.PointerToRawData + IATSize;
@@ -116,17 +138,95 @@ namespace Il2CppInspector
pFuncTable += 8;
}
// Get base of code
GlobalOffset = pe.ImageBase + pe.BaseOfCode - sections.First(x => x.Name == ".text").PointerToRawData;
// 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
BaseStream.SetLength(endOfSignature);
// Re-write the stream (the headers are only necessary in case the user wants to save)
using var writer = new BinaryObjectWriter(BaseStream, Endianness, true);
writer.Position = endOfSignature;
writer.WriteObject(coff);
if (Bits == 32) writer.WriteObject((PEOptHeader32) pe);
else writer.WriteObject((PEOptHeader64) pe);
writer.WriteArray(sections);
writer.Write(peBytes, (int) Position, peBytes.Length - (int) Position);
IsModified = true;
}
// Raw file / unpacked file load strategies
public override IEnumerable<IFileFormatReader> 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;
while ((addr = pe is PEOptHeader32? ReadUInt32() : ReadUInt64()) != 0)
addrs.Add(MapVATR(addr) & 0xfffffffc);
// 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();
}
@@ -155,7 +255,7 @@ namespace Il2CppInspector
return exports.Values;
}
public override uint MapVATR(ulong uiAddr) {
if (uiAddr == 0)
return 0;

View File

@@ -581,8 +581,8 @@ namespace Il2CppInspector
processors.Add(new Il2CppInspector(binary, metadata));
}
else {
Console.Error.WriteLine("Could not process IL2CPP image. This may mean the binary file is packed, encrypted or obfuscated, that the file is not an IL2CPP image or that Il2CppInspector was not able to automatically find the required data.");
Console.Error.WriteLine("Please check the binary file in a disassembler to ensure that it is an unencrypted IL2CPP binary before submitting a bug report!");
Console.Error.WriteLine("Could not process IL2CPP image. This may mean the binary file is packed, encrypted or obfuscated in a way Il2CppInspector cannot process, that the file is not an IL2CPP image or that Il2CppInspector was not able to automatically find the required data.");
Console.Error.WriteLine("Please check the binary file in a disassembler to ensure that it is a valid IL2CPP binary before submitting a bug report!");
}
}
// Unknown architecture