Implement basic trampoline generator

This commit is contained in:
Bepis committed 2020-09-02 04:13:24 +10:00
1 parent f6f03d08bc
commit b5406ff440
10 files changed
+342 -61

No files matched your search

+4 -4
View File
@@ -34,6 +34,9 @@
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup>
<ItemGroup>
<Reference Include="Iced, Version=1.6.0.0, Culture=neutral, PublicKeyToken=5baba79f4264913b, processorArchitecture=MSIL">
<HintPath>..\packages\Iced.1.6.0\lib\net45\Iced.dll</HintPath>
</Reference>
<Reference Include="Il2Cppmscorlib">
<HintPath>..\lib\Il2Cppmscorlib.dll</HintPath>
<Private>False</Private>
@@ -63,10 +66,6 @@
<HintPath>..\lib\UnhollowerBaseLib.dll</HintPath>
<Private>False</Private>
</Reference>
<Reference Include="UnhollowerRuntimeLib">
<HintPath>..\lib\UnhollowerRuntimeLib.dll</HintPath>
<Private>False</Private>
</Reference>
<Reference Include="UnityEngine-IL2CPP">
<HintPath>..\lib\UnityEngine-IL2CPP.dll</HintPath>
<Private>False</Private>
@@ -83,6 +82,7 @@
<Compile Include="IL2CPPChainloader.cs" />
<Compile Include="Preloader.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="TrampolineGenerator.cs" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\BepInEx.Core\BepInEx.Core.csproj">
+113 -40
View File
@@ -8,8 +8,8 @@ using BepInEx.Bootstrap;
using BepInEx.Logging;
using BepInEx.Preloader.Core;
using BepInEx.Preloader.Core.Logging;
using Iced.Intel;
using MonoMod.RuntimeDetour;
using MonoMod.Utils;
using UnhollowerBaseLib.Runtime;
using UnhollowerRuntimeLib;
using UnityEngine;
@@ -21,74 +21,146 @@ namespace BepInEx.IL2CPP
{
private static ManualLogSource UnityLogSource = new ManualLogSource("Unity");
[UnmanagedFunctionPointer(CallingConvention.StdCall)]
private delegate void UnityLogCallbackDelegate([In] [MarshalAs(UnmanagedType.LPStr)] string log);
private static void UnityLogCallback([In] [MarshalAs(UnmanagedType.LPStr)] string log)
public static void UnityLogCallback(string logLine, string exception, LogType type)
{
UnityLogSource.LogInfo(log.Trim());
UnityLogSource.LogInfo(logLine.Trim());
}
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
// public const CallingConvention ArchConvention =
//#if X64
// CallingConvention.Stdcall;
//#else
// CallingConvention.Cdecl;
//#endif
[UnmanagedFunctionPointer(CallingConvention.StdCall)]
private delegate IntPtr RuntimeInvokeDetour(IntPtr method, IntPtr obj, IntPtr parameters, IntPtr exc);
[DllImport("kernel32", CharSet = CharSet.Ansi, ExactSpelling = true, SetLastError = true)]
private static extern IntPtr GetProcAddress(IntPtr hModule, string procName);
private static RuntimeInvokeDetour originalInvoke;
public unsafe IL2CPPChainloader()
private static ManualLogSource unhollowerLogSource = Logger.CreateLogSource("Unhollower");
private class DetourHandler : UnhollowerRuntimeLib.ManagedDetour
{
UnityVersionHandler.Initialize(2019, 3, 15);
File.AppendAllText("log.log", "Initialized unhollower\n");
public T Detour<T>(IntPtr from, T to) where T : Delegate
{
var detour = new NativeDetour(from, to, new NativeDetourConfig { ManualApply = true });
var trampoline = detour.GenerateTrampoline<T>();
detour.Apply();
return trampoline;
}
}
private static void Disassemble(ManualLogSource logSource, IntPtr memoryPtr, int size)
{
byte[] data = new byte[size];
Marshal.Copy(memoryPtr, data, 0, size);
var formatter = new NasmFormatter();
var output = new StringOutput();
var codeReader = new ByteArrayCodeReader(data);
var decoder = Decoder.Create(64, codeReader);
decoder.IP = (ulong)memoryPtr.ToInt64();
while (codeReader.CanReadByte)
{
decoder.Decode(out var instr);
formatter.Format(instr, output);
logSource.LogDebug($"{instr.IP:X16} {output.ToStringAndReset()}");
}
}
public override unsafe void Initialize(string gameExePath = null)
{
base.Initialize(gameExePath);
UnityVersionHandler.Initialize(2019, 2, 17);
// One or the other here for Unhollower to work correctly
////////ClassInjector.Detour = new DetourHandler();
ClassInjector.DoHook = (ptr, intPtr) =>
{
var detour = new NativeDetour(new IntPtr(*((int**)ptr)), intPtr);
detour.Apply();
IntPtr originalFunc = new IntPtr(*(void**)ptr);
unhollowerLogSource.LogDebug($"DoHook 0x{originalFunc.ToString("X")} -> 0x{intPtr.ToString("X")}");
var trampolineAlloc = DetourHelper.Native.MemAlloc(80);
DetourHelper.Native.MakeExecutable(trampolineAlloc, 80);
DetourHelper.Native.MakeWritable(originalFunc, 24);
unhollowerLogSource.LogDebug($"Trampoline allocation: 0x{ptr.ToString("X")}");
unhollowerLogSource.LogDebug("Original (24) asm");
Disassemble(unhollowerLogSource, originalFunc, 24);
var trampolineLength = TrampolineGenerator.Generate(originalFunc, intPtr, trampolineAlloc,
IntPtr.Size == 8 ? 64 : 32);
unhollowerLogSource.LogDebug("Modified (24) asm");
Disassemble(unhollowerLogSource, originalFunc, 24);
unhollowerLogSource.LogDebug($"Trampoline ({trampolineLength}) asm");
Disassemble(unhollowerLogSource, trampolineAlloc, trampolineLength);
*(void**)ptr = (void*)trampolineAlloc;
};
foreach (var processModule in Process.GetCurrentProcess().Modules.Cast<ProcessModule>())
{
File.AppendAllText("wew.log", $"{processModule.ModuleName}\n");
}
var gameAssemblyModule = Process.GetCurrentProcess().Modules.Cast<ProcessModule>().First(x => x.ModuleName.Contains("GameAssembly"));
File.AppendAllText("wew.log", $"Got module: {gameAssemblyModule.ModuleName}; addr: {gameAssemblyModule.BaseAddress}\n");
var functionPtr = GetProcAddress(gameAssemblyModule.BaseAddress, "il2cpp_runtime_invoke"); //DynDll.GetFunction(gameAssemblyModule.BaseAddress, "il2cpp_runtime_invoke");
File.AppendAllText("wew.log", $"Got fptr: {functionPtr}\n");
// RuntimeInvokeDetour invokeHook = (method, obj, parameters, exc) =>
// {
// // UnityLogSource.LogInfo(Marshal.PtrToStringAnsi(UnhollowerBaseLib.IL2CPP.il2cpp_method_get_name(method)));
// return originalInvoke(method, obj, parameters, exc);
// };
// UnhollowerBaseLib.IL2CPP.il2cpp_method_get_name(method)
var invokeDetour = new NativeDetour(functionPtr, Marshal.GetFunctionPointerForDelegate(new RuntimeInvokeDetour(OnInvokeMethod)), new NativeDetourConfig {ManualApply = true});
PreloaderLogger.Log.LogDebug($"Runtime invoke pointer: 0x{functionPtr.ToInt64():X}");
var invokeDetour = new NativeDetour(functionPtr, Marshal.GetFunctionPointerForDelegate(new RuntimeInvokeDetour(OnInvokeMethod)), new NativeDetourConfig { ManualApply = true });
File.AppendAllText("log.log", "Got detour\n");
originalInvoke = invokeDetour.GenerateTrampoline<RuntimeInvokeDetour>();
File.AppendAllText("log.log", "Got trampoline\n");
invokeDetour.Apply();
File.AppendAllText("log.log", "Applied!\n");
}
private static bool HasSet = false;
private static IntPtr OnInvokeMethod(IntPtr method, IntPtr obj, IntPtr parameters, IntPtr exc)
{
lock (originalInvoke)
string methodName = Marshal.PtrToStringAnsi(UnhollowerBaseLib.IL2CPP.il2cpp_method_get_name(method));
if (!HasSet && methodName == "Internal_ActiveSceneChanged")
{
try
{
File.AppendAllText("log.log", $"Got call: {Marshal.PtrToStringAnsi(UnhollowerBaseLib.IL2CPP.il2cpp_method_get_name(method))}\n");
UnityEngine.Application.s_LogCallbackHandler = new Action<string, string, LogType>(UnityLogCallback);
UnityLogSource.LogMessage($"callback set - {methodName}");
UnityEngine.Application.CallLogCallback("test from OnInvokeMethod", "", LogType.Log, true);
}
catch (Exception e)
catch (Exception ex)
{
File.AppendAllText("err.log", e.ToString() + "\n");
UnityLogSource.LogError(ex);
}
return originalInvoke(method, obj, parameters, exc);
HasSet = true;
}
//UnityLogSource.LogDebug(methodName);
return originalInvoke(method, obj, parameters, exc);
}
protected override void InitializeLoggers()
@@ -101,7 +173,6 @@ namespace BepInEx.IL2CPP
Logger.Sources.Add(UnityLogSource);
ManualLogSource unhollowerLogSource = Logger.CreateLogSource("Unhollower");
UnhollowerBaseLib.LogSupport.InfoHandler += unhollowerLogSource.LogInfo;
UnhollowerBaseLib.LogSupport.WarningHandler += unhollowerLogSource.LogWarning;
@@ -122,10 +193,12 @@ namespace BepInEx.IL2CPP
PreloaderLogger.Log.Log(preloaderLogEvent.Level, preloaderLogEvent.Data);
}
//UnityEngine.Application.s_LogCallbackHandler = DelegateSupport.ConvertDelegate<Application.LogCallback>(new Action<string>(UnityLogCallback));
//UnityEngine.Application.s_LogCallbackHandler = (Application.LogCallback)new Action<string>(UnityLogCallback);
var loggerPointer = Marshal.GetFunctionPointerForDelegate(new UnityLogCallbackDelegate(UnityLogCallback));
UnhollowerBaseLib.IL2CPP.il2cpp_register_log_callback(loggerPointer);
//var loggerPointer = Marshal.GetFunctionPointerForDelegate(new UnityLogCallbackDelegate(UnityLogCallback));
//UnhollowerBaseLib.IL2CPP.il2cpp_register_log_callback(loggerPointer);
}
public override BasePlugin LoadPlugin(PluginInfo pluginInfo, Assembly pluginAssembly)
+25 -14
View File
@@ -1,4 +1,5 @@
using System.Diagnostics;
using System;
using System.Diagnostics;
using BepInEx.Logging;
using BepInEx.Preloader.Core;
using BepInEx.Preloader.Core.Logging;
@@ -17,30 +18,40 @@ namespace BepInEx.IL2CPP
public static void Run()
{
PreloaderLog = new PreloaderConsoleListener(false);
Logger.Listeners.Add(PreloaderLog);
try
{
PreloaderLog = new PreloaderConsoleListener(false);
Logger.Listeners.Add(PreloaderLog);
BasicLogInfo.PrintLogInfo(Log);
BasicLogInfo.PrintLogInfo(Log);
Log.LogInfo($"Running under Unity v{FileVersionInfo.GetVersionInfo(Paths.ExecutablePath).FileVersion}");
//Log.LogInfo($"CLR runtime version: {Environment.Version}");
//Log.LogInfo($"Supports SRE: {Utility.CLRSupportsDynamicAssemblies}");
Log.LogInfo($"Running under Unity v{FileVersionInfo.GetVersionInfo(Paths.ExecutablePath).FileVersion}");
//Log.LogInfo($"CLR runtime version: {Environment.Version}");
//Log.LogInfo($"Supports SRE: {Utility.CLRSupportsDynamicAssemblies}");
Log.LogDebug($"Game executable path: {Paths.ExecutablePath}");
Log.LogDebug($"Unhollowed assembly directory: {IL2CPPUnhollowedPath}");
Log.LogDebug($"BepInEx root path: {Paths.BepInExRootPath}");
Log.LogDebug($"Game executable path: {Paths.ExecutablePath}");
Log.LogDebug($"Unhollowed assembly directory: {IL2CPPUnhollowedPath}");
Log.LogDebug($"BepInEx root path: {Paths.BepInExRootPath}");
Logger.Listeners.Remove(PreloaderLog);
Logger.Listeners.Remove(PreloaderLog);
Chainloader = new IL2CPPChainloader();
Chainloader = new IL2CPPChainloader();
Chainloader.Initialize();
Chainloader.Initialize();
Chainloader.Execute();
Chainloader.Execute();
}
catch (Exception ex)
{
Log.LogFatal(ex);
throw;
}
}
}
}
+2 -2
View File
@@ -32,5 +32,5 @@ using System.Runtime.InteropServices;
// You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below:
// [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion("1.0.0.0")]
[assembly: AssemblyFileVersion("1.0.0.0")]
[assembly: AssemblyVersion("6.0.0.0")]
[assembly: AssemblyFileVersion("6.0.0.0")]
+115
View File
@@ -0,0 +1,115 @@
using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using Iced.Intel;
namespace BepInEx.IL2CPP
{
public static class TrampolineGenerator
{
public static int Generate(IntPtr originalFunctionPtr, IntPtr patchedFunctionPtr, IntPtr trampolineFunctionPtr, int bitness)
{
byte[] instructionBuffer = new byte[80];
Marshal.Copy(originalFunctionPtr, instructionBuffer, 0, 80);
// Decode original function up until we go past the needed bytes to write the jump to patchedFunctionPtr
// TODO: set this per arch, and implement x86 E9 jmp
const uint requiredBytes = 10 + 2;
var codeReader = new ByteArrayCodeReader(instructionBuffer);
var decoder = Decoder.Create(bitness, codeReader);
decoder.IP = (ulong)originalFunctionPtr.ToInt64();
uint totalBytes = 0;
var origInstructions = new InstructionList();
while (codeReader.CanReadByte)
{
decoder.Decode(out var instr);
origInstructions.Add(instr);
totalBytes += (uint)instr.Length;
if (instr.Code == Code.INVALID)
throw new Exception("Found garbage");
if (totalBytes >= requiredBytes)
break;
switch (instr.FlowControl)
{
case FlowControl.Next:
break;
case FlowControl.UnconditionalBranch:
if (instr.Op0Kind == OpKind.NearBranch64)
{
var target = instr.NearBranchTarget;
}
goto default;
case FlowControl.IndirectBranch:// eg. jmp reg/mem
case FlowControl.ConditionalBranch:// eg. je, jno, etc
case FlowControl.Return:// eg. ret
case FlowControl.Call:// eg. call method
case FlowControl.IndirectCall:// eg. call reg/mem
case FlowControl.Interrupt:// eg. int n
case FlowControl.XbeginXabortXend:
case FlowControl.Exception:// eg. ud0
default:
throw new Exception("Not supported by this simple example - " + instr.FlowControl);
}
}
if (totalBytes < requiredBytes)
throw new Exception("Not enough bytes!");
if (origInstructions.Count == 0)
throw new Exception("Not enough instructions!");
ref readonly var lastInstr = ref origInstructions[origInstructions.Count - 1];
origInstructions.Add(Instruction.CreateBranch(Code.Jmp_rel32_64, lastInstr.NextIP));
// Generate trampoline from instruction list
var codeWriter = new CodeWriterImpl();
ulong relocatedBaseAddress = (ulong)trampolineFunctionPtr;
var block = new InstructionBlock(codeWriter, origInstructions, relocatedBaseAddress);
bool success = BlockEncoder.TryEncode(decoder.Bitness, block, out var errorMessage, out var result);
if (!success)
{
throw new Exception(errorMessage);
}
// Write generated trampoline
var newCode = codeWriter.ToArray();
Marshal.Copy(newCode, 0, trampolineFunctionPtr, newCode.Length);
// Overwrite the start of trampolineFunctionPtr with a jump to patchedFunctionPtr
var jmpCode = new byte[requiredBytes];
jmpCode[0] = 0x48;// \ 'MOV RAX,imm64'
jmpCode[1] = 0xB8;// /
ulong v = (ulong)patchedFunctionPtr.ToInt64();
for (int i = 0; i < 8; i++, v >>= 8)
jmpCode[2 + i] = (byte)v;
jmpCode[10] = 0xFF;// \ JMP RAX
jmpCode[11] = 0xE0;// /
Marshal.Copy(jmpCode, 0, originalFunctionPtr, (int)requiredBytes);
return newCode.Length;
}
private sealed class CodeWriterImpl : CodeWriter
{
readonly List<byte> allBytes = new List<byte>();
public override void WriteByte(byte value) => allBytes.Add(value);
public byte[] ToArray() => allBytes.ToArray();
}
}
}
+1
View File
@@ -1,5 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="Iced" version="1.6.0" targetFramework="net472" />
<package id="Mono.Cecil" version="0.11.2" targetFramework="net472" />
<package id="MonoMod.RuntimeDetour" version="20.5.21.5" targetFramework="net472" />
<package id="MonoMod.Utils" version="20.5.21.5" targetFramework="net472" />
+9 -1
View File
@@ -9,7 +9,7 @@
<AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>BepInExTests</RootNamespace>
<AssemblyName>BepInExTests</AssemblyName>
<TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
<TargetFrameworkVersion>v4.7.2</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
<ProjectTypeGuids>{3AC096D0-A1C2-E12C-1390-A8335801FDAB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids>
<VisualStudioVersion Condition="'$(VisualStudioVersion)' == ''">10.0</VisualStudioVersion>
@@ -45,6 +45,9 @@
<CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
</PropertyGroup>
<ItemGroup>
<Reference Include="Iced, Version=1.6.0.0, Culture=neutral, PublicKeyToken=5baba79f4264913b, processorArchitecture=MSIL">
<HintPath>..\packages\Iced.1.6.0\lib\net45\Iced.dll</HintPath>
</Reference>
<Reference Include="Microsoft.VisualStudio.TestPlatform.TestFramework, Version=14.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
<HintPath>..\packages\MSTest.TestFramework.1.3.2\lib\net45\Microsoft.VisualStudio.TestPlatform.TestFramework.dll</HintPath>
</Reference>
@@ -66,6 +69,7 @@
</Choose>
<ItemGroup>
<Compile Include="AssemblyInit.cs" />
<Compile Include="TrampolineTests.cs" />
<Compile Include="Configuration\ConfigFileTests.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
</ItemGroup>
@@ -77,6 +81,10 @@
<Project>{4FFBA620-F5ED-47F9-B90C-DAD1316FD9B9}</Project>
<Name>BepInEx.Core</Name>
</ProjectReference>
<ProjectReference Include="..\BepInEx.IL2CPP\BepInEx.IL2CPP.csproj">
<Project>{429d5f83-7fec-4d09-ab82-e868eb61af59}</Project>
<Name>BepInEx.IL2CPP</Name>
</ProjectReference>
<ProjectReference Include="..\BepInEx.Unity\BepInEx.Unity.csproj">
<Project>{EAE9FAE6-8011-45A3-8B6E-0C7F14210533}</Project>
<Name>BepInEx.Unity</Name>
+72
View File
@@ -0,0 +1,72 @@
using System;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using System.Runtime.InteropServices;
using BepInEx.IL2CPP;
using Iced.Intel;
namespace BepInEx.Tests
{
[TestClass]
public class TrampolineTests
{
[TestMethod]
public void TrampolineTest()
{
byte[] exampleCode = new byte[] {
0x48, 0x89, 0x5C, 0x24, 0x10, 0x48, 0x89, 0x74, 0x24, 0x18, 0x55, 0x57, 0x41, 0x56, 0x48, 0x8D,
0xAC, 0x24, 0x00, 0xFF, 0xFF, 0xFF, 0x48, 0x81, 0xEC, 0x00, 0x02, 0x00, 0x00, 0x48, 0x8B, 0x05,
0x18, 0x57, 0x0A, 0x00, 0x48, 0x33, 0xC4, 0x48, 0x89, 0x85, 0xF0, 0x00, 0x00, 0x00, 0x4C, 0x8B,
0x05, 0x2F, 0x24, 0x0A, 0x00, 0x48, 0x8D, 0x05, 0x78, 0x7C, 0x04, 0x00, 0x33, 0xFF
};
var exampleCodePointer = Marshal.AllocHGlobal(80);
var trampolineCodePointer = Marshal.AllocHGlobal(80);
Marshal.Copy(exampleCode, 0, exampleCodePointer, exampleCode.Length);
void Disassemble(byte[] data, ulong ip)
{
var formatter = new NasmFormatter();
var output = new StringOutput();
var codeReader = new ByteArrayCodeReader(data);
var decoder = Decoder.Create(64, codeReader);
decoder.IP = ip;
while (codeReader.CanReadByte)
{
decoder.Decode(out var instr);
formatter.Format(instr, output);
Console.WriteLine($"{instr.IP:X16} {output.ToStringAndReset()}");
}
Console.WriteLine();
}
Console.WriteLine("Original:");
Console.WriteLine();
Disassemble(exampleCode, (ulong)exampleCodePointer.ToInt64());
int trampolineLength = TrampolineGenerator.Generate(exampleCodePointer, new IntPtr(0xBEEF), trampolineCodePointer, 64);
Console.WriteLine();
Console.WriteLine("Trampoline:");
Console.WriteLine();
byte[] trampolineArray = new byte[trampolineLength];
Marshal.Copy(trampolineCodePointer, trampolineArray, 0, trampolineLength);
Disassemble(trampolineArray, (ulong)trampolineCodePointer.ToInt64());
Marshal.FreeHGlobal(exampleCodePointer);
Marshal.FreeHGlobal(trampolineCodePointer);
Assert.IsFalse(false);
}
}
}
+1
View File
@@ -1,5 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="Iced" version="1.6.0" targetFramework="net472" />
<package id="MSTest.TestAdapter" version="1.3.2" targetFramework="net45" />
<package id="MSTest.TestFramework" version="1.3.2" targetFramework="net45" />
</packages>
Binary file not shown.