Browse Source

Implement x86 trampoline generation

Bepis 3 years ago
parent
commit
418476b728
2 changed files with 50 additions and 17 deletions
  1. 44 13
      BepInEx.IL2CPP/TrampolineGenerator.cs
  2. 6 4
      BepInExTests/TrampolineTests.cs

+ 44 - 13
BepInEx.IL2CPP/TrampolineGenerator.cs

@@ -15,8 +15,10 @@ namespace BepInEx.IL2CPP
 
 			// 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 generatedJmp = GenerateAbsoluteJump(patchedFunctionPtr, originalFunctionPtr, bitness == 64);
+
+
+			uint requiredBytes = (uint)generatedJmp.Length;
 
 			var codeReader = new ByteArrayCodeReader(instructionBuffer);
 			var decoder = Decoder.Create(bitness, codeReader);
@@ -67,7 +69,17 @@ namespace BepInEx.IL2CPP
 
 			ref readonly var lastInstr = ref origInstructions[origInstructions.Count - 1];
 
-			origInstructions.Add(Instruction.CreateBranch(Code.Jmp_rel32_64, lastInstr.NextIP));
+			if (lastInstr.FlowControl != FlowControl.Return)
+			{
+				if (bitness == 64)
+				{
+					origInstructions.Add(Instruction.CreateBranch(Code.Jmp_rel32_64, lastInstr.NextIP));
+				}
+				else
+				{
+					origInstructions.Add(Instruction.CreateBranch(Code.Jmp_rel32_32, lastInstr.NextIP));
+				}
+			}
 
 
 			// Generate trampoline from instruction list
@@ -90,20 +102,39 @@ namespace BepInEx.IL2CPP
 
 			// 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);
+			Marshal.Copy(generatedJmp, 0, originalFunctionPtr, (int)requiredBytes);
 
 			return newCode.Length;
 		}
 
+		private static byte[] GenerateAbsoluteJump(IntPtr address, IntPtr currentAddress, bool x64)
+		{
+			byte[] jmpBytes;
+
+			if (x64)
+			{
+				jmpBytes = new byte[]
+				{
+					0xFF, 0x25, 0x00, 0x00, 0x00, 0x00,				// FF25 00000000: JMP [RIP+6]
+					0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00	// Absolute destination address
+				};
+
+				Array.Copy(BitConverter.GetBytes(address.ToInt64()), 0, jmpBytes, 6, 8);
+			}
+			else
+			{
+				jmpBytes = new byte[]
+				{
+					0xE9,					// E9: JMP rel destination
+					0x00, 0x00, 0x00, 0x00	// Relative destination address
+				};
+
+				Array.Copy(BitConverter.GetBytes(currentAddress.ToInt32() - address.ToInt32()), 0, jmpBytes, 1, 4);
+			}
+
+			return jmpBytes;
+		}
+
 
 		private sealed class CodeWriterImpl : CodeWriter
 		{

+ 6 - 4
BepInExTests/TrampolineTests.cs

@@ -9,8 +9,10 @@ namespace BepInEx.Tests
 	[TestClass]
 	public class TrampolineTests
 	{
-		[TestMethod]
-		public void TrampolineTest()
+		[DataTestMethod]
+		[DataRow(64)]
+		[DataRow(32)]
+		public void TrampolineTest(int bitness)
 		{
 			byte[] exampleCode = new byte[] {
 				0x48, 0x89, 0x5C, 0x24, 0x10, 0x48, 0x89, 0x74, 0x24, 0x18, 0x55, 0x57, 0x41, 0x56, 0x48, 0x8D,
@@ -29,7 +31,7 @@ namespace BepInEx.Tests
 				var formatter = new NasmFormatter();
 				var output = new StringOutput();
 				var codeReader = new ByteArrayCodeReader(data);
-				var decoder = Decoder.Create(64, codeReader);
+				var decoder = Decoder.Create(bitness, codeReader);
 				decoder.IP = ip;
 				while (codeReader.CanReadByte)
 				{
@@ -48,7 +50,7 @@ namespace BepInEx.Tests
 			Disassemble(exampleCode, (ulong)exampleCodePointer.ToInt64());
 
 
-			int trampolineLength = TrampolineGenerator.Generate(exampleCodePointer, new IntPtr(0xBEEF), trampolineCodePointer, 64);
+			int trampolineLength = TrampolineGenerator.Generate(exampleCodePointer, new IntPtr(0xBEEF), trampolineCodePointer, bitness);