├── .gitattributes ├── .gitignore ├── DotNetDetour.sln ├── DotNetDetour ├── AssemblyUtil.cs ├── DetourFactory.cs ├── DetourWays │ ├── MethodTableDetour.cs │ ├── NativeDetourFor32Bit.cs │ └── NativeDetourFor64Bit.cs ├── DotNetDetour.csproj ├── DotNetDetour.nuspec ├── IDetour.cs ├── IMethodMonitor.cs ├── LDasm.cs ├── Monitor.cs ├── MonitorAttribute.cs ├── NativeAPI.cs ├── OriginalAttribute.cs └── Properties │ └── AssemblyInfo.cs ├── README.md ├── Test ├── MonitorTest.cs ├── Properties │ └── AssemblyInfo.cs ├── Samples.cs ├── Test.csproj └── test └── azure-pipelines.yml /.gitattributes: -------------------------------------------------------------------------------- 1 | ############################################################################### 2 | # Set default behavior to automatically normalize line endings. 3 | ############################################################################### 4 | * text=auto 5 | 6 | ############################################################################### 7 | # Set default behavior for command prompt diff. 8 | # 9 | # This is need for earlier builds of msysgit that does not have it on by 10 | # default for csharp files. 11 | # Note: This is only used by command line 12 | ############################################################################### 13 | #*.cs diff=csharp 14 | 15 | ############################################################################### 16 | # Set the merge driver for project and solution files 17 | # 18 | # Merging from the command prompt will add diff markers to the files if there 19 | # are conflicts (Merging from VS is not affected by the settings below, in VS 20 | # the diff markers are never inserted). Diff markers may cause the following 21 | # file extensions to fail to load in VS. An alternative would be to treat 22 | # these files as binary and thus will always conflict and require user 23 | # intervention with every merge. To do so, just uncomment the entries below 24 | ############################################################################### 25 | #*.sln merge=binary 26 | #*.csproj merge=binary 27 | #*.vbproj merge=binary 28 | #*.vcxproj merge=binary 29 | #*.vcproj merge=binary 30 | #*.dbproj merge=binary 31 | #*.fsproj merge=binary 32 | #*.lsproj merge=binary 33 | #*.wixproj merge=binary 34 | #*.modelproj merge=binary 35 | #*.sqlproj merge=binary 36 | #*.wwaproj merge=binary 37 | 38 | ############################################################################### 39 | # behavior for image files 40 | # 41 | # image files are treated as binary by default. 42 | ############################################################################### 43 | #*.jpg binary 44 | #*.png binary 45 | #*.gif binary 46 | 47 | ############################################################################### 48 | # diff behavior for common document formats 49 | # 50 | # Convert binary document formats to text before diffing them. This feature 51 | # is only available from the command line. Turn it on by uncommenting the 52 | # entries below. 53 | ############################################################################### 54 | #*.doc diff=astextplain 55 | #*.DOC diff=astextplain 56 | #*.docx diff=astextplain 57 | #*.DOCX diff=astextplain 58 | #*.dot diff=astextplain 59 | #*.DOT diff=astextplain 60 | #*.pdf diff=astextplain 61 | #*.PDF diff=astextplain 62 | #*.rtf diff=astextplain 63 | #*.RTF diff=astextplain 64 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | 4 | # User-specific files 5 | *.suo 6 | *.user 7 | *.userosscache 8 | *.sln.docstates 9 | 10 | # User-specific files (MonoDevelop/Xamarin Studio) 11 | *.userprefs 12 | 13 | # Build results 14 | [Dd]ebug/ 15 | [Dd]ebugPublic/ 16 | [Rr]elease/ 17 | [Rr]eleases/ 18 | x64/ 19 | x86/ 20 | build/ 21 | bld/ 22 | [Bb]in/ 23 | [Oo]bj/ 24 | 25 | # Visual Studio 2015 cache/options directory 26 | .vs/ 27 | 28 | # MSTest test Results 29 | [Tt]est[Rr]esult*/ 30 | [Bb]uild[Ll]og.* 31 | 32 | # NUNIT 33 | *.VisualState.xml 34 | TestResult.xml 35 | 36 | # Build Results of an ATL Project 37 | [Dd]ebugPS/ 38 | [Rr]eleasePS/ 39 | dlldata.c 40 | 41 | # DNX 42 | project.lock.json 43 | artifacts/ 44 | 45 | *_i.c 46 | *_p.c 47 | *_i.h 48 | *.ilk 49 | *.meta 50 | *.obj 51 | *.pch 52 | *.pdb 53 | *.pgc 54 | *.pgd 55 | *.rsp 56 | *.sbr 57 | *.tlb 58 | *.tli 59 | *.tlh 60 | *.tmp 61 | *.tmp_proj 62 | *.log 63 | *.vspscc 64 | *.vssscc 65 | .builds 66 | *.pidb 67 | *.svclog 68 | *.scc 69 | 70 | # Chutzpah Test files 71 | _Chutzpah* 72 | 73 | # Visual C++ cache files 74 | ipch/ 75 | *.aps 76 | *.ncb 77 | *.opensdf 78 | *.sdf 79 | *.cachefile 80 | 81 | # Visual Studio profiler 82 | *.psess 83 | *.vsp 84 | *.vspx 85 | 86 | # TFS 2012 Local Workspace 87 | $tf/ 88 | 89 | # Guidance Automation Toolkit 90 | *.gpState 91 | 92 | # ReSharper is a .NET coding add-in 93 | _ReSharper*/ 94 | *.[Rr]e[Ss]harper 95 | *.DotSettings.user 96 | 97 | # JustCode is a .NET coding add-in 98 | .JustCode 99 | 100 | # TeamCity is a build add-in 101 | _TeamCity* 102 | 103 | # DotCover is a Code Coverage Tool 104 | *.dotCover 105 | 106 | # NCrunch 107 | _NCrunch_* 108 | .*crunch*.local.xml 109 | 110 | # MightyMoose 111 | *.mm.* 112 | AutoTest.Net/ 113 | 114 | # Web workbench (sass) 115 | .sass-cache/ 116 | 117 | # Installshield output folder 118 | [Ee]xpress/ 119 | 120 | # DocProject is a documentation generator add-in 121 | DocProject/buildhelp/ 122 | DocProject/Help/*.HxT 123 | DocProject/Help/*.HxC 124 | DocProject/Help/*.hhc 125 | DocProject/Help/*.hhk 126 | DocProject/Help/*.hhp 127 | DocProject/Help/Html2 128 | DocProject/Help/html 129 | 130 | # Click-Once directory 131 | publish/ 132 | 133 | # Publish Web Output 134 | *.[Pp]ublish.xml 135 | *.azurePubxml 136 | ## TODO: Comment the next line if you want to checkin your 137 | ## web deploy settings but do note that will include unencrypted 138 | ## passwords 139 | #*.pubxml 140 | 141 | *.publishproj 142 | 143 | # NuGet Packages 144 | *.nupkg 145 | # The packages folder can be ignored because of Package Restore 146 | **/packages/* 147 | # except build/, which is used as an MSBuild target. 148 | !**/packages/build/ 149 | # Uncomment if necessary however generally it will be regenerated when needed 150 | #!**/packages/repositories.config 151 | 152 | # Windows Azure Build Output 153 | csx/ 154 | *.build.csdef 155 | 156 | # Windows Store app package directory 157 | AppPackages/ 158 | 159 | # Visual Studio cache files 160 | # files ending in .cache can be ignored 161 | *.[Cc]ache 162 | # but keep track of directories ending in .cache 163 | !*.[Cc]ache/ 164 | 165 | # Others 166 | ClientBin/ 167 | [Ss]tyle[Cc]op.* 168 | ~$* 169 | *~ 170 | *.dbmdl 171 | *.dbproj.schemaview 172 | *.pfx 173 | *.publishsettings 174 | node_modules/ 175 | orleans.codegen.cs 176 | 177 | # RIA/Silverlight projects 178 | Generated_Code/ 179 | 180 | # Backup & report files from converting an old project file 181 | # to a newer Visual Studio version. Backup files are not needed, 182 | # because we have git ;-) 183 | _UpgradeReport_Files/ 184 | Backup*/ 185 | UpgradeLog*.XML 186 | UpgradeLog*.htm 187 | 188 | # SQL Server files 189 | *.mdf 190 | *.ldf 191 | 192 | # Business Intelligence projects 193 | *.rdl.data 194 | *.bim.layout 195 | *.bim_*.settings 196 | 197 | # Microsoft Fakes 198 | FakesAssemblies/ 199 | 200 | # Node.js Tools for Visual Studio 201 | .ntvs_analysis.dat 202 | 203 | # Visual Studio 6 build log 204 | *.plg 205 | 206 | # Visual Studio 6 workspace options file 207 | *.opt 208 | 209 | # LightSwitch generated files 210 | GeneratedArtifacts/ 211 | _Pvt_Extensions/ 212 | ModelManifest.xml 213 | -------------------------------------------------------------------------------- /DotNetDetour.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 14 4 | VisualStudioVersion = 14.0.24720.0 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DotNetDetour", "DotNetDetour\DotNetDetour.csproj", "{900B35A0-C116-46E2-875A-9BAB0C6D5A55}" 7 | EndProject 8 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Test", "Test\Test.csproj", "{9E079A1B-8A31-4F74-A027-1DD2848754D4}" 9 | EndProject 10 | Global 11 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 12 | Debug|Any CPU = Debug|Any CPU 13 | Debug|x64 = Debug|x64 14 | Release|Any CPU = Release|Any CPU 15 | Release|x64 = Release|x64 16 | EndGlobalSection 17 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 18 | {900B35A0-C116-46E2-875A-9BAB0C6D5A55}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 19 | {900B35A0-C116-46E2-875A-9BAB0C6D5A55}.Debug|Any CPU.Build.0 = Debug|Any CPU 20 | {900B35A0-C116-46E2-875A-9BAB0C6D5A55}.Debug|x64.ActiveCfg = Debug|Any CPU 21 | {900B35A0-C116-46E2-875A-9BAB0C6D5A55}.Debug|x64.Build.0 = Debug|Any CPU 22 | {900B35A0-C116-46E2-875A-9BAB0C6D5A55}.Release|Any CPU.ActiveCfg = Release|Any CPU 23 | {900B35A0-C116-46E2-875A-9BAB0C6D5A55}.Release|Any CPU.Build.0 = Release|Any CPU 24 | {900B35A0-C116-46E2-875A-9BAB0C6D5A55}.Release|x64.ActiveCfg = Release|Any CPU 25 | {900B35A0-C116-46E2-875A-9BAB0C6D5A55}.Release|x64.Build.0 = Release|Any CPU 26 | {9E079A1B-8A31-4F74-A027-1DD2848754D4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 27 | {9E079A1B-8A31-4F74-A027-1DD2848754D4}.Debug|Any CPU.Build.0 = Debug|Any CPU 28 | {9E079A1B-8A31-4F74-A027-1DD2848754D4}.Debug|x64.ActiveCfg = Debug|x64 29 | {9E079A1B-8A31-4F74-A027-1DD2848754D4}.Debug|x64.Build.0 = Debug|x64 30 | {9E079A1B-8A31-4F74-A027-1DD2848754D4}.Release|Any CPU.ActiveCfg = Release|Any CPU 31 | {9E079A1B-8A31-4F74-A027-1DD2848754D4}.Release|Any CPU.Build.0 = Release|Any CPU 32 | {9E079A1B-8A31-4F74-A027-1DD2848754D4}.Release|x64.ActiveCfg = Release|x64 33 | {9E079A1B-8A31-4F74-A027-1DD2848754D4}.Release|x64.Build.0 = Release|x64 34 | EndGlobalSection 35 | GlobalSection(SolutionProperties) = preSolution 36 | HideSolutionNode = FALSE 37 | EndGlobalSection 38 | EndGlobal 39 | -------------------------------------------------------------------------------- /DotNetDetour/AssemblyUtil.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | using System.Reflection; 5 | using System.IO; 6 | using System.Linq; 7 | using System.Runtime.Serialization.Formatters.Binary; 8 | using System.Diagnostics; 9 | 10 | namespace DotNetDetour 11 | { 12 | public static class AssemblyUtil 13 | { 14 | public static T CreateInstance(string type) 15 | { 16 | return CreateInstance(type, new object[0]); 17 | } 18 | 19 | /// 20 | /// 在当前的程序集中反射创建实例 21 | /// 22 | /// 23 | /// 24 | /// 25 | /// 26 | public static T CreateInstance(string type, object[] parameters) 27 | { 28 | Type instanceType = null; 29 | var result = default(T); 30 | 31 | instanceType = Type.GetType(type, false,true); 32 | if (instanceType == null) 33 | return default(T); 34 | object instance = Activator.CreateInstance(instanceType, parameters); 35 | result = (T)instance; 36 | return result; 37 | } 38 | 39 | /// 40 | /// 在给定的程序集中反射创建实例 41 | /// 42 | /// 43 | /// 44 | /// 45 | /// 46 | /// 47 | 48 | public static T CreateInstance(string assembleName, string type) 49 | { 50 | Type instanceType = null; 51 | var result = default(T); 52 | var asms = AppDomain.CurrentDomain.GetAssemblies(); 53 | foreach (var assem in asms) 54 | { 55 | if (string.Equals(assem.FullName, assembleName, StringComparison.CurrentCultureIgnoreCase)) 56 | { 57 | var types = assem.GetTypes(); 58 | foreach (var t in types) 59 | { 60 | if (string.Equals(t.ToString(), type, StringComparison.CurrentCultureIgnoreCase)) 61 | { 62 | instanceType = t; 63 | break; 64 | } 65 | } 66 | break; 67 | } 68 | } 69 | if (instanceType == null) 70 | return default(T); 71 | object instance = Activator.CreateInstance(instanceType, new object[0]); 72 | result = (T)instance; 73 | return result; 74 | } 75 | 76 | /// 77 | /// 在给定的程序集中反射创建实例,并且传入构造函数的参数 78 | /// 79 | /// 80 | /// 81 | /// 82 | /// 83 | /// 84 | public static T CreateInstance(string assembleName, string type, object[] parameters) 85 | { 86 | Type instanceType = null; 87 | var result = default(T); 88 | var asms = AppDomain.CurrentDomain.GetAssemblies(); 89 | foreach (var assem in asms) 90 | { 91 | if (string.Equals(assem.FullName, assembleName, StringComparison.CurrentCultureIgnoreCase)) 92 | { 93 | var types = assem.GetTypes(); 94 | foreach (var t in types) 95 | { 96 | if (string.Equals(t.ToString(), type, StringComparison.CurrentCultureIgnoreCase)) 97 | { 98 | instanceType = t; 99 | break; 100 | } 101 | } 102 | break; 103 | } 104 | } 105 | if (instanceType == null) 106 | return default(T); 107 | object instance = Activator.CreateInstance(instanceType, parameters); 108 | result = (T)instance; 109 | return result; 110 | } 111 | 112 | public static IEnumerable GetImplementTypes(this Assembly assembly) 113 | { 114 | return assembly.GetExportedTypes().Where(t => 115 | t.IsSubclassOf(typeof(TBaseType)) && t.IsClass && !t.IsAbstract); 116 | } 117 | 118 | public static IEnumerable GetImplementedObjectsByInterface(this Assembly assembly) 119 | where TBaseInterface : class 120 | { 121 | return GetImplementedObjectsByInterface(assembly, typeof(TBaseInterface)); 122 | } 123 | 124 | public static IEnumerable GetImplementedObjectsByInterface(this Assembly assembly, Type targetType) 125 | where TBaseInterface : class 126 | { 127 | Type[] arrType = assembly.GetTypes(); 128 | 129 | var result = new List(); 130 | 131 | for (int i = 0; i < arrType.Length; i++) 132 | { 133 | var currentImplementType = arrType[i]; 134 | 135 | if (currentImplementType.IsAbstract) 136 | continue; 137 | 138 | if (!targetType.IsAssignableFrom(currentImplementType)) 139 | continue; 140 | 141 | result.Add((TBaseInterface)Activator.CreateInstance(currentImplementType)); 142 | } 143 | 144 | return result; 145 | } 146 | 147 | public static T BinaryClone(this T target) 148 | { 149 | BinaryFormatter formatter = new BinaryFormatter(); 150 | using (MemoryStream ms = new MemoryStream()) 151 | { 152 | formatter.Serialize(ms, target); 153 | ms.Position = 0; 154 | return (T)formatter.Deserialize(ms); 155 | } 156 | } 157 | 158 | private static object[] m_EmptyObjectArray = new object[] { }; 159 | public static T CopyPropertiesTo(this T source, T target) 160 | { 161 | PropertyInfo[] properties = source.GetType().GetProperties(BindingFlags.Instance | BindingFlags.Public | BindingFlags.GetProperty); 162 | Dictionary sourcePropertiesDict = properties.ToDictionary(p => p.Name); 163 | 164 | PropertyInfo[] targetProperties = target.GetType().GetProperties(BindingFlags.Instance | BindingFlags.Public | BindingFlags.GetProperty); 165 | for (int i = 0; i < targetProperties.Length; i++) 166 | { 167 | var p = targetProperties[i]; 168 | PropertyInfo sourceProperty; 169 | 170 | if (sourcePropertiesDict.TryGetValue(p.Name, out sourceProperty)) 171 | { 172 | if (sourceProperty.PropertyType != p.PropertyType) 173 | continue; 174 | 175 | if (!sourceProperty.PropertyType.IsSerializable) 176 | continue; 177 | 178 | p.SetValue(target, sourceProperty.GetValue(source, m_EmptyObjectArray), m_EmptyObjectArray); 179 | } 180 | } 181 | 182 | return target; 183 | } 184 | 185 | public static IEnumerable GetAssembliesFromString(string assemblyDef) 186 | { 187 | return GetAssembliesFromStrings(assemblyDef.Split(new char[] { ',', ';' }, StringSplitOptions.RemoveEmptyEntries)); 188 | } 189 | 190 | public static IEnumerable GetAssembliesFromStrings(string[] assemblies) 191 | { 192 | List result = new List(assemblies.Length); 193 | 194 | foreach (var a in assemblies) 195 | { 196 | result.Add(Assembly.Load(a)); 197 | } 198 | 199 | return result; 200 | } 201 | 202 | public static string GetAssembleVer(string filePath) 203 | { 204 | FileVersionInfo fvi = FileVersionInfo.GetVersionInfo(filePath); 205 | string versionStr = string.Format(" {0}.{1}.{2}.{3}", fvi.ProductMajorPart, fvi.ProductMinorPart, fvi.ProductBuildPart, fvi.ProductPrivatePart); 206 | return versionStr; 207 | } 208 | } 209 | } 210 | -------------------------------------------------------------------------------- /DotNetDetour/DetourFactory.cs: -------------------------------------------------------------------------------- 1 | using DotNetDetour.DetourWays; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Reflection; 6 | using System.Text; 7 | using System.Threading.Tasks; 8 | 9 | namespace DotNetDetour 10 | { 11 | public class DetourFactory 12 | { 13 | public static IDetour CreateDetourEngine() 14 | { 15 | if (IntPtr.Size == 4) 16 | { 17 | return new NativeDetourFor32Bit(); 18 | } 19 | else if (IntPtr.Size == 8) 20 | { 21 | return new NativeDetourFor64Bit(); 22 | } 23 | else 24 | { 25 | throw new NotImplementedException(); 26 | } 27 | } 28 | } 29 | } 30 | 31 | -------------------------------------------------------------------------------- /DotNetDetour/DetourWays/MethodTableDetour.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Reflection; 5 | using System.Reflection.Emit; 6 | using System.Runtime.CompilerServices; 7 | using System.Text; 8 | using System.Threading.Tasks; 9 | 10 | namespace DotNetDetour.DetourWays 11 | { 12 | /// 13 | /// 方法表hook,通过修改jitted code指针实现 14 | /// 15 | public class MethodTableDetour : IDetour 16 | { 17 | object oldPointer; 18 | IntPtr destAdr; 19 | IntPtr srcAdr; 20 | 21 | public object CallOriginalMethod(object o = null, params object[] parameters) 22 | { 23 | throw new NotImplementedException(); 24 | } 25 | 26 | public void Patch(MethodInfo src, MethodInfo dest) 27 | { 28 | if (!MethodSignaturesEqual(src, dest)) 29 | { 30 | throw new ArgumentException("The method signatures are not the same.", "source"); 31 | } 32 | destAdr = GetMethodAddress(dest); 33 | srcAdr = GetMethodAddress(src); 34 | unsafe 35 | { 36 | if (IntPtr.Size == 8) 37 | { 38 | ulong* d = (ulong*)destAdr.ToPointer(); 39 | oldPointer = *d; 40 | *d = *((ulong*)srcAdr.ToPointer()); 41 | } 42 | else 43 | { 44 | uint* d = (uint*)destAdr.ToPointer(); 45 | oldPointer = *d; 46 | *d = *((uint*)srcAdr.ToPointer()); 47 | } 48 | } 49 | } 50 | 51 | private void Recover() 52 | { 53 | unsafe 54 | { 55 | if (IntPtr.Size == 8) 56 | { 57 | ulong* d = (ulong*)destAdr.ToPointer(); 58 | *d = (ulong)oldPointer; 59 | } 60 | else 61 | { 62 | uint* d = (uint*)destAdr.ToPointer(); 63 | *d = (uint)oldPointer; 64 | } 65 | } 66 | } 67 | 68 | private IntPtr GetMethodAddress(MethodBase method) 69 | { 70 | if ((method is DynamicMethod)) 71 | { 72 | return GetDynamicMethodAddress(method); 73 | } 74 | 75 | // Prepare the method so it gets jited 76 | RuntimeHelpers.PrepareMethod(method.MethodHandle); 77 | 78 | // If 3.5 sp1 or greater than we have a different layout in memory. 79 | if (IsNet20Sp2OrGreater()) 80 | { 81 | return GetMethodAddress20SP2(method); 82 | } 83 | 84 | unsafe 85 | { 86 | // Skip these 87 | const int skip = 10; 88 | 89 | // Read the method index. 90 | UInt64* location = (UInt64*)(method.MethodHandle.Value.ToPointer()); 91 | int index = (int)(((*location) >> 32) & 0xFF); 92 | 93 | if (IntPtr.Size == 8) 94 | { 95 | // Get the method table 96 | ulong* classStart = (ulong*)method.DeclaringType.TypeHandle.Value.ToPointer(); 97 | ulong* address = classStart + index + skip; 98 | return new IntPtr(address); 99 | } 100 | else 101 | { 102 | // Get the method table 103 | uint* classStart = (uint*)method.DeclaringType.TypeHandle.Value.ToPointer(); 104 | uint* address = classStart + index + skip; 105 | return new IntPtr(address); 106 | } 107 | } 108 | } 109 | 110 | private IntPtr GetDynamicMethodAddress(MethodBase method) 111 | { 112 | unsafe 113 | { 114 | byte* ptr = (byte*)GetDynamicMethodRuntimeHandle(method).ToPointer(); 115 | if (IsNet20Sp2OrGreater()) 116 | { 117 | if (IntPtr.Size == 8) 118 | { 119 | ulong* address = (ulong*)ptr; 120 | address = (ulong*)*(address + 5); 121 | return new IntPtr(address + 12); 122 | } 123 | else 124 | { 125 | uint* address = (uint*)ptr; 126 | address = (uint*)*(address + 5); 127 | return new IntPtr(address + 12); 128 | } 129 | } 130 | else 131 | { 132 | if (IntPtr.Size == 8) 133 | { 134 | ulong* address = (ulong*)ptr; 135 | address += 6; 136 | return new IntPtr(address); 137 | } 138 | else 139 | { 140 | uint* address = (uint*)ptr; 141 | address += 6; 142 | return new IntPtr(address); 143 | } 144 | } 145 | } 146 | } 147 | 148 | private IntPtr GetDynamicMethodRuntimeHandle(MethodBase method) 149 | { 150 | if (method is DynamicMethod) 151 | { 152 | FieldInfo fieldInfo = typeof(DynamicMethod).GetField("m_method", BindingFlags.NonPublic | BindingFlags.Instance); 153 | return ((RuntimeMethodHandle)fieldInfo.GetValue(method)).Value; 154 | } 155 | return method.MethodHandle.Value; 156 | } 157 | 158 | private static IntPtr GetMethodAddress20SP2(MethodBase method) 159 | { 160 | unsafe 161 | { 162 | return new IntPtr(((int*)method.MethodHandle.Value.ToPointer() + 2)); 163 | } 164 | } 165 | 166 | private bool MethodSignaturesEqual(MethodBase x, MethodBase y) 167 | { 168 | if (x.CallingConvention != y.CallingConvention) 169 | { 170 | return false; 171 | } 172 | Type returnX = GetMethodReturnType(x), returnY = GetMethodReturnType(y); 173 | if (returnX != returnY) 174 | { 175 | return false; 176 | } 177 | ParameterInfo[] xParams = x.GetParameters(), yParams = y.GetParameters(); 178 | if (xParams.Length != yParams.Length) 179 | { 180 | return false; 181 | } 182 | for (int i = 0; i < xParams.Length; i++) 183 | { 184 | if (xParams[i].ParameterType != yParams[i].ParameterType) 185 | { 186 | return false; 187 | } 188 | } 189 | return true; 190 | } 191 | 192 | private Type GetMethodReturnType(MethodBase method) 193 | { 194 | MethodInfo methodInfo = method as MethodInfo; 195 | if (methodInfo == null) 196 | { 197 | // Constructor info. 198 | throw new ArgumentException("Unsupported MethodBase : " + method.GetType().Name, "method"); 199 | } 200 | return methodInfo.ReturnType; 201 | } 202 | 203 | private bool IsNet20Sp2OrGreater() 204 | { 205 | return Environment.Version.Major > 2; 206 | } 207 | 208 | 209 | } 210 | } 211 | -------------------------------------------------------------------------------- /DotNetDetour/DetourWays/NativeDetourFor32Bit.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Reflection; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | using System.Runtime.CompilerServices; 8 | using System.Runtime.InteropServices; 9 | using System.Reflection.Emit; 10 | using System.Threading; 11 | using System.Linq.Expressions; 12 | using System.IO; 13 | using System.Diagnostics; 14 | 15 | namespace DotNetDetour.DetourWays 16 | { 17 | /// 18 | /// inline hook,通过修改函数的前5字节指令为jmp target_addr实现 19 | /// 20 | public unsafe class NativeDetourFor32Bit : IDetour 21 | { 22 | //protected byte[] originalInstrs = new byte[5]; 23 | protected byte[] newInstrs = { 0xE9, 0x90, 0x90, 0x90, 0x90 }; //jmp target 24 | protected byte* rawMethodPtr; 25 | 26 | public NativeDetourFor32Bit() 27 | { 28 | } 29 | 30 | public virtual void Patch(MethodBase rawMethod/*要hook的目标函数*/, 31 | MethodBase hookMethod/*用户定义的函数,可以调用原始占位函数来实现对原函数的调用*/, 32 | MethodBase originalMethod/*原始占位函数*/) 33 | { 34 | //确保jit过了 35 | var typeHandles = rawMethod.DeclaringType.GetGenericArguments().Select(t => t.TypeHandle).ToArray(); 36 | RuntimeHelpers.PrepareMethod(rawMethod.MethodHandle, typeHandles); 37 | 38 | rawMethodPtr = (byte*)rawMethod.MethodHandle.GetFunctionPointer().ToPointer(); 39 | 40 | var hookMethodPtr = (byte*)hookMethod.MethodHandle.GetFunctionPointer().ToPointer(); 41 | //生成跳转指令,使用相对地址,用于跳转到用户定义函数 42 | fixed (byte* newInstrPtr = newInstrs) 43 | { 44 | *(uint*)(newInstrPtr + 1) = (uint)hookMethodPtr - (uint)rawMethodPtr - 5; 45 | } 46 | 47 | 48 | //将对占位函数的调用指向原函数,实现调用占位函数即调用原始函数的功能 49 | if (originalMethod != null) 50 | { 51 | MakePlacholderMethodCallPointsToRawMethod(originalMethod); 52 | } 53 | 54 | 55 | //并且将对原函数的调用指向跳转指令,以此实现将对原始目标函数的调用跳转到用户定义函数执行的目的 56 | Patch(); 57 | 58 | Debug.WriteLine("Patch: Point=" + rawMethod.MethodHandle.GetFunctionPointer().ToInt64() + " Method=" + rawMethod + " Type=" + rawMethod.ReflectedType.FullName); 59 | } 60 | 61 | protected virtual void Patch() 62 | { 63 | uint oldProtect; 64 | //系统方法没有写权限,需要修改页属性 65 | NativeAPI.VirtualProtect((IntPtr)rawMethodPtr, 5, Protection.PAGE_EXECUTE_READWRITE, out oldProtect); 66 | for (int i = 0; i < newInstrs.Length; i++) 67 | { 68 | *(rawMethodPtr + i) = newInstrs[i]; 69 | } 70 | } 71 | 72 | /// 73 | /// 将对originalMethod的调用指向原函数 74 | /// 75 | /// 76 | protected virtual void MakePlacholderMethodCallPointsToRawMethod(MethodBase originalMethod) 77 | { 78 | uint oldProtect; 79 | var needSize = LDasm.SizeofMin5Byte(rawMethodPtr); 80 | var total_length = (int)needSize + 5; 81 | byte[] code = new byte[total_length]; 82 | IntPtr ptr = Marshal.AllocHGlobal(total_length); 83 | //code[0] = 0xcc;//调试用 84 | for (int i = 0; i < needSize; i++) 85 | { 86 | code[i] = rawMethodPtr[i]; 87 | } 88 | code[needSize] = 0xE9; 89 | fixed (byte* p = &code[needSize + 1]) 90 | { 91 | *((uint*)p) = (uint)rawMethodPtr - (uint)ptr - 5; 92 | } 93 | Marshal.Copy(code, 0, ptr, total_length); 94 | NativeAPI.VirtualProtect(ptr, (uint)total_length, Protection.PAGE_EXECUTE_READWRITE, out oldProtect); 95 | RuntimeHelpers.PrepareMethod(originalMethod.MethodHandle); 96 | *((uint*)originalMethod.MethodHandle.Value.ToPointer() + 2) = (uint)ptr; 97 | } 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /DotNetDetour/DetourWays/NativeDetourFor64Bit.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Linq; 5 | using System.Reflection; 6 | using System.Runtime.CompilerServices; 7 | using System.Runtime.InteropServices; 8 | using System.Text; 9 | using System.Threading.Tasks; 10 | 11 | namespace DotNetDetour.DetourWays 12 | { 13 | public unsafe class NativeDetourFor64Bit : NativeDetourFor32Bit 14 | { 15 | byte[] jmp_inst = 16 | { 17 | 0x50, //push rax 18 | 0x48,0xB8,0x90,0x90,0x90,0x90,0x90,0x90,0x90,0x90, //mov rax,target_addr 19 | 0x50, //push rax 20 | 0x48,0x8B,0x44,0x24,0x08, //mov rax,qword ptr ss:[rsp+8] 21 | 0xC2,0x08,0x00 //ret 8 22 | }; 23 | 24 | protected override void MakePlacholderMethodCallPointsToRawMethod(MethodBase method) 25 | { 26 | uint oldProtect; 27 | var needSize = LDasm.SizeofMin5Byte(rawMethodPtr); 28 | byte[] src_instr = new byte[needSize]; 29 | for (int i = 0; i < needSize; i++) 30 | { 31 | src_instr[i] = rawMethodPtr[i]; 32 | } 33 | fixed (byte* p = &jmp_inst[3]) 34 | { 35 | *((ulong*)p) = (ulong)(rawMethodPtr + needSize); 36 | } 37 | var totalLength = src_instr.Length + jmp_inst.Length; 38 | IntPtr ptr = Marshal.AllocHGlobal(totalLength); 39 | Marshal.Copy(src_instr, 0, ptr, src_instr.Length); 40 | Marshal.Copy(jmp_inst, 0, ptr + src_instr.Length, jmp_inst.Length); 41 | NativeAPI.VirtualProtect(ptr, (uint)totalLength, Protection.PAGE_EXECUTE_READWRITE, out oldProtect); 42 | RuntimeHelpers.PrepareMethod(method.MethodHandle); 43 | *((ulong*)((uint*)method.MethodHandle.Value.ToPointer() + 2)) = (ulong)ptr; 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /DotNetDetour/DotNetDetour.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | Debug 6 | AnyCPU 7 | {900B35A0-C116-46E2-875A-9BAB0C6D5A55} 8 | Library 9 | Properties 10 | DotNetDetour 11 | DotNetDetour 12 | v4.5 13 | 512 14 | 15 | 16 | 17 | true 18 | full 19 | false 20 | bin\Debug\ 21 | DEBUG;TRACE 22 | prompt 23 | 4 24 | true 25 | 26 | 27 | pdbonly 28 | true 29 | bin\Release\ 30 | TRACE 31 | prompt 32 | 4 33 | true 34 | AnyCPU 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 68 | -------------------------------------------------------------------------------- /DotNetDetour/DotNetDetour.nuspec: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | kissstudio.DotNetDetour 5 | 1.0.2.0 6 | kissstudio 7 | kissstudio 8 | false 9 | dotnet hook库 10 | Copyright 2016 11 | 12 | -------------------------------------------------------------------------------- /DotNetDetour/IDetour.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Reflection; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | 8 | namespace DotNetDetour 9 | { 10 | public interface IDetour 11 | { 12 | /// 13 | /// 14 | /// 15 | /// 要hook的目标函数 16 | /// 用户定义的函数,可以调用原始占位函数来实现对原函数的调用 17 | /// 原始占位函数 18 | void Patch(MethodBase rawMethod/*要hook的目标函数*/, 19 | MethodBase hookMethod/*用户定义的函数,可以调用原始占位函数来实现对原函数的调用*/, 20 | MethodBase originalMethod/*原始占位函数*/); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /DotNetDetour/IMethodMonitor.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Reflection; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | 8 | namespace DotNetDetour 9 | { 10 | [Obsolete("此接口已变更为IMethodHook")] 11 | public interface IMethodMonitor : IMethodHook { 12 | } 13 | 14 | public interface IMethodHook 15 | { 16 | } 17 | /// 18 | /// 此接口用于支持Hook初始化时进行回调 19 | /// 20 | public interface IMethodHookWithSet : IMethodHook { 21 | /// 22 | /// 当前类每成功进行一个Hook的初始化,就会传入被Hook的原始方法(判断方法名称来确定是初始化的哪个方法),这个方法可用于获取方法所在的类(如:非公开类)。 23 | /// 注意:此方法应当当做静态方法来进行编码。 24 | /// 25 | void HookMethod(MethodBase method); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /DotNetDetour/LDasm.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace DotNetDetour 4 | { 5 | /// 6 | /// 用于计算汇编指令长度,使用的是BlackBone的LDasm.c中的算法,我把他翻译成C#了 7 | /// 8 | public unsafe class LDasm 9 | { 10 | const int F_INVALID = 0x01; 11 | const int F_PREFIX = 0x02; 12 | const int F_REX = 0x04; 13 | const int F_MODRM = 0x08; 14 | const int F_SIB = 0x10; 15 | const int F_DISP = 0x20; 16 | const int F_IMM = 0x40; 17 | const int F_RELATIVE = 0x80; 18 | 19 | const int OP_NONE = 0x00; 20 | const int OP_INVALID = 0x80; 21 | 22 | const int OP_DATA_I8 = 0x01; 23 | const int OP_DATA_I16 = 0x02; 24 | const int OP_DATA_I16_I32 = 0x04; 25 | const int OP_DATA_I16_I32_I64 = 0x08; 26 | const int OP_EXTENDED = 0x10; 27 | const int OP_RELATIVE = 0x20; 28 | const int OP_MODRM = 0x40; 29 | const int OP_PREFIX = 0x80; 30 | 31 | struct ldasm_data 32 | { 33 | public byte flags; 34 | public byte rex; 35 | public byte modrm; 36 | public byte sib; 37 | public byte opcd_offset; 38 | public byte opcd_size; 39 | public byte disp_offset; 40 | public byte disp_size; 41 | public byte imm_offset; 42 | public byte imm_size; 43 | } 44 | 45 | static byte[] flags_table = 46 | { 47 | /* 00 */ OP_MODRM, 48 | /* 01 */ OP_MODRM, 49 | /* 02 */ OP_MODRM, 50 | /* 03 */ OP_MODRM, 51 | /* 04 */ OP_DATA_I8, 52 | /* 05 */ OP_DATA_I16_I32, 53 | /* 06 */ OP_NONE, 54 | /* 07 */ OP_NONE, 55 | /* 08 */ OP_MODRM, 56 | /* 09 */ OP_MODRM, 57 | /* 0A */ OP_MODRM, 58 | /* 0B */ OP_MODRM, 59 | /* 0C */ OP_DATA_I8, 60 | /* 0D */ OP_DATA_I16_I32, 61 | /* 0E */ OP_NONE, 62 | /* 0F */ OP_NONE, 63 | 64 | /* 10 */ OP_MODRM, 65 | /* 11 */ OP_MODRM, 66 | /* 12 */ OP_MODRM, 67 | /* 13 */ OP_MODRM, 68 | /* 14 */ OP_DATA_I8, 69 | /* 15 */ OP_DATA_I16_I32, 70 | /* 16 */ OP_NONE, 71 | /* 17 */ OP_NONE, 72 | /* 18 */ OP_MODRM, 73 | /* 19 */ OP_MODRM, 74 | /* 1A */ OP_MODRM, 75 | /* 1B */ OP_MODRM, 76 | /* 1C */ OP_DATA_I8, 77 | /* 1D */ OP_DATA_I16_I32, 78 | /* 1E */ OP_NONE, 79 | /* 1F */ OP_NONE, 80 | 81 | /* 20 */ OP_MODRM, 82 | /* 21 */ OP_MODRM, 83 | /* 22 */ OP_MODRM, 84 | /* 23 */ OP_MODRM, 85 | /* 24 */ OP_DATA_I8, 86 | /* 25 */ OP_DATA_I16_I32, 87 | /* 26 */ OP_PREFIX, 88 | /* 27 */ OP_NONE, 89 | /* 28 */ OP_MODRM, 90 | /* 29 */ OP_MODRM, 91 | /* 2A */ OP_MODRM, 92 | /* 2B */ OP_MODRM, 93 | /* 2C */ OP_DATA_I8, 94 | /* 2D */ OP_DATA_I16_I32, 95 | /* 2E */ OP_PREFIX, 96 | /* 2F */ OP_NONE, 97 | 98 | /* 30 */ OP_MODRM, 99 | /* 31 */ OP_MODRM, 100 | /* 32 */ OP_MODRM, 101 | /* 33 */ OP_MODRM, 102 | /* 34 */ OP_DATA_I8, 103 | /* 35 */ OP_DATA_I16_I32, 104 | /* 36 */ OP_PREFIX, 105 | /* 37 */ OP_NONE, 106 | /* 38 */ OP_MODRM, 107 | /* 39 */ OP_MODRM, 108 | /* 3A */ OP_MODRM, 109 | /* 3B */ OP_MODRM, 110 | /* 3C */ OP_DATA_I8, 111 | /* 3D */ OP_DATA_I16_I32, 112 | /* 3E */ OP_PREFIX, 113 | /* 3F */ OP_NONE, 114 | 115 | /* 40 */ OP_NONE, 116 | /* 41 */ OP_NONE, 117 | /* 42 */ OP_NONE, 118 | /* 43 */ OP_NONE, 119 | /* 44 */ OP_NONE, 120 | /* 45 */ OP_NONE, 121 | /* 46 */ OP_NONE, 122 | /* 47 */ OP_NONE, 123 | /* 48 */ OP_NONE, 124 | /* 49 */ OP_NONE, 125 | /* 4A */ OP_NONE, 126 | /* 4B */ OP_NONE, 127 | /* 4C */ OP_NONE, 128 | /* 4D */ OP_NONE, 129 | /* 4E */ OP_NONE, 130 | /* 4F */ OP_NONE, 131 | 132 | /* 50 */ OP_NONE, 133 | /* 51 */ OP_NONE, 134 | /* 52 */ OP_NONE, 135 | /* 53 */ OP_NONE, 136 | /* 54 */ OP_NONE, 137 | /* 55 */ OP_NONE, 138 | /* 56 */ OP_NONE, 139 | /* 57 */ OP_NONE, 140 | /* 58 */ OP_NONE, 141 | /* 59 */ OP_NONE, 142 | /* 5A */ OP_NONE, 143 | /* 5B */ OP_NONE, 144 | /* 5C */ OP_NONE, 145 | /* 5D */ OP_NONE, 146 | /* 5E */ OP_NONE, 147 | /* 5F */ OP_NONE, 148 | /* 60 */ OP_NONE, 149 | 150 | /* 61 */ OP_NONE, 151 | /* 62 */ OP_MODRM, 152 | /* 63 */ OP_MODRM, 153 | /* 64 */ OP_PREFIX, 154 | /* 65 */ OP_PREFIX, 155 | /* 66 */ OP_PREFIX, 156 | /* 67 */ OP_PREFIX, 157 | /* 68 */ OP_DATA_I16_I32, 158 | /* 69 */ OP_MODRM | OP_DATA_I16_I32, 159 | /* 6A */ OP_DATA_I8, 160 | /* 6B */ OP_MODRM | OP_DATA_I8, 161 | /* 6C */ OP_NONE, 162 | /* 6D */ OP_NONE, 163 | /* 6E */ OP_NONE, 164 | /* 6F */ OP_NONE, 165 | 166 | /* 70 */ OP_RELATIVE | OP_DATA_I8, 167 | /* 71 */ OP_RELATIVE | OP_DATA_I8, 168 | /* 72 */ OP_RELATIVE | OP_DATA_I8, 169 | /* 73 */ OP_RELATIVE | OP_DATA_I8, 170 | /* 74 */ OP_RELATIVE | OP_DATA_I8, 171 | /* 75 */ OP_RELATIVE | OP_DATA_I8, 172 | /* 76 */ OP_RELATIVE | OP_DATA_I8, 173 | /* 77 */ OP_RELATIVE | OP_DATA_I8, 174 | /* 78 */ OP_RELATIVE | OP_DATA_I8, 175 | /* 79 */ OP_RELATIVE | OP_DATA_I8, 176 | /* 7A */ OP_RELATIVE | OP_DATA_I8, 177 | /* 7B */ OP_RELATIVE | OP_DATA_I8, 178 | /* 7C */ OP_RELATIVE | OP_DATA_I8, 179 | /* 7D */ OP_RELATIVE | OP_DATA_I8, 180 | /* 7E */ OP_RELATIVE | OP_DATA_I8, 181 | /* 7F */ OP_RELATIVE | OP_DATA_I8, 182 | 183 | /* 80 */ OP_MODRM | OP_DATA_I8, 184 | /* 81 */ OP_MODRM | OP_DATA_I16_I32, 185 | /* 82 */ OP_MODRM | OP_DATA_I8, 186 | /* 83 */ OP_MODRM | OP_DATA_I8, 187 | /* 84 */ OP_MODRM, 188 | /* 85 */ OP_MODRM, 189 | /* 86 */ OP_MODRM, 190 | /* 87 */ OP_MODRM, 191 | /* 88 */ OP_MODRM, 192 | /* 89 */ OP_MODRM, 193 | /* 8A */ OP_MODRM, 194 | /* 8B */ OP_MODRM, 195 | /* 8C */ OP_MODRM, 196 | /* 8D */ OP_MODRM, 197 | /* 8E */ OP_MODRM, 198 | /* 8F */ OP_MODRM, 199 | 200 | /* 90 */ OP_NONE, 201 | /* 91 */ OP_NONE, 202 | /* 92 */ OP_NONE, 203 | /* 93 */ OP_NONE, 204 | /* 94 */ OP_NONE, 205 | /* 95 */ OP_NONE, 206 | /* 96 */ OP_NONE, 207 | /* 97 */ OP_NONE, 208 | /* 98 */ OP_NONE, 209 | /* 99 */ OP_NONE, 210 | /* 9A */ OP_DATA_I16 | OP_DATA_I16_I32, 211 | /* 9B */ OP_NONE, 212 | /* 9C */ OP_NONE, 213 | /* 9D */ OP_NONE, 214 | /* 9E */ OP_NONE, 215 | /* 9F */ OP_NONE, 216 | 217 | /* A0 */ OP_DATA_I8, 218 | /* A1 */ OP_DATA_I16_I32_I64, 219 | /* A2 */ OP_DATA_I8, 220 | /* A3 */ OP_DATA_I16_I32_I64, 221 | /* A4 */ OP_NONE, 222 | /* A5 */ OP_NONE, 223 | /* A6 */ OP_NONE, 224 | /* A7 */ OP_NONE, 225 | /* A8 */ OP_DATA_I8, 226 | /* A9 */ OP_DATA_I16_I32, 227 | /* AA */ OP_NONE, 228 | /* AB */ OP_NONE, 229 | /* AC */ OP_NONE, 230 | /* AD */ OP_NONE, 231 | /* AE */ OP_NONE, 232 | /* AF */ OP_NONE, 233 | 234 | /* B0 */ OP_DATA_I8, 235 | /* B1 */ OP_DATA_I8, 236 | /* B2 */ OP_DATA_I8, 237 | /* B3 */ OP_DATA_I8, 238 | /* B4 */ OP_DATA_I8, 239 | /* B5 */ OP_DATA_I8, 240 | /* B6 */ OP_DATA_I8, 241 | /* B7 */ OP_DATA_I8, 242 | /* B8 */ OP_DATA_I16_I32_I64, 243 | /* B9 */ OP_DATA_I16_I32_I64, 244 | /* BA */ OP_DATA_I16_I32_I64, 245 | /* BB */ OP_DATA_I16_I32_I64, 246 | /* BC */ OP_DATA_I16_I32_I64, 247 | /* BD */ OP_DATA_I16_I32_I64, 248 | /* BE */ OP_DATA_I16_I32_I64, 249 | /* BF */ OP_DATA_I16_I32_I64, 250 | 251 | /* C0 */ OP_MODRM | OP_DATA_I8, 252 | /* C1 */ OP_MODRM | OP_DATA_I8, 253 | /* C2 */ OP_DATA_I16, 254 | /* C3 */ OP_NONE, 255 | /* C4 */ OP_MODRM, 256 | /* C5 */ OP_MODRM, 257 | /* C6 */ OP_MODRM | OP_DATA_I8, 258 | /* C7 */ OP_MODRM | OP_DATA_I16_I32, 259 | /* C8 */ OP_DATA_I8 | OP_DATA_I16, 260 | /* C9 */ OP_NONE, 261 | /* CA */ OP_DATA_I16, 262 | /* CB */ OP_NONE, 263 | /* CC */ OP_NONE, 264 | /* CD */ OP_DATA_I8, 265 | /* CE */ OP_NONE, 266 | /* CF */ OP_NONE, 267 | 268 | /* D0 */ OP_MODRM, 269 | /* D1 */ OP_MODRM, 270 | /* D2 */ OP_MODRM, 271 | /* D3 */ OP_MODRM, 272 | /* D4 */ OP_DATA_I8, 273 | /* D5 */ OP_DATA_I8, 274 | /* D6 */ OP_NONE, 275 | /* D7 */ OP_NONE, 276 | /* D8 */ OP_MODRM, 277 | /* D9 */ OP_MODRM, 278 | /* DA */ OP_MODRM, 279 | /* DB */ OP_MODRM, 280 | /* DC */ OP_MODRM, 281 | /* DD */ OP_MODRM, 282 | /* DE */ OP_MODRM, 283 | /* DF */ OP_MODRM, 284 | 285 | /* E0 */ OP_RELATIVE | OP_DATA_I8, 286 | /* E1 */ OP_RELATIVE | OP_DATA_I8, 287 | /* E2 */ OP_RELATIVE | OP_DATA_I8, 288 | /* E3 */ OP_RELATIVE | OP_DATA_I8, 289 | /* E4 */ OP_DATA_I8, 290 | /* E5 */ OP_DATA_I8, 291 | /* E6 */ OP_DATA_I8, 292 | /* E7 */ OP_DATA_I8, 293 | /* E8 */ OP_RELATIVE | OP_DATA_I16_I32, 294 | /* E9 */ OP_RELATIVE | OP_DATA_I16_I32, 295 | /* EA */ OP_DATA_I16 | OP_DATA_I16_I32, 296 | /* EB */ OP_RELATIVE | OP_DATA_I8, 297 | /* EC */ OP_NONE, 298 | /* ED */ OP_NONE, 299 | /* EE */ OP_NONE, 300 | /* EF */ OP_NONE, 301 | 302 | /* F0 */ OP_PREFIX, 303 | /* F1 */ OP_NONE, 304 | /* F2 */ OP_PREFIX, 305 | /* F3 */ OP_PREFIX, 306 | /* F4 */ OP_NONE, 307 | /* F5 */ OP_NONE, 308 | /* F6 */ OP_MODRM, 309 | /* F7 */ OP_MODRM, 310 | /* F8 */ OP_NONE, 311 | /* F9 */ OP_NONE, 312 | /* FA */ OP_NONE, 313 | /* FB */ OP_NONE, 314 | /* FC */ OP_NONE, 315 | /* FD */ OP_NONE, 316 | /* FE */ OP_MODRM, 317 | /* FF */ OP_MODRM 318 | }; 319 | 320 | static byte[] flags_table_ex = 321 | { 322 | /* 0F00 */ OP_MODRM, 323 | /* 0F01 */ OP_MODRM, 324 | /* 0F02 */ OP_MODRM, 325 | /* 0F03 */ OP_MODRM, 326 | /* 0F04 */ OP_INVALID, 327 | /* 0F05 */ OP_NONE, 328 | /* 0F06 */ OP_NONE, 329 | /* 0F07 */ OP_NONE, 330 | /* 0F08 */ OP_NONE, 331 | /* 0F09 */ OP_NONE, 332 | /* 0F0A */ OP_INVALID, 333 | /* 0F0B */ OP_NONE, 334 | /* 0F0C */ OP_INVALID, 335 | /* 0F0D */ OP_MODRM, 336 | /* 0F0E */ OP_INVALID, 337 | /* 0F0F */ OP_MODRM | OP_DATA_I8, //3Dnow 338 | 339 | /* 0F10 */ OP_MODRM, 340 | /* 0F11 */ OP_MODRM, 341 | /* 0F12 */ OP_MODRM, 342 | /* 0F13 */ OP_MODRM, 343 | /* 0F14 */ OP_MODRM, 344 | /* 0F15 */ OP_MODRM, 345 | /* 0F16 */ OP_MODRM, 346 | /* 0F17 */ OP_MODRM, 347 | /* 0F18 */ OP_MODRM, 348 | /* 0F19 */ OP_INVALID, 349 | /* 0F1A */ OP_INVALID, 350 | /* 0F1B */ OP_INVALID, 351 | /* 0F1C */ OP_INVALID, 352 | /* 0F1D */ OP_INVALID, 353 | /* 0F1E */ OP_INVALID, 354 | /* 0F1F */ OP_NONE, 355 | 356 | /* 0F20 */ OP_MODRM, 357 | /* 0F21 */ OP_MODRM, 358 | /* 0F22 */ OP_MODRM, 359 | /* 0F23 */ OP_MODRM, 360 | /* 0F24 */ OP_MODRM | OP_EXTENDED, //SSE5 361 | /* 0F25 */ OP_INVALID, 362 | /* 0F26 */ OP_MODRM, 363 | /* 0F27 */ OP_INVALID, 364 | /* 0F28 */ OP_MODRM, 365 | /* 0F29 */ OP_MODRM, 366 | /* 0F2A */ OP_MODRM, 367 | /* 0F2B */ OP_MODRM, 368 | /* 0F2C */ OP_MODRM, 369 | /* 0F2D */ OP_MODRM, 370 | /* 0F2E */ OP_MODRM, 371 | /* 0F2F */ OP_MODRM, 372 | 373 | /* 0F30 */ OP_NONE, 374 | /* 0F31 */ OP_NONE, 375 | /* 0F32 */ OP_NONE, 376 | /* 0F33 */ OP_NONE, 377 | /* 0F34 */ OP_NONE, 378 | /* 0F35 */ OP_NONE, 379 | /* 0F36 */ OP_INVALID, 380 | /* 0F37 */ OP_NONE, 381 | /* 0F38 */ OP_MODRM | OP_EXTENDED, 382 | /* 0F39 */ OP_INVALID, 383 | /* 0F3A */ OP_MODRM | OP_EXTENDED | OP_DATA_I8, 384 | /* 0F3B */ OP_INVALID, 385 | /* 0F3C */ OP_INVALID, 386 | /* 0F3D */ OP_INVALID, 387 | /* 0F3E */ OP_INVALID, 388 | /* 0F3F */ OP_INVALID, 389 | 390 | /* 0F40 */ OP_MODRM, 391 | /* 0F41 */ OP_MODRM, 392 | /* 0F42 */ OP_MODRM, 393 | /* 0F43 */ OP_MODRM, 394 | /* 0F44 */ OP_MODRM, 395 | /* 0F45 */ OP_MODRM, 396 | /* 0F46 */ OP_MODRM, 397 | /* 0F47 */ OP_MODRM, 398 | /* 0F48 */ OP_MODRM, 399 | /* 0F49 */ OP_MODRM, 400 | /* 0F4A */ OP_MODRM, 401 | /* 0F4B */ OP_MODRM, 402 | /* 0F4C */ OP_MODRM, 403 | /* 0F4D */ OP_MODRM, 404 | /* 0F4E */ OP_MODRM, 405 | /* 0F4F */ OP_MODRM, 406 | 407 | /* 0F50 */ OP_MODRM, 408 | /* 0F51 */ OP_MODRM, 409 | /* 0F52 */ OP_MODRM, 410 | /* 0F53 */ OP_MODRM, 411 | /* 0F54 */ OP_MODRM, 412 | /* 0F55 */ OP_MODRM, 413 | /* 0F56 */ OP_MODRM, 414 | /* 0F57 */ OP_MODRM, 415 | /* 0F58 */ OP_MODRM, 416 | /* 0F59 */ OP_MODRM, 417 | /* 0F5A */ OP_MODRM, 418 | /* 0F5B */ OP_MODRM, 419 | /* 0F5C */ OP_MODRM, 420 | /* 0F5D */ OP_MODRM, 421 | /* 0F5E */ OP_MODRM, 422 | /* 0F5F */ OP_MODRM, 423 | 424 | /* 0F60 */ OP_MODRM, 425 | /* 0F61 */ OP_MODRM, 426 | /* 0F62 */ OP_MODRM, 427 | /* 0F63 */ OP_MODRM, 428 | /* 0F64 */ OP_MODRM, 429 | /* 0F65 */ OP_MODRM, 430 | /* 0F66 */ OP_MODRM, 431 | /* 0F67 */ OP_MODRM, 432 | /* 0F68 */ OP_MODRM, 433 | /* 0F69 */ OP_MODRM, 434 | /* 0F6A */ OP_MODRM, 435 | /* 0F6B */ OP_MODRM, 436 | /* 0F6C */ OP_MODRM, 437 | /* 0F6D */ OP_MODRM, 438 | /* 0F6E */ OP_MODRM, 439 | /* 0F6F */ OP_MODRM, 440 | 441 | /* 0F70 */ OP_MODRM | OP_DATA_I8, 442 | /* 0F71 */ OP_MODRM | OP_DATA_I8, 443 | /* 0F72 */ OP_MODRM | OP_DATA_I8, 444 | /* 0F73 */ OP_MODRM | OP_DATA_I8, 445 | /* 0F74 */ OP_MODRM, 446 | /* 0F75 */ OP_MODRM, 447 | /* 0F76 */ OP_MODRM, 448 | /* 0F77 */ OP_NONE, 449 | /* 0F78 */ OP_MODRM, 450 | /* 0F79 */ OP_MODRM, 451 | /* 0F7A */ OP_INVALID, 452 | /* 0F7B */ OP_INVALID, 453 | /* 0F7C */ OP_MODRM, 454 | /* 0F7D */ OP_MODRM, 455 | /* 0F7E */ OP_MODRM, 456 | /* 0F7F */ OP_MODRM, 457 | 458 | /* 0F80 */ OP_RELATIVE | OP_DATA_I16_I32, 459 | /* 0F81 */ OP_RELATIVE | OP_DATA_I16_I32, 460 | /* 0F82 */ OP_RELATIVE | OP_DATA_I16_I32, 461 | /* 0F83 */ OP_RELATIVE | OP_DATA_I16_I32, 462 | /* 0F84 */ OP_RELATIVE | OP_DATA_I16_I32, 463 | /* 0F85 */ OP_RELATIVE | OP_DATA_I16_I32, 464 | /* 0F86 */ OP_RELATIVE | OP_DATA_I16_I32, 465 | /* 0F87 */ OP_RELATIVE | OP_DATA_I16_I32, 466 | /* 0F88 */ OP_RELATIVE | OP_DATA_I16_I32, 467 | /* 0F89 */ OP_RELATIVE | OP_DATA_I16_I32, 468 | /* 0F8A */ OP_RELATIVE | OP_DATA_I16_I32, 469 | /* 0F8B */ OP_RELATIVE | OP_DATA_I16_I32, 470 | /* 0F8C */ OP_RELATIVE | OP_DATA_I16_I32, 471 | /* 0F8D */ OP_RELATIVE | OP_DATA_I16_I32, 472 | /* 0F8E */ OP_RELATIVE | OP_DATA_I16_I32, 473 | /* 0F8F */ OP_RELATIVE | OP_DATA_I16_I32, 474 | 475 | /* 0F90 */ OP_MODRM, 476 | /* 0F91 */ OP_MODRM, 477 | /* 0F92 */ OP_MODRM, 478 | /* 0F93 */ OP_MODRM, 479 | /* 0F94 */ OP_MODRM, 480 | /* 0F95 */ OP_MODRM, 481 | /* 0F96 */ OP_MODRM, 482 | /* 0F97 */ OP_MODRM, 483 | /* 0F98 */ OP_MODRM, 484 | /* 0F99 */ OP_MODRM, 485 | /* 0F9A */ OP_MODRM, 486 | /* 0F9B */ OP_MODRM, 487 | /* 0F9C */ OP_MODRM, 488 | /* 0F9D */ OP_MODRM, 489 | /* 0F9E */ OP_MODRM, 490 | /* 0F9F */ OP_MODRM, 491 | 492 | /* 0FA0 */ OP_NONE, 493 | /* 0FA1 */ OP_NONE, 494 | /* 0FA2 */ OP_NONE, 495 | /* 0FA3 */ OP_MODRM, 496 | /* 0FA4 */ OP_MODRM | OP_DATA_I8, 497 | /* 0FA5 */ OP_MODRM, 498 | /* 0FA6 */ OP_INVALID, 499 | /* 0FA7 */ OP_INVALID, 500 | /* 0FA8 */ OP_NONE, 501 | /* 0FA9 */ OP_NONE, 502 | /* 0FAA */ OP_NONE, 503 | /* 0FAB */ OP_MODRM, 504 | /* 0FAC */ OP_MODRM | OP_DATA_I8, 505 | /* 0FAD */ OP_MODRM, 506 | /* 0FAE */ OP_MODRM, 507 | /* 0FAF */ OP_MODRM, 508 | 509 | /* 0FB0 */ OP_MODRM, 510 | /* 0FB1 */ OP_MODRM, 511 | /* 0FB2 */ OP_MODRM, 512 | /* 0FB3 */ OP_MODRM, 513 | /* 0FB4 */ OP_MODRM, 514 | /* 0FB5 */ OP_MODRM, 515 | /* 0FB6 */ OP_MODRM, 516 | /* 0FB7 */ OP_MODRM, 517 | /* 0FB8 */ OP_MODRM, 518 | /* 0FB9 */ OP_MODRM, 519 | /* 0FBA */ OP_MODRM | OP_DATA_I8, 520 | /* 0FBB */ OP_MODRM, 521 | /* 0FBC */ OP_MODRM, 522 | /* 0FBD */ OP_MODRM, 523 | /* 0FBE */ OP_MODRM, 524 | /* 0FBF */ OP_MODRM, 525 | 526 | /* 0FC0 */ OP_MODRM, 527 | /* 0FC1 */ OP_MODRM, 528 | /* 0FC2 */ OP_MODRM | OP_DATA_I8, 529 | /* 0FC3 */ OP_MODRM, 530 | /* 0FC4 */ OP_MODRM | OP_DATA_I8, 531 | /* 0FC5 */ OP_MODRM | OP_DATA_I8, 532 | /* 0FC6 */ OP_MODRM | OP_DATA_I8, 533 | /* 0FC7 */ OP_MODRM, 534 | /* 0FC8 */ OP_NONE, 535 | /* 0FC9 */ OP_NONE, 536 | /* 0FCA */ OP_NONE, 537 | /* 0FCB */ OP_NONE, 538 | /* 0FCC */ OP_NONE, 539 | /* 0FCD */ OP_NONE, 540 | /* 0FCE */ OP_NONE, 541 | /* 0FCF */ OP_NONE, 542 | 543 | /* 0FD0 */ OP_MODRM, 544 | /* 0FD1 */ OP_MODRM, 545 | /* 0FD2 */ OP_MODRM, 546 | /* 0FD3 */ OP_MODRM, 547 | /* 0FD4 */ OP_MODRM, 548 | /* 0FD5 */ OP_MODRM, 549 | /* 0FD6 */ OP_MODRM, 550 | /* 0FD7 */ OP_MODRM, 551 | /* 0FD8 */ OP_MODRM, 552 | /* 0FD9 */ OP_MODRM, 553 | /* 0FDA */ OP_MODRM, 554 | /* 0FDB */ OP_MODRM, 555 | /* 0FDC */ OP_MODRM, 556 | /* 0FDD */ OP_MODRM, 557 | /* 0FDE */ OP_MODRM, 558 | /* 0FDF */ OP_MODRM, 559 | 560 | /* 0FE0 */ OP_MODRM, 561 | /* 0FE1 */ OP_MODRM, 562 | /* 0FE2 */ OP_MODRM, 563 | /* 0FE3 */ OP_MODRM, 564 | /* 0FE4 */ OP_MODRM, 565 | /* 0FE5 */ OP_MODRM, 566 | /* 0FE6 */ OP_MODRM, 567 | /* 0FE7 */ OP_MODRM, 568 | /* 0FE8 */ OP_MODRM, 569 | /* 0FE9 */ OP_MODRM, 570 | /* 0FEA */ OP_MODRM, 571 | /* 0FEB */ OP_MODRM, 572 | /* 0FEC */ OP_MODRM, 573 | /* 0FED */ OP_MODRM, 574 | /* 0FEE */ OP_MODRM, 575 | /* 0FEF */ OP_MODRM, 576 | 577 | /* 0FF0 */ OP_MODRM, 578 | /* 0FF1 */ OP_MODRM, 579 | /* 0FF2 */ OP_MODRM, 580 | /* 0FF3 */ OP_MODRM, 581 | /* 0FF4 */ OP_MODRM, 582 | /* 0FF5 */ OP_MODRM, 583 | /* 0FF6 */ OP_MODRM, 584 | /* 0FF7 */ OP_MODRM, 585 | /* 0FF8 */ OP_MODRM, 586 | /* 0FF9 */ OP_MODRM, 587 | /* 0FFA */ OP_MODRM, 588 | /* 0FFB */ OP_MODRM, 589 | /* 0FFC */ OP_MODRM, 590 | /* 0FFD */ OP_MODRM, 591 | /* 0FFE */ OP_MODRM, 592 | /* 0FFF */ OP_INVALID, 593 | }; 594 | 595 | static byte cflags(byte op) 596 | { 597 | return flags_table[op]; 598 | } 599 | 600 | static byte cflags_ex(byte op) 601 | { 602 | return flags_table_ex[op]; 603 | } 604 | 605 | /// 606 | /// 计算大于等于5字节的最少指令的长度 607 | /// 608 | /// 609 | /// 610 | public static uint SizeofMin5Byte(void* code) 611 | { 612 | UInt32 Length; 613 | byte* pOpcode; 614 | UInt32 Result = 0; 615 | ldasm_data data = new ldasm_data(); 616 | bool is64 = IntPtr.Size == 8; 617 | do 618 | { 619 | Length = ldasm(code, data, is64); 620 | 621 | pOpcode = (byte*)code + data.opcd_offset; 622 | Result += Length; 623 | if (Result >= 5) 624 | break; 625 | if ((Length == 1) && (*pOpcode == 0xCC)) 626 | break; 627 | 628 | code = (void*)((ulong)code + Length); 629 | 630 | } while (Length>0); 631 | 632 | return Result; 633 | } 634 | 635 | static uint ldasm(void* code, ldasm_data ld, bool is64) 636 | { 637 | byte* p = (byte*)code; 638 | byte s, op, f; 639 | byte rexw, pr_66, pr_67; 640 | 641 | s = rexw = pr_66 = pr_67 = 0; 642 | 643 | /* dummy check */ 644 | if ((int)code==0) 645 | return 0; 646 | 647 | /* init output data */ 648 | //memset(ld, 0, sizeof(ldasm_data)); 649 | 650 | /* phase 1: parse prefixies */ 651 | while ((cflags(*p) & OP_PREFIX)!=0) 652 | { 653 | if (*p == 0x66) 654 | pr_66 = 1; 655 | if (*p == 0x67) 656 | pr_67 = 1; 657 | p++; s++; 658 | ld.flags |= F_PREFIX; 659 | if (s == 15) 660 | { 661 | ld.flags |= F_INVALID; 662 | return s; 663 | } 664 | } 665 | 666 | /* parse REX prefix */ 667 | if (is64 && *p >> 4 == 4) 668 | { 669 | ld.rex = *p; 670 | rexw = (byte)((ld.rex >> 3) & 1); 671 | ld.flags |= F_REX; 672 | p++; s++; 673 | } 674 | 675 | /* can be only one REX prefix */ 676 | if (is64 && *p >> 4 == 4) 677 | { 678 | ld.flags |= F_INVALID; 679 | s++; 680 | return s; 681 | } 682 | 683 | /* phase 2: parse opcode */ 684 | ld.opcd_offset = (byte)(p - (byte*)code); 685 | ld.opcd_size = 1; 686 | op = *p++; s++; 687 | 688 | /* is 2 byte opcode? */ 689 | if (op == 0x0F) 690 | { 691 | op = *p++; s++; 692 | ld.opcd_size++; 693 | f = cflags_ex(op); 694 | if ((f & OP_INVALID)!=0) 695 | { 696 | ld.flags |= F_INVALID; 697 | return s; 698 | } 699 | /* for SSE instructions */ 700 | if ((f & OP_EXTENDED)!=0) 701 | { 702 | op = *p++; s++; 703 | ld.opcd_size++; 704 | } 705 | } 706 | else { 707 | f = cflags(op); 708 | /* pr_66 = pr_67 for opcodes A0-A3 */ 709 | if (op >= 0xA0 && op <= 0xA3) 710 | pr_66 = pr_67; 711 | } 712 | 713 | /* phase 3: parse ModR/M, SIB and DISP */ 714 | if ((f & OP_MODRM)!=0) 715 | { 716 | byte mod = (byte)(*p >> 6); 717 | byte ro = (byte)((*p & 0x38) >> 3); 718 | byte rm = (byte)(*p & 7); 719 | 720 | ld.modrm = *p++; s++; 721 | ld.flags |= F_MODRM; 722 | 723 | /* in F6,F7 opcodes immediate data present if R/O == 0 */ 724 | if (op == 0xF6 && (ro == 0 || ro == 1)) 725 | f |= OP_DATA_I8; 726 | if (op == 0xF7 && (ro == 0 || ro == 1)) 727 | f |= OP_DATA_I16_I32_I64; 728 | 729 | /* is SIB byte exist? */ 730 | if (mod != 3 && rm == 4 && !(!is64 && pr_67!=0)) 731 | { 732 | ld.sib = *p++; s++; 733 | ld.flags |= F_SIB; 734 | 735 | /* if base == 5 and mod == 0 */ 736 | if ((ld.sib & 7) == 5 && mod == 0) 737 | { 738 | ld.disp_size = 4; 739 | } 740 | } 741 | 742 | switch (mod) 743 | { 744 | case 0: 745 | if (is64) 746 | { 747 | if (rm == 5) 748 | { 749 | ld.disp_size = 4; 750 | if (is64) 751 | ld.flags |= F_RELATIVE; 752 | } 753 | } 754 | else if (pr_67!=0) 755 | { 756 | if (rm == 6) 757 | ld.disp_size = 2; 758 | } 759 | else { 760 | if (rm == 5) 761 | ld.disp_size = 4; 762 | } 763 | break; 764 | case 1: 765 | ld.disp_size = 1; 766 | break; 767 | case 2: 768 | if (is64) 769 | ld.disp_size = 4; 770 | else if (pr_67!=0) 771 | ld.disp_size = 2; 772 | else 773 | ld.disp_size = 4; 774 | break; 775 | } 776 | 777 | if (ld.disp_size>0) 778 | { 779 | ld.disp_offset = (byte)(p - (byte*)code); 780 | p += ld.disp_size; 781 | s += ld.disp_size; 782 | ld.flags |= F_DISP; 783 | } 784 | } 785 | 786 | /* phase 4: parse immediate data */ 787 | if (rexw!=0 && (f & OP_DATA_I16_I32_I64)!=0) 788 | ld.imm_size = 8; 789 | else if ((f & OP_DATA_I16_I32)!=0 || (f & OP_DATA_I16_I32_I64)!=0) 790 | ld.imm_size = (byte)(4 - (pr_66 << 1)); 791 | 792 | /* if exist, add OP_DATA_I16 and OP_DATA_I8 size */ 793 | ld.imm_size += (byte)(f & 3); 794 | 795 | if ((ld.imm_size)!=0) 796 | { 797 | s += ld.imm_size; 798 | ld.imm_offset = (byte)(p - (byte*)code); 799 | ld.flags |= F_IMM; 800 | if ((f & OP_RELATIVE)!=0) 801 | ld.flags |= F_RELATIVE; 802 | } 803 | 804 | /* instruction is too long */ 805 | if (s > 15) 806 | ld.flags |= F_INVALID; 807 | 808 | return s; 809 | } 810 | } 811 | } 812 | -------------------------------------------------------------------------------- /DotNetDetour/Monitor.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Diagnostics; 4 | using System.IO; 5 | using System.Linq; 6 | using System.Reflection; 7 | using System.Text; 8 | using System.Threading.Tasks; 9 | 10 | namespace DotNetDetour 11 | { 12 | class DestAndOri 13 | { 14 | /// 15 | /// Hook代理方法 16 | /// 17 | public MethodBase HookMethod { get; set; } 18 | 19 | /// 20 | /// 目标方法的原始方法 21 | /// 22 | public MethodBase OriginalMethod { get; set; } 23 | 24 | public IMethodHook Obj; 25 | } 26 | 27 | [Obsolete("此类已变更为MethodHook")] 28 | public class Monitor : MethodHook { } 29 | [Obsolete("此类已变更为MethodHook")] 30 | public class ClrMethodHook : MethodHook { } 31 | 32 | public class MethodHook 33 | { 34 | static public BindingFlags AllFlag = BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Static; 35 | static bool installed = false; 36 | static List destAndOris = new List(); 37 | /// 38 | /// 安装监视器 39 | /// 40 | public static void Install(string dir = null) 41 | { 42 | if (installed) 43 | return; 44 | installed = true; 45 | var assemblies = AppDomain.CurrentDomain.GetAssemblies(); 46 | IEnumerable monitors; 47 | if (string.IsNullOrEmpty(dir)) 48 | { 49 | monitors = assemblies.SelectMany(t => t.GetImplementedObjectsByInterface()); 50 | } 51 | else 52 | { 53 | assemblies = assemblies.Concat(Directory 54 | .GetFiles(dir, "*.dll") 55 | .Select(d => { try { return Assembly.LoadFrom(d); } catch { return null; } }) 56 | .Where(x => x != null)) 57 | .Distinct() 58 | .ToArray(); 59 | monitors = assemblies 60 | .SelectMany(d => d.GetImplementedObjectsByInterface()); 61 | } 62 | 63 | foreach (var monitor in monitors) { 64 | var all = monitor.GetType().GetMethods(AllFlag); 65 | var hookMethods = all.Where(t => t.CustomAttributes.Any(a => typeof(HookMethodAttribute).IsAssignableFrom(a.AttributeType))); 66 | var originalMethods = all.Where(t => t.CustomAttributes.Any(a => typeof(OriginalMethodAttribute).IsAssignableFrom(a.AttributeType))).ToArray(); 67 | 68 | var destCount = hookMethods.Count(); 69 | foreach (var hookMethod in hookMethods) { 70 | DestAndOri destAndOri = new DestAndOri(); 71 | destAndOri.Obj = monitor; 72 | destAndOri.HookMethod = hookMethod; 73 | if (destCount == 1) { 74 | destAndOri.OriginalMethod = originalMethods.FirstOrDefault(); 75 | } else { 76 | var originalMethodName = hookMethod.GetCustomAttribute().GetOriginalMethodName(hookMethod); 77 | 78 | destAndOri.OriginalMethod = FindMethod(originalMethods, originalMethodName, hookMethod, assemblies); 79 | } 80 | 81 | destAndOris.Add(destAndOri); 82 | } 83 | } 84 | 85 | InstallInternal(true, assemblies); 86 | AppDomain.CurrentDomain.AssemblyLoad += CurrentDomain_AssemblyLoad; 87 | } 88 | 89 | private static void InstallInternal(bool isInstall, Assembly[] assemblies) 90 | { 91 | foreach (var detour in destAndOris) 92 | { 93 | var hookMethod = detour.HookMethod; 94 | var hookMethodAttribute = hookMethod.GetCustomAttribute(); 95 | 96 | //获取当前程序集中的基础类型 97 | var typeName = hookMethodAttribute.TargetTypeFullName; 98 | if (hookMethodAttribute.TargetType != null) { 99 | typeName = hookMethodAttribute.TargetType.FullName; 100 | } 101 | var type = TypeResolver(typeName, assemblies); 102 | if (type != null && !assemblies.Contains(type.Assembly)) { 103 | type = null; 104 | } 105 | 106 | //获取方法 107 | var methodName = hookMethodAttribute.GetTargetMethodName(hookMethod); 108 | MethodBase rawMethod = null; 109 | if (type != null) { 110 | MethodBase[] methods; 111 | 112 | if (methodName == type.Name || methodName == ".ctor") {//构造方法 113 | methods = type.GetConstructors(AllFlag); 114 | methodName = ".ctor"; 115 | } else { 116 | methods = type.GetMethods(AllFlag); 117 | } 118 | 119 | rawMethod = FindMethod(methods, methodName, hookMethod, assemblies); 120 | } 121 | if (rawMethod != null && rawMethod.IsGenericMethod) { 122 | //泛型方法转成实际方法 123 | rawMethod = ((MethodInfo)rawMethod).MakeGenericMethod(hookMethod.GetParameters().Select(o => { 124 | var rt = o.ParameterType; 125 | var attr = o.GetCustomAttribute(); 126 | if (attr != null && attr.TypeFullNameOrNull != null) { 127 | rt = TypeResolver(attr.TypeFullNameOrNull, assemblies); 128 | } 129 | return rt; 130 | }).ToArray()); 131 | } 132 | 133 | if (rawMethod == null) 134 | { 135 | if (isInstall) { 136 | Debug.WriteLine("没有找到与试图Hook的方法\"{0}, {1}\"匹配的目标方法.", new object[] { hookMethod.ReflectedType.FullName, hookMethod }); 137 | } 138 | continue; 139 | } 140 | if (detour.Obj is IMethodHookWithSet) { 141 | ((IMethodHookWithSet)detour.Obj).HookMethod(rawMethod); 142 | } 143 | 144 | var originalMethod = detour.OriginalMethod; 145 | var engine = DetourFactory.CreateDetourEngine(); 146 | engine.Patch(rawMethod, hookMethod, originalMethod); 147 | 148 | Debug.WriteLine("已将目标方法 \"{0}, {1}\" 的调用指向 \"{2}, {3}\" Ori: \"{4}\".", rawMethod.ReflectedType.FullName, rawMethod 149 | , hookMethod.ReflectedType.FullName, hookMethod 150 | , originalMethod == null ? " (无)" : originalMethod.ToString()); 151 | } 152 | } 153 | 154 | private static Type TypeResolver(string typeName, Assembly[] assemblies) { 155 | return Type.GetType(typeName, null, (a, b, c) => { 156 | Type rt; 157 | if (a != null) { 158 | rt = a.GetType(b); 159 | if (rt != null) { 160 | return rt; 161 | } 162 | } 163 | rt = Type.GetType(b); 164 | if (rt != null) { 165 | return rt; 166 | } 167 | foreach (var asm in assemblies) { 168 | rt = asm.GetType(b); 169 | if (rt != null) { 170 | return rt; 171 | } 172 | } 173 | return null; 174 | }); 175 | } 176 | //查找匹配函数 177 | private static MethodBase FindMethod(MethodBase[] methods, string name, MethodBase like, Assembly[] assemblies) { 178 | var likeParams = like.GetParameters(); 179 | foreach (var item in methods) { 180 | if (item.Name != name) { 181 | continue; 182 | } 183 | 184 | var paramArr = item.GetParameters(); 185 | var len = paramArr.Count(); 186 | if (len != likeParams.Count()) { 187 | continue; 188 | } 189 | 190 | for (var i = 0; i < len; i++) { 191 | var t1 = likeParams[i]; 192 | var t2 = paramArr[i]; 193 | //类型相同 或者 fullname都为null的泛型参数 194 | if (t1.ParameterType.FullName == t2.ParameterType.FullName) { 195 | continue; 196 | } 197 | 198 | //手动保持的类型 199 | var rmtype = t1.GetCustomAttribute(); 200 | if (rmtype != null) { 201 | //泛型参数 202 | if (rmtype.IsGeneric && t2.ParameterType.FullName == null) { 203 | continue; 204 | } 205 | //查找实际类型 206 | if (rmtype.TypeFullNameOrNull != null) { 207 | if (rmtype.TypeFullNameOrNull == t2.ParameterType.FullName) { 208 | continue; 209 | } 210 | 211 | var type = TypeResolver(rmtype.TypeFullNameOrNull, assemblies); 212 | if (type == t2.ParameterType) { 213 | continue; 214 | } 215 | } 216 | } 217 | goto next; 218 | } 219 | return item; 220 | next: 221 | continue; 222 | } 223 | return null; 224 | } 225 | 226 | private static void CurrentDomain_AssemblyLoad(object sender, AssemblyLoadEventArgs args) 227 | { 228 | InstallInternal(false, new[] { args.LoadedAssembly }); 229 | } 230 | } 231 | } 232 | -------------------------------------------------------------------------------- /DotNetDetour/MonitorAttribute.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Reflection; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | 8 | namespace DotNetDetour 9 | { 10 | /// 11 | /// 标记一个方法,使其作为目标方法的替代,这将使.NET框架执行到原始方法时转而执行被此特性标记的方法。 12 | /// 13 | /// 如果提供OriginalMethod,OriginalMethod名称应当为“原始目标方法名_Original”,如果使用其他名称,应当设置相应的OriginalMethodName。 14 | /// 15 | [AttributeUsage(AttributeTargets.Method)] 16 | public class HookMethodAttribute : Attribute 17 | { 18 | public string TargetTypeFullName { get; private set; } 19 | public Type TargetType { get; private set; } 20 | private string TargetMethodName; 21 | private string OriginalMethodName; 22 | 23 | public string GetTargetMethodName(MethodBase method) { 24 | return String.IsNullOrEmpty(TargetMethodName) ? method.Name : TargetMethodName; 25 | } 26 | public string GetOriginalMethodName(MethodBase method) { 27 | return String.IsNullOrEmpty(OriginalMethodName) ? GetTargetMethodName(method) + "_Original" : OriginalMethodName; 28 | } 29 | 30 | /// 31 | /// 标记要hook的目标类型中的指定方法。 32 | /// 类型名称为完全限定名,如果是泛型可以提供type`1[[System.Int32]]这种完整形式。 33 | /// 方法名称targetMethodName可以不提供,默认取当前方法名称。 34 | /// 如果提供OriginalMethod时,OriginalMethod名称应当为“原始目标方法名_Original”,如果使用其他名称,应当设置OriginalMethodName。 35 | /// 36 | public HookMethodAttribute(string targetTypeFullName, string targetMethodName = null, string originalMethodName = null) 37 | { 38 | this.TargetTypeFullName = targetTypeFullName; 39 | this.TargetMethodName = targetMethodName; 40 | this.OriginalMethodName = originalMethodName; 41 | } 42 | 43 | /// 44 | /// 标记要hook的目标类型中的指定方法。 45 | /// 方法名称targetMethodName可以不提供,默认取当前方法名称。 46 | /// 如果提供OriginalMethod时,OriginalMethod名称应当为“原始目标方法名_Original”,如果使用其他名称,应当设置OriginalMethod。 47 | /// 48 | public HookMethodAttribute(Type targetType, string targetMethodName = null, string originalMethodName = null) 49 | { 50 | this.TargetType = targetType; 51 | this.TargetMethodName = targetMethodName; 52 | this.OriginalMethodName = originalMethodName; 53 | } 54 | } 55 | 56 | 57 | 58 | 59 | [Obsolete("此类已变更为HookMethodAttribute")] 60 | [AttributeUsage(AttributeTargets.Method)] 61 | public class MonitorAttribute : HookMethodAttribute { 62 | public MonitorAttribute(string NamespaceName, string ClassName) 63 | : base(NamespaceName + "." + ClassName) { } 64 | public MonitorAttribute(Type type) 65 | : base(type) { } 66 | } 67 | [Obsolete("此类已变更为HookMethodAttribute")] 68 | [AttributeUsage(AttributeTargets.Method)] 69 | public class RelocatedMethodAttribute : HookMethodAttribute { 70 | public RelocatedMethodAttribute(string targetTypeFullName, string targetMethodName) 71 | : base(targetTypeFullName, targetMethodName) { } 72 | public RelocatedMethodAttribute(Type type, string targetMethodName) 73 | : base(type, targetMethodName) { } 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /DotNetDetour/NativeAPI.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Runtime.InteropServices; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | 8 | namespace DotNetDetour 9 | { 10 | public enum Protection 11 | { 12 | PAGE_NOACCESS = 0x01, 13 | PAGE_READONLY = 0x02, 14 | PAGE_READWRITE = 0x04, 15 | PAGE_WRITECOPY = 0x08, 16 | PAGE_EXECUTE = 0x10, 17 | PAGE_EXECUTE_READ = 0x20, 18 | PAGE_EXECUTE_READWRITE = 0x40, 19 | PAGE_EXECUTE_WRITECOPY = 0x80, 20 | PAGE_GUARD = 0x100, 21 | PAGE_NOCACHE = 0x200, 22 | PAGE_WRITECOMBINE = 0x400 23 | } 24 | 25 | public class NativeAPI 26 | { 27 | [DllImport("kernel32")] 28 | public static extern bool VirtualProtect(IntPtr lpAddress, uint dwSize, Protection flNewProtect, out uint lpflOldProtect); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /DotNetDetour/OriginalAttribute.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace DotNetDetour 8 | { 9 | /// 10 | /// 标记一个方法,使得该方法代表原始方法,从而可以被用户代码调用。 11 | /// 此方法命名为 原始目标方法名_Original;如果使用其他名称,在HookMethodAttribute中应当指明。 12 | /// 13 | [AttributeUsage(AttributeTargets.Method)] 14 | public class OriginalMethodAttribute : Attribute { } 15 | /// 16 | /// 标记函数的泛型参数或私有类型参数。 17 | /// 私有类型要指明其完全限定名,如:System.Int32、type`1[[System.Int32]]这种完整形式。 18 | /// 19 | [AttributeUsage(AttributeTargets.Parameter)] 20 | public class RememberTypeAttribute : Attribute 21 | { 22 | public RememberTypeAttribute(string fullName = null, bool isGeneric=false) 23 | { 24 | TypeFullNameOrNull = fullName; 25 | IsGeneric = isGeneric; 26 | } 27 | public string TypeFullNameOrNull { get; private set; } 28 | public bool IsGeneric { get; private set; } 29 | } 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | [Obsolete("此类已变更为OriginalMethodAttribute")] 38 | [AttributeUsage(AttributeTargets.Method)] 39 | public class OriginalAttribute : OriginalMethodAttribute { } 40 | 41 | [Obsolete("此类已变更为无参数的OriginalMethodAttribute,此特性因为带参数已无法兼容", true)] 42 | [AttributeUsage(AttributeTargets.Method)] 43 | public class ShadowMethodAttribute : OriginalMethodAttribute { 44 | [Obsolete("此类已变更为无参数的OriginalMethodAttribute,此特性因为带参数已无法兼容", true)] 45 | public ShadowMethodAttribute(string targetTypeName, string methodName) { } 46 | [Obsolete("此类已变更为无参数的OriginalMethodAttribute,此特性因为带参数已无法兼容", true)] 47 | public ShadowMethodAttribute(Type classType, string methodName) { } 48 | } 49 | 50 | [Obsolete("此类已变更为RememberTypeAttribute")] 51 | [AttributeUsage(AttributeTargets.Parameter)] 52 | public class NonPublicParameterTypeAttribute : RememberTypeAttribute { 53 | public NonPublicParameterTypeAttribute(string fullName) 54 | : base(fullName) { 55 | } 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /DotNetDetour/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // 有关程序集的一般信息由以下 6 | // 控制。更改这些特性值可修改 7 | // 与程序集关联的信息。 8 | [assembly: AssemblyTitle("DotNetDetour")] 9 | [assembly: AssemblyDescription("")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("")] 12 | [assembly: AssemblyProduct("DotNetDetour")] 13 | [assembly: AssemblyCopyright("Copyright © 2015")] 14 | [assembly: AssemblyTrademark("")] 15 | [assembly: AssemblyCulture("")] 16 | 17 | //将 ComVisible 设置为 false 将使此程序集中的类型 18 | //对 COM 组件不可见。 如果需要从 COM 访问此程序集中的类型, 19 | //请将此类型的 ComVisible 特性设置为 true。 20 | [assembly: ComVisible(false)] 21 | 22 | // 如果此项目向 COM 公开,则下列 GUID 用于类型库的 ID 23 | [assembly: Guid("900b35a0-c116-46e2-875a-9bab0c6d5a55")] 24 | 25 | // 程序集的版本信息由下列四个值组成: 26 | // 27 | // 主版本 28 | // 次版本 29 | // 生成号 30 | // 修订号 31 | // 32 | //可以指定所有这些值,也可以使用“生成号”和“修订号”的默认值, 33 | // 方法是按如下所示使用“*”: : 34 | // [assembly: AssemblyVersion("1.0.*")] 35 | [assembly: AssemblyVersion("1.0.0.0")] 36 | [assembly: AssemblyFileVersion("1.0.0.0")] 37 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | [![Build Status](https://dev.azure.com/kissstudio/DotNetDetour/_apis/build/status/kissstudio.DotNetDetour?branchName=master)](https://dev.azure.com/kissstudio/DotNetDetour/_build/latest?definitionId=3?branchName=master) 3 | [![996.icu](https://img.shields.io/badge/link-996.icu-red.svg)](https://996.icu) 4 | [![LICENSE](https://img.shields.io/badge/license-Anti%20996-blue.svg)](https://github.com/halx99/yasio/blob/master/LICENSE) 5 | [![HitCount](http://hits.dwyl.com/bigbaldy1128/DotNetDetour.svg)](http://hits.dwyl.com/bigbaldy1128/DotNetDetour) 6 | 7 | # :open_book:DotNetDetour 8 | DotNetDetour是一个用于.net方法hook的类库 9 | 10 | ## 特点 11 | * 支持32bit和64bit的.net程序 12 | * 支持静态方法,实例方法、属性方法、泛型类型的方法、泛型方法的hook 13 | * 支持.net基础类库方法的hook 14 | * 无任何性能影响,无需知道和改动被hook的方法源码 15 | 16 | ## 实现原理 17 | https://bbs.csdn.net/topics/391958344 18 | 19 | ## 基础示例 20 | 1. git clone本项目最新源码使用;或者NuGet安装(可能未及时更新):`Install-Package DotNetDetour`, 21 | 或者:`Install-Package kissstudio.DotNetDetour`。 22 | 23 | 2. 参考以下例子实现`IMethodHook`接口,使用特性标记要Hook的方法 24 | ``` C# 25 | namespace Test.Solid { 26 | //假设有一个已存在的类(并且无法修改源码,如.Net框架的方法) 27 | public class SolidClass{ 28 | public string Run(string msg){ 29 | return msg+"(run)"; 30 | } 31 | } 32 | } 33 | 34 | namespace Test{ 35 | //我们自行实现一个类来修改Run方法的行为,此类用IMethodHook接口修饰 36 | public class MyClass:IMethodHook{ 37 | //我们实现一个新Run方法,并标记为HookMethod,覆盖SolidClass中的Run方法 38 | [HookMethod("Test.Solid.SolidClass")] 39 | public string Run(string msg){ 40 | return "Hook " + Run_Original(msg); 41 | } 42 | 43 | //实现一个占位方法,此方法代表被Hook覆盖的原始方法 44 | [OriginalMethod] 45 | public string Run_Original(string msg){ 46 | return null; //这里写什么无所谓,能编译过即可 47 | } 48 | } 49 | } 50 | ``` 51 | 52 | 3. 在程序中执行安装操作(只需运行一次即可),最佳运行时机:必须在被Hook方法被调用前执行,最好程序启动时运行一次即可。 53 | ``` C# 54 | MethodHook.Install(); 55 | ``` 56 | 57 | 4. 当执行到被Hook的方法时,该调用将被转到我们的Hook方法执行: 58 | ``` C# 59 | var msg=new SolidClass().Run("Hello World!"); 60 | 61 | //Hook Hello World!(run) 62 | ``` 63 | 64 | 65 | 66 | 67 | 68 | # :open_book:Hook场景 69 | 70 | ## 普通方法Hook 71 | 72 | 静态和非静态的普通方法Hook操作都是一模一样的,两步到位:新建一个类实现`IMethodHook`接口,编写普通Hook方法,用`HookMethod`特性标记此方法,有无static修饰、返回值类型(仅针对引用性质的类型,非int等值类型)不同都不影响,但参数签名要和被Hook的原始方法一致,值类型和引用类型尽量不要混用。 73 | 74 | 75 | ### 第一步:新建一个类实现`IMethodHook`接口 76 | 我们编写的Hook方法所在的类需要实现`IMethodHook`接口,此接口是一个空接口,用于快速的查找Hook方法。 77 | 78 | 或者使用`IMethodHookWithSet`接口(算Plus版吧),此接口带一个`HookMethod(MethodBase method)`方法,这个类每成功进行一个Hook的初始化,就会传入被Hook的原始方法(可判断方法名称来确定是初始化的哪个方法),这个方法可用于获取方法所在的类(如:私有类型),可用于简化后续的反射操作;注意:此方法应当当做静态方法来进行编码。 79 | 80 | 81 | ### 第二步:编写Hook方法,用`HookMethod`特性标记 82 | `HookMethod`(`type`,`targetMethodName`,`originalMethodName`) ,`type`参数支持:Type类型对象、类型完全限定名。如果能直接获取到类型对象,就使用Type类型对象;否则必须使用此类型的完全限定名(如:私有类型),如:`System.Int32`、`System.Collections.Generic.List`1[[System.String]]`。 83 | ``` C# 84 | [HookMethod("Namespace.xxx.MyClass", "TargetMethodName", "OriginalMethodName")] 85 | public string MyMethod(string param){...} 86 | 87 | [HookMethod(typeof(MyClass))] 88 | public string MyMethod(string param){...} 89 | ``` 90 | 如果我们的方法名称和被Hook的目标方法名称一致,无需提供`targetMethodName`参数。 91 | 92 | 如果我们提供目标原始方法的占位方法`OriginalMethod`,并且名称为`目标原始方法名称` `+` `_Original`,或者当前类内只有一个Hook方法,无需提供`originalMethodName`参数。 93 | 94 | ### 注意:方法参数 95 | 参数签名要和被Hook的原始方法一致,如果不一致将导致无法找到原始方法(原因:存在重载方法无法确认是哪个的问题)。 96 | 97 | 如果存在我们无法使用的参数类型的时候(如:私有类型),我们可以用object等其他引用类型代替此类型(注意不要用值类型,否则可能出现内存访问错误),并把此参数用`RememberType`进行标记: 98 | ``` C# 99 | //目标方法: 100 | public string SolidMethod(MyClass data, int code){...} 101 | 102 | //我们的Hook方法: 103 | public string MyMethod([RememberType("Namespace.xxx.MyClass")]object data, int code){...} 104 | ``` 105 | 106 | ### 可选:提供`OriginalMethod`特性标记的原始方法 107 | 如果我们还想调用被Hook的原始方法,我们可以提供一个占位方法,此方法用`OriginalMethod`进行标记即可。此方法只起到代表原始方法的作用,不需要可以不提供,要求:参数签名必须和我们写的Hook方法一致(原因:存在重载方法无法确认是哪个的问题)。 108 | 109 | 此方法默认名称格式为`目标原始方法名称` `+` `_Original`,不使用这个名称也可以,但如果使用其他名称并且当前类中有多个Hook方法,必须在Hook方法`HookMethod`特性中进行设置`originalMethodName`进行关联。 110 | ``` C# 111 | [OriginalMethod] 112 | public string SolidMethod_Original(object data, int code){ 113 | ``` 114 | 115 | ### 可选:给我们的Hook方法传递参数 116 | 我们编写Hook方法是在被Hook的原始方法被调用时才会执行的,我们可能无法修改调用过程的参数(如果是能修改方法的话就跳过此节),虽然我们编写的Hook方法可以是非静态方法,但我们应当把它当静态方法来看待,虽然可以用属性字段(非静态的也当做静态)之类的给我们的Hook方法传递数据,但如果遇到并发,是不可靠的。 117 | 118 | 我们可以通过当前线程相关的上下文来传递数据,比如:`HttpContext`、`CallContext`、`AsyncLocal`、`ThreadLoacl`。推荐使用`CallContext.LogicalSetData`来传递数据,如果可以用`HttpContext`就更好了(底层也是用`CallContext.HostContext`来实现的)。`ThreadLoacl`只能当前线程用,遇到异步、多线程就不行了。`AsyncLocal`当然是最好的,但稍微低些版本的.Net Framework还没有这个。 119 | 120 | ``` C# 121 | [HookMethod("Namespace.xxx.MyClass", "TargetMethodName", "OriginalMethodName")] 122 | public string MyMethod(string param){ 123 | if (CallContext.LogicalGetData("key") == (object)"value") { 124 | //执行特定Hook代码 125 | return; 126 | } 127 | //执行其他Hook代码 128 | ... 129 | } 130 | 131 | //调用 132 | CallContext.LogicalSetData("key", "value"); 133 | new MyClass().MyMethod(""); 134 | CallContext.LogicalSetData("key", null); 135 | ``` 136 | 137 | 注:虽然大部分多线程、异步环境下调用上下文是会被正确复制传递的,但如果哪里使用了`ConfigeAwait(false)`或者其他影响上下文的操作(定时回调、部分异步IO回调好像也没有传递),当我们的Hook方法执行时,可能上下文数据并没有传递进来。 138 | 139 | 140 | ## 异步方法Hook 141 | 异步方法的Hook方法需要用async来修饰、返回Task类型,其他和普通方法Hook没有区别。 142 | 143 | 小提醒:不要在存在SynchronizationContext(如:HttpContext、UI线程)的线程环境中直接在同步方法中调用异步方法,[真发生异步行为时100%死锁](https://blog.stephencleary.com/2012/07/dont-block-on-async-code.html),可以强制关闭SynchronizationContext来规避此种问题,但会引发一系列问题。**如果使用过程中发生死锁,跟我们进行的Hook操作没有关系**。 144 | ``` C# 145 | [HookMethod(typeof(MyClass))] 146 | public async Task MyMethodAsync() {...} 147 | 148 | //异步环境调用 149 | val=await new MyClass().MyMethodAsync(); 150 | 151 | //同步环境调用 152 | var bak = SynchronizationContext.Current; 153 | SynchronizationContext.SetSynchronizationContext(null); 154 | try { 155 | val=new MyClass().MyMethodAsync().Result; 156 | } finally { 157 | SynchronizationContext.SetSynchronizationContext(bak); 158 | } 159 | ``` 160 | 161 | 162 | ## 属性Hook 163 | 属性其实是`get_xxx()`名称的普通方法,比如`MyProperty`属性Hook `get_MyProperty()`这个普通方法即可。 164 | ``` C# 165 | [HookMethod("Namespace.xxx.MyClass")] 166 | public string get_MyProperty(){...} 167 | 168 | [OriginalMethod] 169 | public string get_MyProperty_Original(){...} 170 | ``` 171 | 172 | 或者在get块上方进行标记,规则和普通方法一致: 173 | ``` C# 174 | public string MyProperty{ 175 | [HookMethod("Namespace.xxx.MyClass")] 176 | get{ ... } 177 | } 178 | 179 | public string MyProperty_Original{ 180 | [OriginalMethod] 181 | get{ ... } 182 | } 183 | ``` 184 | 185 | 注:Hook属性时有可能能成功设置此Hook,但不一定会执行我们的代码,可能是编译过程中优化了整个调用过程,跳过了部分属性方法,直接返回了最深层次的调用值,如下面这种类似的属性获取方式: 186 | ``` C# 187 | int A{get{return B;}} 188 | int B{get{return C;}} 189 | int C{get{return 123;}} 190 | ``` 191 | 我们Hook A属性,能成功设置Hook方法,但我们调用A属性时,并不会执行我们的Hook方法。换B也不行,只有Hook C才行。也许是编译的时候把A、B的调用直接优化成了对C的调用,我们只需要对最深层次的属性调用进行Hook就能避免此问题。(这个只是演示可能会出现的问题,我们自己特意写代码去测试并不能复现)。 192 | 193 | 194 | ## ~字段Hook~ 195 | ~不支持,应该直接用反射来操作。~ 196 | 197 | 198 | ## 构造方法Hook 199 | 我们编写个返回值为void、方法名称为类名称的普通方法即可实现。如果方法名称无法使用类名称时,需在`HookMethod`中设置`targetMethodName`为`.ctor`。其他规则和普通方法一致。 200 | ``` C# 201 | [HookMethod("Namespace.xxx.MyClass")] 202 | public void MyClass(string param) { 203 | ... 204 | MyClass_Original(param);//可选调用自身实例化方法 205 | ... 206 | } 207 | 208 | [OriginalMethod] 209 | public void MyClass_Original(string param) {} 210 | ``` 211 | 212 | 213 | ## 泛型类的方法Hook 214 | 215 | 形如`class MyClass{ T MyMethod(T param, object param2){...} }`这种泛型,对里面的方法进行Hook。泛型类中方法的Hook和普通方法Hook没有多大区别,只是在提供`HookMethod`特性的`type`参数时需要对类型具体化,比如调用的地方使用的是int类型,那么我们就Hook int类型的此类:`typeof(MyClass)`、`Namespace.xxx.MyClass`1[[System.Int32]]`,其他和普通方法规则相同。 216 | 217 | 由于存在`引用类型`和`值类型`两种类型,并且表现不一致,我们在具体化时要分开对待。 218 | 219 | ### 值类型泛型参数 220 | 每种使用到的值类型泛型参数的具体类型都需要单独实现Hook,`int`、`bool`等为值类型都要单独实现,如`int`类型写法: 221 | ``` C# 222 | [HookMethod("Namespace.xxx.MyClass`1[[System.Int32]]")] 223 | public int MyMethod(int param, object param2) { 224 | ``` 225 | 226 | 227 | ### 引用类型泛型参数 228 | 每种使用到引用类型参数的具体类型都共用一个Hook,**注意是:同一个泛型类中的同一个方法只能用一个相同方法进行Hook**,`string`、`普通object`等都是引用类型都共用一个Hook,如`string`类型写法: 229 | ``` C# 230 | [HookMethod("Namespace.xxx.MyClass`1[[System.Object]]")] 231 | public object MyMethod(object param, object param2) { 232 | if(param is string){ 233 | ... //string 类型实现代码 234 | } else if(param is xxxx){ 235 | ... //其他引用类型实现代码 236 | } 237 | ``` 238 | 239 | 240 | ## 泛型方法Hook 241 | 242 | 形如`T MyMethod(T param, object param2)`这种泛型方法,我们对这种方法进行Hook时需要把类型具体化,并用`RememberType(isGeneric: true)`特性标记涉及到的泛型参数,比如调用的地方是int类型,那么我们就Hook int类型的此方法`int MyMethod([RememberType(isGeneric: true)]int param, object param2)`,其实最终还是一个普通方法,按普通方法规则来写代码。 243 | 244 | 由于存在`引用类型`和`值类型`两种类型,并且表现不一致,我们在具体化时要分开对待。 245 | 246 | ### 值类型泛型参数 247 | 每种使用到值类型泛型参数的都单独实现Hook,`int`、`bool`等为值类型都要单独实现,如int类型写法: 248 | ``` C# 249 | [HookMethod("Namespace.xxx.MyClass")] 250 | public int MyMethod([RememberType(isGeneric: true)]int param) { 251 | ``` 252 | 253 | ### ~引用类型泛型参数~ 254 | ~不支持,引用类型泛型参数的方法Hook目前是不支持的,如:`MyMethod(object_xxx)`、`MyMethod(string_xxx)`都是不支持的。表现为泛型方法被正确Hook后,并不会走我们的逻辑,具体原因不明。~ 255 | 256 | 257 | 258 | 259 | # :open_book:其他 260 | 261 | ## 关于测试项目内存访问异常 262 | 263 | vs的测试功能会启动一个执行引擎,其默认选项是复用执行引擎。 264 | 反复运行测试时对修改汇编指令会造成影响。 265 | 从菜单关闭该选项`Test` -> `Test Settings` -> `Keep Test Execution Engine Running`,即可解除此影响。 266 | 267 | 另外调试测试是不能得出正确的结果的,可能是汇编代码不能在调试模式下工作。 268 | 269 | 270 | 271 | ## 老版本兼容 272 | 273 | 自[bigbaldy1128](https://github.com/bigbaldy1128) `2016-5`开源此项目后,到`2018-12` [kissstudio](https://github.com/kissstudio) 和 [xiangyuecn](https://github.com/xiangyuecn) 升级了此项目代码,把相关代码方式升级和合理化了一番(参考 [#4](https://github.com/bigbaldy1128/DotNetDetour/issues/4) [#5](https://github.com/bigbaldy1128/DotNetDetour/issues/5) )。 274 | 275 | 已对3个主要的方法都进行了变更: 276 | 277 | 1. `Monitor`、`ClrMethodHook` -> `MethodHook` 278 | 2. `MonitorAttribute`、`RelocatedMethodAttribute` -> `HookMethodAttribute` 279 | 3. `OriginalAttribute`、`ShadowMethodAttribute(不兼容)` -> `OriginalMethodAttribute` 280 | 281 | 除`ShadowMethodAttribute`外(升级需要改动被标记的方法名称,因而无法兼容),这3个变更都是兼容的,但不推荐继续使用老方法,并且将来可能会从类库里面移除。 282 | -------------------------------------------------------------------------------- /Test/MonitorTest.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Microsoft.VisualStudio.TestTools.UnitTesting; 3 | using DotNetDetour; 4 | using System.IO; 5 | using System.Collections.Generic; 6 | using System.Runtime.Remoting.Messaging; 7 | using System.Threading.Tasks; 8 | using System.Threading; 9 | using System.Text; 10 | 11 | namespace Test 12 | { 13 | [TestClass] 14 | public class ThunkMethodTest 15 | { 16 | [TestMethod] 17 | public void A_DotNetSystemMethod() { 18 | MethodHook.Install(); 19 | Assert.AreEqual("Hook My_name_is_:NetFrameworkDetour", File.ReadAllText("test")); 20 | } 21 | 22 | [TestMethod] 23 | public void StaticMethod() 24 | { 25 | MethodHook.Install(); 26 | Assert.AreEqual("Not Intel Core I7", Computer.GetCpu()); 27 | } 28 | 29 | [TestMethod] 30 | public void ConstructorMethod(){ 31 | MethodHook.Install(); 32 | 33 | CallContext.LogicalSetData("OpenComputerConstructorHook", "1"); 34 | var o = new Computer(); 35 | CallContext.LogicalSetData("OpenComputerConstructorHook", null); 36 | 37 | Assert.AreEqual("ConstructorMethod X1", o.Name); 38 | } 39 | 40 | [TestMethod] 41 | public void InternalTypeMethod() { 42 | MethodHook.Install(); 43 | Assert.AreEqual("InternalTypeMethod X1:off", new Computer().PowerOff()); 44 | } 45 | 46 | [TestMethod] 47 | public void InstanceMethod() 48 | { 49 | MethodHook.Install(); 50 | Assert.AreEqual("Hook 512M", new Computer().GetRAMSize()); 51 | } 52 | [TestMethod] 53 | public void InstanceMethod2() { 54 | MethodHook.Install(); 55 | Assert.AreEqual("Hook 土豆(String worked)", new Computer().Work("土豆")); 56 | Assert.AreEqual("Hook 9527(Int32 worked)", new Computer().Work(9527)); 57 | Assert.AreEqual(123456053L, new Computer().Work(new StringBuilder("aaa"))); 58 | 59 | //注意:存在SynchronizationContext时(如:HttpContext),异步方法不能直接在同步方法中调用,真发生异步行为时100%死锁 60 | var bak = SynchronizationContext.Current; 61 | SynchronizationContext.SetSynchronizationContext(null); 62 | try { 63 | Assert.AreEqual("Hook 土豆(workedAsync)", new Computer().WorkAsync("土豆").Result); 64 | } finally { 65 | SynchronizationContext.SetSynchronizationContext(bak); 66 | } 67 | } 68 | [TestMethod] 69 | public async Task InstanceMethod2Async() { 70 | Assert.AreEqual("Hook 土豆(workedAsync)", await new Computer().WorkAsync("土豆")); 71 | } 72 | 73 | [TestMethod] 74 | public void PropertyMethod() 75 | { 76 | MethodHook.Install(); 77 | Assert.AreEqual("Not Windows 10", new Computer().Os); 78 | } 79 | 80 | [TestMethod] 81 | public void GenericMethodMethod() { 82 | MethodHook.Install(); 83 | 84 | Assert.AreEqual("Hook 123", Computer.Any(123)); 85 | //引用类型的没法正确hook,不知道啥原因 86 | Assert.AreEqual("Not HooK str", "Not HooK " + Computer.Any("str")); 87 | Console.WriteLine("引用类型泛型参数的泛型方法无法被hook"); 88 | 89 | 90 | //注意:存在SynchronizationContext时(如:HttpContext),异步方法不能直接在同步方法中调用,真发生异步行为时100%死锁 91 | var bak = SynchronizationContext.Current; 92 | SynchronizationContext.SetSynchronizationContext(null); 93 | try { 94 | Assert.AreEqual("Hook 123Async", Computer.AnyAsync(123).Result); 95 | } finally { 96 | SynchronizationContext.SetSynchronizationContext(bak); 97 | } 98 | } 99 | 100 | [TestMethod] 101 | public void GenericTypeMethod() { 102 | MethodHook.Install(); 103 | 104 | Assert.AreEqual("Hook Jack", new ComputerOf().ComputerIo("Jack")); 105 | 106 | Assert.AreEqual("Hook X1", new ComputerOf().ComputerIo(new Computer()).Name); 107 | 108 | Assert.AreEqual(5, new ComputerOf().ComputerIo(4)); 109 | 110 | //注意:存在SynchronizationContext时(如:HttpContext),异步方法不能直接在同步方法中调用,真发生异步行为时100%死锁 111 | var bak = SynchronizationContext.Current; 112 | SynchronizationContext.SetSynchronizationContext(null); 113 | try { 114 | Assert.AreEqual(5, new ComputerOf().ComputerIoAsync(4).Result); 115 | } finally { 116 | SynchronizationContext.SetSynchronizationContext(bak); 117 | } 118 | } 119 | 120 | 121 | 122 | 123 | 124 | 125 | /// 126 | /// 验证:泛型类<引用类型>的函数是相同的 127 | /// 128 | [TestMethod] 129 | public void GenericPointRefCheck() { 130 | var t1 = typeof(ComputerOf); 131 | var t2 = typeof(ComputerOf); 132 | var h1 = t1.GetMethod("ComputerIo").MethodHandle.GetFunctionPointer(); 133 | var h2 = t2.GetMethod("ComputerIo").MethodHandle.GetFunctionPointer(); 134 | Assert.AreEqual(h1, h2); 135 | } 136 | /// 137 | /// 验证:泛型类<值类型>的函数是不相同的,泛型方法<值类型、引用类型>是不同的 138 | /// 139 | [TestMethod] 140 | public void GenericPointValCheck() { 141 | var t1 = typeof(ComputerOf); 142 | var t2 = typeof(ComputerOf); 143 | var h1 = t1.GetMethod("ComputerIo").MethodHandle.GetFunctionPointer(); 144 | var h2 = t2.GetMethod("ComputerIo").MethodHandle.GetFunctionPointer(); 145 | Assert.AreNotEqual(h1, h2); 146 | 147 | var th1 = typeof(Computer).GetMethod("Any").MakeGenericMethod(typeof(object)); 148 | var th2 = typeof(Computer).GetMethod("Any").MakeGenericMethod(typeof(Computer)); 149 | var th3 = typeof(Computer).GetMethod("Any").MakeGenericMethod(typeof(Computer)); 150 | h1 = th1.MethodHandle.GetFunctionPointer(); 151 | h2 = th2.MethodHandle.GetFunctionPointer(); 152 | var h3 = th3.MethodHandle.GetFunctionPointer(); 153 | Assert.AreNotEqual(h1, h2); 154 | Assert.AreEqual(h3, h2); 155 | 156 | th1 = typeof(Computer).GetMethod("Any").MakeGenericMethod(typeof(int)); 157 | th2 = typeof(Computer).GetMethod("Any").MakeGenericMethod(typeof(bool)); 158 | th3 = typeof(Computer).GetMethod("Any").MakeGenericMethod(typeof(bool)); 159 | h1 = th1.MethodHandle.GetFunctionPointer(); 160 | h2 = th2.MethodHandle.GetFunctionPointer(); 161 | h3 = th3.MethodHandle.GetFunctionPointer(); 162 | Assert.AreNotEqual(h1, h2); 163 | Assert.AreEqual(h3, h2); 164 | } 165 | } 166 | } 167 | -------------------------------------------------------------------------------- /Test/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // General Information about an assembly is controlled through the following 6 | // set of attributes. Change these attribute values to modify the information 7 | // associated with an assembly. 8 | [assembly: AssemblyTitle("Test")] 9 | [assembly: AssemblyDescription("")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("")] 12 | [assembly: AssemblyProduct("Test")] 13 | [assembly: AssemblyCopyright("Copyright © 2016")] 14 | [assembly: AssemblyTrademark("")] 15 | [assembly: AssemblyCulture("")] 16 | 17 | // Setting ComVisible to false makes the types in this assembly not visible 18 | // to COM components. If you need to access a type in this assembly from 19 | // COM, set the ComVisible attribute to true on that type. 20 | [assembly: ComVisible(false)] 21 | 22 | // The following GUID is for the ID of the typelib if this project is exposed to COM 23 | [assembly: Guid("9e079a1b-8a31-4f74-a027-1dd2848754d4")] 24 | 25 | // Version information for an assembly consists of the following four values: 26 | // 27 | // Major Version 28 | // Minor Version 29 | // Build Number 30 | // Revision 31 | // 32 | // You can specify all the values or you can default the Build and Revision Numbers 33 | // by using the '*' as shown below: 34 | // [assembly: AssemblyVersion("1.0.*")] 35 | [assembly: AssemblyVersion("1.0.0.0")] 36 | [assembly: AssemblyFileVersion("1.0.0.0")] 37 | -------------------------------------------------------------------------------- /Test/Samples.cs: -------------------------------------------------------------------------------- 1 | using DotNetDetour; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Diagnostics; 5 | using System.Linq; 6 | using System.Runtime.CompilerServices; 7 | using System.Runtime.Remoting.Messaging; 8 | using System.Text; 9 | using System.Threading.Tasks; 10 | 11 | namespace Test 12 | { 13 | public class Computer 14 | { 15 | public string Name { get; set; } 16 | public Computer() { 17 | Name = "X1"; 18 | } 19 | 20 | public static string GetCpu() 21 | { 22 | return "Intel Core I7"; 23 | } 24 | 25 | public string GetRAMSize() 26 | { 27 | return "512M"; 28 | } 29 | 30 | internal string Os 31 | { 32 | get 33 | { 34 | return "Windows 10"; 35 | } 36 | } 37 | public string Work(string msg) { 38 | return msg + "(" + msg.GetType().Name + " worked)"; 39 | } 40 | public string Work(int msg) { 41 | return msg + "(" + msg.GetType().Name + " worked)"; 42 | } 43 | public int Work(StringBuilder msg) { 44 | return 123456000+ msg.Length; 45 | } 46 | public async Task WorkAsync(string msg) { 47 | await Task.Delay(1); 48 | return msg+"(workedAsync)"; 49 | } 50 | public static string Any(T val) { 51 | return val.ToString(); 52 | } 53 | public static async Task AnyAsync(T val) { 54 | await Task.Delay(1); 55 | return val.ToString()+"Async"; 56 | } 57 | 58 | 59 | 60 | private class Item { 61 | public Computer Value; 62 | public string Status = "-"; 63 | } 64 | public string PowerOff() { 65 | var item = new Item { Value = this }; 66 | return PowerOffList(new List(new Item[] { item })) + ":" + item.Status; 67 | } 68 | private static string PowerOffList(List pcs) { 69 | var rtv = ""; 70 | foreach (var pc in pcs) { 71 | rtv += "," + pc.Value.Name; 72 | pc.Status = "off"; 73 | } 74 | return rtv.Substring(1); 75 | } 76 | private static string PowerOffList(List pcs) { 77 | return "干扰测试"; 78 | } 79 | private static string PowerOffList(object pcs) { 80 | return "干扰测试"; 81 | } 82 | } 83 | 84 | 85 | public class ComputerOf 86 | { 87 | public T ComputerIo(T owner) 88 | { 89 | return owner; 90 | } 91 | public async Task ComputerIoAsync(T owner) { 92 | await Task.Delay(1); 93 | return owner; 94 | } 95 | } 96 | 97 | public class ComputerDetour : IMethodHook { 98 | #region 静态方法HOOK 99 | [HookMethod("Test.Computer")] 100 | public static string GetCpu() { 101 | return "Not " + GetCpu_Original(); 102 | } 103 | 104 | [OriginalMethod] 105 | public static string GetCpu_Original() { 106 | return null; 107 | } 108 | #endregion 109 | 110 | #region 构造方法 111 | [HookMethod("Test.Computer")] 112 | public void Computer() { 113 | Computer_Original(); 114 | 115 | //根据上下文来决定是否要hook 116 | if (CallContext.LogicalGetData("OpenComputerConstructorHook") == (object)"1") { 117 | var This = (Computer)(object)this; 118 | This.Name = "ConstructorMethod " + This.Name; 119 | } 120 | } 121 | 122 | [OriginalMethod] 123 | public void Computer_Original() { 124 | 125 | } 126 | #endregion 127 | 128 | 129 | 130 | #region 实例方法 131 | [HookMethod("Test.Computer")] 132 | public string GetRAMSize() { 133 | return "Hook " + GetRAMSize_Original(); 134 | } 135 | 136 | [OriginalMethod] 137 | public string GetRAMSize_Original() { 138 | return null; 139 | } 140 | #endregion 141 | 142 | #region 实例方法(不实现OriginalMethod、方法名称和原方法名称不同) 143 | [HookMethod(typeof(Computer), "Work", "WorkFn")] 144 | public string XXXWork([RememberType("System.String")]object msg) { 145 | return "Hook " + WorkFn(msg); 146 | } 147 | [HookMethod(typeof(Computer), "Work", "WorkFn")] 148 | public string XXXWork([RememberType("System.Int32")]int msg) { 149 | return "Hook " + WorkFn(msg); 150 | } 151 | [HookMethod(typeof(Computer), "Work", "WorkFn")] 152 | public long XXXWork([RememberType("System.Text.StringBuilder")]Encoding msg) { 153 | return WorkFn(msg)+50; 154 | } 155 | 156 | [OriginalMethod] 157 | public object WorkFn(int msg) {//注意此处值类型的参数必须也是完全一样的值类型 158 | return null; 159 | } 160 | [OriginalMethod] 161 | public long WorkFn(Encoding msg) {//注意此处值类型的返回值必须也是值类型,内存长度必须相同或者更大,否则数据必然丢失 162 | return 0; 163 | } 164 | [OriginalMethod] 165 | public object WorkFn(object msg) {//和string的签名一致即可正确匹配 166 | return null; 167 | } 168 | [OriginalMethod] 169 | public object WorkFn(string msg) {//超级干扰,这个方法没有谁用到了 170 | return null; 171 | } 172 | #endregion 173 | 174 | #region 异步实例方法 175 | [HookMethod(typeof(Computer))] 176 | public async Task WorkAsync(string msg) { 177 | return "Hook " + await WorkAsync_Original(msg); 178 | } 179 | [OriginalMethod] 180 | public async Task WorkAsync_Original(string msg) { 181 | await Task.Delay(1); 182 | return null; 183 | } 184 | #endregion 185 | 186 | 187 | #region 实例属性 188 | //public string get_Os(){...} 不封装成属性也可以 189 | public string Os { 190 | [HookMethod(typeof(Computer))] 191 | get { 192 | return "Not " + Os_Original; 193 | } 194 | } 195 | //public string get_Os_Original(){...} 不封装成属性也可以 196 | public string Os_Original { 197 | [OriginalMethod] 198 | get { 199 | return null; 200 | } 201 | } 202 | #endregion 203 | 204 | 205 | 206 | 207 | 208 | 209 | #region 带私有+内部类型的方法 210 | [HookMethod("Test.Computer")] 211 | private static string PowerOffList([RememberType("System.Collections.Generic.List`1[[Test.Computer+Item]]")] object pcs) { 212 | var msg = PowerOffList_Original(pcs); 213 | return "InternalTypeMethod " + msg; 214 | } 215 | 216 | [OriginalMethod] 217 | private static string PowerOffList_Original(object pcs) { 218 | return null; 219 | } 220 | #endregion 221 | 222 | 223 | 224 | 225 | 226 | 227 | 228 | #region 【不支持】泛型方法<引用类型>,每种使用到的类型都单独实现,如:string,object。引用类型的泛型方法无法正确Hook,原因不明 229 | [HookMethod("Test.Computer")] 230 | public static string Any([RememberType(isGeneric: true)]string val) { 231 | return "Hook " + Any_Original(val); 232 | } 233 | [OriginalMethod] 234 | public static string Any_Original(string val) { 235 | return null; 236 | } 237 | #endregion 238 | 239 | #region 泛型方法<值类型>,每种使用到的类型都单独实现,如:int、bool 240 | [HookMethod("Test.Computer")] 241 | public static string Any([RememberType(isGeneric: true)]int val) { 242 | return "Hook " + Any_Original(val); 243 | } 244 | [OriginalMethod] 245 | public static string Any_Original(int val) { 246 | return null; 247 | } 248 | #endregion 249 | 250 | #region 泛型方法<值类型>,每种使用到的类型都单独实现,如:int、bool 251 | [HookMethod("Test.Computer")] 252 | public static async Task AnyAsync([RememberType(isGeneric: true)]int val) { 253 | return "Hook " + await AnyAsync_Original(val); 254 | } 255 | [OriginalMethod] 256 | public static async Task AnyAsync_Original(int val) { 257 | await Task.Delay(1); 258 | return null; 259 | } 260 | #endregion 261 | 262 | 263 | 264 | 265 | 266 | #region 泛型类型<引用类型>的方法,只能用一个方法进行hook,如:string,object 267 | [HookMethod(typeof(ComputerOf))] 268 | public object ComputerIo(object name) { 269 | if (name is string) { 270 | var human = ComputerIo_Original(name); 271 | human = "Hook " + human; 272 | return human; 273 | } 274 | if (name is Computer) { 275 | var o = (Computer)ComputerIo_Original(name); 276 | o.Name = "Hook " + o.Name; 277 | return o; 278 | } 279 | return null; 280 | } 281 | [OriginalMethod] 282 | public object ComputerIo_Original(object owner) { 283 | return null; 284 | } 285 | #endregion 286 | 287 | #region 泛型类型<值类型>的方法,每种使用到的类型都单独实现,如:int、bool 288 | [HookMethod("Test.ComputerOf`1[[System.Int32]]")] 289 | public int ComputerIo(int name) { 290 | return ComputerIo_Original(name) + 1; 291 | } 292 | [OriginalMethod] 293 | public int ComputerIo_Original(int owner) { 294 | return 0; 295 | } 296 | #endregion 297 | 298 | #region 泛型类型<值类型>的方法,每种使用到的类型都单独实现,如:int、bool 299 | [HookMethod("Test.ComputerOf`1[[System.Int32]]")] 300 | public async Task ComputerIoAsync(int name) { 301 | return await ComputerIoAsync_Original(name) + 1; 302 | } 303 | [OriginalMethod] 304 | public async Task ComputerIoAsync_Original(int owner) { 305 | await Task.Delay(1); 306 | return 0; 307 | } 308 | #endregion 309 | 310 | 311 | 312 | 313 | 314 | 315 | 316 | private class Framework : IMethodHook { 317 | [HookMethod("System.IO.File")] 318 | public static string ReadAllText(string file) { 319 | return "Hook " + ori(file) + ":NetFrameworkDetour"; 320 | } 321 | 322 | //一个hook的OriginalMethod名字可以随便写 323 | [OriginalMethod] 324 | public static string ori(string file) { 325 | return null; 326 | } 327 | } 328 | } 329 | } 330 | -------------------------------------------------------------------------------- /Test/Test.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | Debug 5 | AnyCPU 6 | {9E079A1B-8A31-4F74-A027-1DD2848754D4} 7 | Library 8 | Properties 9 | Test 10 | Test 11 | v4.5 12 | 512 13 | {3AC096D0-A1C2-E12C-1390-A8335801FDAB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} 14 | 10.0 15 | $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) 16 | $(ProgramFiles)\Common Files\microsoft shared\VSTT\$(VisualStudioVersion)\UITestExtensionPackages 17 | False 18 | UnitTest 19 | 20 | 21 | 22 | true 23 | full 24 | false 25 | bin\Debug\ 26 | DEBUG;TRACE 27 | prompt 28 | 4 29 | 30 | 31 | pdbonly 32 | true 33 | bin\Release\ 34 | TRACE 35 | prompt 36 | 4 37 | 38 | 39 | true 40 | bin\x64\Debug\ 41 | DEBUG;TRACE 42 | full 43 | x64 44 | prompt 45 | MinimumRecommendedRules.ruleset 46 | 47 | 48 | bin\x64\Release\ 49 | TRACE 50 | true 51 | pdbonly 52 | x64 53 | prompt 54 | MinimumRecommendedRules.ruleset 55 | 56 | 57 | false 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | {900b35a0-c116-46e2-875a-9bab0c6d5a55} 85 | DotNetDetour 86 | 87 | 88 | 89 | 90 | PreserveNewest 91 | 92 | 93 | 94 | 95 | 96 | 97 | False 98 | 99 | 100 | False 101 | 102 | 103 | False 104 | 105 | 106 | False 107 | 108 | 109 | 110 | 111 | 112 | 113 | 120 | -------------------------------------------------------------------------------- /Test/test: -------------------------------------------------------------------------------- 1 | My_name_is_ -------------------------------------------------------------------------------- /azure-pipelines.yml: -------------------------------------------------------------------------------- 1 | # .NET Desktop 2 | # Build and run tests for .NET Desktop or Windows classic desktop solutions. 3 | # Add steps that publish symbols, save build artifacts, and more: 4 | # https://docs.microsoft.com/azure/devops/pipelines/apps/windows/dot-net 5 | 6 | trigger: 7 | - master 8 | 9 | pool: 10 | vmImage: 'VS2017-Win2016' 11 | 12 | variables: 13 | solution: '**/*.sln' 14 | buildPlatform: 'Any CPU' 15 | buildConfiguration: 'Release' 16 | Major: '1' 17 | Minor: '0' 18 | Patch: '1' 19 | 20 | steps: 21 | - task: NuGetToolInstaller@0 22 | 23 | - task: NuGetCommand@2 24 | inputs: 25 | restoreSolution: '$(solution)' 26 | 27 | - task: VSBuild@1 28 | inputs: 29 | solution: '$(solution)' 30 | platform: '$(buildPlatform)' 31 | configuration: '$(buildConfiguration)' 32 | 33 | - task: NuGetCommand@2 34 | inputs: 35 | command: pack 36 | packagesToPack: '**/DotNetDetour.csproj' 37 | versioningScheme: byPrereleaseNumber 38 | majorVersion: '$(Major)' 39 | minorVersion: '$(Minor)' 40 | patchVersion: '$(Patch)' 41 | 42 | - task: NuGetCommand@2 43 | inputs: 44 | command: push 45 | nuGetFeedType: external 46 | publishFeedCredentials: 'nuget' 47 | versioningScheme: byEnvVar 48 | versionEnvVar: Version 49 | --------------------------------------------------------------------------------