MethodHook.cs 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381
  1. /*
  2. Desc: 一个可以运行时 Hook Mono 方法的工具,让你可以无需修改 UnityEditor.dll 等文件就可以重写其函数功能
  3. Author: Misaka Mikoto
  4. Github: https://github.com/Misaka-Mikoto-Tech/MonoHook
  5. */
  6. using DotNetDetour;
  7. using System;
  8. using System.Diagnostics;
  9. using System.Reflection;
  10. using System.Runtime.InteropServices;
  11. using Unity.Collections.LowLevel.Unsafe;
  12. #if UNITY_EDITOR
  13. using UnityEditor;
  14. #endif
  15. using UnityEngine;
  16. using System.Runtime.CompilerServices;
  17. /*
  18. >>>>>>> 原始 UnityEditor.LogEntries.Clear 一型(.net 4.x)
  19. 0000000000403A00 < | 55 | push rbp |
  20. 0000000000403A01 | 48 8B EC | mov rbp,rsp |
  21. 0000000000403A04 | 48 81 EC 80 00 00 00 | sub rsp,80 |
  22. 0000000000403A0B | 48 89 65 B0 | mov qword ptr ss:[rbp-50],rsp |
  23. 0000000000403A0F | 48 89 6D A8 | mov qword ptr ss:[rbp-58],rbp |
  24. 0000000000403A13 | 48 89 5D C8 | mov qword ptr ss:[rbp-38],rbx | <<
  25. 0000000000403A17 | 48 89 75 D0 | mov qword ptr ss:[rbp-30],rsi |
  26. 0000000000403A1B | 48 89 7D D8 | mov qword ptr ss:[rbp-28],rdi |
  27. 0000000000403A1F | 4C 89 65 E0 | mov qword ptr ss:[rbp-20],r12 |
  28. 0000000000403A23 | 4C 89 6D E8 | mov qword ptr ss:[rbp-18],r13 |
  29. 0000000000403A27 | 4C 89 75 F0 | mov qword ptr ss:[rbp-10],r14 |
  30. 0000000000403A2B | 4C 89 7D F8 | mov qword ptr ss:[rbp-8],r15 |
  31. 0000000000403A2F | 49 BB 00 2D 1E 1A FE 7F 00 00 | mov r11,7FFE1A1E2D00 |
  32. 0000000000403A39 | 4C 89 5D B8 | mov qword ptr ss:[rbp-48],r11 |
  33. 0000000000403A3D | 49 BB 08 2D 1E 1A FE 7F 00 00 | mov r11,7FFE1A1E2D08 |
  34. >>>>>>> 二型(.net 2.x)
  35. 0000000000403E8F | 55 | push rbp |
  36. 0000000000403E90 | 48 8B EC | mov rbp,rsp |
  37. 0000000000403E93 | 48 83 EC 70 | sub rsp,70 |
  38. 0000000000403E97 | 48 89 65 C8 | mov qword ptr ss:[rbp-38],rsp |
  39. 0000000000403E9B | 48 89 5D B8 | mov qword ptr ss:[rbp-48],rbx |
  40. 0000000000403E9F | 48 89 6D C0 | mov qword ptr ss:[rbp-40],rbp | <<(16)
  41. 0000000000403EA3 | 48 89 75 F8 | mov qword ptr ss:[rbp-8],rsi |
  42. 0000000000403EA7 | 48 89 7D F0 | mov qword ptr ss:[rbp-10],rdi |
  43. 0000000000403EAB | 4C 89 65 D0 | mov qword ptr ss:[rbp-30],r12 |
  44. 0000000000403EAF | 4C 89 6D D8 | mov qword ptr ss:[rbp-28],r13 |
  45. 0000000000403EB3 | 4C 89 75 E0 | mov qword ptr ss:[rbp-20],r14 |
  46. 0000000000403EB7 | 4C 89 7D E8 | mov qword ptr ss:[rbp-18],r15 |
  47. 0000000000403EBB | 48 83 EC 20 | sub rsp,20 |
  48. 0000000000403EBF | 49 BB 18 3F 15 13 FE 7F 00 00 | mov r11,7FFE13153F18 |
  49. 0000000000403EC9 | 41 FF D3 | call r11 |
  50. 0000000000403ECC | 48 83 C4 20 | add rsp,20 |
  51. >>>>>>>>> arm64
  52. il2cpp:00000000003DE714 F5 0F 1D F8 STR X21, [SP,#-0x10+var_20]! | << absolute safe
  53. il2cpp:00000000003DE718 F4 4F 01 A9 STP X20, X19, [SP,#0x20+var_10] | << may be safe
  54. il2cpp:00000000003DE71C FD 7B 02 A9 STP X29, X30, [SP,#0x20+var_s0] |
  55. il2cpp:00000000003DE720 FD 83 00 91 ADD X29, SP, #0x20 |
  56. il2cpp:00000000003DE724 B5 30 00 B0 ADRP X21, #_ZZ62GameObject_SetActive_mCF1EEF2A314F3AE | << dangerous: relative instruction, can not be overwritten
  57. il2cpp:00000000003DE728 A2 56 47 F9 LDR method, [X21,#_ZZ62GameObject_SetActive_mCF] ; |
  58. il2cpp:00000000003DE72C F3 03 01 2A MOV W19, W1 |
  59. */
  60. namespace MonoHook
  61. {
  62. /// <summary>
  63. /// Hook 类,用来 Hook 某个 C# 方法
  64. /// </summary>
  65. public unsafe class MethodHook
  66. {
  67. public string tag;
  68. public bool isHooked { get; private set; }
  69. public bool isPlayModeHook { get; private set; }
  70. public MethodBase targetMethod { get; private set; } // 需要被hook的目标方法
  71. public MethodBase replacementMethod { get; private set; } // 被hook后的替代方法
  72. public MethodBase proxyMethod { get; private set; } // 目标方法的代理方法(可以通过此方法调用被hook后的原方法)
  73. private IntPtr _targetPtr; // 目标方法被 jit 后的地址指针
  74. private IntPtr _replacementPtr;
  75. private IntPtr _proxyPtr;
  76. private CodePatcher _codePatcher;
  77. #if UNITY_EDITOR && !UNITY_2020_3_OR_NEWER
  78. /// <summary>
  79. /// call `MethodInfo.MethodHandle.GetFunctionPointer()`
  80. /// will visit static class `UnityEditor.IMGUI.Controls.TreeViewGUI.Styles` and invoke its static constructor,
  81. /// and init static filed `foldout`, but `GUISKin.current` is null now,
  82. /// so we should wait until `GUISKin.current` has a valid value
  83. /// </summary>
  84. private static FieldInfo s_fi_GUISkin_current;
  85. #endif
  86. static MethodHook()
  87. {
  88. #if UNITY_EDITOR && !UNITY_2020_3_OR_NEWER
  89. s_fi_GUISkin_current = typeof(GUISkin).GetField("current", BindingFlags.Static | BindingFlags.NonPublic);
  90. #endif
  91. }
  92. /// <summary>
  93. /// 创建一个 Hook
  94. /// </summary>
  95. /// <param name="targetMethod">需要替换的目标方法</param>
  96. /// <param name="replacementMethod">准备好的替换方法</param>
  97. /// <param name="proxyMethod">如果还需要调用原始目标方法,可以通过此参数的方法调用,如果不需要可以填 null</param>
  98. public MethodHook(MethodBase targetMethod, MethodBase replacementMethod, MethodBase proxyMethod, string data = "")
  99. {
  100. this.targetMethod = targetMethod;
  101. this.replacementMethod = replacementMethod;
  102. this.proxyMethod = proxyMethod;
  103. this.tag = data;
  104. CheckMethod();
  105. }
  106. public void Install()
  107. {
  108. if (LDasm.IsiOS()) // iOS 不支持修改 code 所在区域 page
  109. return;
  110. if (isHooked)
  111. return;
  112. #if UNITY_EDITOR && !UNITY_2020_3_OR_NEWER
  113. if (s_fi_GUISkin_current.GetValue(null) != null)
  114. DoInstall();
  115. else
  116. EditorApplication.update += OnEditorUpdate;
  117. #else
  118. DoInstall();
  119. #endif
  120. isPlayModeHook = Application.isPlaying;
  121. }
  122. public void Uninstall()
  123. {
  124. if (!isHooked)
  125. return;
  126. _codePatcher.RemovePatch();
  127. isHooked = false;
  128. HookPool.RemoveHooker(targetMethod);
  129. }
  130. #region private
  131. private void DoInstall()
  132. {
  133. if (targetMethod == null || replacementMethod == null)
  134. throw new Exception("none of methods targetMethod or replacementMethod can be null");
  135. HookPool.AddHook(targetMethod, this);
  136. if (_codePatcher == null)
  137. {
  138. if (GetFunctionAddr())
  139. {
  140. #if ENABLE_HOOK_DEBUG
  141. UnityEngine.Debug.Log($"Original [{targetMethod.DeclaringType.Name}.{targetMethod.Name}]: {HookUtils.HexToString(_targetPtr.ToPointer(), 64, -16)}");
  142. UnityEngine.Debug.Log($"Original [{replacementMethod.DeclaringType.Name}.{replacementMethod.Name}]: {HookUtils.HexToString(_replacementPtr.ToPointer(), 64, -16)}");
  143. if(proxyMethod != null)
  144. UnityEngine.Debug.Log($"Original [{proxyMethod.DeclaringType.Name}.{proxyMethod.Name}]: {HookUtils.HexToString(_proxyPtr.ToPointer(), 64, -16)}");
  145. #endif
  146. CreateCodePatcher();
  147. _codePatcher.ApplyPatch();
  148. #if ENABLE_HOOK_DEBUG
  149. UnityEngine.Debug.Log($"New [{targetMethod.DeclaringType.Name}.{targetMethod.Name}]: {HookUtils.HexToString(_targetPtr.ToPointer(), 64, -16)}");
  150. UnityEngine.Debug.Log($"New [{replacementMethod.DeclaringType.Name}.{replacementMethod.Name}]: {HookUtils.HexToString(_replacementPtr.ToPointer(), 64, -16)}");
  151. if(proxyMethod != null)
  152. UnityEngine.Debug.Log($"New [{proxyMethod.DeclaringType.Name}.{proxyMethod.Name}]: {HookUtils.HexToString(_proxyPtr.ToPointer(), 64, -16)}");
  153. #endif
  154. }
  155. }
  156. isHooked = true;
  157. }
  158. private void CheckMethod()
  159. {
  160. if (targetMethod == null || replacementMethod == null)
  161. throw new Exception("MethodHook:targetMethod and replacementMethod and proxyMethod can not be null");
  162. string methodName = $"{targetMethod.DeclaringType.Name}.{targetMethod.Name}";
  163. if (targetMethod.IsAbstract)
  164. throw new Exception($"WRANING: you can not hook abstract method [{methodName}]");
  165. #if UNITY_EDITOR && !UNITY_2020_3_OR_NEWER
  166. int minMethodBodySize = 10;
  167. {
  168. if ((targetMethod.MethodImplementationFlags & MethodImplAttributes.InternalCall) != MethodImplAttributes.InternalCall)
  169. {
  170. int codeSize = targetMethod.GetMethodBody().GetILAsByteArray().Length; // GetMethodBody can not call on il2cpp
  171. if (codeSize < minMethodBodySize)
  172. UnityEngine.Debug.LogWarning($"WRANING: you can not hook method [{methodName}], cause its method body is too short({codeSize}), will random crash on IL2CPP release mode");
  173. }
  174. }
  175. if(proxyMethod != null)
  176. {
  177. methodName = $"{proxyMethod.DeclaringType.Name}.{proxyMethod.Name}";
  178. int codeSize = proxyMethod.GetMethodBody().GetILAsByteArray().Length;
  179. if (codeSize < minMethodBodySize)
  180. UnityEngine.Debug.LogWarning($"WRANING: size of method body[{methodName}] is too short({codeSize}), will random crash on IL2CPP release mode, please fill some dummy code inside");
  181. if ((proxyMethod.MethodImplementationFlags & MethodImplAttributes.NoOptimization) != MethodImplAttributes.NoOptimization)
  182. throw new Exception($"WRANING: method [{methodName}] must has a Attribute `MethodImpl(MethodImplOptions.NoOptimization)` to prevent code call to this optimized by compiler(pass args by shared stack)");
  183. }
  184. #endif
  185. }
  186. private void CreateCodePatcher()
  187. {
  188. long addrOffset = Math.Abs(_targetPtr.ToInt64() - _proxyPtr.ToInt64());
  189. if(_proxyPtr != IntPtr.Zero)
  190. addrOffset = Math.Max(addrOffset, Math.Abs(_targetPtr.ToInt64() - _proxyPtr.ToInt64()));
  191. if (LDasm.IsARM())
  192. {
  193. if (IntPtr.Size == 8)
  194. _codePatcher = new CodePatcher_arm64_near(_targetPtr, _replacementPtr, _proxyPtr);
  195. else if (addrOffset < ((1 << 25) - 1))
  196. _codePatcher = new CodePatcher_arm32_near(_targetPtr, _replacementPtr, _proxyPtr);
  197. else if (addrOffset < ((1 << 27) - 1))
  198. _codePatcher = new CodePatcher_arm32_far(_targetPtr, _replacementPtr, _proxyPtr);
  199. else
  200. throw new Exception("address of target method and replacement method are too far, can not hook");
  201. }
  202. else
  203. {
  204. if (IntPtr.Size == 8)
  205. {
  206. if(addrOffset < 0x7fffffff) // 2G
  207. _codePatcher = new CodePatcher_x64_near(_targetPtr, _replacementPtr, _proxyPtr);
  208. else
  209. _codePatcher = new CodePatcher_x64_far(_targetPtr, _replacementPtr, _proxyPtr);
  210. }
  211. else
  212. _codePatcher = new CodePatcher_x86(_targetPtr, _replacementPtr, _proxyPtr);
  213. }
  214. }
  215. /// <summary>
  216. /// 获取对应函数jit后的native code的地址
  217. /// </summary>
  218. private bool GetFunctionAddr()
  219. {
  220. _targetPtr = GetFunctionAddr(targetMethod);
  221. _replacementPtr = GetFunctionAddr(replacementMethod);
  222. _proxyPtr = GetFunctionAddr(proxyMethod);
  223. if (_targetPtr == IntPtr.Zero || _replacementPtr == IntPtr.Zero)
  224. return false;
  225. if (proxyMethod != null && _proxyPtr == null)
  226. return false;
  227. if(_replacementPtr == _targetPtr)
  228. {
  229. throw new Exception($"the addresses of target method {targetMethod.Name} and replacement method {replacementMethod.Name} can not be same");
  230. }
  231. if (LDasm.IsThumb(_targetPtr) || LDasm.IsThumb(_replacementPtr))
  232. {
  233. throw new Exception("does not support thumb arch");
  234. }
  235. return true;
  236. }
  237. [StructLayout(LayoutKind.Sequential, Pack = 1)] // 好像在 IL2CPP 里无效
  238. private struct __ForCopy
  239. {
  240. public long __dummy;
  241. public MethodBase method;
  242. }
  243. /// <summary>
  244. /// 获取方法指令地址
  245. /// </summary>
  246. /// <param name="method"></param>
  247. /// <returns></returns>
  248. private IntPtr GetFunctionAddr(MethodBase method)
  249. {
  250. if (method == null)
  251. return IntPtr.Zero;
  252. if (!LDasm.IsIL2CPP())
  253. return method.MethodHandle.GetFunctionPointer();
  254. else
  255. {
  256. /*
  257. // System.Reflection.MonoMethod
  258. typedef struct Il2CppReflectionMethod
  259. {
  260. Il2CppObject object;
  261. const MethodInfo *method;
  262. Il2CppString *name;
  263. Il2CppReflectionType *reftype;
  264. } Il2CppReflectionMethod;
  265. typedef Il2CppClass Il2CppVTable;
  266. typedef struct Il2CppObject
  267. {
  268. union
  269. {
  270. Il2CppClass *klass;
  271. Il2CppVTable *vtable;
  272. };
  273. MonitorData *monitor;
  274. } Il2CppObject;
  275. typedef struct MethodInfo
  276. {
  277. Il2CppMethodPointer methodPointer; // this is the pointer to native code of method
  278. InvokerMethod invoker_method;
  279. const char* name;
  280. Il2CppClass *klass;
  281. const Il2CppType *return_type;
  282. const ParameterInfo* parameters;
  283. // ...
  284. }
  285. */
  286. __ForCopy __forCopy = new __ForCopy() { method = method };
  287. long* ptr = &__forCopy.__dummy;
  288. ptr++; // addr of _forCopy.method
  289. IntPtr methodAddr = IntPtr.Zero;
  290. if (sizeof(IntPtr) == 8)
  291. {
  292. long methodDataAddr = *(long*)ptr;
  293. byte* ptrData = (byte*)methodDataAddr + sizeof(IntPtr) * 2; // offset of Il2CppReflectionMethod::const MethodInfo *method;
  294. long methodPtr = 0;
  295. methodPtr = *(long*)ptrData;
  296. methodAddr = new IntPtr(*(long*)methodPtr); // MethodInfo::Il2CppMethodPointer methodPointer;
  297. }
  298. else
  299. {
  300. int methodDataAddr = *(int*)ptr;
  301. byte* ptrData = (byte*)methodDataAddr + sizeof(IntPtr) * 2; // offset of Il2CppReflectionMethod::const MethodInfo *method;
  302. int methodPtr = 0;
  303. methodPtr = *(int*)ptrData;
  304. methodAddr = new IntPtr(*(int*)methodPtr);
  305. }
  306. return methodAddr;
  307. }
  308. }
  309. #if UNITY_EDITOR && !UNITY_2020_3_OR_NEWER
  310. private void OnEditorUpdate()
  311. {
  312. if (s_fi_GUISkin_current.GetValue(null) != null)
  313. {
  314. try
  315. {
  316. DoInstall();
  317. }
  318. finally
  319. {
  320. EditorApplication.update -= OnEditorUpdate;
  321. }
  322. }
  323. }
  324. #endif
  325. #endregion
  326. }
  327. }