分类: WINDOWS
2008-03-20 12:18:47
文章这个章节我将集中精力阐述HookTool.DLL的类设计。在开过程中,给类分派职责是一个重要部分,每个给出的类要封装特定的功能并代表一个特定的逻辑实体。CmoduleScope是系统的主要入口,它执行时只有一个实例,并且是线程安全的模式。执行时,它的构造函数接受三个在DLL共享数据段内的指针,这些指针将在所有的进程中使用。这样,由于遵循封装的规则,这些系统级变量的值可以轻松的在类的内部进行维护。当应用程序加载HookTool library时,DLL创建CmoduleScope的一个实例,用于接受DLL_PROCESS_ATTACH消息通知。这一步仅仅完成CmoduleScope实现的初始化工作。在CmoduleScope对象的构造中,一个重要的片段是创建适当的注入对象。注入对象如何创建是在分析完HookTool.ini配置文件后才决定的,而之后也通过Scope段的值决定了UseWindowsHook参数的值。万一系统是运行在Windows 9x下的,这个参数的值就不会被系统检测到,因为Windows 9x不支持通过远程线程注入。
在主处理对象实例化之后,程序会调用ManageModuleEnlistment()方法。下面是这个方法的简化实现版本:
// Called on DLL_PROCESS_ATTACH DLL notification
BOOL CModuleScope::ManageModuleEnlistment()
{
BOOL bResult = FALSE;
// Check if it is the hook server
if (FALSE == *m_pbHookInstalled)
{
// Set the flag, thus we will know that the server
// has been installed
*m_pbHookInstalled = TRUE;
// and return success error code
bResult = TRUE;
}
// and any other process should be examined whether
// it should be hooked up by the DLL
else
{
bResult = m_pInjector->IsProcessForHooking(m_szProcessName);
if (bResult)
InitializeHookManagement();
}
return bResult;
}
ManageModuleEnlistment()方法的实现是浅显直接的,它通过m_pbHookInstalledr的值检查这个方法是否已经被勾子服务程序调用过。如果已经事先被勾子服务程序调用过了,它只简单的间接的把sg_bHookInstalled标记置为真—-真值是提示勾子服务程序已经安装好。
下一步操作是,勾子服务程序通过单独调用DLL的导出函数InstallHook()激活引擎。实际上,这个调用是一个“委托”调用,它执行的是CmoduleScope的方法InstallHookMethod().这个方法的主要目的是强制目的进程加载或卸载HookTool.DLL.
// Activate/Deactivate hooking engine
BOOL CModuleScope::InstallHookMethod( BOOL bActivate,
HWND hWndServer)
{
BOOL bResult;
if (bActivate)
{
*m_phwndServer = hWndServer;
bResult = m_pInjector->InjectModuleIntoAllProcesses();
}
else
{
m_pInjector->EjectModuleFromAllProcesses();
*m_phwndServer = NULL;
bResult = TRUE;
}
return bResult;
}
HookTool.DLL提供了两种把自己注入到外部进程地址空间的机制。一种是使用Windows勾子,另一种是通过CreateRemoteThread API注入DLL。在这个监视系统的构架中,它定义了一个抽象类Cinjector,这个抽象类暴露一个用于注入和卸出DLL的纯虚函数.类CwinHookInjector和CremThreadInjector继承于相同的基类Cinjector.可就这样,他们提供纯虚函数InjectModuleIntoAllProcesses()和EjectModuleFromAllProcesses()两个不同的实现,这两个函数都定义为Cinjector的接口函数。
CwinHookInjector类实现了Windows勾子的注入机制,它通过下面调用安装过滤函数(即安装勾子)
// Inject the DLL into all running processes
BOOL CWinHookInjector::InjectModuleIntoAllProcesses()
{
*sm_pHook = ::SetWindowsHookEx(WH_GETMESSAGE,
(HOOKPROC)(GetMsgProc),
ModuleFromAddress(GetMsgProc),
0 );
return (NULL != *sm_pHook);
}
正如你所见,CwinHookInjector要求向操作系统安装WH_GETMESSAGE勾子。勾子服务程序仅仅会执行这个方法一次。SetWindowsHookEx()的最后一个参数是0,因为GetMsgProc()是设计为一个系统级的回调勾子的。GetMsgProc()这个回调函数将被系统在每次要处理一个特定消息时所调用。有意思的是,如果我们不想监视消息处理,我们需要提供一个回调函数GetMsgProce()的“哑”(dummy)实现。我们这样实现只是为了能够从操作系统那获取一个自由的注入机制.
当调用SetWindowsHookEx()时,操作系统会先检查导出GetMsgProc()函数的DLL(HookToll.DLL)是否已经映射到了所有的GUI进程中。如果指定的DLL还没有加载,Windows会强制这些GUI进程映射它。有意思的是,系统级的勾子DLL不应该在对应的DllMain()里立即返回FALSE.那是因为操作系统检测DllMain()的返回值并一直尝试加载DLL直到相应的DllMain()最终返回TRUE.
一个差别很大的方法在CremThreadInjector类中阐述。这个实现是基于使用远程线程注入的。CremThreadInjector类设计一个接收进程创建和中止消息的方法,以此来扩展对windows进程的维护。它还内聚一个CntInjectorThread对象,用于捕获内核模式的驱动发送过来消息通知。因此,每次当一个进程被创建时,CNtInjectorThread ::OnCreateProcess()就会被处理一次,相应的当进程退出运行时,CNtInjectorThread ::OnTerminateProcess()也会被自动调用.不像windows勾子,这种方法依赖于远程线程,当一个新进程被创建时,还需要(注入程序)手动注入DLL。当新进程开启时,这样监控进程活动提供给我们一个简单的修改(进程行为)的途径。
CntDriverController类实现了对管理系统服务和驱动的API函数的封装功能,设计它是为了掌控内核模式驱动NTProcDrv.sys的加载和卸载。具体实现将在稍后章节中讨论。
当HookTool.DLL被成功都注入到一个特定的进程之后,在DllMain()中会调用ManageModuleEnlistment()方法。回忆一下我前面介绍的这个方法的实现,它通过CmoduleScope的成员m_pbHookInstalled检测了共享变量sg_bHookInstalled的值。
因为勾子服务程序在初始化时已经把sg_bHookInstalled的值设为TRUE,监视系统检查当前的那个应用程序是不是必须要被“勾挂”(监视),如果是,则实际上是对激活针对这个进程的监视引擎.
开启了监视引擎是在CModuleScope::InitializeHookManagement()中实现的。这个方法的目的是为一些重要的函数比如LoadLibrary()和GetProcAddress API家庭安装勾子。通过这种途径,可以监视进程初始化后对DLLS的加载。每次当一个新的DLL准备要映射时,有必要先查找它的“导入表”,以确保监视系统不会错过对任何想捕获的函数的调用。
在InitializeHookManagement()方法结束时,我们对真正想欲监控的函数进行一些初始化工作。
因为示例代码中阐述的怎么捕获多个用户提供的函数(译注:欲监控的API),我们必须对每个函数提供单独的监控实现。这意味着,用这种方法时,不能只是改变IAT中不同导入函数的地址以指向唯一的通用的干预函数.监控函数需要知道调用指向的是哪个函数。有一点也非常关键的是,指向的函数调用方式必须和原来WINAPI原型精确匹配,否则堆栈就会被破坏。举个例子,CmoduleScope实现三个静态方法MyTextOutA(),MyTextOutW() 和 MyExitProcess().一旦HookTool.DLL被注入到进程的地址空间并且监控引擎就被激活,每次当有对原始API TextOutA()的调用时,CModuleScope:: MyTextOutA()会替换原来的调用。
已经提出的这个监控引擎的设计本身非常有效并且提供了很大的灵活度。不管怎样,它适合于大多数情况,在这些情况中,欲干预的函数集是提前知道的并且他们数量是有限的。
如果你想对系统增加新的勾子,你只需要简单的声明并实现干预函数,就像我已经做好的MyTextOutA()和MyExitProcess那样。然后你需要在InitializeHookManagement()的实现加注册他们。
对于要实现有控制外部进程需求的系统来说,干预和跟踪进程执行是非常有用的机制。当一个进程开启时,把信息通知到感兴趣的第三方是关于开发监控系统和系统级勾子的一相经典问题。Win32 API提供了有用的库集(PSAPI 和ToolHelp [16]),允许你枚举系统当前运行的进程。尽管这些API功能相当强大,但是当一个进程开始或终止时,你并不能通过他们获得消息。幸运的是, NT/2K提供了一组由NTOSKRNL导出API,在Windows DDK文档《Process Structure Routines》有详细描述.这些API其中有一个是PsSetCreateProcessNotifyRoutine(),能够让你注册一个系统级的回调函数,在每次有新的进程创建,停止或被强行终止时,操作系统都会调用这个函数。这个被提及的函数可以简单的通过实现一个内核模式的驱动和用户级的控制程序来监控所有进程。这个“Windows进程的守护者” NTProcDrv提供了最小化的功能要求,可用基于NT系统的进程监控。欲知更详细的细节,请参见文章[11]和[15].这个驱动(NTProcDrv)的代码可以在NTProcDrv.c中找到。因为是在用户模式实现驱动的加载和卸载,当前登录的用户必须要用系统管理员的权限,否则你将无法安装驱动,进程的监控也就被打断。一个退一步的方法是,你可以手动以系统管理员的身份手动安装它或执行windows 2k提供的HookSrv.exe.以一个不同的用户身份运行。
最后的也是最重要的,这里提供的工具(即编译出来的可执行文件)可以简单的通过改变INI文件(HookTool.ini)的配置进行管理。这个文件决定是使用Windows勾子(9x和NT/2k有效)还是使用CreateRemoteThread()(只在NT/2k下有效)进行注入.文件也可以提供一个方法去指定哪个进程应该“勾挂”和哪个不要。如果你想监控某个进程,则在[Trace]有一个选项Enabled,允许你记录系统活动。这个选项允许你使用ClogFile类暴露的方法来报告丰富的错误信息。实际上,ClogFile是线程安全的实现并且你不用去关心和访问系统资源(比如log file)相关的同步问题。欲知细节,请参见ClogFile类和HookTool.ini文件的内容。
示例代码
这个工程用VC6++SP4编译通过,并要求有SDK平台。在Windows NT产品的环境下,你需要提供PSAPI.DLL,以实现CtaskManager类。在你运行示例代码之前,确保HookTool.ini文件已经根据你的特定需要进行配置。
如果喜欢更加底层的东西,并且对未来内核模式驱动NTProcDrv代码的开发感兴趣的话,需要安装WINDOWS DDK.
超出本文范围的讨论
为了简单起见,一些我在此文中有意不提的主题如下:
a. 监控本地API调用
b. 监控Windows 9x系统进程执行的驱动
UNICODE支持也没有提,尽管你也能监控导入的UNICODE API.
总结
这篇文章并不提供针对所有API监控主题的完整的指南,显然是因为还缺少很多细节的东西。不管怎么样,我已经尝试了在这个篇幅的文章里用足够重要的信息来帮助那些对用户模式Win32 API监控感兴趣的读者。
参考
[1] [2] ,
[3] , Mark Russinovich and Bryce Cogswell, Dr.Dobb's Journal January 1997
[4] ,
[5] "Undocumented Windows 2000 Secrets" ,
[6] "Peering Inside the PE: A Tour of the Win32 Portable Executable File Format" by Matt Pietrek, March 1994
[7] MSDN Knowledge base Q197571
[8] , Wayne J. Radburn
[9] "Load Your 32-bit DLL into Another Process's Address Space Using INJLIB" MSJ May 1994
[10] "Programming Windows Security", Keith Brown
[11] Detecting Windows NT/2K process execution Ivo Ivanov, 2002
[12]
[13a] , part 1, Matt Pietrek, MSJ February 2002
[13b] , part 2, Matt Pietrek, MSJ March 2002
[14] ,
[15]
[16] , Ivo Ivanov, 2001
[17] "Undocumented Windows NT" , Prasad Dabak, Sandeep Phadke and Milind Borate
[18]
参考原文:
下载: