《Undocumented Windows 2000 Secrets》翻译 --- 第三章(4)
第三章 编写内核模式驱动程序
翻译: Kendiv
更新: 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 的 srcw2k_load 目录下找到。 列表 3-9 给出了相关的代码,该工具仅是一种示意性的实现。因为大量的工作都是由 w2k_lib.dll 中的 w2kServiceLoadEx() 和 w2kServiceUnloadEx() 完成的。
// =================================================================
// GLOBAL STRINGS
// =================================================================
Word awUsage [] =
L'rn'
L'Usage: ' SW(MAIN_MODULE) L' <driver path>rn'
L' ' SW(MAIN_MODULE) L' <driver path> %srn'
L' ' SW(MAIN_MODULE) L' <driver name> %srn';
WORD awUnload [] = L'/unload';
WORD awOk [] = L'OKrn';
WORD awError [] = L'ERRORrn';
// =================================================================
// COMMAND HANDLERS
// =================================================================
BOOL WINAPI DriverLoad (PWORD pwPath)
{
SC_HANDLE hManager;
BOOL fOk = FALSE;
_printf (L'rnLoading '%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'rnUnloading '%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 成员来为所有的名称选择合适的水平对齐方式。你可以在光盘的 srcw2k_svc 目录下找到此工具的源代码。其可执行文件对应光盘中的 binw2k_svc.exe 。 示列 3-4 列出了在我的机器上运行该工具,列出的所有活动的内核驱动程序(使用命令选项 /drivers /active )。
在下一章中,我们将开始开发一个可实际工作的内核驱动程序,它会侦测内核使用的内存,并且会 Crack 基本的内存管理数据结构。这个工程将伴随你阅读第 4 、 5 和 6 章,在每一章中,该驱动程序都会被加强。最后将得到一个通用的 Windows 2000 Kernel Spy 。