In this tutorial we will create a remote file monitor using EasyHook. We will cover how to:git
使用EasyHook建立一個全局文件監控程序,包括github
For this tutorial we will be creating a solution with two projects:windows
這個教程裏,咱們要建立兩個解決方案安全
第1步:建立注入器:FileMonitorHook類庫app
Create a new C# class library project called FileMonitorHook
.less
解決方案下新建建一個叫FileMonitorHook的類庫
ide
Add the EasyHook NuGet package (right-click on the FileMonitorHook project, Manage NuGet Packages…), search for EasyHook and install.函數
添加EasyHook.dll的引用(EasyHook32.dll和EasyHook64.dll要跟被引用的EasyHook.dll放在同一目錄)oop
Next we will add the interface class that will be used to communicate from the payload within the target process back to the FileMonitor
console app we will add shortly. It is important that this class, and any objects it needs to pass back and forth descend from MarshalByRefObject
.性能
而後添加接口類
/// <summary> /// Provides an interface for communicating from the client (target) to the server (injector) /// </summary> public class ServerInterface : MarshalByRefObject { public void IsInstalled(int clientPID) { Console.WriteLine("FileMonitor has injected FileMonitorHook into process {0}.\r\n", clientPID); } /// <summary> /// Output messages to the console. /// </summary> /// <param name="clientPID"></param> /// <param name="fileNames"></param> public void ReportMessages(int clientPID, string[] messages) { for (int i = 0; i < messages.Length; i++) { Console.WriteLine(messages[i]); } } public void ReportMessage(int clientPID, string message) { Console.WriteLine(message); } /// <summary> /// Report exception /// </summary> /// <param name="e"></param> public void ReportException(Exception e) { Console.WriteLine("The target process has reported an error:\r\n" + e.ToString()); } /// <summary> /// Called to confirm that the IPC channel is still open / host application has not closed /// </summary> public void Ping() { } }
Lastly we need to create a public class that implements EasyHook.IEntryPoint
so that the EasyHook injection process can call our custom logic once the payload has been injected.
而後,須要建立一個公共類來實現EasyHook.IEntryPoint
接口,當內容被注入後,EasyHook就能調用咱們本身寫的程序
This class will include a constructor that sets up the IPC connection, and a method called Run
with the same parameters as the constructor.
這個類必須有一個IPC鏈接的構造函數,而且簽名相同的Run函數
The constructor and Run
method must include at least one parameter of type EasyHook.RemoteHooking.IContext
, and then any number of additional parameters providing they are serializable.
這兩個函數必須有一個EasyHook.RemoteHooking.IContext類型的參數,其它參數都是序列化的
For example, a minimal IEntryPoint
that does nothing would need look like this:
例如,一個最小的IEntryPoint
以下
public class MySimpleEntryPoint: EasyHook.IEntryPoint { public MySimpleEntryPoint(EasyHook.RemoteHooking.IContext context) { } public void Run(EasyHook.RemoteHooking.IContext context) { } }
In the above example, once the payload has been injected, the instance of MySimpleEntryPoint
will be created, the Run
method called, and then immediately afterwards the payload will be unloaded.
一旦內容被注入,會建立一個MySimpleEntryPoint實例,Run函數會被調用,內容馬上被卸載
Our file monitor example is going to accomplish the following:
咱們的例子實現以下:
ServerInterface
ServerInterface
鏈接IPCServerInterface.Ping
method we created earlier to confirm the channel is accessibleServerInterface
Ping咱們能建立的方法來判斷可否訪問Thread.Sleep
so that you do not cause performance problems in the target application. If a loop is not used then consider using a signal event such as an EventWaitHandle
to prevent the Run
method exiting prematurely.EventWaitHandle
來提早結束它
/// <summary> /// EasyHook will look for a class implementing <see cref="EasyHook.IEntryPoint"/> during injection. This /// becomes the entry point within the target process after injection is complete. /// </summary> public class InjectionEntryPoint: EasyHook.IEntryPoint { /// <summary> /// Reference to the server interface within FileMonitor /// </summary> ServerInterface _server = null; /// <summary> /// Message queue of all files accessed /// </summary> Queue<string> _messageQueue = new Queue<string>(); /// <summary> /// EasyHook requires a constructor that matches <paramref name="context"/> and any additional parameters as provided /// in the original call to <see cref="EasyHook.RemoteHooking.Inject(int, EasyHook.InjectionOptions, string, string, object[])"/>. /// /// Multiple constructors can exist on the same <see cref="EasyHook.IEntryPoint"/>, providing that each one has a corresponding Run method (e.g. <see cref="Run(EasyHook.RemoteHooking.IContext, string)"/>). /// </summary> /// <param name="context">The RemoteHooking context</param> /// <param name="channelName">The name of the IPC channel</param> public InjectionEntryPoint( EasyHook.RemoteHooking.IContext context, string channelName) { // Connect to server object using provided channel name _server = EasyHook.RemoteHooking.IpcConnectClient<ServerInterface>(channelName); // If Ping fails then the Run method will be not be called _server.Ping(); } /// <summary> /// The main entry point for our logic once injected within the target process. /// This is where the hooks will be created, and a loop will be entered until host process exits. /// EasyHook requires a matching Run method for the constructor /// </summary> /// <param name="context">The RemoteHooking context</param> /// <param name="channelName">The name of the IPC channel</param> public void Run( EasyHook.RemoteHooking.IContext context, string channelName) { // Injection is now complete and the server interface is connected _server.IsInstalled(EasyHook.RemoteHooking.GetCurrentProcessId()); // Install hooks // CreateFile https://msdn.microsoft.com/en-us/library/windows/desktop/aa363858(v=vs.85).aspx var createFileHook = EasyHook.LocalHook.Create( EasyHook.LocalHook.GetProcAddress("kernel32.dll", "CreateFileW"), new CreateFile_Delegate(CreateFile_Hook), this); // ReadFile https://msdn.microsoft.com/en-us/library/windows/desktop/aa365467(v=vs.85).aspx var readFileHook = EasyHook.LocalHook.Create( EasyHook.LocalHook.GetProcAddress("kernel32.dll", "ReadFile"), new ReadFile_Delegate(ReadFile_Hook), this); // WriteFile https://msdn.microsoft.com/en-us/library/windows/desktop/aa365747(v=vs.85).aspx var writeFileHook = EasyHook.LocalHook.Create( EasyHook.LocalHook.GetProcAddress("kernel32.dll", "WriteFile"), new WriteFile_Delegate(WriteFile_Hook), this); // Activate hooks on all threads except the current thread createFileHook.ThreadACL.SetExclusiveACL(new Int32[] { 0 }); readFileHook.ThreadACL.SetExclusiveACL(new Int32[] { 0 }); writeFileHook.ThreadACL.SetExclusiveACL(new Int32[] { 0 }); _server.ReportMessage("CreateFile, ReadFile and WriteFile hooks installed"); // Wake up the process (required if using RemoteHooking.CreateAndInject) EasyHook.RemoteHooking.WakeUpProcess(); try { // Loop until FileMonitor closes (i.e. IPC fails) while (true) { System.Threading.Thread.Sleep(500); string[] queued = null; lock (_messageQueue) { queued = _messageQueue.ToArray(); _messageQueue.Clear(); } // Send newly monitored file accesses to FileMonitor if (queued != null && queued.Length > 0) { _server.ReportMessages(queued); } else { _server.Ping(); } } } catch { // Ping() or ReportMessages() will raise an exception if host is unreachable } // Remove hooks createFileHook.Dispose(); readFileHook.Dispose(); writeFileHook.Dispose(); // Finalise cleanup of hooks EasyHook.LocalHook.Release(); } /// <summary> /// P/Invoke to determine the filename from a file handle /// https://msdn.microsoft.com/en-us/library/windows/desktop/aa364962(v=vs.85).aspx /// </summary> /// <param name="hFile"></param> /// <param name="lpszFilePath"></param> /// <param name="cchFilePath"></param> /// <param name="dwFlags"></param> /// <returns></returns> [DllImport("Kernel32.dll", SetLastError = true, CharSet = CharSet.Auto)] static extern uint GetFinalPathNameByHandle(IntPtr hFile, [MarshalAs(UnmanagedType.LPTStr)] StringBuilder lpszFilePath, uint cchFilePath, uint dwFlags); #region CreateFileW Hook /// <summary> /// The CreateFile delegate, this is needed to create a delegate of our hook function <see cref="CreateFile_Hook(string, uint, uint, IntPtr, uint, uint, IntPtr)"/>. /// </summary> /// <param name="filename"></param> /// <param name="desiredAccess"></param> /// <param name="shareMode"></param> /// <param name="securityAttributes"></param> /// <param name="creationDisposition"></param> /// <param name="flagsAndAttributes"></param> /// <param name="templateFile"></param> /// <returns></returns> [UnmanagedFunctionPointer(CallingConvention.StdCall, CharSet = CharSet.Unicode, SetLastError = true)] delegate IntPtr CreateFile_Delegate( String filename, UInt32 desiredAccess, UInt32 shareMode, IntPtr securityAttributes, UInt32 creationDisposition, UInt32 flagsAndAttributes, IntPtr templateFile); /// <summary> /// Using P/Invoke to call original method. /// https://msdn.microsoft.com/en-us/library/windows/desktop/aa363858(v=vs.85).aspx /// </summary> /// <param name="filename"></param> /// <param name="desiredAccess"></param> /// <param name="shareMode"></param> /// <param name="securityAttributes"></param> /// <param name="creationDisposition"></param> /// <param name="flagsAndAttributes"></param> /// <param name="templateFile"></param> /// <returns></returns> [DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true, CallingConvention = CallingConvention.StdCall)] static extern IntPtr CreateFileW( String filename, UInt32 desiredAccess, UInt32 shareMode, IntPtr securityAttributes, UInt32 creationDisposition, UInt32 flagsAndAttributes, IntPtr templateFile); /// <summary> /// The CreateFile hook function. This will be called instead of the original CreateFile once hooked. /// </summary> /// <param name="filename"></param> /// <param name="desiredAccess"></param> /// <param name="shareMode"></param> /// <param name="securityAttributes"></param> /// <param name="creationDisposition"></param> /// <param name="flagsAndAttributes"></param> /// <param name="templateFile"></param> /// <returns></returns> IntPtr CreateFile_Hook( String filename, UInt32 desiredAccess, UInt32 shareMode, IntPtr securityAttributes, UInt32 creationDisposition, UInt32 flagsAndAttributes, IntPtr templateFile) { try { lock (this._messageQueue) { if (this._messageQueue.Count < 1000) { string mode = string.Empty; switch (creationDisposition) { case 1: mode = "CREATE_NEW"; break; case 2: mode = "CREATE_ALWAYS"; break; case 3: mode = "OPEN_ALWAYS"; break; case 4: mode = "OPEN_EXISTING"; break; case 5: mode = "TRUNCATE_EXISTING"; break; } // Add message to send to FileMonitor this._messageQueue.Enqueue( string.Format("[{0}:{1}]: CREATE ({2}) \"{3}\"", EasyHook.RemoteHooking.GetCurrentProcessId(), EasyHook.RemoteHooking.GetCurrentThreadId() , mode, filename)); } } } catch { // swallow exceptions so that any issues caused by this code do not crash target process } // now call the original API... return CreateFileW( filename, desiredAccess, shareMode, securityAttributes, creationDisposition, flagsAndAttributes, templateFile); } #endregion #region ReadFile Hook /// <summary> /// The ReadFile delegate, this is needed to create a delegate of our hook function <see cref="ReadFile_Hook(IntPtr, IntPtr, uint, out uint, IntPtr)"/>. /// </summary> /// <param name="hFile"></param> /// <param name="lpBuffer"></param> /// <param name="nNumberOfBytesToRead"></param> /// <param name="lpNumberOfBytesRead"></param> /// <param name="lpOverlapped"></param> /// <returns></returns> [UnmanagedFunctionPointer(CallingConvention.StdCall, SetLastError = true)] delegate bool ReadFile_Delegate( IntPtr hFile, IntPtr lpBuffer, uint nNumberOfBytesToRead, out uint lpNumberOfBytesRead, IntPtr lpOverlapped); /// <summary> /// Using P/Invoke to call the orginal function /// </summary> /// <param name="hFile"></param> /// <param name="lpBuffer"></param> /// <param name="nNumberOfBytesToRead"></param> /// <param name="lpNumberOfBytesRead"></param> /// <param name="lpOverlapped"></param> /// <returns></returns> [DllImport("kernel32.dll", SetLastError = true, CallingConvention = CallingConvention.StdCall)] static extern bool ReadFile( IntPtr hFile, IntPtr lpBuffer, uint nNumberOfBytesToRead, out uint lpNumberOfBytesRead, IntPtr lpOverlapped); /// <summary> /// The ReadFile hook function. This will be called instead of the original ReadFile once hooked. /// </summary> /// <param name="hFile"></param> /// <param name="lpBuffer"></param> /// <param name="nNumberOfBytesToRead"></param> /// <param name="lpNumberOfBytesRead"></param> /// <param name="lpOverlapped"></param> /// <returns></returns> bool ReadFile_Hook( IntPtr hFile, IntPtr lpBuffer, uint nNumberOfBytesToRead, out uint lpNumberOfBytesRead, IntPtr lpOverlapped) { bool result = false; lpNumberOfBytesRead = 0; // Call original first so we have a value for lpNumberOfBytesRead result = ReadFile(hFile, lpBuffer, nNumberOfBytesToRead, out lpNumberOfBytesRead, lpOverlapped); try { lock (this._messageQueue) { if (this._messageQueue.Count < 1000) { // Retrieve filename from the file handle StringBuilder filename = new StringBuilder(255); GetFinalPathNameByHandle(hFile, filename, 255, 0); // Add message to send to FileMonitor this._messageQueue.Enqueue( string.Format("[{0}:{1}]: READ ({2} bytes) \"{3}\"", EasyHook.RemoteHooking.GetCurrentProcessId(), EasyHook.RemoteHooking.GetCurrentThreadId() , lpNumberOfBytesRead, filename)); } } } catch { // swallow exceptions so that any issues caused by this code do not crash target process } return result; } #endregion #region WriteFile Hook /// <summary> /// The WriteFile delegate, this is needed to create a delegate of our hook function <see cref="WriteFile_Hook(IntPtr, IntPtr, uint, out uint, IntPtr)"/>. /// </summary> /// <param name="hFile"></param> /// <param name="lpBuffer"></param> /// <param name="nNumberOfBytesToWrite"></param> /// <param name="lpNumberOfBytesWritten"></param> /// <param name="lpOverlapped"></param> /// <returns></returns> [UnmanagedFunctionPointer(CallingConvention.StdCall, CharSet = CharSet.Unicode, SetLastError = true)] [return: MarshalAs(UnmanagedType.Bool)] delegate bool WriteFile_Delegate( IntPtr hFile, IntPtr lpBuffer, uint nNumberOfBytesToWrite, out uint lpNumberOfBytesWritten, IntPtr lpOverlapped); /// <summary> /// Using P/Invoke to call original WriteFile method /// </summary> /// <param name="hFile"></param> /// <param name="lpBuffer"></param> /// <param name="nNumberOfBytesToWrite"></param> /// <param name="lpNumberOfBytesWritten"></param> /// <param name="lpOverlapped"></param> /// <returns></returns> [DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true, CallingConvention = CallingConvention.StdCall)] [return: MarshalAs(UnmanagedType.Bool)] static extern bool WriteFile( IntPtr hFile, IntPtr lpBuffer, uint nNumberOfBytesToWrite, out uint lpNumberOfBytesWritten, IntPtr lpOverlapped); /// <summary> /// The WriteFile hook function. This will be called instead of the original WriteFile once hooked. /// </summary> /// <param name="hFile"></param> /// <param name="lpBuffer"></param> /// <param name="nNumberOfBytesToWrite"></param> /// <param name="lpNumberOfBytesWritten"></param> /// <param name="lpOverlapped"></param> /// <returns></returns> bool WriteFile_Hook( IntPtr hFile, IntPtr lpBuffer, uint nNumberOfBytesToWrite, out uint lpNumberOfBytesWritten, IntPtr lpOverlapped) { bool result = false; // Call original first so we get lpNumberOfBytesWritten result = WriteFile(hFile, lpBuffer, nNumberOfBytesToWrite, out lpNumberOfBytesWritten, lpOverlapped); try { lock (this._messageQueue) { if (this._messageQueue.Count < 1000) { // Retrieve filename from the file handle StringBuilder filename = new StringBuilder(255); GetFinalPathNameByHandle(hFile, filename, 255, 0); // Add message to send to FileMonitor this._messageQueue.Enqueue( string.Format("[{0}:{1}]: WRITE ({2} bytes) \"{3}\"", EasyHook.RemoteHooking.GetCurrentProcessId(), EasyHook.RemoteHooking.GetCurrentThreadId() , lpNumberOfBytesWritten, filename)); } } } catch { // swallow exceptions so that any issues caused by this code do not crash target process } return result; } #endregion }
第2步:建立一個FileMonitor控制檯程序
Add a new C# console app project to the solution called FileMonitor
.
Add the EasyHook NuGet package (right-click on the FileMonitor project, Manage NuGet Packages…), search for EasyHook and install.
添加EasyHook引用
Add a reference to FileMonitorHook
that we added previously so that we can make use of the ServerInterface
class we created.
添加FileMonitorHook
引用,這樣就能使用ServerInterface
類
Within Program.cs we will create the following two static methods:
在Program.cs 中建立兩個靜態方法
Within the console app we will be doing the following:
在控制檯程序,咱們作以下事情:
EasyHook.RemoteHooking.IpcCreateServer<T>
helper. The channel name will be generated for us for added security and we will be passing that into the target process during injection.EasyHook.RemoteHooking.IpcCreateServer<T>
),它能安全的注入目標進程
class Program { static void Main(string[] args) { Int32 targetPID = 0; string targetExe = null; // Will contain the name of the IPC server channel string channelName = null; // Process command line arguments or print instructions and retrieve argument value ProcessArgs(args, out targetPID, out targetExe); if (targetPID <= 0 && string.IsNullOrEmpty(targetExe)) return; // Create the IPC server using the FileMonitorIPC.ServiceInterface class as a singleton EasyHook.RemoteHooking.IpcCreateServer<FileMonitorHook.ServerInterface>(ref channelName, System.Runtime.Remoting.WellKnownObjectMode.Singleton); // Get the full path to the assembly we want to inject into the target process string injectionLibrary = Path.Combine(Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().Location), "FileMonitorHook.dll"); try { // Injecting into existing process by Id if (targetPID > 0) { Console.WriteLine("Attempting to inject into process {0}", targetPID); // inject into existing process EasyHook.RemoteHooking.Inject( targetPID, // ID of process to inject into injectionLibrary, // 32-bit library to inject (if target is 32-bit) injectionLibrary, // 64-bit library to inject (if target is 64-bit) channelName // the parameters to pass into injected library // ... ); } // Create a new process and then inject into it else if (!string.IsNullOrEmpty(targetExe)) { Console.WriteLine("Attempting to create and inject into {0}", targetExe); // start and inject into a new process EasyHook.RemoteHooking.CreateAndInject( targetExe, // executable to run "", // command line arguments for target 0, // additional process creation flags to pass to CreateProcess EasyHook.InjectionOptions.DoNotRequireStrongName, // allow injectionLibrary to be unsigned injectionLibrary, // 32-bit library to inject (if target is 32-bit) injectionLibrary, // 64-bit library to inject (if target is 64-bit) out targetPID, // retrieve the newly created process ID channelName // the parameters to pass into injected library // ... ); } } catch (Exception e) { Console.ForegroundColor = ConsoleColor.Red; Console.WriteLine("There was an error while injecting into target:"); Console.ResetColor(); Console.WriteLine(e.ToString()); } Console.ForegroundColor = ConsoleColor.DarkGreen; Console.WriteLine("<Press any key to exit>"); Console.ResetColor(); Console.ReadKey(); } static void ProcessArgs(string[] args, out int targetPID, out string targetExe) { targetPID = 0; targetExe = null; // Load any parameters while ((args.Length != 1) || !Int32.TryParse(args[0], out targetPID) || !File.Exists(args[0])) { if (targetPID > 0) { break; } if (args.Length != 1 || !File.Exists(args[0])) { Console.WriteLine("Usage: FileMonitor ProcessID"); Console.WriteLine(" or: FileMonitor PathToExecutable"); Console.WriteLine(""); Console.WriteLine("e.g. : FileMonitor 1234"); Console.WriteLine(" to monitor an existing process with PID 1234"); Console.WriteLine(@" or : FileMonitor ""C:\Windows\Notepad.exe"""); Console.WriteLine(" create new notepad.exe process using RemoteHooking.CreateAndInject"); Console.WriteLine(); Console.WriteLine("Enter a process Id or path to executable"); Console.Write("> "); args = new string[] { Console.ReadLine() }; if (String.IsNullOrEmpty(args[0])) return; } else { targetExe = args[0]; break; } } } }
Here are some screenshots of the example output from the full solution found in the EasyHook-Tutorials GitHub repository.
下面是運行截圖
CreateAndInject into a new notepad.exe process
建立而且新建一個notepad.exe的進程
Inject into an existing notepad.exe process using the process Id
注入剛纔建立的notepad程序的進程id
Messages being returned to the console app from the injected payload.
打印注入的內容
問題記錄
FileMonitor要添加System.Runtime.Remoting引用