delphi开发驱动程序 下载本文

End;

下面让我们来看一个简单驱动程序例子SystemModules,该例子的主要动作集中在DriverEntry函数里。我们会分配分页内存(你应该记得 DriverEntry运行在IRQL =PASSIVE_LEVEL等级,所以使用分页内存自然是没问题了),然后写进一些信息,再释放,并让系统卸载驱动程序。

代码:

{ ---------------------------------------------------------------- 用Delphi编写驱动程序的例子

------------------------------------------------------------------}

unit SystemModules;

interface uses

nt_status, ntoskrnl, native;

function _DriverEntry(pDriverObject: PDRIVER_OBJECT; pusRegistryPath: PUNICODE_STRING): NTSTATUS; stdcall;

implementation

function _DriverEntry(pDriverObject: PDRIVER_OBJECT; pusRegistryPath: PUNICODE_STRING): NTSTATUS; var

cb: DWORD;

p, pTemp: PVOID;

dwNumModules: DWORD;

pMessage, pModuleName: PCHAR; buffer: array[0..295] of char;

szModuleName: array[0..100] of char; iCnt, iPos: integer; begin

DbgPrint('SystemModules: Entering DriverEntry'); cb := 0;

ZwQuerySystemInformation(SystemModuleInformation, @p, 0, cb); if cb <> 0 then begin

p := ExAllocatePool(PagedPool, cb); if p <> nil then begin

DbgPrint('SystemModules: %u bytes of paged memory allocted at address X', cb, p); if ZwQuerySystemInformation(SystemModuleInformation, p, cb, cb) = STATUS_SUCCESS then begin

pTemp := p;

dwNumModules := DWORD(p^);

cb := (sizeof(SYSTEM_MODULE_INFORMATION) + 100) * 2; pMessage := ExAllocatePool(PagedPool, cb); if pMessage <> nil then begin

DbgPrint('SystemModules: %u bytes of paged memory allocted at address X', cb, pMessage); memset(pMessage, 0, cb);

inc(PCHAR(pTemp), sizeof(DWORD)); for iCnt := 1 to dwNumModules do begin

iPos := (PSYSTEM_MODULE_INFORMATION(pTemp))^.ModuleNameOffset;

pModuleName := @((PSYSTEM_MODULE_INFORMATION(pTemp))^.ImageName[iPos]); if (_strnicmp(pModuleName, 'ntoskrnl.exe', length('ntoskrnl.exe')) = 0) or (_strnicmp(pModuleName, 'ntice.sys', length('ntice.sys')) = 0) then begin

memset(@szModuleName, 0, sizeof(szModuleName)); strcpy(@szModuleName, pModuleName);

_snprintf(@buffer, sizeof(buffer),

'SystemModules: Found %s base: X size: X', @szModuleName,

(PSYSTEM_MODULE_INFORMATION(pTemp))^.Base, (PSYSTEM_MODULE_INFORMATION(pTemp))^._Size); strcat(pMessage, @buffer); end;

inc(PCHAR(pTemp), sizeof(SYSTEM_MODULE_INFORMATION)); end;

if pMessage[0] <> #0 then begin

DbgPrint(pMessage); end else begin

DbgPrint('SystemModules: Found neither ntoskrnl nor ntice'); end;

ExFreePool(pMessage);

DbgPrint('SystemModules: Memory at address X released', pMessage); end; end;

ExFreePool(p);

DbgPrint('SystemModules: Memory at address X released', p); end; end;

DbgPrint('SystemModules: Leaving DriverEntry'); result := STATUS_DEVICE_CONFIGURATION_ERROR; end; end.

为了写点有用的信息,我们加载了一些模块到系统地址空间(包括以下系统模块:ntoskrnl.exe, hal.dll等,还有设备驱动程序),然后去找ntoskrnl.exe和ntice.sys。我们用SystemModuleInformation 信息类作为参数调用ZwQuerySystemInformation来得到系统模块列表。读者可以到Garry Nebbett的书《winodwsNT/2000 内部API参考》去找这些函数的描述。顺便说一下,ZwQuerySystemInformation 是一个独特的函数,它返回大量的系统信息。

这个例子中没有提供驱动控制程序。你可以使用KmdKit4D工具包中的KmdManager或者类似的工具,还可以使用DebugView 或 SoftICE 控制台来查看调试信息。

现在我们来分析分析这段程序吧??

代码:cb := 0;

ZwQuerySystemInformation(SystemModuleInformation, @p, 0, cb);

首先我们要决定我们要使用多少空间。上面调用对ZwQuerySystemInformation的调用使我们获得 STATUS_INFO_LENGTH_MISMATCH(这是正常,因为buffer的尺寸为零。),但是cb变量接收到了buffer尺寸(为零或非零)。于是我们就得到了需要的buffer尺寸。这里需要地址p是为了ZwQuerySystemInformation函数的正常执行。

代码:if cb <> 0 then begin

p := ExAllocatePool(PagedPool, cb);

ExAllocatePool从分页内存池分配需要数量的内存。如果是不分页内存呢,就把第一个参数PagedPool相应地改成 NonPagedPool。ExAllocatePool比用户模式的HeapAlloc 简单一些,只有两个参数:第一个参数是内存池类型(分页、不分页),第二个参数是需要的内存尺寸。简单吧!

代码:if p <> nil then

如果ExAllocatePool 返回非零值,那么它就是一个指向分配buffer的指针。 检查调试信息会发现ExAllocatePool 分配的buffer地址是页尺寸大小的倍数。假如请求的内存的大小大于或等于(>=)页尺寸(我们这个例子中,是明显地大了),分配的内存会从页边界开始分配。

代码:if ZwQuerySystemInformation(SystemModuleInformation, p, cb, cb) = STATUS_SUCCESS then begin

pTemp := p;

我们再次调用ZwQuerySystemInformation,这次使用buffer的指针和尺寸作为参数。

如果返回的是STATUS_SUCCESS,那么buffer之中就包含了系统模块列表,数据以SYSTEM_MODULE_INFORMATION(在

native.dcu中定义)结构队列的形式存在。

代码:SYSTEM_MODULE_INFORMATION = packed record Reserved: array[0..1] of DWORD; Base: PVOID; _Size: DWORD; Flags: DWORD; Index: WORD; Unknown: WORD; LoadCount: WORD;

ModuleNameOffset: WORD;

ImageName: array[0..255] of char; end;

cb变量接受实际返回的字节的数量,但是我们目前用不到它。

我假设在两次调用ZwQuerySystemInformation 之间没有其它新模块出现。这种可能性当然是很小的。我们这里只是为了学习目的嘛!你最好使用更安全的办法:在循环中反复调用 ZwQuerySystemInformation来逐次增加buffer大小,直到该大小满足需求!

代码:dwNumModules := DWORD(p^);

buffer中的第一个双字(double word)包含模块的数量,这些模块紧跟在SYSTEM_MODULE_INFORMATION队列的后面。然后,模块的数量会保存在dwNumModules中。

代码:cb := (sizeof(SYSTEM_MODULE_INFORMATION) + 100) * 2; pMessage := ExAllocatePool(PagedPool, cb);

我们需要另一个buffer来保存我们寻找的两个模块的名字和其它信息。我们假定这个尺寸(((sizeof(SYSTEM_MODULE_INFORMATION) + 100) * 2)是足够的。

注意:这次的buffer地址不是页尺寸的倍数,是因为buffer尺寸小于一个页尺寸。

代码:memset(pMessage, 0, cb);

用memset填充buffer为零,这是为了安全考虑,使字符串肯定以零结束。学习过c语言的朋友对此函数应该很熟悉。(事实上你也可以使用Delphi的FillChar函数)

代码:inc(PCHAR(pTemp), sizeof(DWORD));

跳过保存了模块数量的大小的第一个双字,现在pTemp就指向了第一个SYSTEM_MODULE_INFORMATION结构。

代码:for iCnt := 1 to dwNumModules do begin

我们对结构队列循环dwNumModules次数,来寻找ntoskrnl.exe 和 ntice.sys。

在多处理器的系统ntoskrnl.exe模块的名字应该是ntkrnlmp.exe,如果你使用的是带PAE(物理地址扩展)的系统,那么系统会分别地支持ntkrnlpa.exe 和 ntkrpamp.exe。我这里当然假定你不会拥有那么牛的机器了^_^。

代码:iPos := (PSYSTEM_MODULE_INFORMATION(pTemp))^.ModuleNameOffset; pModuleName := @((PSYSTEM_MODULE_INFORMATION(pTemp))^.ImageName[iPos]);

ImageName和ModuleNameOffset 域分别包含了模块的全路径和路径内模块名的相对偏移。

代码:if (_strnicmp(pModuleName, 'ntoskrnl.exe', length('ntoskrnl.exe')) = 0) or (_strnicmp(pModuleName, 'ntice.sys', length('ntice.sys')) = 0) then Begin

strnicmp 做不区分大小写的两ANSI标准字符串比较。第三个参数是比较的字符的数量。这里也许不必要使用__strnicmp,因为 SYSTEM_MODULE_INFORMATION里模块名是零结束的,使用_stricmp就可以了。这里使用__strnicmp是为了更加安全。

顺便提一句,ntoskrnl.exe提供了许多基本的字符串函数,如strcmp、strcpy和strlen等。

代码:memset(@szModuleName, 0, sizeof(szModuleName)); strcpy(@szModuleName, pModuleName); _snprintf(@buffer, sizeof(buffer),

'SystemModules: Found %s base: X size: X', @szModuleName,

(PSYSTEM_MODULE_INFORMATION(pTemp))^.Base, (PSYSTEM_MODULE_INFORMATION(pTemp))^._Size);

strcat(pMessage, @buffer); end;

假如上文提及的模块被找到,我们就用_snprintf(内核提供)函数格式化字符串,字符串中包含了模块名、基地址和尺寸,然后将字符串加到buffer去。

代码:if pMessage[0] <> #0 then begin

DbgPrint(pMessage); end else begin

DbgPrint('SystemModules: Found neither ntoskrnl nor ntice'); end;

这里很容易看懂,由于我们前面把字符串都置了零,这里很容易判断我们是否找到了什么东西。 代码:ExFreePool(pMessage);

DbgPrint('SystemModules: Memory at address X released', pMessage); end; end;

ExFreePool(p);

释放在系统内存池里分配的内存。

代码:result := STATUS_DEVICE_CONFIGURATION_ERROR; 返回失败代码,这样强制系统将驱动程序从内存中卸载。

现在你清楚了吧,使用系统内存池比使用用户模式的堆简单多了。唯一需要注意的问题就是正确地定义内存池类型。

用户模式下的ntdll.dll提供了许多ZwXxx系列函数,他们是进入内核模式的大门。注意:他们的参数的数量和含义都是一样的。你可以省不少事儿了吧!

由于内核的错误会导致系统瘫痪,所以你可以在用户模式下调试,然后小小地改动(如果需要)后拷贝到你的驱动程序。例如:ntdll.dll的 ZwQuerySystemInformation 调用返回同样的信息。使用这个技巧你就不用总是重新启动你的机器了。

1. 用dcc32 –U ..\\include -B -CG -JP -$A-,C-,D-,G-,H-,I-,L-,P-,V-,W+,Y- beeper.pas生成目标文件(此处的..\\inc是我保存相关delphi单元文件的目录,你的可能不是这个目录哟)

2. 用omf2d beeper.obj /U_*转换目标文件,使其能被m$ link链接

3. 用link /NOLOGO /ALIGN:32 /BASE:0x10000 /SUBSYSTEM:NATIVE /DRIVER /FORCE:UNRESOLVED /FORCE:MULTIPLE /ENTRY:DriverEntry ..\\lib\\hal.lib beeper.obj /OUT:beeper.sys生成最终的驱动文件。(注意这里用/FORCE:UNRESOLVED是因为dcc32会在delphi的目标文件中加入一些单元的初始化及销毁代码,这些东东在驱动程序中是不需要的,所以强行忽略之,还会出现一堆链接警告,也不用理会)。

执行完以上的步骤,在你的目录下就会生成一个beeper.sys文件了。把它拷贝到KmdKit的beeper目录中,用它的SCP文件加载,PC的喇叭果然发出的清脆的声音,证明我们的delphi驱动是正确的。用此种方法生成的beeper.sys只有1376字节,只比用KmdKit的汇编代码的beeper.sys大几百个字节,而用DDDK生成的beeper.sys则要超过3K。

打完这么多字真不容易,这篇教程就到这里吧,下一篇我们再来用delphi做一个更有趣的东东。

工具及环境搭建

上篇教程主要是讲解了用Delphi开发Windows驱动程序需要解决的一些技术上的问题,虽然啰嗦了一大堆,也不知道讲清楚了没有^_^。本篇我们开始讲述用Delphi构建驱动开发环境。