EasyHook A helper class for determining the address of COM object functions for hooking given a COM class id (CLSID) and COM interface id (IID), or COM class type and COM interface type. The following three examples result in the same output: // 1. Use imported Class and Interface Types COMClassInfo cci1 = new COMClassInfo(typeof(CLSID_DirectInputDevice8), typeof(IID_IDirectInputDevice8W), "GetCapabilities"); // 2. Use Guid from class and interface types COMClassInfo cci2 = new COMClassInfo(typeof(CLSID_DirectInputDevice8).GUID, typeof(IID_IDirectInputDevice8W).GUID, 3); // 3. Use class and interface Guids directly (no need to have class and interface types defined) COMClassInfo cci3 = new COMClassInfo(new Guid("25E609E5-B259-11CF-BFC7-444553540000"), new Guid("54D41081-DC15-4833-A41B-748F73A38179"), 3); // Will output False if dinput8.dll is not already loaded Console.WriteLine(cci1.IsModuleLoaded()); cci1.Query(); cci2.Query(); cci3.Query(); // Will output True as dinput8.dll will be loaded by .Query() if not already Console.WriteLine(cci1.IsModuleLoaded()); // Output the function pointers we queried Console.WriteLine(cci1.FunctionPointers[0]); Console.WriteLine(cci2.FunctionPointers[0]); Console.WriteLine(cci3.FunctionPointers[0]); ... [ComVisible(true)] [Guid("25E609E5-B259-11CF-BFC7-444553540000")] public class CLSID_DirectInputDevice8 { } [ComVisible(true)] [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] [Guid("54D41081-DC15-4833-A41B-748F73A38179")] public interface IID_IDirectInputDevice8W { /*** IDirectInputDevice8W methods ***/ int GetCapabilities(IntPtr deviceCaps); // fourth method due to IUnknown methods QueryInterface, AddRef and Release // other methods... } Creates a new COMClassInfo using the COM class and interface types. The function names to retrieve the addresses for should be provided as strings The COM object's class type The COM object's interface type The methods to retrieve addresses for Creates a new COMClassInfo instance using the COM class and interface Guids. The function indexes to retrieve the addresses for as defined by the order of the methods in the COM interface. The class id (CLSID) of the COM object The interface id (IID) of the COM interface. This interface MUST inherit from IUnknown. One or more method indexes to retrieve the address for. Index 0 == QueryInterface, 1 == AddRef, 2 == Release, 3 == first method and so on, i.e. the order that the methods appear in the interface's C++ header file. Will contain the method addresses after a call to . The index corresponds to the order method names / indexes are passed into or . Retrieve the module for the COM class. Only available after a call to . Query the COM class for the specified method addresses. If not already loaded the COM module will be loaded. True if the COM class exists, False otherwise. Thrown if the method index extends beyond the interface and into protected memory. If the provided interface type is not an interface, or the class type is not visible to COM. Thrown if the class instance does not support the specified interface. Determines if the module containing the COM class is already loaded True if the module is loaded, otherwise False Currently only provides a mechanism to register assemblies in the GAC. The following demonstrates how to use and : using System; using System.Collections.Generic; using System.Runtime.Remoting; using System.Text; using System.IO; using EasyHook; namespace FileMon { public class FileMonInterface : MarshalByRefObject { public void IsInstalled(Int32 InClientPID) { Console.WriteLine("FileMon has been installed in target {0}.\r\n", InClientPID); } public void OnCreateFile(Int32 InClientPID, String[] InFileNames) { for (int i = 0; i < InFileNames.Length; i++) { Console.WriteLine(InFileNames[i]); } } public void ReportException(Exception InInfo) { Console.WriteLine("The target process has reported an error:\r\n" + InInfo.ToString()); } public void Ping() { } } class Program { static String ChannelName = null; static void Main(string[] args) { try { Config.Register( "A FileMon like demo application.", "FileMon.exe", "FileMonInject.dll"); RemoteHooking.IpcCreateServer<FileMonInterface>(ref ChannelName, WellKnownObjectMode.SingleCall); RemoteHooking.Inject( Int32.Parse(args[0]), "FileMonInject.dll", "FileMonInject.dll", ChannelName); Console.ReadLine(); } catch (Exception ExtInfo) { Console.WriteLine("There was an error while connecting to target:\r\n{0}", ExtInfo.ToString()); } } } } The path where dependant files, like EasyHook(32|64)Svc.exe are stored. Defaults to no path being specified. The path where helper files, like EasyHook(32|64).dll are stored. Defaults to the location of the assembly containing the Config type Get the directory name of the current process, ending with a backslash. Directory name of the current process Get the name of the EasyHook SVC executable. Automatically determine whether to use the 64-bit or the 32-bit version. Executable name Get the name of the EasyHook SVC executable to use for WOW64 bypass. If this process is 64-bit, return the 32-bit service executable and vice versa. Get the EasyHook SVC executable name with the custom dependency path prepended. Full path to the executable REQUIRES ADMIN PRIVILEGES. Installs EasyHook and all given user NET assemblies into the GAC and ensures that all references are cleaned up if the installing application is shutdown. Cleanup does not depend on the calling application... ATTENTION: There are some problems when debugging processes whose libraries are added to the GAC. Visual Studio won't start the debug session! There is only one chance for you to workaround this issue if you want to install libraries AND debug them simultanously. This is simply to debug only one process which is the default setting of Visual Studio. Because the libraries are added to the GAC AFTER Visual Studio has initialized the debug session, there won't be any conflicts; at least so far... In debug versions of EasyHook, you may also check the "Application" event log, which holds additional information about the GAC registration, after calling this method. In general this method works transactionally. This means if something goes wrong, the GAC state of all related libraries won't be violated! The problem with NET assemblies is that the CLR only searches the GAC and directories starting with the application base directory for assemblies. To get injected assemblies working either all of them have to be located under the target base directory (which is not suitable in most cases) or reside in the GAC. EasyHook provides a way to automatically register all of its own assemblies and custom ones temporarily in the GAC. It also ensures that all of these assemblies are removed if the installing process exists. So you don't need to care about and may write applications according to the XCOPY standard. If your application ships with an installer, you may statically install all of your assemblies and the ones of EasyHook into the GAC. In this case just don't call . Of course EasyHook does also take care of multiple processes using the same injection libraries. So if two processes are sharing some of those DLLs, a stable reference counter ensures that the libraries are kept in the GAC if one process is terminated while the other continues running and so continues holding a proper GAC reference. Please note that in order to add your library to the GAC, it has to be a valid NET assembly and expose a so called "Strong Name". Assemblies without a strong name will be rejected by this method! A description under which the installed files should be referenced. A list of user assemblies as relative or absolute paths. At least one of the files specified could not be found! Unable to load at least one of the given files for reflection. At least one of the given files does not have a strong name. This class will provide various static members to be used with local hooking and is also the instance class of a hook. The following demonstrates how to use : using System; using System.Collections.Generic; using System.Text; using System.Threading; using System.Runtime.InteropServices; using EasyHook; namespace FileMonInject { public class Main : EasyHook.IEntryPoint { FileMon.FileMonInterface Interface; LocalHook CreateFileHook; Stack<String> Queue = new Stack<String> (); public Main( RemoteHooking.IContext InContext, String InChannelName) { // connect to host... Interface = RemoteHooking.IpcConnectClient<FileMon.FileMonInterface>(InChannelName); // validate connection... Interface.Ping(); } public void Run( RemoteHooking.IContext InContext, String InChannelName) { // install hook... try { CreateFileHook = LocalHook.Create( LocalHook.GetProcAddress("kernel32.dll", "CreateFileW"), new DCreateFile(CreateFile_Hooked), this); CreateFileHook.ThreadACL.SetExclusiveACL(new Int32[] { 0 }); } catch (Exception ExtInfo) { Interface.ReportException(ExtInfo); return; } Interface.IsInstalled(RemoteHooking.GetCurrentProcessId()); RemoteHooking.WakeUpProcess(); // wait for host process termination... try { while (true) { Thread.Sleep(500); // transmit newly monitored file accesses... if (Queue.Count > 0) { String[] Package = null; lock (Queue) { Package = Queue.ToArray(); Queue.Clear(); } Interface.OnCreateFile(RemoteHooking.GetCurrentProcessId(), Package); } else Interface.Ping(); } } catch { // Ping() will raise an exception if host is unreachable } } [UnmanagedFunctionPointer(CallingConvention.StdCall, CharSet = CharSet.Unicode, SetLastError = true)] delegate IntPtr DCreateFile( String InFileName, UInt32 InDesiredAccess, UInt32 InShareMode, IntPtr InSecurityAttributes, UInt32 InCreationDisposition, UInt32 InFlagsAndAttributes, IntPtr InTemplateFile); // just use a P-Invoke implementation to get native API access from C# (this step is not necessary for C++.NET) [DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true, CallingConvention = CallingConvention.StdCall)] static extern IntPtr CreateFile( String InFileName, UInt32 InDesiredAccess, UInt32 InShareMode, IntPtr InSecurityAttributes, UInt32 InCreationDisposition, UInt32 InFlagsAndAttributes, IntPtr InTemplateFile); // this is where we are intercepting all file accesses! static IntPtr CreateFile_Hooked( String InFileName, UInt32 InDesiredAccess, UInt32 InShareMode, IntPtr InSecurityAttributes, UInt32 InCreationDisposition, UInt32 InFlagsAndAttributes, IntPtr InTemplateFile) { try { Main This = (Main)HookRuntimeInfo.Callback; lock (This.Queue) { This.Queue.Push("[" + RemoteHooking.GetCurrentProcessId() + ":" + RemoteHooking.GetCurrentThreadId() + "]: \"" + InFileName + "\""); } } catch { } // call original API... return CreateFile( InFileName, InDesiredAccess, InShareMode, InSecurityAttributes, InCreationDisposition, InFlagsAndAttributes, InTemplateFile); } } } RIP relocation is disabled by default. If you want to enable it, just call this method which will attach a debugger to the current process. There may be circumstances under which this might fail and this is why it is not done by default. On 32-Bit system this method will always succeed and do nothing... Tries to get the underlying thread ID for a given handle. This is not always possible. The handle has to be opened with THREAD_QUERY_INFORMATION access. A valid thread handle. A valid thread ID associated with the given thread handle. The given handle was not opened with THREAD_QUERY_INFORMATION access. The handle is invalid. Should never occur and just notifies you that a handle to thread ID conversion is not available on the current platform. Tries to get the underlying process ID for a given handle. This is not always possible. The handle has to be opened with PROCESS_QUERY_INFORMATION access. A valid process handle. A valid process ID associated with the given process handle. The given handle was not opened with PROCESS_QUERY_INFORMATION access. The handle is invalid. Should never occur and just notifies you that a handle to thread ID conversion is not available on the current platform. Reads the kernel object name for a given windows usermode handle. Executes in approx. 100 micro secounds. This allows you to translate a handle back to the associated filename for example. But keep in mind that such names are only valid for kernel service routines, like NtCreateFile. You won't have success when calling CreateFile on such object names! The regular windows user mode API has some methods that will allow you to convert such kernelmode names back into usermode names. I know this because I did it some years ago but I've already forgotten how it has to be done! I can only give you some hints: FindFirstVolume(), FindFirstVolumeMountPoint(), QueryDosDevice(), GetVolumePathNamesForVolumeName() A valid usermode handle. The kernel object name associated with the given handle. The given handle is invalid or could not be accessed for unknown reasons. Ensures that each instance is always terminated with . The callback passed to . Returns the thread ACL associated with this hook. Refer to for more information about access negotiation. The underlying hook is already disposed. Returns the trampoline bypass address associated with this hook. The underlying hook has been disposed. Checks whether a given thread ID will be intercepted by the underlying hook. This method provides an interface to the internal negotiation algorithm. You may use it to check whether your ACL provides expected results. The following is a pseudo code of how this method is implemented: if(InThreadID == 0) InThreadID = GetCurrentThreadId(); if(GlobalACL.Contains(InThreadID)) { if(LocalACL.Contains(InThreadID)) { if(LocalACL.IsExclusive) return false; } else { if(GlobalACL.IsExclusive) return false; if(!LocalACL.IsExclusive) return false; } } else { if(LocalACL.Contains(InThreadID)) { if(LocalACL.IsExclusive) return false; } else { if(!GlobalACL.IsExclusive) return false; if(!LocalACL.IsExclusive) return false; } } return true; A native OS thread ID; or zero if you want to check the current thread. true if the thread is intercepted, false otherwise. The underlying hook is already disposed. Returns the gloabl thread ACL associated with ALL hooks. Refer to for more information about access negotiation. If you want to immediately uninstall a hook, the only way is to dispose it. A disposed hook is guaranteed to never invoke your handler again but may still consume memory even for process life-time! As we are living in a manged world, you don't have to dispose a hook because the next garbage collection will do it for you, assuming that your code does not reference it anymore. But there are times when you want to uninstall it excplicitly, with no delay. If you dispose a disposed or not installed hook, nothing will happen! Installs a managed hook. After this you'll have to activate it by setting a proper . Note that not all entry points are hookable! In general methods like CreateFileW won't cause any trouble. But there might be methods that are not hookable because their entry point machine code is not eligable to be hooked. You should test all hooks on common environments like "Windows XP x86/x64 SP2/SP3" and "Windows Vista x86/x64 (SP1)". This is the only way to ensure that your application will work well on most machines. Your handler delegate has to use the and shall map to the same native method signature, otherwise the application will crash! The best way is to use predefined delegates used in related P-Invoke implementations usually found with Google. If you know how to write such native delegates you won't need internet resources of course. I recommend using C++.NET which allows you to just copy the related windows API to your managed class and thread it as delegate without any changes. This will also speed up the whole thing because no unnecessary marshalling is required! C++.NET is also better in most cases because you may access the whole native windows API from managed code without any effort what significantly eases writing of hook handlers. The given delegate is automatically prevented from being garbage collected until the hook itself is collected... A target entry point that should be hooked. A handler with the same signature as the original entry point that will be invoked for every call that has passed the Fiber Deadlock Barrier and various integrity checks. An uninterpreted callback that will later be available through . A handle to the newly created hook. Not enough memory available to complete the operation. On 64-Bit this may also indicate that no memory can be allocated within a 31-Bit boundary around the given entry point. The given function pointer does not map to executable memory (valid machine code) or you passed null as delegate. The given entry point contains machine code that can not be hooked. The maximum amount of hooks has been installed. This is currently set to MAX_HOOK_COUNT (1024). Installs an unmanaged hook. After this you'll have to activate it by setting a proper . WON'T be supported! Refer to the native "LhBarrierXxx" APIs to access unmanaged hook runtime information. Note that not all entry points are hookable! In general methods like CreateFileW won't cause any trouble. But there may be methods that are not hookable because their entry point machine code is not eligable to be hooked. You should test all hooks on common environments like "Windows XP x86/x64 SP1/SP2/SP3" and "Windows Vista x86/x64 (SP1)". This is the only way to ensure that your application will work well on most machines. Unmanaged hooks will require a native DLL which handles the requests. This way you will get a high-performance interface, because a switch from unmanaged to managed code seems to be rather time consuming without doing anything useful (at least nothing visible); so a hook omitting this switch will be handled one or two orders of magnitudes faster until finally your handler gains execution. But as a managed hook is still executed within at last 1000 nano-seconds, even the "slow" managed implementation will be fast enough in most cases. With C++.NET you would be able to provide such native high-speed hooks for frequently called API methods, while still using managed ones for usual API methods, within a single assembly! A pure unmanaged, empty hook executes in approx. 70 nano-seconds, which is incredible fast considering the thread deadlock barrier and thread ACL negotiation that are already included in this benchmark! A target entry point that should be hooked. A handler with the same signature as the original entry point that will be invoked for every call that has passed the Thread Deadlock Barrier and various integrity checks. An uninterpreted callback that will later be available through LhBarrierGetCallback(). A handle to the newly created hook. Not enough memory available to complete the operation. On 64-Bit this may also indicate that no memory can be allocated within a 31-Bit boundary around the given entry point. The given function pointer does not map to executable memory (valid machine code) or you passed null as delegate. The given entry point contains machine code that can not be hooked. The maximum amount of hooks has been installed. This is currently set to MAX_HOOK_COUNT (1024). Will return the address for a given DLL export symbol. The specified module has to be loaded into the current process space and also export the given method. If you wonder how to get native entry points in a managed environment, this is the anwser. You will only be able to hook native code from a managed environment if you have access to a method like this, returning the native entry point. Please note that you will also hook any managed code, which of course ultimately relies on the native windows API! A system DLL name like "kernel32.dll" or a full qualified path to any DLL. An exported symbol name like "CreateFileW". The entry point for the given API method. The given module is not loaded into the current process. The given module does not export the desired method. Will return a delegate for a given DLL export symbol. The specified module has to be loaded into the current process space and also export the given method. This method is usually not useful to hook something but it allows you to dynamically load native API methods into your managed environment instead of using the static P-Invoke approach provided by . A delegate using the and exposing the same method signature as the specified native symbol. A system DLL name like "kernel32.dll" or a full qualified path to any DLL. An exported symbol name like "CreateFileW". The managed delegate wrapping around the given native symbol. The given module is not loaded into the current process. The given module does not export the given method. Processes any pending hook removals. Warning! This method can be quite slow (1 second) under certain circumstances. Wraps the data needed for the connection to the host. Gets the state of the current . Gets the unmanaged data containing the pointer to the memory block containing ; Loads from the specified. When not using the GAC, the BinaryFormatter fails to recognise the InParam when attempting to deserialise. A custom DeserializationBinder works around this (see http://spazzarama.com/2009/06/25/binary-deserialize-unable-to-find-assembly/) Loads the user library (trying the strong name first, then the file name), creates an instance for the specified in the library and invokes the Run() method specified in that instance. The assembly strong name provided by the user, located in the global assembly cache. The assembly file name provided by the user to be loaded. Array of parameters to use with the constructor and with the Run() method. Note that all but the first parameter should be binary encoded. to use for reporting to the host side. The exit code to be returned by the main() method. Finds the in the specified . An is thrown if the given user library does not export a proper type implementing the interface. The strong name of the assembly provided by the user. The file name of the assembly provided by the user. The functioning as for the user provided . Finds a user defined Run() method in the specified matching the specified . Name of the method to search. to extract the method from. Array of parameters to match to the method's defined parameters. for the matching method, if any. Initializes an instance from the specified using the specified . Returns whether the specified can be used as parameters when invoking the specified . Constructs a message for a containing more specific information about the expected paramaters. Name of the missing method. Array of the expected parameters. Provides a managed interface to the native thread ACLs. Refer to the official guide to learn more about why thread ACLs are useful. They can be used to exclude/include dedicated threads from interception or to dynamically apply different kind of hooks to different threads. Even if you could do this in managed code, it is not that easy to implement and also EasyHook evaluates those ACLs in unmanaged code. So if any thread is not intercepted, it will never enter the manged environment what will speed up things about orders of magnitudes. Is this ACL an exclusive one? Refer to for more information. Is this ACL an inclusive one? Refer to for more information. Sets an inclusive ACL. This means all threads that are enumerated through are intercepted while all others are NOT. Of course this will overwrite the existing ACL. Please note that this is not necessarily the final negotiation result. Refer to for more information. In general inclusive ACLs will restrict exclusive ACLs while local ACLs will overwrite the global ACL. Threads to be explicitly included in negotiation. The limit of 128 access entries is exceeded! Sets an exclusive ACL. This means all threads that are enumerated through are NOT intercepted while all others are. Of course this will overwrite the existing ACL. Please note that this is not necessarily the final negotiation result. Refer to for more information. In general inclusive ACLs will restrict exclusive ACLs while local ACLs will overwrite the global ACL. Threads to be explicitly included in negotiation. The limit of 128 access entries is exceeded! Creates a copy of the internal thread list associated with this ACL. You may freely modify it without affecting the internal entries. A copy of the internal thread entries. This class is intended to be used within hook handlers, to access associated runtime information. Other hooking libraries on the market require that you keep track of such information yourself, what can be a burden. Is the current thread within a valid hook handler? This is only the case if your handler was called through the hooked entry point... Executes in max. one micro secound. The user callback initially passed to either or . Executes in max. one micro secound. The current thread is not within a valid hook handler. The hook handle initially returned by either or . Executes in max. one micro secound. The current thread is not within a valid hook handler. Allows you to explicitly update the unmanaged module list which is required for , and . Normally this is not necessary, but if you hook a process that frequently loads/unloads modules, you may call this method in a LoadLibrary hook to always operate on the latest module list. Retrives the unmanaged module that contains the given pointer. If no module can be found, null is returned. This method will automatically update the unmanaged module list from time to time. Executes in less than one micro secound. Determines the first unmanaged module on the current call stack. This is always the module that invoked the hook. Executes in max. 15 micro secounds. The problem is that if the calling module is a NET assembly and invokes the hook through a P-Invoke binding, you will get "mscorwks.dll" as calling module and not the NET assembly. This is only an example but I think you got the idea. To solve this issue, refer to and ! Determines the first managed module on the current call stack. This is always the module that invoked the hook. Executes in max. 40 micro secounds. Imagine your hook targets CreateFile. A NET assembly will now invoke this hook through FileStream, for example. But because System.IO.FileStream invokes the hook, you will get "System.Core" as calling module and not the desired assembly. To solve this issue, refer to and ! Returns the address where execution is continued, after you hook has been completed. This is always the instruction behind the hook invokation. Executes in max. one micro secound. A stack address pointing to . Executes in max. one micro secound. Creates a call stack trace of the unmanaged code path that finally lead to your hook. To detect whether the desired module is within the call stack you will have to walk through the whole list! Executes in max. 20 micro secounds. This method is not supported on Windows 2000 and will just return the calling unmanaged module wrapped in an array on that platform. Creates a call stack trace of the managed code path that finally lead to your hook. To detect whether the desired module is within the call stack you will have to walk through the whole list! Executes in max. 80 micro secounds. EasyHook will search in the injected user library for a class which implements this interface. You should only have one class exposing this interface, otherwise it is undefined which one will be chosen. See remarks for more details on how you should create this class. To implement this interface is not the only thing to do. The related class shall implement two methods. The first one is a constructor ctor(IContext, ...) which will let you initialize your library. You should immediately complete this call and only connect to your host application for further error reporting. This initialization method allows you to redirect all unhandled exceptions to your host application automatically. So even if all things in your library initialization would fail, you may still report exceptions! Such unhandled exceptions will be thrown by in your host. But make sure that you are using serializable exception objects only as all standard NET ones are, but not all custom ones. Otherwise you will only intercept a general exception with no specific information attached. The second one is Run(IContext, ...) and should only exit/return when you want to unload your injected library. Unhandled exceptions WON'T be redirected automatically and are likely to crash the target process. As you are expected to connect to your host in the ctor(), you are now also expected to report errors by yourself. The parameter list described by (IContext, ...) will always contain a instance as the first parameter. All further parameters will depend on the arguments passed to at your injection host. ctor() and Run() must have the same custom parameter list as composed by the one passed to Inject(). Otherwise an exception will be thrown. For example if you call with Inject(..., ..., ..., ..., "MyString1", "MyString2"), you have supplied a custom argument list of the format String, String to Inject. This list will be converted to an object array and serialized. The injected library stub will later deserialize this array and pass it to ctor() and Run(), both expected to have a signature of IContext, String, String in our case. So Run will now be called with (IContext, "MyString1", "MyString2"). You should avoid using static fields or properties within such a class, as this might lead to bugs in your code when multiple library instances are injected into the same target! All supported options that will influence the way your library is injected. Default injection procedure. Use of services is not permitted. Use of WOW64 bypass is not permitted. Allow injection without a strong name (e.g. no GAC registration). This option requires that the full path to injected assembly be provided Provides all things related to library injection, inter-process-communication (IPC) and helper routines for common remote tasks. The following demonstrates how to use and : using System; using System.Collections.Generic; using System.Runtime.Remoting; using System.Text; using System.IO; using EasyHook; namespace FileMon { public class FileMonInterface : MarshalByRefObject { public void IsInstalled(Int32 InClientPID) { Console.WriteLine("FileMon has been installed in target {0}.\r\n", InClientPID); } public void OnCreateFile(Int32 InClientPID, String[] InFileNames) { for (int i = 0; i < InFileNames.Length; i++) { Console.WriteLine(InFileNames[i]); } } public void ReportException(Exception InInfo) { Console.WriteLine("The target process has reported an error:\r\n" + InInfo.ToString()); } public void Ping() { } } class Program { static String ChannelName = null; static void Main(string[] args) { try { Config.Register( "A FileMon like demo application.", "FileMon.exe", "FileMonInject.dll"); RemoteHooking.IpcCreateServer<FileMonInterface>(ref ChannelName, WellKnownObjectMode.SingleCall); RemoteHooking.Inject( Int32.Parse(args[0]), "FileMonInject.dll", "FileMonInject.dll", ChannelName); Console.ReadLine(); } catch (Exception ExtInfo) { Console.WriteLine("There was an error while connecting to target:\r\n{0}", ExtInfo.ToString()); } } } } A context contains some basic information about the environment in which your library main method has been invoked. You will always get an instance of this interface in your library Run method and your library constructor. Returns the process ID of the host that has injected this library. If the library was injected with , this will finally start the current process. You should call this method in the library Run() method after all hooks have been installed. true if we are running with administrative privileges, false otherwise. Due to UAC on Windows Vista, this property in general will be false even if the user is in the builtin-admin group. As you can't hook without administrator privileges you should just set the UAC level of your application to requireAdministrator. Creates a globally reachable, managed IPC-Port. Because it is something tricky to get a port working for any constellation of target processes, I decided to write a proper wrapper method. Just keep the returned alive, by adding it to a global list or static variable, as long as you want to have the IPC port open. A class derived from which provides the method implementations this server should expose. if you want to handle each call in an new object instance, otherwise. The latter will implicitly allow you to use "static" remote variables. Either null to let the method generate a random channel name to be passed to or a predefined one. If you pass a value unequal to null, you shall also specify all SIDs that are allowed to connect to your channel! Provide a TRemoteObject object to be made available as a well known type on the server end of the channel. If no SID is specified, all authenticated users will be allowed to access the server channel by default. You must specify an SID if is unequal to null. An that shall be keept alive until the server is not needed anymore. If a predefined channel name is being used, you are required to specify a list of well known SIDs which are allowed to access the newly created server. The given channel name is already in use. Creates a globally reachable, managed IPC-Port. Because it is something tricky to get a port working for any constellation of target processes, I decided to write a proper wrapper method. Just keep the returned alive, by adding it to a global list or static variable, as long as you want to have the IPC port open. A class derived from which provides the method implementations this server should expose. if you want to handle each call in an new object instance, otherwise. The latter will implicitly allow you to use "static" remote variables. Either null to let the method generate a random channel name to be passed to or a predefined one. If you pass a value unequal to null, you shall also specify all SIDs that are allowed to connect to your channel! If no SID is specified, all authenticated users will be allowed to access the server channel by default. You must specify an SID if is unequal to null. An that shall be keept alive until the server is not needed anymore. If a predefined channel name is being used, you are required to specify a list of well known SIDs which are allowed to access the newly created server. The given channel name is already in use. Connects to a globally reachable, managed IPC port. All requests have to be made through the returned object instance. Please note that even if you might think that managed IPC is quiet slow, this is not usually the case. Internally a mechanism is being used to directly continue execution within the server process, so that even if your thread does nothing while dispatching the request, no CPU time is lost, because the server thread seemlessly takes over exection. And to be true, the rare conditions in which you will need high-speed IPC ports are not worth the effort to break with NET's exciting IPC capabilities. In times of Quad-Cores, managed marshalling isn't that slow anymore. An object derived from which provides the method implementations this server should provide. Note that only calls through the returned object instance will be redirected to the server process! ATTENTION: Static fields and members are always processed locally only... The name of the channel to connect to, usually obtained with . An remote object instance which member accesses will be redirected to the server. Unable to create remote object or invalid channel name... Injects the given user library into the target process. No memory leaks are left in the target, even if injection fails for unknown reasons. There are two possible user library paths. The first one should map to a 32-bit library, and the second one should map to 64-bit library. If your code has been compiled for "AnyCPU", like it's the default for C#, you may even specify one library path for both parameters. Please note that your library including all of it's dependencies must be registered in the Global Assembly Cache (GAC). Refer to for more information about how to get them there. If you inject a library into any target process please keep in mind that your working directory will be switched. EasyHook will automatically add the directory of the injecting application as first directory of the target's PATH environment variable. So make sure that all required dependencies are either located within the injecting application's directory, a system directory or any directory already contained in the PATH variable. EasyHook provides extensive error information during injection. Any kind of failure is being caught and thrown as an exception by this method. If for example your library does not expose a class implementing , an exception will be raised in the target process during injection. The exception will be redirected to this method and you can catch it in a try-catch statement around . You will often have to pass parameters to your injected library. names are common, but also any other kind of data can be passed. You may add a custom list of objects marked with the . All common NET classes will be serializable by default, but if you are using your own classes you might have to provide serialization by yourself. The custom parameter list will be passed unchanged to your injected library entry points Run and construcotr. Verify that all required type libraries to deserialize your parameter list are either registered in the GAC or otherwise accessible to your library by being in the same path. It is supported to inject code into 64-bit processes from within 32-bit processes and vice versa. It is also supported to inject code into other terminal sessions. Of course this will require additional processes and services to be created, but as they are managed internally, you won't notice them! There will be some delay when injecting the first library. Even if it would technically be possible to inject a library for debugging purposes into the current process, it will throw an exception. This is because it heavily depends on your injected library whether the current process will be damaged. Any kind of communication may lead into deadlocks if you hook the wrong APIs. Just use the capability of Visual Studio to debug more than one process simultanously which will allow you to debug your library as if it would be injected into the current process without running into any side-effects. The given exceptions are those which are thrown by EasyHook code. The NET framework might throw any other exception not listed here. Don't rely on the exception type. If you passed valid parameters, the only exceptions you should explicitly check for are and . All others shall be caught and treated as bad environment or invalid parameter error. The target process ID. A valid combination of options. A partially qualified assembly name or a relative/absolute file path of the 32-bit version of your library. For example "MyAssembly, PublicKeyToken=248973975895496" or ".\Assemblies\MyAssembly.dll". A partially qualified assembly name or a relative/absolute file path of the 64-bit version of your library. For example "MyAssembly, PublicKeyToken=248973975895496" or ".\Assemblies\MyAssembly.dll". A serializable list of parameters being passed to your library entry points Run() and constructor (see ). It is unstable to inject libraries into the same process. This exception is disabled in DEBUG mode. Access to target process denied or the current user is not an administrator. The given process does not exist or unable to serialize/deserialize one or more pass thru arguments. The given user library could not be found. Unable to allocate unmanaged memory in current or target process. It is not supported to inject into the target process. This is common on Windows Vista and Server 2008. Unable to wait for user library to be initialized. Check your library's constructor. The given user library does not export a class implementing the interface. See for more information. The target process ID. A partially qualified assembly name or a relative/absolute file path of the 32-bit version of your library. For example "MyAssembly, PublicKeyToken=248973975895496" or ".\Assemblies\MyAssembly.dll". A partially qualified assembly name or a relative/absolute file path of the 64-bit version of your library. For example "MyAssembly, PublicKeyToken=248973975895496" or ".\Assemblies\MyAssembly.dll". A serializable list of parameters being passed to your library entry points Run() and constructor (see ). Determines if the target process is 64-bit or not. This will work only if the current process has PROCESS_QUERY_INFORMATION access to the target. A typical mistake is to enumerate processes under system privileges and calling this method later when required. This won't work in most cases because you'll also need system privileges to run this method on processes in other sessions! The PID of the target process. true if the given process is 64-bit, false otherwise. The given process is not accessible. The given process does not exist. Returns the of the user the target process belongs to. You need PROCESS_QUERY_INFORMATION access to the target. An accessible target process ID. The identity of the target owner. The given process is not accessible. The given process does not exist. Returns the current native system process ID. The native system process ID. Returns the current native system thread ID. Even if currently each dedicated managed thread (not a thread from a ) exactly maps to one native system thread, this behavior may change in future versions. If you would like to have unintercepted threads, you should make sure that they are dedicated ones, e.g. derived from . The native system thread ID. Will execute the given static method under system privileges. For some tasks it is necessary to have unrestricted access to the windows API. For example if you want to enumerate all running processes in all sessions. But keep in mind that you only can access these information within the given static method and only if it is called through this service. To accomplish this task, your assembly is loaded into a system service which executes the given static method in a remoted manner. This implies that the return type shall be marked with . All handles or other process specific things obtained in the service, will be invalid in your application after the call is completed! Also the service will use a new instance of your class, so you should only rely on the given parameters and avoid using any external variables.. Your method shall be threaded as isolated! The next thing to mention is that all assemblies required for executing the method shall either be in the GAC or in the directory of the related EasyHook-Library. Otherwise the service won't be able to use your assembly! All unhandled exceptions will be rethrown by the local . A class containing the given static method. A public static method exposed by the given public class. A list of serializable parameters being passed to your static method. The same value your method is returning or null if a void method is called. The current user is not an administrator. private static void OnProcessUpdate(Object InCallback) { ProcessTimer.Change(Timeout.Infinite, Timeout.Infinite); try { ProcessInfo[] Array = (ProcessInfo[])RemoteHooking.ExecuteAsService<Form1>("EnumProcesses"); SortedDictionary<String, ProcessInfo> Result = new SortedDictionary<string, ProcessInfo>(); // sort by name... lock (ProcessList) { ActivePIDList.Clear(); for (int i = 0; i < Array.Length; i++) { Result.Add(System.IO.Path.GetFileName(Array[i].FileName) + "____" + i, Array[i]); ActivePIDList.Add(Array[i].Id); } Result.Values.CopyTo(Array, 0); ProcessList.Clear(); ProcessList.AddRange(Array); } } catch (AccessViolationException) { MessageBox.Show("This is an administrative task!", "Permission denied...", MessageBoxButtons.OK); Process.GetCurrentProcess().Kill(); } finally { ProcessTimer.Change(5000, 5000); } } [Serializable] public class ProcessInfo { public String FileName; public Int32 Id; public Boolean Is64Bit; public String User; } public static ProcessInfo[] EnumProcesses() { List<ProcessInfo> Result = new List<ProcessInfo>(); Process[] ProcList = Process.GetProcesses(); for (int i = 0; i < ProcList.Length; i++) { Process Proc = ProcList[i]; try { ProcessInfo Info = new ProcessInfo(); Info.FileName = Proc.MainModule.FileName; Info.Id = Proc.Id; Info.Is64Bit = RemoteHooking.IsX64Process(Proc.Id); Info.User = RemoteHooking.GetProcessIdentity(Proc.Id).Name; Result.Add(Info); } catch { } } return Result.ToArray(); } Creates a new process which is started suspended until you call from within your injected library Run() method. This allows you to hook the target BEFORE any of its usual code is executed. In situations where a target has debugging and hook preventions, you will get a chance to block those mechanisms for example... Please note that this method might fail when injecting into managed processes, especially when the target is using the CLR hosting API and takes advantage of AppDomains. For example, the Internet Explorer won't be hookable with this method. In such a case your only options are either to hook the target with the unmanaged API or to hook it after (non-supended) creation with the usual method. See for more information. The exceptions listed here are additional to the ones listed for . A relative or absolute path to the desired executable. Optional command line parameters for process creation. Internally CREATE_SUSPENDED is already passed to CreateProcess(). With this parameter you can add more flags like DETACHED_PROCESS, CREATE_NEW_CONSOLE or whatever! A valid combination of options. A partially qualified assembly name or a relative/absolute file path of the 32-bit version of your library. For example "MyAssembly, PublicKeyToken=248973975895496" or ".\Assemblies\\MyAssembly.dll". A partially qualified assembly name or a relative/absolute file path of the 64-bit version of your library. For example "MyAssembly, PublicKeyToken=248973975895496" or ".\Assemblies\\MyAssembly.dll". The process ID of the newly created process. A serializable list of parameters being passed to your library entry points Run() and constructor (see ). The given EXE path could not be found. Creates a new process which is started suspended until you call from within your injected library Run() method. This allows you to hook the target BEFORE any of its usual code is executed. In situations where a target has debugging and hook preventions, you will get a chance to block those mechanisms for example... Please note that this method might fail when injecting into managed processes, especially when the target is using the CLR hosting API and takes advantage of AppDomains. For example, the Internet Explorer won't be hookable with this method. In such a case your only options are either to hook the target with the unmanaged API or to hook it after (non-supended) creation with the usual method. See for more information. The exceptions listed here are additional to the ones listed for . A relative or absolute path to the desired executable. Optional command line parameters for process creation. Internally CREATE_SUSPENDED is already passed to CreateProcess(). With this parameter you can add more flags like DETACHED_PROCESS, CREATE_NEW_CONSOLE or whatever! A partially qualified assembly name or a relative/absolute file path of the 32-bit version of your library. For example "MyAssembly, PublicKeyToken=248973975895496" or ".\Assemblies\\MyAssembly.dll". A partially qualified assembly name or a relative/absolute file path of the 64-bit version of your library. For example "MyAssembly, PublicKeyToken=248973975895496" or ".\Assemblies\\MyAssembly.dll". The process ID of the newly created process. A serializable list of parameters being passed to your library entry points Run() and constructor (see ). The given EXE path could not be found. Returns true if the operating system is 64-Bit Windows, false otherwise. Installs the EasyHook support driver. After this step you may use to install your kernel mode hooking component. Loads the given driver into the kernel and immediately marks it for deletion. The installed driver will be registered with the service control manager under the you specify. Please note that you should use to find out which driver to load. Even if your process is running on 32-Bit this does not mean, that the OS kernel is running on 32-Bit! The IAssemblyCache interface is the top-level interface that provides access to the GAC. Represents an assembly name. An assembly name includes a predetermined set of name-value pairs. The assembly name is described in detail in the .NET Framework SDK. The IAssemblyName::SetProperty method adds a name-value pair to the assembly name, or, if a name-value pair with the same name already exists, modifies or deletes the value of a name-value pair. The ID that represents the name part of the name-value pair that is to be added or to be modified. Valid property IDs are defined in the ASM_NAME enumeration. A pointer to a buffer that contains the value of the property. The length of the pvProperty buffer in bytes. If cbProperty is zero, the name-value pair is removed from the assembly name. The IAssemblyName::GetProperty method retrieves the value of a name-value pair in the assembly name that specifies the name. The ID that represents the name of the name-value pair whose value is to be retrieved. Specified property IDs are defined in the ASM_NAME enumeration. A pointer to a buffer that is to contain the value of the property. The length of the pvProperty buffer, in bytes. The IAssemblyName::Finalize method freezes an assembly name. Additional calls to IAssemblyName::SetProperty are unsuccessful after this method has been called. The IAssemblyName::GetDisplayName method returns a string representation of the assembly name. A pointer to a buffer that is to contain the display name. The display name is returned in Unicode. The size of the buffer in characters (on input). The length of the returned display name (on return). One or more of the bits defined in the ASM_DISPLAY_FLAGS enumeration: Enumerates the assemblies in the GAC. Enumerates the assemblies in the GAC. Must be null Pointer to a memory location that is to receive the interface pointer to the assembly name of the next assembly that is enumerated. Must be zero. The IInstallReferenceItem interface represents a reference that has been set on an assembly in the GAC. Instances of IInstallReferenceIteam are returned by the interface. Returns a FUSION_INSTALL_REFERENCE structure, . A pointer to a FUSION_INSTALL_REFERENCE structure. The memory is allocated by the GetReference method and is freed when is released. Callers must not hold a reference to this buffer after the IInstallReferenceItem object is released. To avoid allocation issues with the interop layer, the is not marshaled directly - therefore use of out IntPtr The IInstllReferenceEnum interface enumerates all references that are set on an assembly in the GAC. Note: references that belong to the assembly are locked for changes while those references are being enumerated. Returns the next reference information for an assembly Pointer to a memory location that receives the IInstallReferenceItem pointer. Must be zero. Must be null. Return values are as follows: S_OK: - The next item is returned successfully. S_FALSE: - No more items. Install assembly commit flags (mutually exclusive) If the assembly is already installed in the gAC and the file version numbers of the assembly being installed are the same or later, the files are replaced. The files of an existing assembly are overwritten regardless of their version number. The uninstall action taken Unknown The assembly files have been removed from the GAC An application is using the assembly. The assembly does not exist in the GAC Not used. The assembly has not been removed from the GAC because another application reference exists. The that was specified is not found in the GAC. The ASM_CACHE_FLAGS enumeration used in . Enumerates the cache of precompiled assemblies by using Ngen.exe. Enumerates the GAC. Enumerates the assemblies that have been downloaded on-demand or that have been shadow-copied. The CREATE_ASM_NAME_OBJ_FLAGS enumeration, used in If this flag is specified, the szAssemblyName parameter is a full assembly name and is parsed to the individual properties. If the flag is not specified, szAssemblyName is the "Name" portion of the assembly name. If this flag is specified, certain properties, such as processor architecture, are set to their default values. The ASM_NAME enumeration property ID describes the valid names of the name-value pairs in an assembly name. See the .NET Framework SDK for a description of these properties. ASM_DISPLAY_FLAGS: . Includes the version number as part of the display name. Includes the culture. Includes the public key token. Includes the public key. Includes the custom part of the assembly name. Includes the processor architecture. Includes the language ID. Include all attributes. The FUSION_INSTALL_REFERENCE structure represents a reference that is made when an application has installed an assembly in the GAC. Create a new InstallReference Possible values for the guidScheme field can be one of the following (): FUSION_REFCOUNT_MSI_GUID The assembly is referenced by an application that has been installed by using Windows Installer. The szIdentifier field is set to MSI, and szNonCannonicalData is set to Windows Installer. This scheme must only be used by Windows Installer itself. FUSION_REFCOUNT_UNINSTALL_SUBKEY_GUID The assembly is referenced by an application that appears in Add/Remove Programs. The szIdentifier field is the token that is used to register the application with Add/Remove programs. FUSION_REFCOUNT_FILEPATH_GUID The assembly is referenced by an application that is represented by a file in the file system. The szIdentifier field is the path to this file. FUSION_REFCOUNT_OPAQUE_STRING_GUID The assembly is referenced by an application that is only represented by an opaque string. The szIdentifier is this opaque string. The GAC does not perform existence checking for opaque references when you remove this. A unique string that identifies the application that installed the assembly. A string that is only understood by the entity that adds the reference. The GAC only stores this string. The entity that adds the reference. A unique string that identifies the application that installed the assembly. A string that is only understood by the entity that adds the reference. The GAC only stores this string. The size of the structure in bytes. Reserved, must be zero. Possible values for the guidScheme for . Ensures that the provided Guid is one of the valid reference guids defined in (excluding and ). The Guid to validate True if the Guid is , , or . FUSION_REFCOUNT_UNINSTALL_SUBKEY_GUID - The assembly is referenced by an application that appears in Add/Remove Programs. The szIdentifier field is the token that is used to register the application with Add/Remove programs. FUSION_REFCOUNT_FILEPATH_GUID - The assembly is referenced by an application that is represented by a file in the file system. The szIdentifier field is the path to this file. FUSION_REFCOUNT_OPAQUE_STRING_GUID - The assembly is referenced by an application that is only represented by an opaque string. The szIdentifier is this opaque string. The GAC does not perform existence checking for opaque references when you remove this. This GUID cannot be used for installing into GAC. FUSION_REFCOUNT_MSI_GUID - The assembly is referenced by an application that has been installed by using Windows Installer. The szIdentifier field is set to MSI, and szNonCannonicalData is set to Windows Installer. This scheme must only be used by Windows Installer itself. This GUID cannot be used for installing into GAC. Provides methods to install or remove assemblies from the Global Assembly Cache (GAC) Install assembly into GAC Install the provided assemblies to GAC Uninstall an assembly from the GAC. has to be fully specified name. E.g., for v1.0/v1.1 assemblies, it should be "name, Version=xx, Culture=xx, PublicKeyToken=xx". For v2.0+ assemblies, it should be "name, Version=xx, Culture=xx, PublicKeyToken=xx, ProcessorArchitecture=xx". If is not fully specified, a random matching assembly could be uninstalled. Uninstall the provided assembly names from the GAC. have to be fully specified names. E.g., for v1.0/v1.1 assemblies, it should be "name, Version=xx, Culture=xx, PublicKeyToken=xx". For v2.0+ assemblies, it should be "name, Version=xx, Culture=xx, PublicKeyToken=xx, ProcessorArchitecture=xx". If is not fully specified, a random matching assembly could be uninstalled. Query an assembly in the GAC. has to be a fully specified name. E.g., for v1.0/v1.1 assemblies, it should be "name, Version=xx, Culture=xx, PublicKeyToken=xx". For v2.0+ assemblies, it should be "name, Version=xx, Culture=xx, PublicKeyToken=xx, ProcessorArchitecture=xx". If is not fully specified, a random matching assembly may be found. Enumerate assemblies within the Global Assembly Cache (GAC) Enumerate assemblies in the GAC null means enumerate all the assemblies Get the next assembly within the enumerator. Enumerate referenced assemblies installed in the global assembly cache. Create enumerator for provided assembly name has to be a fully specified name. E.g., for v1.0/v1.1 assemblies, it should be "name, Version=xx, Culture=xx, PublicKeyToken=xx". For v2.0+ assemblies, it should be "name, Version=xx, Culture=xx, PublicKeyToken=xx, ProcessorArchitecture=xx". Get next reference Fusion DllImports To obtain an instance of the API, call the CreateAssemblyEnum API Pointer to a memory location that contains the IAssemblyEnum pointer. Must be null. An assembly name that is used to filter the enumeration. Can be null to enumerate all assemblies in the GAC. Exactly one item from the ASM_CACHE_FLAGS enumeration, . Must be NULL. An instance of is obtained by calling the CreateAssemblyNameObject API Pointer to a memory location that receives the IAssemblyName pointer that is created. A string representation of the assembly name or of a full assembly reference that is determined by dwFlags. The string representation can be null. Zero or more of the bits that are defined in the CREATE_ASM_NAME_OBJ_FLAGS enumeration, . Must be null. To obtain an instance of the API, call the CreateAssemblyCache API Pointer to return Must be zero. To obtain an instance of the API, call the CreateInstallReferenceEnum API A pointer to a memory location that receives the IInstallReferenceEnum pointer. The assembly name for which the references are enumerated. Must be zero. Must be null.