├── HookingAssembly ├── HookingAssembly.sln ├── HookingAssembly │ ├── HookingAssembly.cs │ ├── HookingAssembly.csproj │ ├── HookingAssembly.snk │ ├── HookingAssemblyIEX.cs │ └── Properties │ │ └── AssemblyInfo.cs └── HookingLibrary │ ├── MinHook.x64.dll │ └── MinHook.x86.dll ├── LICENSE ├── README.md └── Slides ├── CodeBlue_1110.pdf └── CodeBlue_1110_JP.pdf /HookingAssembly/HookingAssembly.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 15 4 | VisualStudioVersion = 15.0.27004.2005 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "HookingAssembly", "HookingAssembly\HookingAssembly.csproj", "{593BB47C-E6AC-4DE9-BA90-8D4A4F7EA0B7}" 7 | EndProject 8 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{0901E57C-0B9C-473C-BDA8-1B5E086DE3BA}" 9 | ProjectSection(SolutionItems) = preProject 10 | ..\README.md = ..\README.md 11 | EndProjectSection 12 | EndProject 13 | Global 14 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 15 | Debug|Any CPU = Debug|Any CPU 16 | Debug|x64 = Debug|x64 17 | Debug|x86 = Debug|x86 18 | Release|Any CPU = Release|Any CPU 19 | Release|x64 = Release|x64 20 | Release|x86 = Release|x86 21 | EndGlobalSection 22 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 23 | {593BB47C-E6AC-4DE9-BA90-8D4A4F7EA0B7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 24 | {593BB47C-E6AC-4DE9-BA90-8D4A4F7EA0B7}.Debug|Any CPU.Build.0 = Debug|Any CPU 25 | {593BB47C-E6AC-4DE9-BA90-8D4A4F7EA0B7}.Debug|x64.ActiveCfg = Debug|Any CPU 26 | {593BB47C-E6AC-4DE9-BA90-8D4A4F7EA0B7}.Debug|x64.Build.0 = Debug|Any CPU 27 | {593BB47C-E6AC-4DE9-BA90-8D4A4F7EA0B7}.Debug|x86.ActiveCfg = Debug|Any CPU 28 | {593BB47C-E6AC-4DE9-BA90-8D4A4F7EA0B7}.Debug|x86.Build.0 = Debug|Any CPU 29 | {593BB47C-E6AC-4DE9-BA90-8D4A4F7EA0B7}.Release|Any CPU.ActiveCfg = Release|Any CPU 30 | {593BB47C-E6AC-4DE9-BA90-8D4A4F7EA0B7}.Release|Any CPU.Build.0 = Release|Any CPU 31 | {593BB47C-E6AC-4DE9-BA90-8D4A4F7EA0B7}.Release|x64.ActiveCfg = Release|Any CPU 32 | {593BB47C-E6AC-4DE9-BA90-8D4A4F7EA0B7}.Release|x64.Build.0 = Release|Any CPU 33 | {593BB47C-E6AC-4DE9-BA90-8D4A4F7EA0B7}.Release|x86.ActiveCfg = Release|Any CPU 34 | {593BB47C-E6AC-4DE9-BA90-8D4A4F7EA0B7}.Release|x86.Build.0 = Release|Any CPU 35 | EndGlobalSection 36 | GlobalSection(SolutionProperties) = preSolution 37 | HideSolutionNode = FALSE 38 | EndGlobalSection 39 | GlobalSection(ExtensibilityGlobals) = postSolution 40 | SolutionGuid = {76DA305D-BE60-43F2-A05A-A45C9218AF88} 41 | EndGlobalSection 42 | EndGlobal 43 | -------------------------------------------------------------------------------- /HookingAssembly/HookingAssembly/HookingAssembly.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Diagnostics; 3 | using System.Reflection; 4 | using System.Runtime.CompilerServices; 5 | using System.Runtime.InteropServices; 6 | using System.Security; 7 | using static HookingAssembly.MinHook.NativeMethods; 8 | 9 | namespace HookingAssembly 10 | { 11 | // 12 | // An AppDomainManager derived class used to be loaded automatically. 13 | // 14 | public class CustomeAppDomainManager1 : AppDomainManager 15 | { 16 | private readonly HookScanContent m_HookScanContent = new HookScanContent(); 17 | } 18 | 19 | // 20 | // An implementation of .NET native code hocking against the ScanContent 21 | // method. 22 | // 23 | internal class HookScanContent 24 | { 25 | private static readonly AssemblyLoadEventHandler s_EventHandler = 26 | new AssemblyLoadEventHandler(OnAssemblyLoad); 27 | 28 | // 29 | // Constructor. Starts monitoring of assembly loading to detect a 30 | // target assembly (ie, System.Management.Automation). 31 | // 32 | internal 33 | HookScanContent ( 34 | ) 35 | { 36 | if (!AppDomain.CurrentDomain.IsDefaultAppDomain()) 37 | { 38 | return; 39 | } 40 | 41 | AppDomain.CurrentDomain.AssemblyLoad += s_EventHandler; 42 | Console.WriteLine("[*] AssemblyLoad event handler registered."); 43 | } 44 | 45 | // 46 | // An assembly load event handler. 47 | // 48 | private 49 | static 50 | void 51 | OnAssemblyLoad ( 52 | object Sender, 53 | AssemblyLoadEventArgs Args 54 | ) 55 | { 56 | // 57 | // STEP1: Wait for System.Management.Automation (SMA) 58 | // 59 | string assemblyName = Args.LoadedAssembly.GetName().Name; 60 | Console.WriteLine("[*] Loading assembly " + assemblyName); 61 | if (assemblyName != "System.Management.Automation") 62 | { 63 | return; 64 | } 65 | 66 | AppDomain.CurrentDomain.AssemblyLoad -= s_EventHandler; 67 | Assembly smaAssembly = Args.LoadedAssembly; 68 | 69 | // 70 | // You may want to break into a debugger for debugging. 71 | // 72 | //Debugger.Launch(); 73 | 74 | // 75 | // STEP2: Determine a version of SMA 76 | // 77 | // Need a version of SMA since the ScanContent method exists only 78 | // in PowerShell v5 or later. PowerShell version can be obtained 79 | // via the PSVersion property of the PSVersionInfo class. 80 | // 81 | Type psVersionInfo = smaAssembly.GetType( 82 | "System.Management.Automation.PSVersionInfo"); 83 | PropertyInfo psVersion = psVersionInfo.GetProperty( 84 | "PSVersion", 85 | BindingFlags.Static | BindingFlags.NonPublic, 86 | null, 87 | typeof(Version), 88 | Type.EmptyTypes, 89 | null); 90 | var version = (Version)psVersion.GetValue(null, null); 91 | if (version.Major != 5) 92 | { 93 | Console.WriteLine("[-] Unsupported PowerShell version detected."); 94 | return; 95 | } 96 | 97 | // 98 | // STEP3: Find methods via reflection 99 | // 100 | // We need tree methods per target method: 101 | // target - A method to be hooked. It normally exists out side of 102 | // our code. 103 | // handler - A detour method to be called instead of the target 104 | // method after a hook is installed. 105 | // trampoline - A method used to call an original of the target 106 | // method after a hook is installed. 107 | // 108 | const BindingFlags anyType = BindingFlags.Static | 109 | BindingFlags.Instance | 110 | BindingFlags.Public | 111 | BindingFlags.NonPublic; 112 | 113 | // 114 | // Indicates what parameters the methods take. Reflection requires 115 | // this information on the top of a method name since a method can 116 | // be overloaded for a different set of parameters. 117 | // 118 | // In our case, the methods are defined as follows: 119 | // static AMSI_RESULT ScanContent(string Content, 120 | // string SourceMetadata); 121 | // static AMSI_RESULT ScanContentHookHandler(string Content, 122 | // string SourceMetadata); 123 | // static AMSI_RESULT ScanContentTrampoline(string Content, 124 | // string SourceMetadata); 125 | // 126 | var targetMethodType = new Type[] { typeof(string), typeof(string), }; 127 | var handlerMethodType = new Type[] { typeof(string), typeof(string), }; 128 | var trampolineMethodType = new Type[] { typeof(string), typeof(string), }; 129 | 130 | Type targetMethodClass = smaAssembly.GetType( 131 | "System.Management.Automation.AmsiUtils"); 132 | Type handlerMethodClass = typeof(HookScanContent); 133 | Type trampolineMethodClass = typeof(HookScanContent); 134 | 135 | MethodInfo target = targetMethodClass.GetMethod( 136 | "ScanContent", 137 | anyType, 138 | null, 139 | targetMethodType, 140 | null); 141 | MethodInfo hookHandler = handlerMethodClass.GetMethod( 142 | "ScanContentHookHandler", 143 | anyType, 144 | null, 145 | handlerMethodType, 146 | null); 147 | MethodInfo trampoline = trampolineMethodClass.GetMethod( 148 | "ScanContentTrampoline", 149 | anyType, 150 | null, 151 | trampolineMethodType, 152 | null); 153 | 154 | // 155 | // STEP4: Get addresses of native code of the methods 156 | // 157 | RuntimeHelpers.PrepareMethod(target.MethodHandle); 158 | RuntimeHelpers.PrepareMethod(hookHandler.MethodHandle); 159 | RuntimeHelpers.PrepareMethod(trampoline.MethodHandle); 160 | 161 | IntPtr targetAddr = target.MethodHandle.GetFunctionPointer(); 162 | IntPtr hookHandlerAddr = hookHandler.MethodHandle.GetFunctionPointer(); 163 | IntPtr trampolineAddr = trampoline.MethodHandle.GetFunctionPointer(); 164 | 165 | // 166 | // STEP5: Install a hook on to the target method 167 | // 168 | // Overwrite native code of the ScanContent method. This is standard 169 | // inline hooking, only differences are that we initiate hooking 170 | // from C# (typically C/C++) and a target is compiled .NET native 171 | // code. 172 | // 173 | // This example code uses MinHook (https://github.com/TsudaKageyu/minhook) 174 | // for installing hooks since the author did not find any suitable 175 | // inline hooking library for C#. 176 | // 177 | if (!MinHook.InstallHook(targetAddr, hookHandlerAddr, trampolineAddr)) 178 | { 179 | return; 180 | } 181 | 182 | // 183 | // STEP6: PROFIT! 184 | // 185 | Console.WriteLine("[*] The ScanContent method has been hooked."); 186 | } 187 | 188 | private enum AMSI_RESULT 189 | { 190 | AMSI_RESULT_CLEAN = 0, 191 | AMSI_RESULT_NOT_DETECTED = 1, 192 | AMSI_RESULT_BLOCKED_BY_ADMIN_START = 16384, 193 | AMSI_RESULT_BLOCKED_BY_ADMIN_END = 20479, 194 | AMSI_RESULT_DETECTED = 32768, 195 | } 196 | 197 | // 198 | // A methods that is executed when the target method (ie, ScanContent) 199 | // is called after a hook is installed. 200 | // 201 | private 202 | static 203 | AMSI_RESULT 204 | ScanContentHookHandler ( 205 | string Content, 206 | string SourceMetadata 207 | ) 208 | { 209 | Console.WriteLine("[EMULATOR] " + Content); 210 | 211 | // 212 | // Perform our own scan here. If maliciousness is detected, return 213 | // AMSI_RESULT_DETECTED. 214 | // 215 | const string eicar = 216 | @"X5O!P%@AP[4\PZX54(P^)7CC)7}$EICAR-STANDARD-ANTIVIRUS-TEST-FILE!$H+H*"; 217 | if (Content.IndexOf(eicar) != -1) 218 | { 219 | return AMSI_RESULT.AMSI_RESULT_DETECTED; 220 | } 221 | 222 | // 223 | // Call the original implementation of the ScanContent otherwise. 224 | // 225 | return ScanContentTrampoline(Content, SourceMetadata); 226 | } 227 | 228 | // 229 | // A dummy method that is overwritten to jump to the original 230 | // implementation of the ScanContent method. 231 | // 232 | [MethodImpl(MethodImplOptions.NoInlining)] 233 | private 234 | static 235 | AMSI_RESULT 236 | ScanContentTrampoline ( 237 | string Content, 238 | string SourceMetadata 239 | ) 240 | { 241 | // 242 | // This should never happen. 243 | // 244 | // NB: Be careful with updating the 'trampoline' method. It must 245 | // be large enough to be safely overwritten with the STEP5 above. 246 | // Making the 'trampoline' method smaller than necessary bytes to 247 | // install JMP could corrupt other, irrelevant method on memory. 248 | // For example, this Trace.Assert cannot be Debug.Assert to keep a 249 | // size of this method on release build. 250 | // 251 | Trace.Assert(false); 252 | throw new Exception("It is a bug. Fix it bro!"); 253 | } 254 | 255 | } 256 | 257 | #region MinHook specific. You very likely need your code for hooking. 258 | internal static class MinHook 259 | { 260 | // 261 | // Helper function to install hook using MinHook. 262 | // 263 | internal 264 | static 265 | bool 266 | InstallHook ( 267 | IntPtr TargetAddr, 268 | IntPtr HookHandlerAddr, 269 | IntPtr TrampolineAddr 270 | ) 271 | { 272 | // 273 | // This code expects either MinHook.x86.dll or MinHook.x64.dll is 274 | // located in any of the DLL search path. Such as the current folder 275 | // and %PATH%. 276 | // 277 | string architecture = (IntPtr.Size == 4) ? "x86" : "x64"; 278 | string dllPath = "MinHook." + architecture + ".dll"; 279 | IntPtr moduleHandle = LoadLibrary(dllPath); 280 | if (moduleHandle == IntPtr.Zero) 281 | { 282 | Console.WriteLine("[-] An inline hook DLL not found. Did you locate " + 283 | dllPath + " under the DLL search path?"); 284 | return false; 285 | } 286 | 287 | var MH_Initialize = GetExport(moduleHandle, "MH_Initialize"); 288 | var MH_CreateHook = GetExport(moduleHandle, "MH_CreateHook"); 289 | var MH_EnableHook = GetExport(moduleHandle, "MH_EnableHook"); 290 | 291 | 292 | MH_STATUS status = MH_Initialize(); 293 | Trace.Assert(status == MH_STATUS.MH_OK); 294 | 295 | // 296 | // Modify the target method to jump to the HookHandler method. The 297 | // original receives an address of trampoline code to call the 298 | // original implementation of the target method. 299 | // 300 | status = MH_CreateHook(TargetAddr, HookHandlerAddr, out IntPtr original); 301 | Trace.Assert(status == MH_STATUS.MH_OK); 302 | 303 | // 304 | // Modify the Trampoline method to jump to the original 305 | // implementation of the target method. 306 | // 307 | status = MH_CreateHook(TrampolineAddr, original, out _); 308 | Trace.Assert(status == MH_STATUS.MH_OK); 309 | 310 | // 311 | // Commit and activate the above two hooks. 312 | // 313 | status = MH_EnableHook(MH_ALL_HOOKS); 314 | Trace.Assert(status == MH_STATUS.MH_OK); 315 | 316 | return true; 317 | } 318 | 319 | // 320 | // Helper function to resolve an export of a DLL. 321 | // 322 | private 323 | static 324 | ProcType 325 | GetExport ( 326 | IntPtr ModuleHandle, 327 | string ExportName 328 | ) where ProcType : class 329 | { 330 | // 331 | // Get a function pointer, convert it to delegate, and return it as 332 | // a requested type. 333 | // 334 | IntPtr pointer = GetProcAddress(ModuleHandle, ExportName); 335 | if (pointer == IntPtr.Zero) 336 | { 337 | return null; 338 | } 339 | 340 | Delegate function = Marshal.GetDelegateForFunctionPointer( 341 | pointer, 342 | typeof(ProcType)); 343 | return function as ProcType; 344 | } 345 | 346 | [SuppressUnmanagedCodeSecurity] 347 | internal static class NativeMethods 348 | { 349 | [DllImport("kernel32.dll", 350 | EntryPoint = "LoadLibraryW", 351 | SetLastError = true, 352 | CharSet = CharSet.Unicode)] 353 | internal 354 | static 355 | extern 356 | IntPtr 357 | LoadLibrary ( 358 | string FileName 359 | ); 360 | 361 | [DllImport("kernel32.dll", 362 | EntryPoint = "GetProcAddress", 363 | SetLastError = true, 364 | CharSet = CharSet.Ansi, 365 | BestFitMapping = false)] 366 | internal 367 | static 368 | extern 369 | IntPtr 370 | GetProcAddress ( 371 | IntPtr Module, 372 | string ProcName 373 | ); 374 | 375 | // 376 | // MinHook specific. 377 | // 378 | internal static IntPtr MH_ALL_HOOKS = IntPtr.Zero; 379 | internal enum MH_STATUS 380 | { 381 | MH_OK = 0, 382 | } 383 | 384 | [UnmanagedFunctionPointer(CallingConvention.Winapi)] 385 | internal 386 | delegate 387 | MH_STATUS 388 | MH_InitializeType ( 389 | ); 390 | 391 | [UnmanagedFunctionPointer(CallingConvention.Winapi)] 392 | internal 393 | delegate 394 | MH_STATUS 395 | MH_CreateHookType ( 396 | IntPtr Target, 397 | IntPtr Detour, 398 | out IntPtr Original 399 | ); 400 | 401 | [UnmanagedFunctionPointer(CallingConvention.Winapi)] 402 | internal 403 | delegate 404 | MH_STATUS 405 | MH_EnableHookType ( 406 | IntPtr Target 407 | ); 408 | } 409 | } 410 | #endregion 411 | } 412 | -------------------------------------------------------------------------------- /HookingAssembly/HookingAssembly/HookingAssembly.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | Debug 6 | AnyCPU 7 | {593BB47C-E6AC-4DE9-BA90-8D4A4F7EA0B7} 8 | Library 9 | Properties 10 | HookingAssembly 11 | HookingAssembly 12 | v2.0 13 | 512 14 | 15 | 16 | 17 | true 18 | full 19 | false 20 | bin\Debug\ 21 | DEBUG;TRACE 22 | prompt 23 | 4 24 | 25 | 26 | pdbonly 27 | true 28 | bin\Release\ 29 | TRACE 30 | prompt 31 | 4 32 | 33 | 34 | true 35 | 36 | 37 | HookingAssembly.snk 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | copy "$(SolutionDir)HookingLibrary\*.dll" "$(TargetDir)" /y 55 | 56 | -------------------------------------------------------------------------------- /HookingAssembly/HookingAssembly/HookingAssembly.snk: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tandasat/DotNetHooking/bc589edc62834b15d5ef7c5ccab1072f427e88de/HookingAssembly/HookingAssembly/HookingAssembly.snk -------------------------------------------------------------------------------- /HookingAssembly/HookingAssembly/HookingAssemblyIEX.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Diagnostics; 3 | using System.Reflection; 4 | using System.Runtime.CompilerServices; 5 | using System.Security; 6 | 7 | namespace HookingAssembly 8 | { 9 | // 10 | // An AppDomainManager derived class used to be loaded automatically. 11 | // 12 | public class CustomeAppDomainManager2 : AppDomainManager 13 | { 14 | private readonly HookProcessRecord m_HookProcessRecord = new HookProcessRecord(); 15 | } 16 | 17 | // 18 | // An implementation of .NET native code hocking against the ProcessRecord 19 | // method. 20 | // 21 | internal class HookProcessRecord 22 | { 23 | private static readonly AssemblyLoadEventHandler s_EventHandler = 24 | new AssemblyLoadEventHandler(OnAssemblyLoad); 25 | 26 | // 27 | // Constructor. Starts monitoring of assembly loading to detect a 28 | // target assembly (ie, Microsoft.PowerShell.Commands.Utility). 29 | // 30 | internal 31 | HookProcessRecord ( 32 | ) 33 | { 34 | if (!AppDomain.CurrentDomain.IsDefaultAppDomain()) 35 | { 36 | return; 37 | } 38 | 39 | AppDomain.CurrentDomain.AssemblyLoad += s_EventHandler; 40 | Console.WriteLine("[*] AssemblyLoad event handler registered."); 41 | } 42 | 43 | // 44 | // An assembly load event handler. 45 | // 46 | private 47 | static 48 | void 49 | OnAssemblyLoad ( 50 | object Sender, 51 | AssemblyLoadEventArgs Args 52 | ) 53 | { 54 | // 55 | // STEP1: Wait for Microsoft.PowerShell.Commands.Utility (MPCU) 56 | // 57 | string assemblyName = Args.LoadedAssembly.GetName().Name; 58 | Console.WriteLine("[*] Loading assembly " + assemblyName); 59 | if (assemblyName != "Microsoft.PowerShell.Commands.Utility") 60 | { 61 | return; 62 | } 63 | 64 | AppDomain.CurrentDomain.AssemblyLoad -= s_EventHandler; 65 | Assembly mpcuAssembly = Args.LoadedAssembly; 66 | 67 | // 68 | // You may want to break into a debugger for debugging. 69 | // 70 | //Debugger.Launch(); 71 | 72 | // 73 | // STEP2: Find methods via reflection 74 | // 75 | const BindingFlags anyType = BindingFlags.Static | 76 | BindingFlags.Instance | 77 | BindingFlags.Public | 78 | BindingFlags.NonPublic; 79 | 80 | var targetMethodType = Type.EmptyTypes; 81 | var handlerMethodType = new Type[] { typeof(HookProcessRecord), }; 82 | var trampolineMethodType = Type.EmptyTypes; 83 | 84 | Type targetMethodClass = mpcuAssembly.GetType( 85 | "Microsoft.PowerShell.Commands.InvokeExpressionCommand"); 86 | Type handlerMethodClass = typeof(HookProcessRecord); 87 | Type trampolineMethodClass = typeof(HookProcessRecord); 88 | 89 | MethodInfo target = targetMethodClass.GetMethod( 90 | "ProcessRecord", 91 | anyType, 92 | null, 93 | targetMethodType, 94 | null); 95 | MethodInfo hookHandler = handlerMethodClass.GetMethod( 96 | "ProcessRecordHookHandler", 97 | anyType, 98 | null, 99 | handlerMethodType, 100 | null); 101 | MethodInfo trampoline = trampolineMethodClass.GetMethod( 102 | "ProcessRecordTrampoline", 103 | anyType, 104 | null, 105 | trampolineMethodType, 106 | null); 107 | 108 | // 109 | // STEP4: Get addresses of native code of the methods 110 | // 111 | RuntimeHelpers.PrepareMethod(target.MethodHandle); 112 | RuntimeHelpers.PrepareMethod(hookHandler.MethodHandle); 113 | RuntimeHelpers.PrepareMethod(trampoline.MethodHandle); 114 | 115 | IntPtr targetAddr = target.MethodHandle.GetFunctionPointer(); 116 | IntPtr hookHandlerAddr = hookHandler.MethodHandle.GetFunctionPointer(); 117 | IntPtr trampolineAddr = trampoline.MethodHandle.GetFunctionPointer(); 118 | 119 | // 120 | // STEP5: Install a hook on to the target method 121 | // 122 | if (!MinHook.InstallHook(targetAddr, hookHandlerAddr, trampolineAddr)) 123 | { 124 | return; 125 | } 126 | 127 | // 128 | // STEP6: PROFIT! 129 | // 130 | Console.WriteLine("[*] The ProcessRecord method has been hooked."); 131 | } 132 | 133 | // 134 | // A methods that is executed when the target method (ie, ProcessRecord) 135 | // is called after a hook is installed. 136 | // 137 | private 138 | static 139 | void 140 | ProcessRecordHookHandler ( 141 | HookProcessRecord ThisObject 142 | ) 143 | { 144 | // 145 | // Get content of the _command field through the Command property. 146 | // 147 | const BindingFlags anyInstanceType = BindingFlags.Instance | 148 | BindingFlags.Public | 149 | BindingFlags.NonPublic; 150 | 151 | PropertyInfo property = ThisObject.GetType().GetProperty("Command", anyInstanceType); 152 | var Content = (string)property.GetValue(ThisObject, null); 153 | 154 | // 155 | // A file name is unavailable. 156 | // 157 | Console.WriteLine("[IEX] " + Content); 158 | 159 | // 160 | // Perform our own scan here. If maliciousness is detected, throw an 161 | // exception with a message to display. 162 | // 163 | const string eicar = 164 | @"X5O!P%@AP[4\PZX54(P^)7CC)7}$EICAR-STANDARD-ANTIVIRUS-TEST-FILE!$H+H*"; 165 | if (Content.IndexOf(eicar) != -1) 166 | { 167 | const string detectionMessage = 168 | "This script contains malicious content and has been blocked by" + 169 | " your antivirus software."; 170 | 171 | throw new SecurityException(detectionMessage); 172 | } 173 | 174 | // 175 | // Call the original implementation of the ProcessRecord otherwise. 176 | // 177 | ThisObject.ProcessRecordTrampoline(); 178 | } 179 | 180 | // 181 | // A dummy method that is overwritten to jump to the original 182 | // implementation of the ProcessRecord method. 183 | // 184 | [MethodImpl(MethodImplOptions.NoInlining)] 185 | private 186 | void 187 | ProcessRecordTrampoline ( 188 | ) 189 | { 190 | // 191 | // This should never happen. 192 | // 193 | // NB: Be careful with updating the 'trampoline' method. It must 194 | // be large enough to be safely overwritten with the STEP5 above. 195 | // Making the 'trampoline' method smaller than necessary bytes to 196 | // install JMP could corrupt other, irrelevant method on memory. 197 | // For example, this Trace.Assert cannot be Debug.Assert to keep a 198 | // size of this method on release build. 199 | // 200 | Trace.Assert(false); 201 | throw new Exception("It is a bug. Fix it bro!"); 202 | } 203 | } 204 | } 205 | -------------------------------------------------------------------------------- /HookingAssembly/HookingAssembly/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("HookingAssembly")] 9 | [assembly: AssemblyDescription("")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("")] 12 | [assembly: AssemblyProduct("HookingAssembly")] 13 | [assembly: AssemblyCopyright("Copyright © 2017")] 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("593bb47c-e6ac-4de9-ba90-8d4a4f7ea0b7")] 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 | -------------------------------------------------------------------------------- /HookingAssembly/HookingLibrary/MinHook.x64.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tandasat/DotNetHooking/bc589edc62834b15d5ef7c5ccab1072f427e88de/HookingAssembly/HookingLibrary/MinHook.x64.dll -------------------------------------------------------------------------------- /HookingAssembly/HookingLibrary/MinHook.x86.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tandasat/DotNetHooking/bc589edc62834b15d5ef7c5ccab1072f427e88de/HookingAssembly/HookingLibrary/MinHook.x86.dll -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Satoshi Tanda 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | DotNetHooking 2 | ============== 3 | 4 | Introduction 5 | ------------- 6 | This project demonstrates how to use the .NET native code hooking technique. For 7 | more details of the technique, see the attached presentation slides. 8 | 9 | Source Navigation 10 | -------------- 11 | The high level flow of this code is: 12 | 1. This assembly is loaded via a mechanism of AppDomainManager 13 | 2. The HookScanContent class is instantiated registering an assembly load 14 | event handler 15 | 3. When System.Management.Automation, which contains implementation of our 16 | target method "ScanContent", is loaded, this assembly locates its native 17 | code address and installs a hook on it to redirect to the 18 | ScanContentHookHandler method 19 | 4. When PowerShell content is executed and the ScanContent is called, our 20 | ScanContentHookHandler is executed instead of original ScanContent 21 | 22 | Hints 23 | ------ 24 | Few things worth noting: 25 | 1. This project targets .NET 2.0. This lets this assembly be loadable on 26 | practically any platforms since .NET Framework 2.0 is installed by 27 | default since Windows 7, and such an assembly can be loaded into a 28 | process using a newer version of .NET Framework. Therefore, such an 29 | assembly can be loaded into through PowerShell v2 to v5 universally. 30 | 2. This assembly is signed and compiled as a strongly named assembly. This 31 | allows this assembly to be registered with Global Assembly Cache (GAC). 32 | Registering with GAC is required to load this assembly into any process 33 | because CLR cannot find this assembly when this assembly is registered 34 | as an AppDomainManager but not located in the folder where an EXE file 35 | of the process exists or GAC either. Registering this assembly with GAC 36 | allows CLR to find it regardless of where the EXE file exists. 37 | 3. Code in this project intentionally emits error checks or exception 38 | handling. One using this code should add error handling as necessary. 39 | 40 | Installation 41 | ---------- 42 | As noted above, this assembly must be registered with GAC or located in 43 | the same folder as a target executable file (powershell.exe, in our case). 44 | While registering with GAC will be required in the real use cases, skipping 45 | registration is more convenient for debugging and testing. The below is the 46 | instructions for both ways: 47 | 48 | No GAC Installation (+ testing with locally copied powershell.exe) 49 | 50 | 1. Build the solution with Visual Studio 2017 51 | 2. Launch the command prompt and navigate to an output folder 52 | 53 | > cd 54 | 55 | 3. Copy powershell.exe to the current folder for testing 56 | 57 | > copy C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe . /y 58 | 59 | 4. Set environment variable to specify a custom AppDomainManager 60 | 61 | > set APPDOMAIN_MANAGER_ASM=HookingAssembly, Version=1.0.0.0, Culture=neutral, PublicKeyToken=c8b8e7ea5047757d, processorArchitecture=MSIL 62 | > set APPDOMAIN_MANAGER_TYPE=HookingAssembly.CustomeAppDomainManager1 63 | 64 | 5. Start the copied powershell.exe 65 | 66 | > powershell.exe 67 | [*] AssemblyLoad event handler registered. 68 | [*] Loading assembly System 69 | [*] Loading assembly Microsoft.PowerShell.ConsoleHost 70 | [*] Loading assembly System.Management.Automation 71 | [*] The ScanContent method has been hooked. 72 | Windows PowerShell 73 | Copyright (C) Microsoft Corporation.All rights reserved. 74 | 75 | GAC Installation (+ powershell.exe) 76 | 1. Build the solution with Visual Studio 2017 77 | 2. Launch the elevated command prompt for Visual Studio 2017 and navigate to an 78 | output folder 79 | 80 | > cd 81 | 82 | 3. Install the assembly to GAC 83 | 84 | > gacutil /i HookingAssembly.dll 85 | Microsoft (R) .NET Global Assembly Cache Utility.Version 4.0.30319.0 86 | Copyright (c) Microsoft Corporation.All rights reserved. 87 | 88 | Assembly successfully added to the cache 89 | 90 | 4. Set environment variable to specify a custom AppDomainManager 91 | 92 | > set APPDOMAIN_MANAGER_ASM=HookingAssembly, Version=1.0.0.0, Culture=neutral, PublicKeyToken=c8b8e7ea5047757d, processorArchitecture=MSIL 93 | > set APPDOMAIN_MANAGER_TYPE=HookingAssembly.CustomeAppDomainManager1 94 | 95 | 5. Add the current folder to %PATH%, so that the hooking DLLs can be found 96 | 97 | > set PATH=%PATH%;%~dp0 98 | 99 | 6. Start powershell.exe 100 | 101 | > powershell 102 | 103 | To uninstall the assembly from GAC: 104 | 105 | > gacutil /u HookingAssembly 106 | 107 | 108 | Simulate Detection by AMSI 109 | --------------------------- 110 | 111 | On the hooked PowerShell session, run this command: 112 | 113 | PS> 'X5O!P%@AP[4\PZX54(P^)7CC)7}$EICAR-STANDARD-ANTIVIRUS-TEST-FILE!$H+H*' 114 | 115 | At line:1 char:1 116 | + 'X5O!P%@AP[4\PZX54(P^)7CC)7}$EICAR-STANDARD-ANTIVIRUS-TEST-FILE!$H+H* ... 117 | + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 118 | This script contains malicious content and has been blocked by your antivirus software. 119 | + CategoryInfo : ParserError: (:) [], ParentContainsErrorRecordException 120 | + FullyQualifiedErrorId : ScriptContainedMaliciousContent 121 | -------------------------------------------------------------------------------- /Slides/CodeBlue_1110.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tandasat/DotNetHooking/bc589edc62834b15d5ef7c5ccab1072f427e88de/Slides/CodeBlue_1110.pdf -------------------------------------------------------------------------------- /Slides/CodeBlue_1110_JP.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tandasat/DotNetHooking/bc589edc62834b15d5ef7c5ccab1072f427e88de/Slides/CodeBlue_1110_JP.pdf --------------------------------------------------------------------------------