科技时代新浪首页 > 科技时代 > 学园 > 正文

编程技巧:利用钩子技术控制进程创建


http://www.sina.com.cn 2005年11月21日 09:39 天极yesky

  一、 简介

  最近,我了解到一个叫做Sanctuary的相当有趣的安全产品。它能够阻止任何程序的运行-这些程序没有显示在软件列表中-该表中的程序被允许在一个特定的机器上运行。结果,PC用户得到保护而免于各种插件间谍软件、蠕虫和特洛伊木马的侵袭-就算能够进入他/她的计算机,它们也没有机会执行,并因此没有机会对该机器造成任何损害。当然,我觉得这个特征相当有趣;并且,在稍作思考以后,我就有了一个自己的实现。因此,本文将描述如何通过钩住本机API的方式来实现监控一个进程的创建并在系统级上对之进行控制。

  本文大胆假设,目标进程是以一种用户模式(外壳函数,CreateProcess(),用一系列的本机API调用的手工的进程创建,等等)创建的。尽管从理论上,一个进程能够以内核方式启动;不过从实际来看,如此的可能性是可以忽略不计的,因此我们不必为此担心。为什么?请逻辑地思考一下-为了以内核方式启动一个进程,用户必须装载一个驱动程序,该驱动程序反过来首先要暗示某种用户模式代码的执行。因此,为了防止未被授权程序的执行,我们可以安全地在系统级上以用户模式限制我们自己控制的进程的创建。

  二、 定义策略

  首先让我们明确,之所以这样做的目的是为了在系统级上监视和控制进程创建。

  进程创建是一件相当复杂的事情-它包含相当多的工作(如果你不相信我,可以反汇编CreateProcess(),这样你就会亲眼看到这点)。为了启动一个进程,可以使用下列步骤:

  1.可执行文件必须被以FILE_EXECUTE存取方式打开。

  2.可执行映像必须被装载进RAM。

  3.必须建立进程执行对象(EPROCESS,KPROCESS和PEB结构)。

  4.必须为新建进程分配地址空间。

  5.必须建立进程的主线程的线程执行对象(ETHREAD,KTHREAD和TEBstructures)。

  6.必须为主线程分配堆栈。

  7.必须建立进程的主线程的执行上下文。

  8.必须通知Win32子系统有关该新进程的创建情况。

  为确保这些步骤中的任何一步的成功,所有其前面的步骤必须是成功执行的(你不能够在没有一个可执行区句柄的情况下建立一个可执行进程对象;没有文件句柄的情况下你无法映射一个可执行区,等等)。因此,如果我们决定退出任何这些步骤,所有后面的步骤也会失败,以至于整个进程创建会失败。上面所有的步骤都可以通过调用某些本机API函数的方式来实现,这是可以理解的。因此,为了监视和控制进程创建,我们所有要做的就是钩住这些API函数-它们无法旁路掉要创建一新进程所要执行的代码。

  我们应该钩住哪些本机API函数呢?尽管NtCreateProcess()似乎是问题的最显然的答案,但是,这个答案是错误的-有可能不需要调用这个函数也可以创建一个新的进程。例如,CreateProcess()可以创建与进程相关的内核模式结构而不是调用NtCreateProcess()。因此,这样以来钩住NtCreateProcess()对我们毫无帮助。

  为了监视进程的创建,我们必须或者钩住NtCreateFile()和NtOpenFile(),或者钩住NtCreateSection()-不经调用这些API是绝对无法运行任何可执行文件的。如果我们决定监视对NtCreateFile()和NtOpenFile()的调用,那么我们必须区别开进程创建和常规的文件IO操作。这项任务并不总是那么容易。例如,如果一些可执行文件正在被以FILE_ALL_ACCESS存取方式打开,我们该怎么办?这仅是一个IO操作还是一个进程创建的一部分?在这点上,是很难判断的-我们需要了解调用线程下一步要干什么。因此,钩住NtCreateFile()和NtOpenFile()不是最好的可能性选择。

  钩住NtCreateSection()是更为合理的-如果我们想拦截对NtCreateSection()的调用,发出的请求是作为一个映像(SEC_IMAGE属性)映射可执行文件(SEC_IMAGE属性),同时请求允许执行的页面保护;那么,我们可以确信该进程将要被启动。在这一点上,我们是能够作出决定的,并且在我们不想要创建该进程的情况下,让NtCreateSection()返回STATUS_ACCESS_DENIED。因此,为了完全控制目标机器上的进程创建,所有我们要做的是在系统级上钩住NtCreateSection()。

  象来自于ntdll.dll中的任何其它代理一样,NtCreateSection()用服务索引加载EAX,使EDX指向函数参数,并且把执行权传递到KiDispatchService()内核模式例程(这是通过Windows NT/2000中的INT 0x2E指令或者Windows XP下的SYSENTER指令实现的)。在校验完函数参数之后,KiDispatchService()把执行权传递到服务的实际实现部分-它的地址可用于服务描述表(指向这个表的指针由ntoskrnl.exe作为KeServiceDescriptorTable变量所输出,所以它对于内核模式驱动程序是可用的)中。服务描述表通过下列结构所描述:

  struct SYS_SERVICE_TABLE {

  void **ServiceTable;

  unsigned long CounterTable;

  unsigned long ServiceLimit;

  void **ArgumentsTable;

  };

  这个结构中的ServiceTable字段指向一个数组-它拥有所有实现系统服务的函数的地址。因此,为了在系统级上钩住任何本机API函数,所有我们必须做的是把我们的代理函数的地址写入被KeServiceDescriptorTable的ServiceTable字段所指向的数组的第i个入口(i是服务索引)。

  至此,看起来我们已了解了在系统级上监视和控制进程创建的一切。现在让我们开始实际的工作。

 [1] [2] [3] [下一页]

发表评论

爱问(iAsk.com)



评论】【论坛】【收藏此页】【 】【多种方式看新闻】【下载点点通】【打印】【关闭




科技时代意见反馈留言板 电话:010-82628888-5595   欢迎批评指正

新浪简介 | About Sina | 广告服务 | 联系我们 | 招聘信息 | 网站律师 | SINA English | 会员注册 | 产品答疑

Copyright © 1996 - 2005 SINA Inc. All Rights Reserved

版权所有 新浪网