第三章 编写内核模式驱动程序
翻译:Kendiv( [email protected] )
更新: Thursday, February 10, 2005
声明:转载请注明出处,并保证文章的完整性,本人保留译文的所有权利。
表3-4列出了定义于列表3-8中的函数,同时还给出了简短的介绍。其中的一些函数的名字,如w2kServiceStart()和w2kServiceControl()和SC管理器的原生API函数---StartService()和ControlService()比较类似。这没有什么不一致,在这些外包函数的核心位置都能找到对这些原生函数的调用。外包函数和原生函数的主要区别在于:StartService()和ControlService()的操作对象是服务句柄,而w2kServiceOpen()和w2kServiceClose()则是服务的名称。这些名字会在内部调用w2kServiceOpen()和w2kServiceClose()转化为对应的句柄,w2kServiceOpen()和w2kServiceClose()会依次调用OpenService()和CloseServiceHandle()。
名 称 |
描 述 |
w2kServiceAdd |
向系统中增加一个服务/驱动程序 |
w2kServiceClose |
关闭一个服务句柄 |
w2kServiceConnect |
连接到服务控制管理器 |
w2kServiceContinue |
继续执行暂停的服务/驱动程序 |
w2kServiceControl |
停止、暂停、继续、查询或通知一个已加载的服务/驱动程序 |
w2kServiceDisconnect |
断开和服务控制管理器的连接 |
w2kServiceLoad |
加载和启动(可选的)一个服务/驱动程序 |
w2kServiceLoadEx |
加载和启动(可选的)一个服务/驱动程序(自动生成名称) |
w2kServiceManager |
打开/关闭一个临时的服务控制管理器句柄 |
w2kServiceOpen |
获取一个已加载的服务/驱动程序的句柄 |
w2kServicePause |
暂停一个正在运行的服务/驱动程序 |
w2kServiceRemove |
从系统中移除一个服务/驱动程序 |
w2kServiceStart |
启动一个已加载的服务/驱动程序 |
w2kServiceStop |
停止一个正在运行的服务/驱动程序 |
w2kServiceUnload |
停止和卸载一个服务/驱动程序 |
w2kServiceUnloadEx |
停止和卸载一个服务/驱动程序(自动生成名称) |
表3-4. w2k_lib.dll提供的SC管理器的外包函数
表3-4中函数的典型用法都需遵循如下的指导方针:
l 使用w2kServiceLoad()或w2kServiceLoadEx()来加载一个服务。后一个函数会根据可执行文件的路径和版本信息自动生成服务的显示名称。逻辑变量fStart用来确定是否在成功加载服务后自动执行该服务。在成功的情况下,该函数会为后续的调用返回一个管理器句柄。如果服务已经加载或服务已经开始运行而fStart为TRUE,调用该函数不会返回任何错误。但如果发生错误,如有必要,发生错误的服务会被自动卸载。
l 使用w2kServiceUnload()和w2kServiceUnloadEx()来卸载一个服务,这需要用到w2kServiceLoad()或w2kServiceLoadEx()返回的管理器句柄。w2kServiceUnloadEx()会根据可执行文件的路径自动生成服务名称。如果你已经关闭了管理器句柄,可使用w2kServiceConnect()来或取一个新的管理器句柄或者简单的传递一个NULL(这表示使用临时的管理器句柄)。管理器句柄会由w2kServiceUnload()自动关闭。如果服务已经有删除标志,则不会返回任何错误,但并不会立即删除服务,这是因为打开的设备句柄还存在着。
l 使用w2kServiceStart()、w2kServiceStop()、w2kServicePause()或w2kServiceContinue()来控制一个服务。这些函数也需要使用w2kServiceLoad()或w2kServiceLoadEx()返回的管理器句柄。如果你提供一个值为NULL的管理器句柄,则使用临时管理器句柄。如果指定的服务已处于所要求的状态,则不会返回任何错误。
l 调用w2kServiceDisconnect()来关闭一个管理器句柄。你可以在任何时候调用w2kServiceConnect()来获取一个管理器句柄。
w2kServiceLoadEx()是一个十分强大的函数。它会构建自动加载一个服务时所需的全部参数,但你要提供可执行文件的路径。SC管理器的CreateService()函数所需要的服务名称将从可执行文件名(会去掉文件的扩展名)中派生出来。为了给新创建的服务构建一个适当的用于显示名称,w2kServiceLoadEx()会尝试从文件的版本信息中读取FileDescription字符串。如果可执行文件中不包含版本信息,或者FileDescription字符串不可用,则将使用缺省的服务名称。
和w2kServiceLoad()不同,w2kServiceLoadEx()支持路径中的环境变量。换句话说,如果路径字符串中包含如%SystemRoot%或%TEMP%这样的子串,它们会被相应系统变量的当前值替换掉。w2kServiceUnloadEx()是 w2kServiceLoadEx()的很好的搭档,它会从提供的路径中提取服务的名称,与前面提及的展开过程类似,并将提取出来的服务名称传递给w2kServiceUnload()。这两个函数是需要加载/卸载第三方设备驱动的应用程序的理想搭档,只需提供这些驱动的全路径即可。本书的光盘中包含一个这样的示例程序。
控制台模式的工具-----w2k_load.exe是一个通用的内核驱动程序加载/卸载器,它为w2kServiceLoadEx()和w2kServiceUnloadEx()提供了简单的命令行接口。其源代码可以在随书CD的/src/w2k_load目录下找到。列表3-9给出了相关的代码,该工具仅是一种示意性的实现。因为大量的工作都是由w2k_lib.dll中的w2kServiceLoadEx()和w2kServiceUnloadEx()完成的。
// =================================================================
// GLOBAL STRINGS
// =================================================================
WORD awUsage [] =
L"/r/n"
L"Usage: " SW(MAIN_MODULE) L" <driver path>/r/n"
L" " SW(MAIN_MODULE) L" <driver path> %s/r/n"
L" " SW(MAIN_MODULE) L" <driver name> %s/r/n";
WORD awUnload [] = L"/unload";
WORD awOk [] = L"OK/r/n";
WORD awError [] = L"ERROR/r/n";
// =================================================================
// COMMAND HANDLERS
// =================================================================
BOOL WINAPI DriverLoad (PWORD pwPath)
{
SC_HANDLE hManager;
BOOL fOk = FALSE;
_printf (L"/r/nLoading /"%s/" ... ", pwPath);
if ((hManager = w2kServiceLoadEx (pwPath, TRUE)) != NULL)
{
w2kServiceDisconnect (hManager);
fOk = TRUE;
}
_printf (fOk ? awOk : awError);
return fOk;
}
// -----------------------------------------------------------------
BOOL WINAPI DriverUnload (PWORD pwPath)
{
BOOL fOk = FALSE;
_printf (L"/r/nUnloading /"%s/" ... ", pwPath);
fOk = w2kServiceUnloadEx (pwPath, NULL);
_printf (fOk ? awOk : awError);
return fOk;
}
// =================================================================
// MAIN PROGRAM
// =================================================================
DWORD Main (DWORD argc, PTBYTE *argv, PTBYTE *argp)
{
_printf (atAbout);
if (argc == 2)
{
DriverLoad (argv [1]);
}
else
{
if ((argc == 3) && (!lstrcmpi (argv [2], awUnload)))
{
DriverUnload (argv [1]);
}
else
{
_printf (awUsage, awUnload, awUnload);
}
}
return 0;
}
// =================================================================
// END OF PROGRAM
// =================================================================
列表3-9. 加载/卸载设备驱动
表3-4中剩余的库函数在更低一级的层面上工作,它们都在w2k_lib.dll内部使用。当然,如果你喜欢的话,你也可以从你的程序里调用它们。从列表3-8给出的它们的源代码中,可以很容易得出它们的使用方式。
枚举服务和驱动
有时很有必要知道系统当前加载了那个服务或驱动,以及它们现在处于什么状态。为了实现这一目的,SC管理器提供了另一个名为EnumServiceStatus()的强大函数。该函数需要一个管理器句柄和一个类型为ENUM_SERVICE_STATUS的数组,该数组中将包含有关当前已加载的服务或驱动的信息。这个列表可以根据服务/驱动的类型和状态来过滤。如果调用者提供的缓冲区不能一次性的容纳所有项目,可反复调用该函数直到获取所有的项目。
不过很难预先计算出所需的缓冲区大小,这是因为缓冲区必须为那些大小未知的字符串提供额外的空间,这些字符串由ENUM_SERVICE_STATUS的成员引用。幸运的是,EnumServiceStatus()会返回剩余的项目所需的字节数,因此可以通过反复尝试得出确定的缓冲区大小。列表3-10给出了SERVICE_STATUS 和ENUM_SERVICE_STATUS结构的定义。这些声明位于Win32头文件WinSvc.h中。
typedef struct _SERVICE_STATUS
{
DWORD dwServiceType;
DWORD dwCurrentState;
DWORD dwControlAccepted;
DWORD dwWin32ExitCode;
DWORD dwServiceSpecificExitCode;
DWORD dwCheckPoint;
DWORD dwWaitHint;
} SERVICE_STATUS, *LPSERVICE_STATUS;
typedef struct _ENUM_SERVICE_STATUS
{
LPTSTR lpServiceName;
LPTSTR lpDisplayName;
SERVICE_STATUS ServiceStatus;
} ENUM_SERVICE_STATUS;
列表3-10 SERVICE_STATUS 和ENUM_SERVICE_STATUS结构的定义
列表3-11给出的w2kServiceList()函数是来自w2k_lib.dll工具库的另一个好东东。它省略了前面提到的动作,并返回一个随时可用的结构,该结构中包含所有请求的数据以及一对扩展结构。该函数将返回一个指向W2K_SERVICES结构的指针,该结构定义于w2k_lib.h,在列表3-11的顶部给出了其定义。随ENUM_SERVICE_STATUS结构数组aess[],W2K_SERVICES结构体还包含四个附加成员。dEntries表示向状态数组中复制了多少项目,dBytes表示返回的W2K_SERVICES结构的大小。dDisplayName和dServiceName被分别设置为aess[]中的lpDisplayName和lpServiceName字符串的最大长度。这些值将提供很大的方便,尤其是当你编写一个控制台模式的程序,在屏幕上输出服务/驱动列表,并要求名称列采用合适的对齐方式。
为了提供精确的系统快照,w2kServiceList()试图通过一次调用EnumServiceStatus()来获取所有的项目。为此目的,该函数首先提供一个长度为0的缓冲区,这通常会导致返回ERROR_MORE_DATA错误代码。在此种情况下,EnumServiceStatus()将返回需要的缓冲区大小,然后按照此大小分配适当的缓冲区,然后再次调用EnumServiceStatus()。此时,EnumServiceStatus()应该返回成功。不过,这存在一个很小的概率事件---在两次调用EnumServiceStatus()之间另一个项目可能会被增加到列表中。因此,将会在一个循环中重复这一过程直到所有的一切都正确或者一个非ERROR_MORE_DATA的错误返回。
// -----------------------------------------------------------------
typedef struct _W2K_SERVICES
{
DWORD dEntries; // number of entries in aess[]
DWORD dBytes; // overall number of bytes
DWORD dDisplayName; // maximum display name length
DWORD dServiceName; // maximum service name length
ENUM_SERVICE_STATUS aess []; // service/driver status array
}
W2K_SERVICES, *PW2K_SERVICES, **PPW2K_SERVICES;
#define W2K_SERVICES_ sizeof (W2K_SERVICES)
#define W2K_SERVICES__(_n) /
(W2K_SERVICES_ + ((_n) * ENUM_SERVICE_STATUS_))
// -----------------------------------------------------------------
PW2K_SERVICES WINAPI w2kServiceList (BOOL fDriver,
BOOL fWin32,
BOOL fActive,
BOOL fInactive)
{
SC_HANDLE hManager;
DWORD dType, dState, dBytes, dResume, dName, i;
PW2K_SERVICES pws = NULL;
if ((pws = w2kMemoryCreate (W2K_SERVICES_)) != NULL)
{
pws->dEntries = 0;
pws->dBytes = 0;
pws->dDisplayName = 0;
pws->dServiceName = 0;
if ((fDriver || fWin32) && (fActive || fInactive))
{
if ((hManager = w2kServiceConnect ()) != NULL)
{
dType = (fDriver ? SERVICE_DRIVER : 0) |
(fWin32 ? SERVICE_WIN32 : 0);
dState = (fActive && fInactive
? SERVICE_STATE_ALL
: (fActive
? SERVICE_ACTIVE
: SERVICE_INACTIVE));
dBytes = pws->dBytes;
while (pws != NULL)
{
pws->dEntries = 0;
pws->dBytes = dBytes;
pws->dDisplayName = 0;
pws->dServiceName = 0;
dResume = 0;
if (EnumServicesStatus (hManager, dType, dState,
pws->aess, pws->dBytes,
&dBytes, &pws->dEntries,
&dResume))
break;
dBytes += pws->dBytes;
pws = w2kMemoryDestroy (pws);
if (GetLastError () != ERROR_MORE_DATA) break;
pws = w2kMemoryCreate (W2K_SERVICES_ + dBytes);
}
w2kServiceDisconnect (hManager);
}
else
{
pws = w2kMemoryDestroy (pws);
}
}
if (pws != NULL)
{
for (i = 0; i < pws->dEntries; i++)
{
dName = lstrlen (pws->aess [i].lpDisplayName);
pws->dDisplayName = max (pws->dDisplayName, dName);
dName = lstrlen (pws->aess [i].lpServiceName);
pws->dServiceName = max (pws->dServiceName, dName);
}
}
}
return pws;
}
列表3-11. 枚举服务/驱动程序
w2kServiceList()需要四个逻辑类型的参数,以确定要返回的列表的内容。通过fDriver和fWin32参数,你可以分别选择是否包含驱动程序或服务。如果这两个参数都为TRUE,那么返回的列表将同时包含驱动和服务。fActive和fInactive标志用于控制加于列表上的状态过滤器。。fInactive参数选择剩余的模块,也就是说,这些模块已经加载但已经停止运行。如果所有的四个参数都为FALSE,函数返回的W2K_SERVICES结构将包含一个空的状态数组。光盘中的示例代码包含一个简单的服务/驱动浏览器,它被设计为Win32控制台模式,并依赖于w2k_lib.dll中的w2kServiceList()。它使用W2K_SERVICES结构(参见列表3-11)中的dDisplayName和dServiceName成员来为所有的名称选择合适的水平对齐方式。你可以在光盘的/src/w2k_svc目录下找到此工具的源代码。其可执行文件对应光盘中的/bin/w2k_svc.exe。示列3-4列出了在我的机器上运行该工具,列出的所有活动的内核驱动程序(使用命令选项 /drivers /active)。
在下一章中,我们将开始开发一个可实际工作的内核驱动程序,它会侦测内核使用的内存,并且会Crack基本的内存管理数据结构。这个工程将伴随你阅读第4、5和6章,在每一章中,该驱动程序都会被加强。最后将得到一个通用的Windows 2000 Kernel Spy。
< 本章完 >