CodePatcher.cs 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320
  1. using DotNetDetour;
  2. using System;
  3. using System.Collections;
  4. using System.Collections.Generic;
  5. using UnityEngine;
  6. using System.Linq;
  7. namespace MonoHook
  8. {
  9. public unsafe abstract class CodePatcher
  10. {
  11. public bool isValid { get; protected set; }
  12. protected void* _pTarget, _pReplace, _pProxy;
  13. protected int _jmpCodeSize;
  14. protected byte[] _targetHeaderBackup;
  15. public CodePatcher(IntPtr target, IntPtr replace, IntPtr proxy, int jmpCodeSize)
  16. {
  17. _pTarget = target.ToPointer();
  18. _pReplace = replace.ToPointer();
  19. _pProxy = proxy.ToPointer();
  20. _jmpCodeSize = jmpCodeSize;
  21. }
  22. public void ApplyPatch()
  23. {
  24. BackupHeader();
  25. EnableAddrModifiable();
  26. PatchTargetMethod();
  27. PatchProxyMethod();
  28. FlushICache();
  29. }
  30. public void RemovePatch()
  31. {
  32. if (_targetHeaderBackup == null)
  33. return;
  34. EnableAddrModifiable();
  35. RestoreHeader();
  36. FlushICache();
  37. }
  38. protected void BackupHeader()
  39. {
  40. if (_targetHeaderBackup != null)
  41. return;
  42. uint requireSize = LDasm.SizeofMinNumByte(_pTarget, _jmpCodeSize);
  43. _targetHeaderBackup = new byte[requireSize];
  44. fixed (void* ptr = _targetHeaderBackup)
  45. HookUtils.MemCpy(ptr, _pTarget, _targetHeaderBackup.Length);
  46. }
  47. protected void RestoreHeader()
  48. {
  49. if (_targetHeaderBackup == null)
  50. return;
  51. HookUtils.MemCpy_Jit(_pTarget, _targetHeaderBackup);
  52. }
  53. protected void PatchTargetMethod()
  54. {
  55. byte[] buff = GenJmpCode(_pTarget, _pReplace);
  56. HookUtils.MemCpy_Jit(_pTarget, buff);
  57. }
  58. protected void PatchProxyMethod()
  59. {
  60. if (_pProxy == null)
  61. return;
  62. // copy target's code to proxy
  63. HookUtils.MemCpy_Jit(_pProxy, _targetHeaderBackup);
  64. // jmp to target's new position
  65. long jmpFrom = (long)_pProxy + _targetHeaderBackup.Length;
  66. long jmpTo = (long)_pTarget + _targetHeaderBackup.Length;
  67. byte[] buff = GenJmpCode((void*)jmpFrom, (void*)jmpTo);
  68. HookUtils.MemCpy_Jit((void*)jmpFrom, buff);
  69. }
  70. protected void FlushICache()
  71. {
  72. HookUtils.FlushICache(_pTarget, _targetHeaderBackup.Length);
  73. HookUtils.FlushICache(_pProxy, _targetHeaderBackup.Length * 2);
  74. }
  75. protected abstract byte[] GenJmpCode(void* jmpFrom, void* jmpTo);
  76. #if ENABLE_HOOK_DEBUG
  77. protected string PrintAddrs()
  78. {
  79. if (IntPtr.Size == 4)
  80. return $"target:0x{(uint)_pTarget:x}, replace:0x{(uint)_pReplace:x}, proxy:0x{(uint)_pProxy:x}";
  81. else
  82. return $"target:0x{(ulong)_pTarget:x}, replace:0x{(ulong)_pReplace:x}, proxy:0x{(ulong)_pProxy:x}";
  83. }
  84. #endif
  85. private void EnableAddrModifiable()
  86. {
  87. HookUtils.SetAddrFlagsToRWX(new IntPtr(_pTarget), _targetHeaderBackup.Length);
  88. HookUtils.SetAddrFlagsToRWX(new IntPtr(_pProxy), _targetHeaderBackup.Length + _jmpCodeSize);
  89. }
  90. }
  91. public unsafe class CodePatcher_x86 : CodePatcher
  92. {
  93. protected static readonly byte[] s_jmpCode = new byte[] // 5 bytes
  94. {
  95. 0xE9, 0x00, 0x00, 0x00, 0x00, // jmp $val ; $val = $dst - $src - 5
  96. };
  97. public CodePatcher_x86(IntPtr target, IntPtr replace, IntPtr proxy) : base(target, replace, proxy, s_jmpCode.Length) { }
  98. protected override unsafe byte[] GenJmpCode(void* jmpFrom, void* jmpTo)
  99. {
  100. byte[] ret = new byte[s_jmpCode.Length];
  101. int val = (int)jmpTo - (int)jmpFrom - 5;
  102. fixed(void * p = &ret[0])
  103. {
  104. byte* ptr = (byte*)p;
  105. *ptr = 0xE9;
  106. int* pOffset = (int*)(ptr + 1);
  107. *pOffset = val;
  108. }
  109. return ret;
  110. }
  111. }
  112. /// <summary>
  113. /// x64下2G 内的跳转
  114. /// </summary>
  115. public unsafe class CodePatcher_x64_near : CodePatcher_x86 // x64_near pathcer code is same to x86
  116. {
  117. public CodePatcher_x64_near(IntPtr target, IntPtr replace, IntPtr proxy) : base(target, replace, proxy) { }
  118. }
  119. /// <summary>
  120. /// x64下距离超过2G的跳转
  121. /// </summary>
  122. public unsafe class CodePatcher_x64_far : CodePatcher
  123. {
  124. protected static readonly byte[] s_jmpCode = new byte[] // 12 bytes
  125. {
  126. // 由于 rax 会被函数作为返回值修改,并且不会被做为参数使用,因此修改是安全的
  127. 0x48, 0xB8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // mov rax, <jmpTo>
  128. 0x50, // push rax
  129. 0xC3 // ret
  130. };
  131. //protected static readonly byte[] s_jmpCode2 = new byte[] // 14 bytes
  132. //{
  133. // 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // <jmpTo>
  134. // 0xFF, 0x25, 0xF2, 0xFF, 0xFF, 0xFF // jmp [rip - 0xe]
  135. //};
  136. public CodePatcher_x64_far(IntPtr target, IntPtr replace, IntPtr proxy) : base(target, replace, proxy, s_jmpCode.Length) { }
  137. protected override unsafe byte[] GenJmpCode(void* jmpFrom, void* jmpTo)
  138. {
  139. byte[] ret = new byte[s_jmpCode.Length];
  140. fixed (void* p = &ret[0])
  141. {
  142. byte* ptr = (byte*)p;
  143. *ptr++ = 0x48;
  144. *ptr++ = 0xB8;
  145. *(long*)ptr = (long)jmpTo;
  146. ptr += 8;
  147. *ptr++ = 0x50;
  148. *ptr++ = 0xC3;
  149. }
  150. return ret;
  151. }
  152. }
  153. public unsafe class CodePatcher_arm32_near : CodePatcher
  154. {
  155. private static readonly byte[] s_jmpCode = new byte[] // 4 bytes
  156. {
  157. 0x00, 0x00, 0x00, 0xEA, // B $val ; $val = (($dst - $src) / 4 - 2) & 0x1FFFFFF
  158. };
  159. public CodePatcher_arm32_near(IntPtr target, IntPtr replace, IntPtr proxy) : base(target, replace, proxy, s_jmpCode.Length)
  160. {
  161. if (Math.Abs((long)target - (long)replace) >= ((1 << 25) - 1))
  162. throw new ArgumentException("address offset of target and replace must less than ((1 << 25) - 1)");
  163. #if ENABLE_HOOK_DEBUG
  164. Debug.Log($"CodePatcher_arm32_near: {PrintAddrs()}");
  165. #endif
  166. }
  167. protected override unsafe byte[] GenJmpCode(void* jmpFrom, void* jmpTo)
  168. {
  169. byte[] ret = new byte[s_jmpCode.Length];
  170. int val = ((int)jmpTo - (int)jmpFrom) / 4 - 2;
  171. fixed (void* p = &ret[0])
  172. {
  173. byte* ptr = (byte*)p;
  174. *ptr++ = (byte)val;
  175. *ptr++ = (byte)(val >> 8);
  176. *ptr++ = (byte)(val >> 16);
  177. *ptr++ = 0xEA;
  178. }
  179. return ret;
  180. }
  181. }
  182. public unsafe class CodePatcher_arm32_far : CodePatcher
  183. {
  184. private static readonly byte[] s_jmpCode = new byte[] // 8 bytes
  185. {
  186. 0x04, 0xF0, 0x1F, 0xE5, // LDR PC, [PC, #-4]
  187. 0x00, 0x00, 0x00, 0x00, // $val
  188. };
  189. public CodePatcher_arm32_far(IntPtr target, IntPtr replace, IntPtr proxy) : base(target, replace, proxy, s_jmpCode.Length)
  190. {
  191. if (Math.Abs((long)target - (long)replace) < ((1 << 25) - 1))
  192. throw new ArgumentException("address offset of target and replace must larger than ((1 << 25) - 1), please use InstructionModifier_arm32_near instead");
  193. #if ENABLE_HOOK_DEBUG
  194. Debug.Log($"CodePatcher_arm32_far: {PrintAddrs()}");
  195. #endif
  196. }
  197. protected override unsafe byte[] GenJmpCode(void* jmpFrom, void* jmpTo)
  198. {
  199. byte[] ret = new byte[s_jmpCode.Length];
  200. fixed (void* p = &ret[0])
  201. {
  202. uint* ptr = (uint*)p;
  203. *ptr++ = 0xE51FF004;
  204. *ptr = (uint)jmpTo;
  205. }
  206. return ret;
  207. }
  208. }
  209. /// <summary>
  210. /// arm64 下 ±128MB 范围内的跳转
  211. /// </summary>
  212. public unsafe class CodePatcher_arm64_near : CodePatcher
  213. {
  214. private static readonly byte[] s_jmpCode = new byte[] // 4 bytes
  215. {
  216. /*
  217. * from 0x14 to 0x17 is B opcode
  218. * offset bits is 26
  219. * https://developer.arm.com/documentation/ddi0596/2021-09/Base-Instructions/B--Branch-
  220. */
  221. 0x00, 0x00, 0x00, 0x14, // B $val ; $val = (($dst - $src)/4) & 7FFFFFF
  222. };
  223. public CodePatcher_arm64_near(IntPtr target, IntPtr replace, IntPtr proxy) : base(target, replace, proxy, s_jmpCode.Length)
  224. {
  225. if (Math.Abs((long)target - (long)replace) >= ((1 << 26) - 1) * 4)
  226. throw new ArgumentException("address offset of target and replace must less than (1 << 26) - 1) * 4");
  227. #if ENABLE_HOOK_DEBUG
  228. Debug.Log($"CodePatcher_arm64: {PrintAddrs()}");
  229. #endif
  230. }
  231. protected override unsafe byte[] GenJmpCode(void* jmpFrom, void* jmpTo)
  232. {
  233. byte[] ret = new byte[s_jmpCode.Length];
  234. int val = (int)((long)jmpTo - (long)jmpFrom) / 4;
  235. fixed (void* p = &ret[0])
  236. {
  237. byte* ptr = (byte*)p;
  238. *ptr++ = (byte)val;
  239. *ptr++ = (byte)(val >> 8);
  240. *ptr++ = (byte)(val >> 16);
  241. byte last = (byte)(val >> 24);
  242. last &= 0b11;
  243. last |= 0x14;
  244. *ptr = last;
  245. }
  246. return ret;
  247. }
  248. }
  249. /// <summary>
  250. /// arm64 远距离跳转
  251. /// </summary>
  252. public unsafe class CodePatcher_arm64_far : CodePatcher
  253. {
  254. private static readonly byte[] s_jmpCode = new byte[] // 20 bytes(字节数过多,太危险了,不建议使用)
  255. {
  256. /*
  257. * ADR: https://developer.arm.com/documentation/ddi0596/2021-09/Base-Instructions/ADR--Form-PC-relative-address-
  258. * BR: https://developer.arm.com/documentation/ddi0596/2021-09/Base-Instructions/BR--Branch-to-Register-
  259. */
  260. 0x6A, 0x00, 0x00, 0x10, // ADR X10, #C
  261. 0x4A, 0x01, 0x40, 0xF9, // LDR X10, [X10,#0]
  262. 0x40, 0x01, 0x1F, 0xD6, // BR X10
  263. 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 // $dst
  264. };
  265. public CodePatcher_arm64_far(IntPtr target, IntPtr replace, IntPtr proxy, int jmpCodeSize) : base(target, replace, proxy, jmpCodeSize)
  266. {
  267. }
  268. protected override unsafe byte[] GenJmpCode(void* jmpFrom, void* jmpTo)
  269. {
  270. throw new NotImplementedException();
  271. }
  272. }
  273. }