PE: Add LoadLibrary DLL unpacker load strategy
This commit is contained in:
@@ -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.
|
All rights reserved.
|
||||||
*/
|
*/
|
||||||
@@ -17,7 +17,10 @@ namespace Il2CppInspector
|
|||||||
|
|
||||||
IMAGE_SCN_CNT_CODE = 0x00000020,
|
IMAGE_SCN_CNT_CODE = 0x00000020,
|
||||||
IMAGE_SCN_CNT_INITIALIZED_DATA = 0x00000040,
|
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
|
#pragma warning disable CS0649
|
||||||
@@ -38,7 +41,7 @@ namespace Il2CppInspector
|
|||||||
{
|
{
|
||||||
PE ExpectedMagic { get; }
|
PE ExpectedMagic { get; }
|
||||||
ushort Magic { get; }
|
ushort Magic { get; }
|
||||||
ulong ImageBase { get; }
|
ulong ImageBase { get; set; }
|
||||||
uint BaseOfCode { get; }
|
uint BaseOfCode { get; }
|
||||||
RvaEntry[] DataDirectory { get; }
|
RvaEntry[] DataDirectory { get; }
|
||||||
}
|
}
|
||||||
@@ -47,7 +50,7 @@ namespace Il2CppInspector
|
|||||||
{
|
{
|
||||||
public PE ExpectedMagic => PE.IMAGE_NT_OPTIONAL_HDR32_MAGIC;
|
public PE ExpectedMagic => PE.IMAGE_NT_OPTIONAL_HDR32_MAGIC;
|
||||||
public ushort Magic => f_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 uint BaseOfCode => f_BaseOfCode;
|
||||||
public RvaEntry[] DataDirectory => f_DataDirectory;
|
public RvaEntry[] DataDirectory => f_DataDirectory;
|
||||||
|
|
||||||
@@ -90,7 +93,7 @@ namespace Il2CppInspector
|
|||||||
{
|
{
|
||||||
public PE ExpectedMagic => PE.IMAGE_NT_OPTIONAL_HDR64_MAGIC;
|
public PE ExpectedMagic => PE.IMAGE_NT_OPTIONAL_HDR64_MAGIC;
|
||||||
public ushort Magic => f_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 uint BaseOfCode => f_BaseOfCode;
|
||||||
public RvaEntry[] DataDirectory => f_DataDirectory;
|
public RvaEntry[] DataDirectory => f_DataDirectory;
|
||||||
|
|
||||||
|
|||||||
@@ -14,6 +14,8 @@ namespace Il2CppInspector
|
|||||||
public ulong ImageBase { get; set; }
|
public ulong ImageBase { get; set; }
|
||||||
|
|
||||||
// For Linux process memory map inputs, we need the full path so we can find the .bin files
|
// 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; }
|
public string BinaryFilePath { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -8,6 +8,9 @@ using System;
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
using System.Text.RegularExpressions;
|
||||||
|
using NoisyCowStudios.Bin2Object;
|
||||||
|
|
||||||
namespace Il2CppInspector
|
namespace Il2CppInspector
|
||||||
{
|
{
|
||||||
@@ -16,10 +19,24 @@ namespace Il2CppInspector
|
|||||||
// PE format specification: https://docs.microsoft.com/en-us/windows/win32/debug/pe-format?redirectedfrom=MSDN
|
// PE format specification: https://docs.microsoft.com/en-us/windows/win32/debug/pe-format?redirectedfrom=MSDN
|
||||||
internal class PEReader : FileFormatReader<PEReader>
|
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 COFFHeader coff;
|
||||||
private IPEOptHeader pe;
|
private IPEOptHeader pe;
|
||||||
private PESection[] sections;
|
private PESection[] sections;
|
||||||
private uint pFuncTable;
|
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) {}
|
public PEReader(Stream stream) : base(stream) {}
|
||||||
|
|
||||||
@@ -84,21 +101,26 @@ namespace Il2CppInspector
|
|||||||
// Get sections table
|
// Get sections table
|
||||||
sections = ReadArray<PESection>(coff.NumberOfSections);
|
sections = ReadArray<PESection>(coff.NumberOfSections);
|
||||||
|
|
||||||
// Packed with Themida?
|
// Unpacking must be done starting here, one byte after the end of the headers
|
||||||
// TODO: Deal with Themida packing (issues #56, #95, #101)
|
// Packed or previously packed with Themida? This is purely for information
|
||||||
if (sections.FirstOrDefault(x => x.Name == ".themida") is PESection _) {
|
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!");
|
Console.WriteLine("Themida protection detected");
|
||||||
}
|
|
||||||
|
|
||||||
// Packed with something else?
|
// Packed with anything (including Themida)?
|
||||||
if (sections.FirstOrDefault(x => x.Name == ".rdata") is null) {
|
mightBePacked = 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!");
|
|
||||||
}
|
// 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
|
// Confirm that .rdata section begins at same place as IAT
|
||||||
var rData = sections.First(x => x.Name == ".rdata");
|
var rData = sections.First(x => x.Name == ".rdata");
|
||||||
if (rData.VirtualAddress != IATStart)
|
mightBePacked |= rData.VirtualAddress != IATStart;
|
||||||
return false;
|
|
||||||
|
|
||||||
// Calculate start of function pointer table
|
// Calculate start of function pointer table
|
||||||
pFuncTable = rData.PointerToRawData + IATSize;
|
pFuncTable = rData.PointerToRawData + IATSize;
|
||||||
@@ -116,17 +138,95 @@ namespace Il2CppInspector
|
|||||||
pFuncTable += 8;
|
pFuncTable += 8;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get base of code
|
// In the fist go round, we signal that this is at least a valid PE file; we don't try to unpack yet
|
||||||
GlobalOffset = pe.ImageBase + pe.BaseOfCode - sections.First(x => x.Name == ".text").PointerToRawData;
|
|
||||||
return true;
|
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() {
|
public override uint[] GetFunctionTable() {
|
||||||
|
if (pFuncTable == 0)
|
||||||
|
return Array.Empty<uint>();
|
||||||
|
|
||||||
Position = pFuncTable;
|
Position = pFuncTable;
|
||||||
var addrs = new List<uint>();
|
var addrs = new List<uint>();
|
||||||
ulong addr;
|
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();
|
return addrs.ToArray();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -155,7 +255,7 @@ namespace Il2CppInspector
|
|||||||
|
|
||||||
return exports.Values;
|
return exports.Values;
|
||||||
}
|
}
|
||||||
|
|
||||||
public override uint MapVATR(ulong uiAddr) {
|
public override uint MapVATR(ulong uiAddr) {
|
||||||
if (uiAddr == 0)
|
if (uiAddr == 0)
|
||||||
return 0;
|
return 0;
|
||||||
|
|||||||
@@ -581,8 +581,8 @@ namespace Il2CppInspector
|
|||||||
processors.Add(new Il2CppInspector(binary, metadata));
|
processors.Add(new Il2CppInspector(binary, metadata));
|
||||||
}
|
}
|
||||||
else {
|
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("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 an unencrypted IL2CPP binary before submitting a bug report!");
|
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
|
// Unknown architecture
|
||||||
|
|||||||
Reference in New Issue
Block a user