危险漫步博客
新鲜的“黑客思维”就是从全新的角度看待黑客技术,从更高的层面去思考;专注于黑客精神及技术交流分享的独立博客。
文章2289 浏览18982119

浅说驱动程序的加载过程

在开始之前,首先简要介绍一下本文的主题,这篇文章是关于将内核模块加载到操作系统内核的方法的介绍。所谓“内核模块”,指的便是通常所说的驱动程序。不过因为加载到内核的程序通常是用一来操作硬件的,所以驱动程序的名字要更常见些。在以下的叙述中,我将主要使用“驱动程序”这个词。众所周知,Windows操作系统将程序划分为用户模式和内核模式,在x86计算机上,用户模式的程序运行在Ring3,而内核模式的程序运行在Ring0。运行在Rin g3级别上的程序有诸多限制,这方面的例子是不胜枚举的。对于用户模式程序,主

要使用由操作系统提供的面向用户模式的程序接口来调用操作系统提供的功能,这就是常说的Win32APls。从应用程序的角度来看,操作系统提供的函数接口是相当丰富的,甚至很少有人知道windows提供的API的确切的数目。可是从Hacking的角度来看,API提供的功能却是远远不能满足Hacker们的好奇心的。操作系统对于用户模式与内核模式的划分,本意自然是出于维持系统稳定的目的,可某些时候我们并不领情。

关于把代码加载到内核中并孰行的讨论,主要是围绕着Rootkit这一主题进行的。不得不承认Rootkit是高深的技术,绝非看一两本书编写几个例程就能掌握的。本文不是对Rootkit细节的描述,但却介绍了通常Rootkit是如何将自己加载到内核的。在本文中,我将循序渐进的展示一个驱动程序的编译,加载,以及执行的过程。在进行接下来的正式的叙述之前,我假设读者已经对C语言有一定的理解,并对win32程序设计略知一二。实际上,正如大多数Windows程序一样,所谓的加载,其实只是.对几个特定的函数的顺序调用,其间并不涉及复杂的算法,所以读者没必要担心自己无法理解文章内容。

在介绍如何加载驱动程序之前,首先介绍如何编译一个基本的驱动程序。

通常,编写Windows系统的驱动程序必然要用到微软提供的WDK,即Windows Driver Kits。WDK是新近才有的名字,过去同样的东西被称作是DDK,pDriver Development Kits。后者已经过时,新学驱动程序开发的程序员,推荐使用WDK。

如何下载和安装WDK的内容就不提了。安装完成之后,在开始菜单中会有Windows Driver Kits文件夹,其中包含的Build Environments文件夹是要注意一下的,稍后我还会提到这里。编译驱动程序通常使用build工具来构建的。

内核驱动程序通常是用C语言编写的,但这里我为了省事,在程序使用了一点简单的汇编代码极少,所以这个程序很简单。但观察代码可以发现,这段代码竟然试图访问地址0。这要是在用户模式,这个程序肯定被Windows无情的终止掉。但这段代码最终是在内核模式作为驱动程序运行的。结果嘛,则是发生蓝屏死机(BSoD,Blue Screen of Death)。蓝屏死机可不经常见到,虽说蓝屏死机在Windows 98的时代是家常便饭。但后来更新的操作系统很少会发生这样豹事情。而这个程序不简单的地方就在这里。

对于参数表中出现的IN我也要略微解释一下。这是个空的宏,它用来说明这个参数是要求调用者输入一个值的。与之对应的宏是OUT,这个宏的意义恰好相反,它表示某个参数是要向调用者返回一个值的。这一对宏在驱动程序开发中极为常见,但却又很容易迷惑初学者,所以在这里提一下。

在明白了这些之后,我们新建一个项目文件夹,把以上代码保存为simpledriver.c。使用buildl具构建驱动程序,除代码文件外,还需要两个额外的文件,即SOURCES和MAKEFILE两个无扩展名的文件。本例中,SOURCES文件的内容如下:

TARGETNAME=ROOTKIT

TARGETPATH=obj

TARGETTYPE=DRIVER

SOURCES=simpledriver.c

MAKEFILE文件内容如下:

!INCLUDE $(NTMAKEENV)\makefile.def

在建立了这三个文件之后,打开Build Environments文件夹,选择一个目标操作系统。其中Checked表示的是调试模式,而Free表示的是发行模式。这二者的关系与通常使用V C++编写应用程序时的Debug与Release类似。我使用的是Windows 7系统,但这里我们仍然选择Windows XP,并选择Checked模式。单击之后出现命令提示符,转到刚刚建立的项目文件夹,输入build并回车确认。短暂的等待之后,驱动编译完成。这时在你的项目文件夹里面会出现一个保存有新创建的驱动程序的新的文件夹。本例中,至此,我已经得到了一个名为rootkit.sys的驱动程序。

以上所展示的是一个简单的驱动程序的编译过程,接下来我们编写加载驱动程序到内核的代码。

通常的方案是将驱动程序作为加载程序的一项资源,包括到加载程序中去,在加载程序运行的时候再将驱动程序释放出来。这样做的好处就是最后只用一个可执行文件就可以做所有的事。首先编写一个简单的资源脚本文件rootkit.rc,将rootkit.sys命名为rootkit,并定义为RCDATA类型,最后我们得到rootkit.res资源文件。我们如何在程序中释放资源呢?下面的代码是主要代码文件loader.c的一个片段,展示了如何从程序中获得资源并释放的过程。

在以上的代码中,我故意省略掉若干错误处理代码以使代码显得紧凑。简单对以上代码做些解释。从文件中取得资源的步骤很简单,首先调用FindResource()找到资源,这个函数返回一个HRSRC类型的值。然后使用这个值调用SizeofResource0获得资源的文件长度。这个参数在生成文件的时候会用到,但这并不是加载资源的过程本身所需要的步

骤。然后就要将资源实际加载到内存,这步使用LoadResource()函数完成,这步完成后返回的是一Al HGLOBAL类型的值,注意与前面返回的HRSRC类型的值是不一样的。然后利用刚刚得到的值调用LockResource()锁定资源。这个函数返回一个指向资源的指针。然后创建文件和写入内容的步骤很简单,便不再赘述了。要查找关于Windows编程的资料,常用的还是MSDN,对此我就不多说了。下面

来看最终的加载步骤,代码就不一一列举了啊!

将驱动程序加载到内核的标准方法是:首先调用OpenSCManager()函数打开所谓的SCM,这是系统的服务管理器。这里我们提到“服务”两字,基本可以认为它等价于“驱动”二字。通常我们把那些用户级的后台程序也称为服务,但是此服务非彼服务也。服务可以用用户模式程序提供,也可以由内核驱动提供,所以微软将其统称为服务也未尝不可。调用成功后,我们获得SCM的操作句柄。下一步就要建立服务了。调用CreateService()建立服务的函数时指定了服务是由驱动程序提供的。此函数参数颇多,但是却都很简单。简单说明一下,第一个参数是上一步得到的SCM的操作句柄。第二和第三个参数分别指定了服务的“服务名”和“显示名”。对服务的访问是具有权限控制的,在创建完成服务后,同时得到服务的操作句柄,这个句柄所具有的权限,就是由第四个参数指定的。第五、第七两个参数不做过多解释,第六个参数要额外注意一下。因为例子提供的测试驱动是会导致系统蓝屏死机的,所以这里指定为SERVIC E—DEMAND_START,意思是此服务只能由程序通过调用StartSen'ice0函数才能启动。如果读者指定了开机自动启动或者是其他自动启劫选项,那么连续蓝屏就是不可避免的了。

如果服务已经被创建,那么CreateService()就不会成功。这时我们只需要打开已存在的服务就可以了。这一步调用OpenService0并获得服务操作句柄,然后进一步调用StartService0启动服务。启动服务的时候,系统调用我们编写的驱动程序的入口函数。

在驱动的初始化函数没有返回之前,StartService0函数不会返回,在我们这里,它也不可能返回。与以上程序同时提供的还有一个unload.exe程序,这个程序不过是调用DeleteService()删除了服务而已,代码十分简单,就不在这里展示了。

记得以上编写完成时,我一时心急,直接在我的windows 7上运行测试了一下,结果悲哀的蓝屏死机后,机器自动重启,然后还有二三倒霉事我就不提了,建议读者不要在自己的计算机上执行它。

以上就是本文的全部内容。此后的工作,就是在那个驱动程序上下功夫了。市面上有很多讲述编写驱动程序的书,有兴趣做进一步研究的读者可以买来阅读,本文并不是编写真正的rootkit。事实上,本文的技术含量离rootkit还很远。

相关推荐