TrampolineGenerator.cs 6.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Runtime.InteropServices;
  4. using BepInEx.Logging;
  5. using Iced.Intel;
  6. using MonoMod.RuntimeDetour;
  7. namespace BepInEx.IL2CPP
  8. {
  9. public static class TrampolineGenerator
  10. {
  11. public static IntPtr Generate(IntPtr originalFunctionPtr, IntPtr patchedFunctionPtr, out int trampolineLength)
  12. {
  13. var trampolineAlloc = DetourHelper.Native.MemAlloc(80);
  14. DetourHelper.Native.MakeExecutable(trampolineAlloc, 80);
  15. DetourHelper.Native.MakeWritable(originalFunctionPtr, 24);
  16. trampolineLength = Generate(originalFunctionPtr, patchedFunctionPtr, trampolineAlloc, IntPtr.Size == 8 ? 64 : 32);
  17. return trampolineAlloc;
  18. }
  19. private static void Disassemble(ManualLogSource logSource, IntPtr memoryPtr, int size)
  20. {
  21. byte[] data = new byte[size];
  22. Marshal.Copy(memoryPtr, data, 0, size);
  23. var formatter = new NasmFormatter();
  24. var output = new StringOutput();
  25. var codeReader = new ByteArrayCodeReader(data);
  26. var decoder = Decoder.Create(64, codeReader);
  27. decoder.IP = (ulong)memoryPtr.ToInt64();
  28. while (codeReader.CanReadByte)
  29. {
  30. decoder.Decode(out var instr);
  31. formatter.Format(instr, output);
  32. logSource.LogDebug($"{instr.IP:X16} {output.ToStringAndReset()}");
  33. }
  34. }
  35. public static IntPtr Generate(ManualLogSource logSource, IntPtr originalFunctionPtr, IntPtr patchedFunctionPtr)
  36. {
  37. logSource.LogDebug($"DoHook 0x{originalFunctionPtr.ToString("X")} -> 0x{patchedFunctionPtr.ToString("X")}");
  38. var trampolineAlloc = DetourHelper.Native.MemAlloc(80);
  39. DetourHelper.Native.MakeExecutable(trampolineAlloc, 80);
  40. DetourHelper.Native.MakeWritable(originalFunctionPtr, 24);
  41. logSource.LogDebug($"Trampoline allocation: 0x{trampolineAlloc.ToString("X")}");
  42. logSource.LogDebug("Original (24) asm");
  43. Disassemble(logSource, originalFunctionPtr, 24);
  44. var trampolineLength = Generate(originalFunctionPtr, patchedFunctionPtr, trampolineAlloc, IntPtr.Size == 8 ? 64 : 32);
  45. logSource.LogDebug("Modified (24) asm");
  46. Disassemble(logSource, originalFunctionPtr, 24);
  47. logSource.LogDebug($"Trampoline ({trampolineLength}) asm");
  48. Disassemble(logSource, trampolineAlloc, trampolineLength);
  49. return trampolineAlloc;
  50. }
  51. public static int Generate(IntPtr originalFunctionPtr, IntPtr patchedFunctionPtr, IntPtr trampolineFunctionPtr, int bitness)
  52. {
  53. byte[] instructionBuffer = new byte[80];
  54. Marshal.Copy(originalFunctionPtr, instructionBuffer, 0, 80);
  55. // Decode original function up until we go past the needed bytes to write the jump to patchedFunctionPtr
  56. var generatedJmp = GenerateAbsoluteJump(patchedFunctionPtr, originalFunctionPtr, bitness == 64);
  57. uint requiredBytes = (uint)generatedJmp.Length;
  58. var codeReader = new ByteArrayCodeReader(instructionBuffer);
  59. var decoder = Decoder.Create(bitness, codeReader);
  60. decoder.IP = (ulong)originalFunctionPtr.ToInt64();
  61. uint totalBytes = 0;
  62. var origInstructions = new InstructionList();
  63. while (codeReader.CanReadByte)
  64. {
  65. decoder.Decode(out var instr);
  66. origInstructions.Add(instr);
  67. totalBytes += (uint)instr.Length;
  68. if (instr.Code == Code.INVALID)
  69. throw new Exception("Found garbage");
  70. if (totalBytes >= requiredBytes)
  71. break;
  72. switch (instr.FlowControl)
  73. {
  74. case FlowControl.Next:
  75. break;
  76. case FlowControl.UnconditionalBranch:
  77. if (instr.Op0Kind == OpKind.NearBranch64)
  78. {
  79. var target = instr.NearBranchTarget;
  80. }
  81. goto default;
  82. case FlowControl.IndirectBranch:// eg. jmp reg/mem
  83. case FlowControl.ConditionalBranch:// eg. je, jno, etc
  84. case FlowControl.Return:// eg. ret
  85. case FlowControl.Call:// eg. call method
  86. case FlowControl.IndirectCall:// eg. call reg/mem
  87. case FlowControl.Interrupt:// eg. int n
  88. case FlowControl.XbeginXabortXend:
  89. case FlowControl.Exception:// eg. ud0
  90. default:
  91. throw new Exception("Not supported by this simple example - " + instr.FlowControl);
  92. }
  93. }
  94. if (totalBytes < requiredBytes)
  95. throw new Exception("Not enough bytes!");
  96. if (origInstructions.Count == 0)
  97. throw new Exception("Not enough instructions!");
  98. ref readonly var lastInstr = ref origInstructions[origInstructions.Count - 1];
  99. if (lastInstr.FlowControl != FlowControl.Return)
  100. {
  101. if (bitness == 64)
  102. {
  103. origInstructions.Add(Instruction.CreateBranch(Code.Jmp_rel32_64, lastInstr.NextIP));
  104. }
  105. else
  106. {
  107. origInstructions.Add(Instruction.CreateBranch(Code.Jmp_rel32_32, lastInstr.NextIP));
  108. }
  109. }
  110. // Generate trampoline from instruction list
  111. var codeWriter = new CodeWriterImpl();
  112. ulong relocatedBaseAddress = (ulong)trampolineFunctionPtr;
  113. var block = new InstructionBlock(codeWriter, origInstructions, relocatedBaseAddress);
  114. bool success = BlockEncoder.TryEncode(decoder.Bitness, block, out var errorMessage, out var result);
  115. if (!success)
  116. {
  117. throw new Exception(errorMessage);
  118. }
  119. // Write generated trampoline
  120. var newCode = codeWriter.ToArray();
  121. Marshal.Copy(newCode, 0, trampolineFunctionPtr, newCode.Length);
  122. // Overwrite the start of trampolineFunctionPtr with a jump to patchedFunctionPtr
  123. Marshal.Copy(generatedJmp, 0, originalFunctionPtr, (int)requiredBytes);
  124. return newCode.Length;
  125. }
  126. private static byte[] GenerateAbsoluteJump(IntPtr address, IntPtr currentAddress, bool x64)
  127. {
  128. byte[] jmpBytes;
  129. if (x64)
  130. {
  131. jmpBytes = new byte[]
  132. {
  133. 0xFF, 0x25, 0x00, 0x00, 0x00, 0x00, // FF25 00000000: JMP [RIP+6]
  134. 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 // Absolute destination address
  135. };
  136. Array.Copy(BitConverter.GetBytes(address.ToInt64()), 0, jmpBytes, 6, 8);
  137. }
  138. else
  139. {
  140. jmpBytes = new byte[]
  141. {
  142. 0xE9, // E9: JMP rel destination
  143. 0x00, 0x00, 0x00, 0x00 // Relative destination address
  144. };
  145. Array.Copy(BitConverter.GetBytes(address.ToInt32() - (currentAddress.ToInt32() + 5)), 0, jmpBytes, 1, 4);
  146. }
  147. return jmpBytes;
  148. }
  149. private sealed class CodeWriterImpl : CodeWriter
  150. {
  151. readonly List<byte> allBytes = new List<byte>();
  152. public override void WriteByte(byte value) => allBytes.Add(value);
  153. public byte[] ToArray() => allBytes.ToArray();
  154. }
  155. }
  156. }