Skip to content

Commit

Permalink
Implemented runtime module dump to support obscure game assembly
Browse files Browse the repository at this point in the history
  • Loading branch information
krulci authored and js6pak committed May 18, 2024
1 parent 70c9e1f commit 0c11680
Showing 1 changed file with 117 additions and 1 deletion.
118 changes: 117 additions & 1 deletion Il2CppInterop.Runtime/MemoryUtils.cs
Original file line number Diff line number Diff line change
@@ -1,15 +1,17 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using Il2CppInterop.Common;
using Il2CppInterop.Common.XrefScans;
using Microsoft.Extensions.Logging;

namespace Il2CppInterop.Runtime;

internal class MemoryUtils
public class MemoryUtils
{
[DllImport("kernel32.dll", SetLastError = true)]
internal static extern bool ReadProcessMemory(IntPtr hProcess, IntPtr lpBaseAddress, [Out] byte[] lpBuffer, int dwSize, out IntPtr lpNumberOfBytesRead);
Expand Down Expand Up @@ -94,6 +96,120 @@ public static void SetModuleRegions(List<MEMORY_BASIC_INFORMATION> protectedRegi
}
}

public static void RuntimeModuleDump(ILogger logger, out byte[] il2cppBytes, out byte[] metadataBytes, byte[] metadataSignatureToScan, byte[] magicToFix, int metadataSignatureOffset = 252)
{
Process process = Process.GetCurrentProcess();
var module = process
.Modules.OfType<ProcessModule>()
.Single((x) => x.ModuleName is "GameAssembly.dll" or "GameAssembly.so" or "UserAssembly.dll"); ;
if (module.ModuleName == null)
{
logger.LogError("GameAssembly.dll or GameAssembly.so or UserAssembly.dll not found");
il2cppBytes = [];
metadataBytes = [];
return;
}
var moduleBytes = new byte[module.ModuleMemorySize];
GetModuleRegions(module, out var protectedRegions);
SetModuleRegions(protectedRegions, PAGE_EXECUTE_READWRITE);
if (!ReadProcessMemory(process.Handle, module.BaseAddress, moduleBytes, module.ModuleMemorySize, out _))
{
logger.LogError("Failed to read process memory");
il2cppBytes = [];
metadataBytes = [];
return;
}
SetModuleRegions(protectedRegions);
using (var stream = new MemoryStream(moduleBytes))
using (var reader = new BinaryReader(stream))
using (var writer = new BinaryWriter(stream))
{
// Parse the PE header to get the section headers
stream.Position = 0x3C;
var peHeaderOffset = reader.ReadInt32();
logger.LogDebug("peHeaderOffset: {peHeaderOffset}", peHeaderOffset);
stream.Position = peHeaderOffset + 6;
var numberOfSections = reader.ReadUInt16();
var timeDateStame = reader.ReadUInt32();
var pointerToSymbolTable = reader.ReadUInt32();
var numberOfSymbols = reader.ReadUInt32();
var sizeOfOptionalHeader = reader.ReadUInt16();
var characteristics = reader.ReadUInt16();
var section0StartPosition = (int)stream.Position + sizeOfOptionalHeader;

// Update each section header's PointerToRawData and SizeOfRawData fields
for (var i = 0; i < numberOfSections; i++)
{
logger.LogDebug("numberOfSections: {numberOfSections}", numberOfSections);
stream.Position = section0StartPosition + (i * 40);
logger.LogDebug("stream.Position: {stream.Position}", stream.Position);
var sectionNameBytes = reader.ReadBytes(8);
var sectionName = Encoding.ASCII.GetString(sectionNameBytes).TrimEnd('\0');
logger.LogDebug("sectionName: {sectionName}", sectionName);
var virtualSize = reader.ReadUInt32();
logger.LogDebug("VirtualSize: {virtualSize:X} stream.Position: {stream.Position}", virtualSize, stream.Position);
var virtualAddress = reader.ReadUInt32();
logger.LogDebug("VirtualAddress: {virtualAddress:X} stream.Position: {stream.Position}", virtualAddress, stream.Position);
writer.Write(virtualSize);
logger.LogDebug("Replacing SizeOfRawData with VirtualSize value of {virtualSize:X} stream.Position: {stream.Position}", virtualSize, stream.Position);
writer.Write(virtualAddress);
logger.LogDebug("Replacing SizeOfRawData with VirtualSize value of {virtualAddress:X} stream.Position: {stream.Position}", virtualAddress, stream.Position);
}
}

logger.LogDebug("Processed {module.ModuleName}", module.ModuleName);
il2cppBytes = moduleBytes;

var byteArray = moduleBytes;

// search for pattern in the byte array
var index = Array.IndexOf(byteArray, (byte)metadataSignatureToScan[0]);
while (index >= 0 && index <= byteArray.Length - metadataSignatureToScan.Length)
{
if (byteArray.Skip(index).Take(metadataSignatureToScan.Length).SequenceEqual(metadataSignatureToScan))
{
// pattern found, trim everything before it
var trimmedArray = new byte[byteArray.Length - index + metadataSignatureOffset];
// copy the metadata bytes
Array.Copy(byteArray, index - metadataSignatureOffset, trimmedArray, 0, trimmedArray.Length);
// this is required for il2cppdumper to work
if (magicToFix.Length >= 0)
Array.Copy(magicToFix, 0, trimmedArray, 0, magicToFix.Length);

byteArray = trimmedArray;
break;
}
index = Array.IndexOf(byteArray, (byte)metadataSignatureToScan[0], index + 1);
}

logger.LogDebug("Processed global-metadata.dat");
metadataBytes = byteArray;
return;
}

public static void ValidateMetadata(ILogger logger, string metadataPath, byte[] il2cppBytes, ref byte[] metadataBytes)
{
//metadataBytes will equal il2cppBytes if the search pattern did not match.
//In this case, global-metadata.dat is not embedded in GameAssembly.dll and most likely at the default path.
if (il2cppBytes == metadataBytes)
{
logger.LogWarning("global-metadata.dat is not embedded in GameAssembly.dll.");
if (File.Exists(metadataPath))
{
logger.LogWarning("Found global-metadata.dat at the default path, using it instead.");
metadataBytes = File.ReadAllBytes(metadataPath);
}
else
{
logger.LogWarning("global-meatadata.dat is not found at the default location. " +
"It may be hidden somewhere else. " +
"\n Input the file path: (Example: C:\\Users\\_\\{YourGame}\\fake-global-metadata-name.fakeExtension", null);
metadataPath = Path.Combine(Console.ReadLine() ?? string.Empty);
metadataBytes = File.ReadAllBytes(metadataPath);
}
}
}

public const uint PAGE_EXECUTE_READWRITE = 0x40;

public struct SignatureDefinition
Expand Down

0 comments on commit 0c11680

Please sign in to comment.