C# EasyHook2.5 中文翻译

来源:互联网 发布:mac唇膏火鹤好看吗 编辑:程序博客网 时间:2024/05/09 14:19

 在网上找到一遍中英文的教程(但是很多地方中文没翻译好),现在也正需要学习它,看英文资料有时候会更实在,所以边学边继续帮忙翻译吧。由于本人水平有限,如果翻译有问题请及时指出以方便其他英文不太好的朋友学习。谢谢
部分翻译来自:http://blog.cuile.com/blog/archives/98
原文:http://code.google.com/p/easyhook-continuing-detours/wiki/Introduction

 

 

 

 

Continuing Detours: the reinvention of Windows API Hooking
继微软的Detour Hook :现在又一个新的HooK面世了。

Microsoft® Detours latest release was in December 2006. Now times have changed and the NET Framework has become more and more popular. Besides the well known unmanaged code hooking, EasyHook provides a way to hook unmanaged code from a managed environment. This implies several advantages:
微软的Detours最新的版本是在2006年12月发布。而现在.Net框架越来越流行,除了大家知道的非托管理代码挂接,EasyHook提供一个从托管环境挂接非托管代码的方法。这意味着几个优点:

No resource or memory leaks are left in the target
在目标中不会有资源和内存泄漏。

You can write pure managed hook handlers for unmanaged APIs
你能够针对非托管API写纯粹的托管钩子句柄

All hooks are installed and automatically removed in a stable manner
所有钩子的安装和自动移除的方式都是稳定的。

You can use all the convenience managed code provides, like NET Remoting, WCF and WPF
你可以使用所有托管代码的便利,像Net Rmoting, WCF 和 WPF。

You will be able to write injection libraries and host processes compiled for AnyCPU, which will allow you to inject your code into 32- and 64-Bit processes from 64- and 32-Bit processes by using the very same assembly in all cases.
你可以为各种CPU写注入库和托管进程,你可以用相同的汇编将你的代码注入32和64位进程。

This way hooking has become a simple task and you can now write hooking applications like FileMon or RegMon with a few lines of code.
这使得使用钩子变为一个简单的工作,你只需要少许代码就能够马上写像FileMon 或者 RegMon这样的程序。

Further EasyHook 2.5 provides additional features like:
此外EasyHook2.5提供其它的功能如下:

Experimental stealth injection for unmanaged code not raising attention of any current AV
对目前任何反病毒软件,尝试秘密注入非托管代码,都未达到被很重视的程度。(这貌似想拉拢写病毒的人)

32- and 64-Bit Kernel mode hooking support, since Windows XP.
从Windows XP以后,Hooking均支持32位和64位核心的系统。

A pure unmanaged hooking core which will improve performance, stability and compatibility.
一个纯非托管钩子核心,无疑将运作得更良好、稳定和兼容。

A solid unmanaged API for writing hooking apps and libraries without the NET Framework
一个稳定的非托管API用于在没有.NET环境下使用钩子应用和库。

The unmanaged core does not require CRT bindings and thus will reduce deployment size about some megabytes. Also Windows 2000 SP4 and Windows Server 2008 SP1 can now be targeted with the same EasyHook binary.
非托管核心不需要CRT绑定,因此将减少部署大小。并且现在有针对Windows 2000 SP4和Windows Server 2008 SP1的EasyHook二进制版本。

Minimal software requirements for end-users to execute applications using EasyHook:
Windows 2000 SP4 or later
Microsoft NET Framework 2.0 Redistributable
使用EasyHook最小软件需求:
Windows 2000 SP4 或者 更高版本
Microsoft NET Framework 2.0 Redistributable

Table of Content
1 Continuing Detours: the reinvention of Windows API Hooking
1.1 Security Advisor
1.2 A simple FileMon derivate
2 A deep look under the hook
2.1 Global Assembly Cache
2.2 Windows Defender
2.3 Injection – A burden made easy
2.3.1 Creating an already hooked process
2.4 The injected library entry point
2.4.1 The library constructor
2.4.2 The library Run-Method
2.5 Injection helper routines
2.6 How to install a hook
2.7 How to write a hook handler
2.8 Using Thread ACLs
2.9 Using handler utilities
2.10 The IPC helper API
2.11 Guidelines for stable hooking
2.12 A look into the future

ATTENTION

This Guide will cover the managed part of EasyHook only. Most things also apply to the unmanaged API. Refer to the “Unmanaged API Reference” for more information. The “Managed API Reference” also contains much additional information to the stuff covered here.

LICENSE CHANGE

EasyHook is now released under the Lesser GPL instead of the MIT License.

ProcessMonitor Screenshot

The following is a screenshot of my ProcessMonitor demo, shipping with the source code and the binary package:

It allows you to intercept CreateFile calls of any process currently running in your system.

1.1 Security Advisor

Unlike what some (commercial) hooking libraries out there are advertising to boost sales, user-mode hooking can NEVER be an option to apply additional security checks in any safe manner. If you only want to “sandbox” a dedicated process, you know well about, and the process in fact doesn’t know about EasyHook, this might succeed! But don’t ever attempt to write any security software based on user mode hooking. It won’t work, I promise you… This is also why EasyHook does not support a so called “System wide” injection, which in fact is just an illusion, because as I said, with user-mode hooks this will always be impossible. But if you want to keep this illusion you may stick with other (commercial) libraries attempting to do so…
Since EasyHook 2.5, you are able to easily hook 32-Bit kernels. Even if EasyHook would allow hooking 64-Bit kernels, I don’t recommend this because then you would get trouble with PatchGuard. Bypassing PatchGuard is possible, at least these days, but the chance of BSODing your customer’s PCs is too big. You should consider purchasing the PatchGuard API which will allow you to write security apps based on kernel mode interceptions. Kernel mode hooking (or the PatchGuard API) is the only option to apply additional security checks. Since Windows Vista, also the Windows Filtering Platform and other Vista specific APIs will be helpful to write security software!

So what is user-mode hooking for? In general, user-mode hooking is intended for API monitoring, like Mark Russinovich’s ProcessMonitor (alias FileMon/RegMon), resource leak detection, various malware which doesn’t need to care about security issues, extending applications and libraries you don’t have the source code for (also cracks may fall in this category), adding a compatibility layer for existing applications to run on newer OSes, etc.

If anyone uses security in context of user-mode hooks, your alarm bells should ring!

1.2 A simple FileMon derivate
1.2一个简单的文件监视程序

To prove that EasyHook really makes hooking simple, look at the following demo application, which will log all file accesses from a given process. We need a host process which injects the library and displays file accesses. It is possible to combine injection library and host process in one file as both are just threaded as valid NET assemblies, but I think to separate them is a more consistent approach. This demo will be used throughout the whole guide:
为了证明EasyHook的确让挂接变得简单,看看下面的常规应用,它会将所有给定进程的文件获取全部记录下来.我们需要一个托管进程将库注入并显示文件访问信息.我们可能通过注入库和托管进程在一个文件中结合并作为有效的NET程序集,但我想还是把它们分开更协调一点,该演示会在整个指南中使用。

using System;using System.Collections.Generic;using System.Runtime.Remoting;using System.Text;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.Install(typeof(Config).Assembly.Location);                Config.Register(                    "A FileMon like demo application.",                    "FileMon.exe",                    "FileMonInject.dll");                RemoteHooking.IpcCreateServer<FileMonInterface>(ref ChannelName, WellKnownObjectMode.SingleCall);                RemoteHooking.Inject(                    Int32.Parse(args[0]),                    InjectionOptions.None,                    "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 most complex part is the injected library which has to fulfill various requirements. We are hooking the CreateFile-API and redirecting all requests to our host process. The library will be unloaded if the host process is terminated:
最复杂的部分是应用于各种需求的注入库。我们现在挂在CreateFile-API并重定向所有的请求到我们的托管进程。如果托管进程结束,库将卸载。

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);        }        public void Run(            RemoteHooking.IContext InContext,            String InChannelName)        {            // install hook...            try            {                LocalHook.BeginUpdate(true);                CreateFileHook = LocalHook.Create(                    LocalHook.GetProcAddress("kernel32.dll", "CreateFileW"),                    new DCreateFile(CreateFile_Hooked),                    this);                LocalHook.EndUpdate();                CreateFileHook.ThreadACL.SetExclusiveACL(new Int32[] { 0 });            }            catch (Exception ExtInfo)            {                Interface.ReportException(ExtInfo);                return;            }            Interface.IsInstalled(RemoteHooking.GetCurrentProcessId());            // 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            {                // NET Remoting 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(InFileName);                }            }            catch            {            }            // call original API...            return CreateFile(                InFileName,                InDesiredAccess,                InShareMode,                InSecurityAttributes,                InCreationDisposition,                InFlagsAndAttributes,                InTemplateFile);        }    }}


Even if this might look strange, the next chapters will explain what is done there and why. You may start this application with a user defined target process ID as one and only parameter from command line. I recommend using the PID of “explorer.exe” because this will immediately produce output! Just browse your file system while running the FileMon utility...
即使看上去挺奇怪,下一章将解释来龙去脉。你可以使用一自定义的进程ID启动该程序,只需要一个参数。我建议使用”exploerer.exe”进程,因为他很快就会有输出。只要在命令行上这样运行程序即可:
Command line utility-> FileMon.exe %PID%

It is also possible to output the whole thing into a file what might provide more convenience:
也可以这样输出放到一个文件中:
Command line utility-> FileMon.exe %PID% > “C:\MyLog.txt”

2 A deep look under the hook
深入了解Hook

Now that you have seen the basic ideas of EasyHook and some sample code, we should start to discover what is really going on under the hood. In this chapter you will learn how to utilize most parts of the EasyHook API injecting libraries into any process and hooking any API you want.
现在你已经看到EasyHook的一些基本思想和一些示例代码,我们开始看看到底是怎么回事.这一章你会学到怎样用EasyHook API的主要部分,把库注入到任何进程和挂接任何你想要的API。

If I refer to a specific EasyHook-API, please look them up in the API-Reference manual. Mostly I will explain them on introduction but I can’t cover all the services you will get for every API at this point! To point out the difference between this guide and the API-Reference: the latter one describes the use of every single method, class and all of its parameters in detail, whereas the first one covers how all common APIs may be used as a whole to hook any process. In fact this guide does only cover a small excerpt of what you will learn by reading the API-Reference and vice versa.
如果我特别提及的Easy-hook-API,请查看它相关的手册。大部分时候我都会在介绍里解释,但不会像手册那么详细。为了指出此教程和常规API的不同声明:用于覆盖常规函数(钩子函数)将会手工描述其细节,包括方法,类及参数的描述。

You should still look at the examples and try to understand them before you start writing your own applications!
在你学习该内容之前,你应该查看并尝试理解该教程附带的源码。

2.1 Injection – A burden made easy
注入更加容易

In general, library injection is one of the most complicated parts of any hooking library. But EasyHook goes further. It provides three layers of injection abstraction and your library is the fourth one. The first layer is pure, relocatable assembler code. It launches the second layer, an unmanaged C++ method. The assembler code itself is really stable. It provides extensive error information and is able to unload itself without leaving any resource leaks in the target. The C++ layer starts the managed injection loader and adjusts the target’s PATH variable by adding the injecting process’ application base directory as first entry. This way you will have access to any file you would also have access to from your injecting process. The managed injection loader uses NET Reflection and NET Remoting to provide extensive error reports in case of failure and to find a proper entry point in your injection library. It also cares about graceful hook removal and resource cleanup. It is supported to load the same library multiple times into the same target!
一般来说,库注入是所有Hooking中最复杂的部分。但是EasyHook让他不一样了。它提供了注入抽像的三个层而你的库在第四个。第一层是纯的汇编代码。第二层,非托管的C++方法。汇编代码本身相当稳定。它提供很多的错误信息并能够将自己在目标中卸除不会留下资源泄露。C++层开始管理注入装载器并调整目标路径变量,通过添加进程应用主目录作为第一个入口。这样你的钩子进程就能够监听到进程(引用了被你植入的库的你想注入的进程)所访问的任何文件。托管的注入装载器使用NET反射和Net Remoting在很多出错时报告并发现在你注入库中的正确的入口点。它还关注挂钩的移除和资源的清除。它支持将同一个库多次的装入到同一目标中!

Another complex part is run on host side. It is supported to inject libraries into other terminal sessions, system services and even through WOW64 boundaries. To you, all cases seem the same. EasyHook will automatically select the right injection procedure. If EasyHook has succeeded injection, you can be 99% sure that your library has been successfully loaded and executed. If it fails you can be 99% sure that no resource leaks are left in the target and it remains in a stable, hookable state! Nearly all possible failures are being caught and it would be like a lottery win to see a target getting crashed by library injection! Please note that Windows Vista has advanced security for its subsystem services. They are running in a protected environment like the “Protected Media Path”. It is not possible to hook such services with EasyHook or any other user-mode library. The following shows the API method that we are talking about:
另外一个复杂的部分是运行在本机端的。它支持将库注入到其它终端会话,系统服务甚至WOW64边界。对你来说,所以有情况都是一样。EasyHook可以自动的选择正确的注入进程。如果EasyHook成功注入,你可99%确定你的库成功装载并运行。如果失败,则99%确保隐定和不泄露。几乎所有可能的错误都能被捕捉到,注入失败发生的几率等于你去买彩票一样了。请留意Windows Vista是否已经设置advanced security for its subsystem services。这个是运行在环境保护,像保护媒体路径。这个是不可能用EasyHook 和其他用户模式的库索Hooking到的。接下来将会介绍所讲述到的API方法。

RemoteHooking.Inject(         Int32.Parse(args[0]),         InjectionOptions.None,         "FileMonInject.dll", // 32-Bit version         "FileMonInject.dll", // 64-Bit version         ChannelName); 


The first four parameters are required. If you only want to hook either 32- or 64-Bit targets, you can set the unused path to null. You may either specify a file path that EasyHook will automatically translate to a full qualified assembly name or a partial assembly name like “FileMonInject, PublicKeyToken = 3287453648abcdef”. Currently there is only one injection option preventing EasyHook from attaching a debugger to the target but you should only set this option if the target does not like an attached debugger. EasyHook will detach it before injection is completed so in general there is nothing to worry about and it increases injection stability about magnitudes by using the target symbol addresses instead of assuming that the local ones remain valid in the target!
第一和第四个参数是必须的,如果你仅仅想Hook 32或64位目标,你可以把对应的路径参数改成null。你无需指定文件的路径,EasyHooke将会自动转换到一个full qualified 程序集名或局部的程序集名如“FileMonInject, PublicKeyToken = 3287453648abcdef”. 当前仅是一个注入选项而已,这是EasyHook附加一个debugger到目标,但你还是应该设置该选项如果目标看起来不像一个debuger,EasyHook会在完成注入后将它分离。无需担心注入程序集的路径是否有错。

You can pass as many additional parameters as you like but be aware of that you shall only pass types that are accessible through GAC, otherwise the injected library is not able to deserialize the parameter list. In such a case the exception will be redirected to the host process and you may catch it with a try-catch statement around RemoteHooking.Inject(). That's one of the great advantages!
如果你喜欢,你可以添加些参数,但是你必须知道,你输入的参数将会通过传达到GAC里头,GAC是不允许不匹配的参数,否则将会在程序里抛出错误,你可以通过try-catch来调试RemoteHooking.Inject(),这样抛出引用是一个不错的做法。

 The injected library will automatically get access to all additional parameters you specify after the fourth one. This way you can easily pass channel names to the target so that your injected library is able to connect to your host. Attention
在你指定第四个参数后,被注入库将会自动得到你添加指定的参数。这样你能够容易地指定频道名到目标里头,所以你注入的库能连接到你的主机(这里我没看明白)?注意:

Keep in mind that the CLR will unload your library only if the target is being terminated. Even if EasyHook releases all associated resources much earlier, you won’t be able to change the injected DLL which implies that the corresponding GAC library is not updateable until the target is terminated. So if you need to change your injected library very frequently (during development) you should always terminate the target after each debugging session. This will ensure that no application depends on the library and it can be removed from the GAC.
牢记,CLR会移除你的库如果目标已经终止,即使如果EasyHook更早释放所有相关的资源。你不能更改注入DLL,这意味这相应的GAC库不能更新直到目标终止为止。所以如果你需要频繁地更改你的注入库(开发期间),你应该总是在调试完毕后关闭目标,这是为了确保没有其他应用程序依然依靠着你要监听的库,和能从GAC里移除。

 

2.1.1 Creating an already hooked process
创建一个已存在的钩子进程

Sometimes it is necessary to hook a process from the beginning. This is no big deal, just call RemoteHooking.CreateAndInject() instead of Inject(). This will execute your library main method before any other instruction. You can resume the newly created process by calling RemoteHooking.WakeUpProcess() from your injected library Run() method. This only makes sense in conjunction with CreateAndInject(), otherwise it will do nothing.
有时候我们需要一开始就要Hook一个进程。这个不会太难,只要调用RemoteHooking.CreateAndInject()替换Inject()就可以了。这会比其他代码先执行库里的main方法。你也可以通过从库里的main方法执行RemoteHooking.WakeUpProcess() 创建进程。但这只是用于和CreateAndInject()的进程,否则将不起作用。

2.2 The injected library entry point
注入库入口

All injected libraries have to export at least one public class implementing the EasyHook.IEntryPoint interface. The interface itself is empty but identifies your class as entry point. A class marked as entry point this way, is expected to export an instance constructor and a Run() instance method having the signature “void Run(IContext, %ArgumentList%)” and “.ctor(IContext, %ArgumentList%)”. Please note that “%ArgumentList%” is a placeholder for additional parameters passed to RemoteHooking.Inject(). The list is starting with the fifth parameter you passed to Inject() and will be passed to both, the constructor and Run(). The list is not passed as array but as expanded parameter list. For example if you call Inject(Target, Options, Path32, Path64, String, Int32, MemoryStream), then %ArgumentList% would be “String, Int32, MemoryStream” and your expected Run() signature “void Run(IContext, String, Int32, MemoryStream)”. EasyHook enforces strict binding which means that the parameter list is not casted in any way. The types passed to Inject() shall be exactly the same as in the Run() signature. I hope this explains it.

The next thing to mention is that you should avoid using static fields or properties. Only if you know for sure that it is not possible having two instances of your library in the same target simultaneously, you can safely use static variables!


所有注入库必须导出最后一个类去实现EasyHook.IEntryPoint的接口。这个接口本身是空的,但是它会将你的类变成一个接口点。一个类通过这样的方法演变成接口点,目的是实例化一个构造器和一个Run()实例方法有注明“void Run(IContext, %ArgumentList%)” 和 “.ctor(IContext, %ArgumentList%)”. 请留意%ArgumentList%,这是一个占位符,用于添加参数去RemoteHooking.Inject(). 这个参数是由你指定的第5个参数开始,这个列表参数不能使用array,但可以作为扩展参数列表。例如,如果你调用Inject(Target, Options, Path32, Path64, String, Int32, MemoryStream), 因此%ArgumentList% 将会是 “String, Int32, MemoryStream”和你想 Run() signature “void Run(IContext, String, Int32, MemoryStream)”. EasyHook 将会严格约定,而你的参数将会让方法没被执行。同样Inject() 也完全一样地和Run()一样使用。
下面将会提及不应该使用静态修饰和属性。除非你能确定不会有两个实例同时调用你的库文件,这样用静态变量还算安全。

2.2.1 The library constructor
库构造器

The constructor is called immediately after EasyHook has gained control in the target process. You should only connect to your host and validate the parameters. At this point EasyHook already has a working connection to the host so all exceptions you are leaving unhandled, will automatically be redirected to the host process. A common constructor may look like this:
构造器是在EasyHook已经获得目标进程后直接使用。你仅应该连接到你的主机和有效的参数。在这点上,EasyHook已经做了除你要离开句柄的所有有关连接到主机的工作,这将会自动重定位到主机进程。一个普通的构造器应该像下边这样:

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();    }} 


 

2.2.2 The library Run-Method
库的Run方法

The Run() method can be threaded as application entry point. If you return from it, your library will be unloaded. But this is not really true ;-). In fact your library stays alive until the CLR decides to unload it. This behavior might change in future EasyHook versions by utilizing the CLR Hosting API, but currently we simply don’t know about! EasyHook will continue to call a cleanup thread every thirty seconds that initiates a GC for your library. In general this will soon cleanup all consumed resources and hooks.
Run方法可以作为应用程序入口点。如果你从这里返回,你的库将会被释放。但也不真是这样,实际上你的库依然存在,直到CLR决定释放它。这个特性将会在以后的EasyHook版本中改为由利用CLR主机的API实现。但当前我们不用在意这个问题。EasyHook会每30秒调用cleanup线程让GC去清理你的库。一般来讲会很快清理掉占用的资源和钩子。

In contrast to the constructor, your Run() method has no exception redirection. If you leave any exception unhandled, it will just initiate the usual unload procedure. In debug versions of EasyHook, you will find such unhandled exceptions in event logs. You should install all hooks and notify your host about success, what might look like this: Collapse
和构造器比较起来,你的Run方法是不会产生抛出意外,如果你留下你如何未经处理的抛出,这只会引发一个常规的释放程序。在调试版的EasyHook,你会看到很多未知句柄抛出的事件日志。你应该安装所有钩子和注明主机成功。就好象下边这样:死循环

public void Run(RemoteHooking.IContext InContext, String InChannelName){    // install hook...    try    {        LocalHook.BeginUpdate(true);        CreateFileHook = LocalHook.Create(            LocalHook.GetProcAddress("kernel32.dll", "CreateFileW"),            new DCreateFile(CreateFile_Hooked),            this);        LocalHook.EndUpdate();        CreateFileHook.ThreadACL.SetExclusiveACL(new Int32[] {0});    }    catch(Exception ExtInfo)    {        Interface.ReportException(ExtInfo);        return;    }    Interface.IsInstalled(RemoteHooking.GetCurrentProcessId());    // 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    {        // NET Remoting will raise an exception if host is unreachable    }} 


The loop simply sends the currently queued files accesses to the host process. If the host process is being terminated, such attempts throw an exception which causes the CLR to return from the Run() method and automatically unload your library!
这个循环是简单地发送当前队列的文件成功标记到主机进程。如果主机进程关闭,那会企图抛出一个意外,使CLR从Run方法返回和自动释放库。

2.3 Injection helper routines
注入辅助程序

There are several methods that you will find useful when dealing with injection. To query if the current user is administrator, you can use RemoteHooking.IsAdministrator. Please note that injection will fail in most cases if you don’t have admin privileges! Vista is using the UAC evaluating to admin privileges and so you should read some MSDN articles about how to utilize it.
有几中方法你会发现在注入里非常有用的。查询当前用户是否administartor,你可以使用RemoteHooking.IsAdministrator。请留意如果你不是有系统权限的话,很可能会出错。Vista是使用UAC判断admin权限,因此你应该看一下MSDN的文章关于如何利用它的。

If you already are admin, you may use the RemoteHooking.ExecuteAsService() method to execute a given static method under system privileges without the need to start a service. This is potentially useful when enumerating running processes of all sessions or for any other information querying task, which might require highest privileges. Keep in mind that the static method will be executed within a system service. So any handle or other process related information will be invalid when transmitted back into your process. You should design such a method so that you retrieve all information and store it in a serializable, process independent form. This form shall be an object that is returned and this way sent back to your application by NET Remoting.
如果你已经是admin,你可以在系统权限下使用RemoteHooking.ExecuteAsService() 方法去执行执行一个静态方法,而无需通过启动一个服务来执行。这个将会对列举进行所有会话很有用,或者查询任务的其他信息,这些都需要最高权限(翻译外话:我有点为EH的将来担心,这样好像在利用系统漏洞一样)。请记住静态方法会在系统服务中执行。所以任何句柄或者其他进程的信息将会无效,当你把它传到你的进程的时候。你应该设计一个方法使你要获得的所有信息储存在一个serializable里,处理独立于form,而这个form应该是一个对象被返回,和通过NET Remoting用这样的方法发送回你的应用程序。

If you want to determine whether a target process is 64-Bit or not, you may use RemoteHooking.IsX64Process(). But be aware of that you need PROCESS_QUERY_INFORMATION access to complete the call. It will also work on 32-Bit only Windows versions like Windows 2000, of course, by returning false in any case. Further there are RemoteHooking.GetCurrentProcessId() and GetCurrentThreadId() which might help to query the real native values in a pure managed environment! Managed thread IDs don’t necessarily map to native ones, when thinking about the coming NET FX.

如果你想知道进程是否属于64位,你可以使用Remotehooking.IsX64Process()。但是你就要去关注执行完毕后那个PROCESS_QUERY_INFORMATION了。这个也适用于32位的Windows环境(如Windows2000),当然了如果任何情况出错将返回false。进而某些函数如RemoteHooking.GetCurrentProcessId() 和 GetCurrentThreadID() 都可以帮助你在托管环境里检索到真实的值!当考虑到以后新版的NET FX,托管线程IDs非必须的。

2.4 How to install a hook

To install a hook you are required to pass at least two parameters. The first one is the entry point address you want to hook and the second one is the delegate where calls should be redirected to. The delegate shall have the UnmanagedFunctionPointerAttribute and also the exact call signature as the corresponding P-Invoke implementation. The best way is to look for a well tested P-Invoke implementation already out in the net and just make a delegate out of it. The managed hook handler also has to match this signature what is automatically enforced by the compiler… A P-Invoke implementation with the DllImportAttribute may be used to call the original API within the handler which will be necessary in most cases. Don’t forget that most APIs are expected to SetLastError() in case of failure. So you should set it to ERROR_ACCESS_DENIED or ERROR_INTERNAL_ERROR for example if your code does not want to execute the call. Otherwise external code might behave unexpected!

安装一个Hook你需要至少准备两个参数,第一个你想植入的入口地址,第二个是用于委托回调的。那个委托的需要提供非托管函数指针属性——被传递的P-invoke 执行。

A third parameter provides a way to associate an uninterpreted callback object with the hook. This is exactly the object accessible through HookRuntimeInfo.Callback later in the handler. EasyHook provides a level based transactional way for installing hooks. That means you may use several levels of hook installation code and commit/rollback each level separately. Only the final call to LocalHook.EndUpdate() will install all remaining hooks (some hooks might be removed due to rollback of sublevels; of course you could cancel the overall installation in such cases). BeginUpdate() will also take one argument specifying if the engine should ignore warnings. Warnings are evaluated in the final EndUpdate() call and occur if the engine “is thinking” that a hook may violate process stability. For example if the entry point is already hooked or contains other unusual machine code potentially causing side effects, a warning is raised. If you ignore warnings this will have no effect but otherwise the installation will be rolled back and an exception thrown. Note that you may set the warning state for each level. If no exception is thrown by the finally EndUpdate() all hooks of all non-cancelled levels have been installed; otherwise you can be sure that no hook has been installed! And this is why it is transactional. As only the last EndUpdate() call will install any hooks the overall process is something like a delayed installation. Local thread ACLs and various other properties are only accessible for installed hooks and will throw an exception if accessed during the root BeginUpdate() EndUpdate() level or any sublevel. You may always use the LocalHook.IsInstalled property to check if a given hook is installed. If so you can also use all other properties!

To uninstall a hook just remove all references to the object obtained during creation. To prevent it from being uninstalled you have to keep the reference of course… This is always a delayed removal because you won’t know when a hook is finally removed and your handler is never called again. If you want to remove it immediately you have to call LocalHook.Dispose() like known from dealing with unmanaged resources as file streams are. The following code snipped is an example of how to install a hook that is excluding the current thread from being intercepted:

LocalHook.BeginUpdate(true);{    CreateFileHook = LocalHook.Create(            LocalHook.GetProcAddress("kernel32.dll", "CreateFileW"),            new DCreateFile(CreateFile_Hooked),            this);}LocalHook.EndUpdate();CreateFileHook.ThreadACL.SetExclusiveACL(new Int32[] {0}); 


 

In debug versions of EasyHook, this will also output some extensive error information in the console and in the event logs. To access such information in release versions, you may call LocalHook.QueryJournal(). If you encounter any weird error during hooking, please include this journal in your error report. A journal does only contain useful information AFTER all hooks have been (tried to be) installed!

EasyHook also does provide a way to install pure unmanaged hooks using LocalHook.CreateUnmanaged(). You may write them using C++.NET that allows you to combine managed and unmanaged code. But keep in mind that you won’t have access to the HookRuntimeInformation class, this is why you can’t specify a callback for unmanaged hooks. All protection mechanisms (see next paragraph) will still wrap around your code. An empty unmanaged hook is about magnitudes faster than an empty managed one. If your handler once has gained execution, both are running with the same speed. The costly operation is the switch from unmanaged to managed environment and vice versa, which is not required when using pure unmanaged hooks! So your handler will be invoked in approx. 70 nanoseconds whereas a managed handler requires up to some microseconds… In some scenarios you will need this speed gain and this is why EasyHook offers it.

2.5 How to write a hook handler

Until now there was nothing complicated and I hope you agree. But writing a hook handler is something very strange. EasyHook already does provide several mechanisms to make writing hook handlers much easier or let’s say possible at all (I can’t imagine writing stable hook handlers for Detours):

  • A Thread Deadlock Barrier (TDB) which will allow you and any subcalls to invoke the hooked API from within its handler again. Normally this would lead into a deadlock because the handler would invoke itself again and again. EasyHook will prevent such loops! This also provides the advantage that you don’t need to keep track of a clean entry point.
  • An OS loader lock protection which will prevent your handler from being executed in an OS loader lock and in case of managed handler code attempting so would crash the process!
  • A TDB self protection which will protect the TDB itself from invoking any hook. This way you are able to hook any API except TlsGetValue/TlsSetValue or FlsGetValue/FlsSetValue (if supported), because even if the TDB requires some well known API methods, like HeapAlloc(), you can still hook them because of the TDB self protection!
  • A Thread ACL model allowing you to exclude well known dedicated threads, used to manage your hooking library (for example threads that are communicating with your host), from being intercepted. Refer to the chapter “Guidelines for stable hooking”, to learn about the differences and why the TDB is not enough!
  • A mechanism to provide hook specific callbacks through a static class namedHookRuntimeInfo. This way you are able to access the library instance without using a static variable for example. You may even query the hook’s return address and initial RSP value, providing information about which code portion has invoked the current interception. A stack trace can only be made with this information because the EasyHook prolog code will prevent a usual stack trace!

Without some of the above mechanisms it would be simply impossible to use managed code as hook handler and this is what is unique to EasyHook. All of those mechanisms are very stable and heavily tested with hundred simultaneous threads executing hundred thousands of hooks (on a quad-core CPU).

Using a hook handler you can simply provide your own implementation for the hooked API. But you should read and understand the related API documentation in detail, to provide the correct behavior for external code. If it is possible you should handle an interception as fast as possible and negotiate access or whatever within the injected library. Only in rare cases you should redirect calls to the host application in a synchronous manner as this will heavily slow down the hooked application; for example if an access negotiation can’t be completed with the knowledge of the library. In a real world application you should queue all requests and transmit them periodically as an array and not every single call. This can be done like it is shown in the FileMon demo.

Keep in mind that if you are compiling for 64-Bit or AnyCPU, you have to use the right type replacements. For example HANDLE does NOT map to Int32, but to IntPtr. In case of 32-Bit this is not important but when switching to 64-Bit a handle is 64-Bit wide, like IntPtr. A DWORD in contrast will always be 32-Bit as its name implies. The following is an example hook handler as used in the FileMon demo: Collapse

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(InFileName);        }    }    catch    {    }    // call original API...    return CreateFile(        InFileName,         InDesiredAccess,         InShareMode,         InSecurityAttributes,         InCreationDisposition,        InFlagsAndAttributes,         InTemplateFile);}


 

2.6 Using Thread ACLs

EasyHook manages a global ThreadACL and also an ACL for every hook. Further each ACL can either be inclusive or exclusive. This allows you to compose nearly any kind of access negotiation based on thread IDs without much effort. By default EasyHook sets an empty global exclusive ACL, which will grant access for all threads, and an empty inclusive local ACL for every hook, which will finally deny access for all threads. You see that the local ACLs are of higher importance and will overwrite the global ACL for a specific hook. All hooks are installed virtually suspended meaning no threads will pass access negotiation. This is to prevent hook handler invocation before you are able to initialize possible structures, like ACLs for example. To enable a hook for all threads just set its local ACL to an empty exclusive one. To enable it for the current thread only, just set a local inclusive ACL with zero as one and only entry. A thread ID of zero will automatically be replaced by the current thread ID BEFORE the ACL is set (this is negotiation will later use your thread ID and doesn’t know anything about zero). The following is a pseudo-code of IsThreadIntercepted() and should be self explaining:

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 return value of true will grant access and false will deny it. Just play around with them and use LocalHook.IsThreadIntercepted() to check whether your ACLs will provide expected access negotiation.

2.7 Using handler utilities

EasyHook exposes some debugging routines which may be extended in future versions. They are statically available through the EasyHook.Debugger class. Currently they solve the following issues which are common when writing hook handlers:

  • Translate a thread handle back to its thread ID (requires the handle to have THREAD_QUERY_INFORMATION access).
  • Translate a process handle back to its process ID (requires the handle to have PROCESS_QUERY_INFORMATION access).
  • Query kernel object information for any given handle. This way you are able to convert a file handle obtained by CreateFile() back to its file name. This will even work if the handle has no access to anything! Please note that this method is quiet slow and you should maintain a handle directory by hooking CloseHandle() to call it only once per handle!
  • Disassemble a given machine code portion into human readable text. This will allow you to provide advanced error information in case of failure… EasyHook is using this internally and I don’t know if you will need it ;-).

There is nothing special to know about, just read the corresponding API references. The latter two will only work if debugging is available and enabled (which is the default). On windows 2000 a debugger is not available and so also the latter two methods… To still support them, just ship the 32-Bit libraries “dbgeng.dll” and “dbghelp.dll” of the “Microsoft Debugging Tools for Windows 32-Bit Version” with your application and put them into the application base directory where also “EasyHook.dll” should be located! This will additionally consume four MBs of space… For windows XP and later you don’t need to do this because all required libraries are already included in every clean installation. Of course EasyHook itself is designed to work without a debugger if it is not available!

The first two methods will supply additional error information if a debugger is available. This is whether the handle is valid or not, whether it was opened with required access and whether the handle type is matching.

2.8 The IPC helper API

The core part of any target-host scenario is IPC. With NET remoting this has become really amazing. As you can see in the FileMon demo it is a thing of two lines to setup a stable, fast and secure IPC channel between the injected library and host. Of course this is only possible with the IPC helper routines exposed by EasyHook. Using the native IpcChannels the code blows up to three A4 pages which is still quiet small. The helper routines will take care of serialization setup and channel privileges so that you can even connect a system service with a normal user application running without admin privileges. It also offers to generate a random port name. This service should always be used because it is the only way to get a connection secure! If you want to provide your own name, you also have to specify proper well known SIDs, which are allowed to access the channel. You should always specify the built in admin group in this case, because all admin users could crash the whole system so you don’t have to worry about being exploited by an admin!

To create a server with a random port name, just call:

String ChannelName = null;RemoteHooking.IpcCreateServer<FileMonInterface>(            ref ChannelName,             WellKnownObjectMode.SingleCall);


Pass the generated port name to the client and call:

FileMon.FileMonInterface Interface =     RemoteHooking.IpcConnectClient<FileMon.FileMonInterface>(InChannelName); 


From now on you are able to call the server by using the client instance of the returned underlying MarshalByRefObject and those calls will be automatically redirected to the server. Isn’t that great?! But be aware of that this will only apply to instance members! Static members and fields will always be processed locally…

 

原创粉丝点击