《Undocumented Windows 2000 Secrets》翻译 --- 第三章(3)

第三章  编写内核模式驱动程序

翻译:Kendiv( fcczj@263.net )

更新: Thursday, February 10, 2005

声明:转载请注明出处,并保证文章的完整性,本人保留译文的所有权利。

 

设备I/O控制

就像在本章开头的简介中提到的,在本书中,我们不会构建某一具体硬件的驱动程序。替代的是,我们将利用功能强大的内核驱动程序来研究Windows 2000的秘密。从实际结果来看,驱动程序的强大之处在于它们能在CPU的最高特权级别上运行。这意味着内核驱动可以访问所有的系统资源,可以读取所有的内存空间,而且也被允许执行CPU的特权指令,如,读取CPU控制寄存器的当前值等。而处于用户模式下的程序如果试图从内核空间中读取一个字节或者试图执行像MOV EAX,CR3这样的汇编指令都会被立即终止掉。不过,这种强大的底线是驱动程序的一个很小的错误就会让整个系统崩溃。即使是非常小的错误发生,也会让系统蓝屏,因此开发内核程序的人员必须比Win32应用程序或DLL的开发人员更加仔细的处理错误。还记得我们在第一章里使用的导致系统蓝屏的Windows 2000 Killer device driver吗?它所作的一切只是触及了虚拟内存地址0x00000000,然后就---Boom!!!你应该意识到在开发内核驱动时,你会比以往更频繁的重启你的机器。

 

在随后章节中,我给出的驱动程序代码将采用称为设备I/O控制(IOCTL)的技术,以允许用户模式下的代码实现一定程序的“远程控制”。如果应用程序需要访问在用户模式下无法触及的系统资源,那内核驱动程序将可很好的完成此项工作,而IOCTL则是联系二者的桥梁。事实上,IOCTL并不是Windows 2000采用的新技术。即使旧的操作系统---DOS 2.11也具有这种能力,0x44函数及其子函数构成了DOSIOCTL。基本上,IOCTL是通过控制通路和设备通讯的一中手段,控制通路在逻辑上独立于数据通路。想象一个硬盘设备通过其主数据通路传递磁盘扇区中的内容。如果客户想获取当前设备使用的媒体信息,它就必须使用另一个不同的通路。例如,DOS函数0x44,其子函数0x0d0x66构成了DOSIOCTL,调用这些函数就可读取磁盘的32位连续数据(参考Brown and Kyle 19911993)。

 

设备I/O控制根据要控制的设备,可以有多种实现方式。就其一般形式来说,IOCTL有如下几类:

l         客户端通过一个特殊的进入点来控制设备。在DOS中,这个进入点为INT 21h、函数号0x44。在Windows 2000中,则通过Kernel32.dll导出的Win32函数DeviceIoControl()

l         客户端通过提供设备的唯一标识符、控制代码以及一个存放输入数据的缓冲区、一个存放输出数据的缓冲区来调用IOCTL的进入点。对于Windows 2000,设备标识符是成功打开的设备的句柄(HANDLE)。

l         控制代码用于告诉目标设备的IOCTL分派器(dispatcher),客户端请求的是哪一个控制函数。

l         输入缓冲区中可包含任意地附加数据,设备可能需要这些数据来完成客户所请求的操作。

l         客户所请求的操作产生的任何数据,都会保存在客户端提供的输出缓冲区中。

l         IOCTL操作的整体结果通过返回给客户端的状态代码来表示

 

很显然这是一种强大的通用机制,这种机制可以适用于很大范围的控制请求。例如,应用程序在访问系统内核所占用的内存空间时会被禁止,这是因为当程序触及该内存空间时会立即抛出一个异常,不过程序可以通过加载一个内核驱动程序来完成此项工作,这样就可避免出现异常。交互的两个模块都需遵循IOCTL协议来管理数据的传输。例如,程序可能通过给驱动程序发送控制代码0x80002000来读取内存或发送0x80002001来向内存中写入数据。对于读取请求,IOCTL输入缓冲区或许要提供基地址和要读取的字节数。内核驱动程序能获取这些请求并通过控制代码来判断是读取操作还是写入操作。对于读取请求,内核驱动程序会将请求的内存范围内的数据复制到调用者提供的输出缓冲区中,如果输出缓冲区足够容纳这些数据,则返回成功代码。对于写入请求,驱动程序会将输入缓冲区中的数据复制到指定的内存中(该内存的起始位置也由输入缓冲区指定)。在第四章,我将提供一个Memory Spy的示列代码。

 

现在,可以看出IOCTLWin32应用程序的一种后门,通过IOCTL,程序可以执行几乎所有的操作,而在此之前,这些操作仅允许特权模块执行。当然,这需要首先编写一个特权级的模块,但是,一旦你拥有一个运行于系统中的Spy模块,一切就变得很简单了。本书的两个目标是:详细展示如何编写内核模式的驱动程序以及一个可以完成很多让人惊异的事的驱动程序的示例代码。

 

Windows 2000Killer Device

在开始更高级的驱动程序工程之前,让我们先看看一个非常简单的驱动程序。在第一章中,我介绍了Windows 2000Killer Device----w2k_kill.sys,它被设计为引发一个良性的系统崩溃。这个驱动程序并不需要示例3-3中的大多数代码,因为它在有机会收到第一个I/O请求包之前就会使系统崩溃。示例3-7给出了它的实现代码。这里没有给出w2k_kill.h文件,因为它不不包含任何我们感兴趣的代码。

 

示列3-7中的代码没有在DriverEntry()中执行初始化操作,因为系统会在DriverEntry()返回前就崩溃,所以没有必要进行这些额外的工作。

#define  _W2K_KILL_SYS_

#include <ddk/ntddk.h>

#include "w2k_kill.h"

// =================================================================

// DISCARDABLE FUNCTIONS

// =================================================================

 

NTSTATUS DriverEntry (PDRIVER_OBJECT  pDriverObject,

                      PUNICODE_STRING pusRegistryPath);

 

#ifdef ALLOC_PRAGMA

#pragma alloc_text (INIT, DriverEntry)

#endif

 

// =================================================================

// DRIVER INITIALIZATION

// =================================================================

 

NTSTATUS DriverEntry (PDRIVER_OBJECT  pDriverObject,

                      PUNICODE_STRING pusRegistryPath)

    {

    return *((NTSTATUS *) 0);

    }

 

// =================================================================

// END OF PROGRAM

// =================================================================

示列3-7.  一个小巧的系统崩溃者

 

加载/卸载驱动程序

在完成一个内核驱动程序之后,你可能会想立即执行它。怎么做呢?典型的做法是,在系统启动时加载驱动程序并执行之。但这是不是就意味着我们每次更新驱动程序后,都必须重新启动系统呢?很幸运,这并不是必须的。Windows 2000的一个特色就是提供了一个Win32接口以允许在运行时加载或卸载驱动程序。这是由服务控制管理器(Service Control ManagerSCM)完成的,下面的将详细介绍它的用法。

 

服务控制管理器

“服务控制管理器”这个名字容易让人误解,因为它暗示该组件仅用于服务的管理。服务(Service)是Windows 2000的一类非常强大的模块,它们在后台运行配套的程序,并且不需要用户交互(也就是说没有常见的用户界面或者控制台)。换句话说,一个服务就是一个始终运行于系统中的Win32进程,即使没有用户登陆进来也如此。尽管开发服务是一个令人兴奋的话题,但它并不属于本书的范畴。想进一步了解服务的开发,请阅读Windows Developer’s JournalWDJ)(Tomlinson 1996a)中Paula Tomlinson提供的非常不错的教程,以及随后在她的WDJ专栏----Understanding NT中发表的有关服务的论文。

 

SC管理器(即服务控制管理器)可以控制服务和驱动程序。为了简单起见,我在这里使用“服务”一词来代表SC管理器控制的所有对象,这包括严格意义上的服务和内核驱动程。SC的接口对于Win32程序是可用的,它由Win32子系统组件----advapi32.dll提供,这个DLL还提供了很多有趣的API函数。3-3给出了用于加载、控制和卸载服务的API函数的名称,同时还给出了简单的描述。在你可以加载或访问任何服务之前,你必须获取SC管理器的句柄(通过调用OpenSCManager()),在随后的讨论中,该句柄将被称为:管理器句柄。CreateService()OpenService()都需要此句柄,而这些函数返回的句柄将被称为:服务句柄。这种类型的句柄可以传递给需要引用一个服务的函数,如ControlService()DeleteService()StartService()。这两种类型的SC句柄都通过CloseServiceHandle()函数来释放。

 

  

           

CloseServiceHandle

关闭来自OpenSCManager()CreateService()OpenService()的句柄

ControlService

停止、暂停、继续、查询或通知已加载的服务/驱动程序

CreateService

加载一个服务/驱动程序

DeleteService

卸载一个服务/驱动程序

OpenSCManager

获取SC管理器的句柄

OpenService

获取一个已加载的服务/驱动程序的句柄

QueryServiceStatus

查询一个服务/驱动程序的属性和当前状态

StartService

启动一个已加载的服务/驱动程序

3-3.  基本的服务控制函数

 

 

加载和运行一个服务需要执行的典型操作步骤:

1.         调用OpenSCManager()以获取一个管理器句柄

2.         调用CreateService()来向系统中添加一个服务

3.         调用StartService()来运行一个服务

4.         调用CloseServiceHandle()来释放管理器或服务句柄

 

要确保当一个错误发生时,要回滚到最后一个成功的调用,然后再开始。例如,你在调用StartService()SC管理器报告了一个错误,你就需要调用DeleteService()。否则,服务将保持在一个非预期的状态。另一个使用SC管理器API易犯的错误是,必须为CreateService()函数提供可执行文件的全路径名,否则,如果该函数在当前目录中没有找到可执行文件的话,就会失败。因此,你应该使用Win32函数---GetFullPathName()来规格化传递给CreateService()的所有文件名,除非可以保证它们已经是全路径的。

 

高层的驱动程序管理函数

为了更容易的和SC管理器进行交互,本书附带的CD提供了多个更高级的外包函数,这些函数屏蔽了原有的一些不方便的特殊要求。这些函数是本书提供的庞大的Windows 2000工具库(位于随书CD中的/src/w2k_lib)中的一部分。w2k_lib.dll导出的所有函数都有一个全局的名字前缀w2k,服务和驱动程序管理函数都使用w2kService前缀。列表3-8给出了本书提供的工具库中实现的加载、控制和卸载服务/驱动程序的函数的细节。

 

// =================================================================

// SERVICE/DRIVER MANAGEMENT

// =================================================================

 

SC_HANDLE WINAPI w2kServiceConnect (void)

    {

    return OpenSCManager (NULL, NULL, SC_MANAGER_ALL_ACCESS);

    }

 

// -----------------------------------------------------------------

 

SC_HANDLE WINAPI w2kServiceDisconnect (SC_HANDLE hManager)

    {

    if (hManager != NULL) CloseServiceHandle (hManager);

    return NULL;

    }

 

// -----------------------------------------------------------------

 

SC_HANDLE WINAPI w2kServiceManager (SC_HANDLE  hManager,

                                    PSC_HANDLE phManager,

                                    BOOL       fOpen)

    {

    SC_HANDLE hManager1 = NULL;

 

    if (phManager != NULL)

        {

        if (fOpen)

            {

            if (hManager == NULL)

                {

                *phManager = w2kServiceConnect ();

                }

            else

                {

                *phManager = hManager;

                }

            }

        else

            {

            if (hManager == NULL)

                {

                *phManager = w2kServiceDisconnect (*phManager);

                }

            }

        hManager1 = *phManager;

        }

    return hManager1;

    }

 

// -----------------------------------------------------------------

 

SC_HANDLE WINAPI w2kServiceOpen (SC_HANDLE hManager,

                                 PWORD     pwName)

    {

    SC_HANDLE hManager1;

    SC_HANDLE hService = NULL;

 

    w2kServiceManager (hManager, &hManager1, TRUE);

 

    if ((hManager1 != NULL) && (pwName != NULL))

        {

        hService = OpenService (hManager1, pwName,

                                SERVICE_ALL_ACCESS);

        }

    w2kServiceManager (hManager, &hManager1, FALSE);

    return hService;

    }

 

// -----------------------------------------------------------------

 

BOOL WINAPI w2kServiceClose (SC_HANDLE hService)

    {

    return (hService != NULL) && CloseServiceHandle (hService);

    }

 

// -----------------------------------------------------------------

 

BOOL WINAPI w2kServiceAdd (SC_HANDLE hManager,

                           PWORD     pwName,

                           PWORD     pwInfo,

                           PWORD     pwPath)

    {

    SC_HANDLE hManager1, hService;

    PWORD     pwFile;

    WORD      awPath [MAX_PATH];

    DWORD     n;

    BOOL      fOk = FALSE;

 

    w2kServiceManager (hManager, &hManager1, TRUE);

 

    if ((hManager1 != NULL) && (pwName != NULL) &&

        (pwInfo    != NULL) && (pwPath != NULL) &&

        (n = GetFullPathName (pwPath, MAX_PATH, awPath, &pwFile)) &&

        (n < MAX_PATH))

        {

        if ((hService = CreateService (hManager1, pwName, pwInfo,

                                       SERVICE_ALL_ACCESS,

                                       SERVICE_KERNEL_DRIVER,

                                       SERVICE_DEMAND_START,

                                       SERVICE_ERROR_NORMAL,

                                       awPath, NULL, NULL,

                                       NULL, NULL, NULL))

            != NULL)

            {

            w2kServiceClose (hService);

            fOk = TRUE;

            }

        else

            {

            fOk = (GetLastError () ==

                   ERROR_SERVICE_EXISTS);

            }

        }

    w2kServiceManager (hManager, &hManager1, FALSE);

    return fOk;

    }

 

// -----------------------------------------------------------------

 

BOOL WINAPI w2kServiceRemove (SC_HANDLE hManager,

                              PWORD     pwName)

    {

    SC_HANDLE hService;

    BOOL      fOk = FALSE;

 

    if ((hService = w2kServiceOpen (hManager, pwName)) != NULL)

        {

        if (DeleteService (hService))

            {

            fOk = TRUE;

            }

        else

            {

            fOk = (GetLastError () ==

                   ERROR_SERVICE_MARKED_FOR_DELETE);

            }

        w2kServiceClose (hService);

        }

    return fOk;

    }

 

// -----------------------------------------------------------------

 

BOOL WINAPI w2kServiceStart (SC_HANDLE hManager,

                             PWORD     pwName)

    {

    SC_HANDLE hService;

    BOOL      fOk = FALSE;

 

    if ((hService = w2kServiceOpen (hManager, pwName)) != NULL)

        {

        if (StartService (hService, 1, &pwName))

            {

            fOk = TRUE;

            }

        else

            {

            fOk = (GetLastError () ==

                   ERROR_SERVICE_ALREADY_RUNNING);

            }

        w2kServiceClose (hService);

        }

    return fOk;

    }

 

// -----------------------------------------------------------------

 

BOOL WINAPI w2kServiceControl (SC_HANDLE hManager,

                               PWORD     pwName,

                               DWORD     dControl)

    {

    SC_HANDLE      hService;

    SERVICE_STATUS ServiceStatus;

    BOOL           fOk = FALSE;

 

    if ((hService = w2kServiceOpen (hManager, pwName)) != NULL)

        {

        if (QueryServiceStatus (hService, &ServiceStatus))

            {

            switch (ServiceStatus.dwCurrentState)

                {

                case SERVICE_STOP_PENDING:

                case SERVICE_STOPPED:

                    {

                    fOk = (dControl == SERVICE_CONTROL_STOP);

                    break;

                    }

                case SERVICE_PAUSE_PENDING:

                case SERVICE_PAUSED:

                    {

                    fOk = (dControl == SERVICE_CONTROL_PAUSE);

                    break;

                    }

                case SERVICE_START_PENDING:

                case SERVICE_CONTINUE_PENDING:

                case SERVICE_RUNNING:

                    {

                    fOk = (dControl == SERVICE_CONTROL_CONTINUE);

                    break;

                    }

                }

            }

        fOk = fOk ||

              ControlService (hService, dControl, &ServiceStatus);

 

        w2kServiceClose (hService);

        }

    return fOk;

    }

 

// -----------------------------------------------------------------

 

BOOL WINAPI w2kServiceStop (SC_HANDLE hManager,

                            PWORD     pwName)

    {

    return w2kServiceControl (hManager, pwName,

                              SERVICE_CONTROL_STOP);

    }

 

// -----------------------------------------------------------------

 

BOOL WINAPI w2kServicePause (SC_HANDLE hManager,

                             PWORD     pwName)

    {

    return w2kServiceControl (hManager, pwName,

                              SERVICE_CONTROL_PAUSE);

    }

 

// -----------------------------------------------------------------

 

BOOL WINAPI w2kServiceContinue (SC_HANDLE hManager,

                                PWORD     pwName)

    {

    return w2kServiceControl (hManager, pwName,

                              SERVICE_CONTROL_CONTINUE);

    }

 

// -----------------------------------------------------------------

 

SC_HANDLE WINAPI w2kServiceLoad (PWORD pwName,

                                 PWORD pwInfo,

                                 PWORD pwPath,

                                 BOOL  fStart)

    {

    BOOL      fOk;

    SC_HANDLE hManager = NULL;

 

    if ((hManager = w2kServiceConnect ()) != NULL)

        {

        fOk = w2kServiceAdd (hManager, pwName, pwInfo, pwPath);

 

        if (fOk && fStart)

            {

            if (!(fOk = w2kServiceStart (hManager, pwName)))

                {

                w2kServiceRemove (hManager, pwName);

                }

            }

        if (!fOk)

            {

            hManager = w2kServiceDisconnect (hManager);

            }

        }

    return hManager;

    }

 

// -----------------------------------------------------------------

 

SC_HANDLE WINAPI w2kServiceLoadEx (PWORD pwPath,

                                   BOOL  fStart)

    {

    PVS_VERSIONDATA pvvd;

    PWORD           pwPath1, pwInfo;

    WORD            awName [MAX_PATH];

    DWORD           dName, dExtension;

    SC_HANDLE       hManager = NULL;

 

    if (pwPath != NULL)

        {

        dName = w2kPathName (pwPath, &dExtension);

 

        lstrcpyn (awName, pwPath + dName,

                  min (MAX_PATH, dExtension - dName + 1));

 

        pwPath1 = w2kPathEvaluate (pwPath,  NULL);

        pvvd    = w2kVersionData  (pwPath1, -1);

 

        pwInfo  = ((pvvd != NULL) && pvvd->awFileDescription [0]

                   ? pvvd->awFileDescription

                   : awName);

 

        hManager = w2kServiceLoad (awName, pwInfo, pwPath1, fStart);

 

        w2kMemoryDestroy (pvvd);

        w2kMemoryDestroy (pwPath1);

        }

    return hManager;

    }

 

// -----------------------------------------------------------------

 

BOOL WINAPI w2kServiceUnload (PWORD     pwName,

                              SC_HANDLE hManager)

    {

    SC_HANDLE hManager1 = hManager;

    BOOL      fOk       = FALSE;

 

    if (pwName != NULL)

        {

        if (hManager1 == NULL)

            {

            hManager1 = w2kServiceConnect ();

            }

        if (hManager1 != NULL)

            {

            w2kServiceStop (hManager1, pwName);

            fOk = w2kServiceRemove (hManager1, pwName);

            }

        }

    w2kServiceDisconnect (hManager1);

    return fOk;

    }

 

// -----------------------------------------------------------------

 

BOOL WINAPI w2kServiceUnloadEx (PWORD     pwPath,

                                SC_HANDLE hManager)

    {

    DWORD dName, dExtension;

    WORD  awName [MAX_PATH];

    PWORD pwName = NULL;

 

    if (pwPath != NULL)

        {

        dName = w2kPathName (pwPath, &dExtension);

 

        lstrcpyn (pwName = awName, pwPath + dName,

                  min (MAX_PATH, dExtension - dName + 1));

        }

    return w2kServiceUnload (pwName, hManager);

    }

 

// -----------------------------------------------------------------

列表3-8.  服务和驱动管理库函数

 

 ....................待续.........................

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值