用Delphi开发驱动程序所必须的工具:
Dcc32.exe – Delphi编译器,我用的是Delphi 2007的dcc32
Omf2d -- Delphi目标文件转换工具
Link.exe -- microsoft链接器,不要使用7.1xx版的,似乎有bug
DDK相关结构、APIs的Delphi声明文件(我已经完成部分结构、APIs的声明转换,放在我的KmdKit4D工具包里)
有上面的东东就可以开发Windows驱动程序了,下面就让我们来写一个最简单的驱动程序:
unit driver;
interface
uses nt_status, ntoskrnl;
function _DriverEntry(DriverObject:PDriverObject;RegistryPath:PUnicodeString):NTSTATUS; stdcall;
implementation
procedure DriverUnload(DriverObject:PDriverObject); stdcall;
begin
DbgPrint( ' DriverUnload(DriverObject:0x%.8X) ' ,[DriverObject]);
DbgPrint( ' DriverUnload(-) ' ,[]);
end ;
function _DriverEntry(DriverObject:PDriverObject;RegistryPath:PUnicodeString):NTSTATUS; stdcall;
begin
DbgPrint( ' DriverEntry(DriverObject:0x%.8X;RegistryPath:0x%.8X) ' ,[DriverObject,RegistryPath]);
DriverObject^.DriverUnload: = @DriverUnload;
Result: = STATUS_SUCCESS;
DbgPrint( ' DriverEntry(-):0x%.8X ' ,[Result]);
end ;
end .
interface
uses nt_status, ntoskrnl;
function _DriverEntry(DriverObject:PDriverObject;RegistryPath:PUnicodeString):NTSTATUS; stdcall;
implementation
procedure DriverUnload(DriverObject:PDriverObject); stdcall;
begin
DbgPrint( ' DriverUnload(DriverObject:0x%.8X) ' ,[DriverObject]);
DbgPrint( ' DriverUnload(-) ' ,[]);
end ;
function _DriverEntry(DriverObject:PDriverObject;RegistryPath:PUnicodeString):NTSTATUS; stdcall;
begin
DbgPrint( ' DriverEntry(DriverObject:0x%.8X;RegistryPath:0x%.8X) ' ,[DriverObject,RegistryPath]);
DriverObject^.DriverUnload: = @DriverUnload;
Result: = STATUS_SUCCESS;
DbgPrint( ' DriverEntry(-):0x%.8X ' ,[Result]);
end ;
end .
function
_DriverEntry(DriverObject:PDriverObject;RegistryPath:PUnicodeString):NTSTATUS; stdcall;
NAME
=
driver
DCC = dcc32
INCLUDE = d:\mickeylan\KmdKit4D\include
LIB_PATH = d:\mickeylan\KmdKit4D\lib
DCCFLAGS =- U$(INCLUDE) - B - CG - JP - $A - ,C - ,D - ,G - ,H - ,I - ,L - ,P - ,V - ,W + ,Y -
LIBS = ntoskrnl.lib hal.lib win32k.lib ntdll.lib
LINKFLAGS =/ NOLOGO / ALIGN: 32 / BASE:0x10000 / SUBSYSTEM:NATIVE / DRIVER / LIBPATH:$(LIB_PATH) / FORCE:UNRESOLVED / FORCE:MULTIPLE / ENTRY:DriverEntry
all : $(NAME).sys
$(NAME).sys : $(NAME).obj
omf2d $(NAME).obj / U_ *
link $(LINKFLAGS) $(LIBS) / out:$(NAME).sys $(NAME).obj
$(NAME).obj : $(NAME).pas
$(DCC) $(DCCFLAGS) $(NAME).pas
clean :
del * .obj
del * .dcu
del * .sys
DCC = dcc32
INCLUDE = d:\mickeylan\KmdKit4D\include
LIB_PATH = d:\mickeylan\KmdKit4D\lib
DCCFLAGS =- U$(INCLUDE) - B - CG - JP - $A - ,C - ,D - ,G - ,H - ,I - ,L - ,P - ,V - ,W + ,Y -
LIBS = ntoskrnl.lib hal.lib win32k.lib ntdll.lib
LINKFLAGS =/ NOLOGO / ALIGN: 32 / BASE:0x10000 / SUBSYSTEM:NATIVE / DRIVER / LIBPATH:$(LIB_PATH) / FORCE:UNRESOLVED / FORCE:MULTIPLE / ENTRY:DriverEntry
all : $(NAME).sys
$(NAME).sys : $(NAME).obj
omf2d $(NAME).obj / U_ *
link $(LINKFLAGS) $(LIBS) / out:$(NAME).sys $(NAME).obj
$(NAME).obj : $(NAME).pas
$(DCC) $(DCCFLAGS) $(NAME).pas
clean :
del * .obj
del * .dcu
del * .sys
到这里,你应该对用Delphi开发驱动程序有了个大体的了解了,下面让我们再来写一个很有趣的驱动程序以加深了解。这个程序是从Four-F的KmdKit的giveio转换来的(我比较懒,不想写新的^_^),写个驱动程序让用户模式下的进程能通过读取端口来访问电脑的CMOS。
大家都知道,端口是被Windows保护起来的,正常情况下,用户模式下的程序是无法直接操作端口的,通过我们的驱动程序修改I/O许可位图(I/O permission bit map,IOPM),这样用户模式下的相应进程就被允许自由地存取I/O端口,这方面详细资料见http://www.intel.com/design/intarch/techinfo/pentium/PDF/inout.pdf。每个进程都有自己的I/O许可位图,每个单独的I/O端口的访问权限都可以对每个进程进行单独授权,如果相关的位被设置的话,对对应端口的访问就是被禁止的,如果相关的位被清除,那么进程就可以访问对应的端口。既然I/O地址空间由64K个可单独寻址的8位I/O端口组成,IOPM表的最大尺寸就是2000h字节(注:每个端口的权限用1个bit表示,64K个端口除以8得到的就是IOPM的字节数,也就是65536/8=8192字节=2000h字节)。
TSS的设计意图是为了在任务切换的时候保存处理器状态,从执行效率的考虑出发,Windows NT并没有使用这个特征,它只维护一个TSS供多个进程共享,这就意味着IOPM也是共享的,因此某个进程改变了IOPM的话,造成的影响是系统范围的。
ntoskrnl.exe中有些未公开的函数是用来维护IOPM的,它们是Ke386QueryIoAccessMap和Ke386SetIoAccessMap函数。
function
Ke386QueryIoAccessMap(
dwFlag:DWORD;
pIopm:PVOID): NTSTATUS; stdcall;
dwFlag:DWORD;
pIopm:PVOID): NTSTATUS; stdcall;
各参数描述如下:
◎ dwFlag--0表示将全部缓冲区用0FFh填写,也就是所有的位都被设置,所有的端口都被禁止访问;1表示从TSS中将当前IOPM拷贝到缓冲区中
◎ pIopm--用来接收当前IOPM的缓冲区指针,注意缓冲区的大小不能小于2000h字节
如果函数执行成功的话会在返回值的低8位返回非0值;如果执行失败则返回零。
function
Ke386SetIoAccessMap(
dwFlag:DWORD;
pIopm:PVOID): NTSTATUS; stdcall;
dwFlag:DWORD;
pIopm:PVOID): NTSTATUS; stdcall;
各参数描述如下:
◎ dwFlag--这个参数只能是1,其他任何值函数都会返回失败
◎ pIopm--指向包含IOPM数据的缓冲区,缓冲区的尺寸不能小于2000h字节
如果函数执行成功的话会在返回值的低8位返回非0值;如果执行失败则返回零
当IOPM拷贝到TSS后,IOPM的偏移指针必须被定位到新的数据中去,这可以通过Ke386IoSetAccessProcess函数来完成,这也是ntoskrnl.exe中的一个很有用的未公开函数。
function
Ke386IoSetAccessProcess(
pProcess: PKPROCESS;
dwFlag:DWORD): NTSTATUS; stdcall;
pProcess: PKPROCESS;
dwFlag:DWORD): NTSTATUS; stdcall;
◎ pProcess--指向KPROCESS结构
◎ dwFlag--0表示禁止对I/O端口进行存取,将IOPM的偏移指针指到TSS段外面;1表示允许存取I/O端口,将IOPM的偏移指针指到TSS段的88h中
如果函数执行成功的话会在返回值的低8位返回非0值;如果执行失败则返回零
顺便提一下,ntoskrnl中的所有函数都有前缀,通过这个前缀你就可以辨别该函数属于系统功能中的哪一类。不同的前缀表示不同的功能--如i前缀表示内部使用(internal)、p表示私有函数(private)、f表示fastcall。再如,Ke表示内核函数(kernel),Psp表示内部进程支持函数(internal process support),Mm表示内存管理函数(Memory Manager)等等。
Ke386IoSetAccessProcess函数的第一个参数指向进程对象,也就是KPROCESS结构(在\include\nt_status.dcu中定义),Ke386IoSetAccessProcess会将KPROCESS结构中IopmOffset字段的值设置为合适的值。
unit giveio;
interface
uses
nt_status, ntoskrnl, ntutils;
const
IOPM_SIZE = $ 2000 ;
function _DriverEntry(DriverObject:PDriverObject;pusRegistryPath:PUnicodeString):NTSTATUS; stdcall;
implementation
function _DriverEntry(DriverObject:PDriverObject;pusRegistryPath:PUnicodeString):NTSTATUS; stdcall;
var
status:NTSTATUS;
oa:OBJECT_ATTRIBUTES;
hKey:HANDLE;
kvpi:KEY_VALUE_PARTIAL_INFORMATION;
pIopm:PVOID;
pProcess: PVOID;
iRet: NTSTATUS;
resultLen: ULONG;
KeyValue: TUnicodeString;
begin
DbgPrint( ' giveio: Entering DriverEntry ' ,[]);
status : = STATUS_DEVICE_CONFIGURATION_ERROR;
InitializeObjectAttributes(oa, pusRegistryPath, 0 , 0 , nil );
iRet : = ZwOpenKey(hKey, KEY_READ, @oa);
if iRet = STATUS_SUCCESS then
begin
RtlInitUnicodeString(KeyValue, ' ProcessId ' );
if (ZwQueryValueKey(hKey, @KeyValue,
KeyValuePartialInformation, PVOID(@kvpi),
sizeof(kvpi), resultLen) <> STATUS_OBJECT_NAME_NOT_FOUND) and
(resultLen <> 0 ) then
begin
DbgPrint( ' giveio: Process ID: %X ' , [kvpi.dData]);
{ Allocate a buffer for the I/O permission map }
pIopm : = MmAllocateNonCachedMemory(IOPM_SIZE);
if pIopm <> nil then
begin
if PsLookupProcessByProcessId(kvpi.dData, pProcess) = STATUS_SUCCESS then
begin
DbgPrint( ' giveio: PTR KPROCESS: %08X ' , [@pProcess]);
iRet : = Ke386QueryIoAccessMap( 0 , pIopm);
if iRet and $ff <> 0 then
begin
{ I/O access for 70h port }
asm
pushad
mov ecx, pIopm
add ecx, 70h / 8
mov eax, [ecx]
btr eax, 70h MOD 8
mov [ecx], eax
{ I/O access for 71h port }
mov ecx, pIopm
add ecx, 71h / 8
mov eax, [ecx]
btr eax, 71h MOD 8
mov [ecx], eax
popad
end ;
iRet : = Ke386SetIoAccessMap( 1 , pIopm);
if iRet and $FF <> 0 then
begin
iRet : = Ke386IoSetAccessProcess(pProcess, 1 );
if iRet and $FF <> 0 then
begin
DbgPrint( ' giveio: I/O permission is successfully given ' ,[]);
end else
begin
DbgPrint( ' giveio: I/O permission is failed ' ,[]);
status : = STATUS_IO_PRIVILEGE_FAILED;
end ;
end else
begin
status : = STATUS_IO_PRIVILEGE_FAILED;
end ;
end else
begin
status : = STATUS_IO_PRIVILEGE_FAILED;
end ;
ObfDereferenceObject(pProcess);
end else
begin
status : = STATUS_OBJECT_TYPE_MISMATCH;
end ;
MmFreeNonCachedMemory(pIopm, IOPM_SIZE);
end else
begin
DbgPrint( ' giveio: Call to MmAllocateNonCachedMemory failed ' ,[]);
status : = STATUS_INSUFFICIENT_RESOURCES;
end ;
end ;
ZwClose(hKey);
end ;
DbgPrint( ' giveio: Leaving DriverEntry ' ,[]);
result : = status;
end ;
end .
interface
uses
nt_status, ntoskrnl, ntutils;
const
IOPM_SIZE = $ 2000 ;
function _DriverEntry(DriverObject:PDriverObject;pusRegistryPath:PUnicodeString):NTSTATUS; stdcall;
implementation
function _DriverEntry(DriverObject:PDriverObject;pusRegistryPath:PUnicodeString):NTSTATUS; stdcall;
var
status:NTSTATUS;
oa:OBJECT_ATTRIBUTES;
hKey:HANDLE;
kvpi:KEY_VALUE_PARTIAL_INFORMATION;
pIopm:PVOID;
pProcess: PVOID;
iRet: NTSTATUS;
resultLen: ULONG;
KeyValue: TUnicodeString;
begin
DbgPrint( ' giveio: Entering DriverEntry ' ,[]);
status : = STATUS_DEVICE_CONFIGURATION_ERROR;
InitializeObjectAttributes(oa, pusRegistryPath, 0 , 0 , nil );
iRet : = ZwOpenKey(hKey, KEY_READ, @oa);
if iRet = STATUS_SUCCESS then
begin
RtlInitUnicodeString(KeyValue, ' ProcessId ' );
if (ZwQueryValueKey(hKey, @KeyValue,
KeyValuePartialInformation, PVOID(@kvpi),
sizeof(kvpi), resultLen) <> STATUS_OBJECT_NAME_NOT_FOUND) and
(resultLen <> 0 ) then
begin
DbgPrint( ' giveio: Process ID: %X ' , [kvpi.dData]);
{ Allocate a buffer for the I/O permission map }
pIopm : = MmAllocateNonCachedMemory(IOPM_SIZE);
if pIopm <> nil then
begin
if PsLookupProcessByProcessId(kvpi.dData, pProcess) = STATUS_SUCCESS then
begin
DbgPrint( ' giveio: PTR KPROCESS: %08X ' , [@pProcess]);
iRet : = Ke386QueryIoAccessMap( 0 , pIopm);
if iRet and $ff <> 0 then
begin
{ I/O access for 70h port }
asm
pushad
mov ecx, pIopm
add ecx, 70h / 8
mov eax, [ecx]
btr eax, 70h MOD 8
mov [ecx], eax
{ I/O access for 71h port }
mov ecx, pIopm
add ecx, 71h / 8
mov eax, [ecx]
btr eax, 71h MOD 8
mov [ecx], eax
popad
end ;
iRet : = Ke386SetIoAccessMap( 1 , pIopm);
if iRet and $FF <> 0 then
begin
iRet : = Ke386IoSetAccessProcess(pProcess, 1 );
if iRet and $FF <> 0 then
begin
DbgPrint( ' giveio: I/O permission is successfully given ' ,[]);
end else
begin
DbgPrint( ' giveio: I/O permission is failed ' ,[]);
status : = STATUS_IO_PRIVILEGE_FAILED;
end ;
end else
begin
status : = STATUS_IO_PRIVILEGE_FAILED;
end ;
end else
begin
status : = STATUS_IO_PRIVILEGE_FAILED;
end ;
ObfDereferenceObject(pProcess);
end else
begin
status : = STATUS_OBJECT_TYPE_MISMATCH;
end ;
MmFreeNonCachedMemory(pIopm, IOPM_SIZE);
end else
begin
DbgPrint( ' giveio: Call to MmAllocateNonCachedMemory failed ' ,[]);
status : = STATUS_INSUFFICIENT_RESOURCES;
end ;
end ;
ZwClose(hKey);
end ;
DbgPrint( ' giveio: Leaving DriverEntry ' ,[]);
result : = status;
end ;
end .
NAME
=
giveio
DCC = dcc32
INCLUDE = e:\mickeylan\KmdKit4D\include
LIB_PATH = e:\mickeylan\KmdKit4D\lib
DCCFLAGS =- U$(INCLUDE) - B - CG - JP - $A - ,C - ,D - ,G - ,H - ,I - ,L - ,P - ,V - ,W + ,Y -
LIBS = ntoskrnl.lib hal.lib win32k.lib ntdll.lib
LINKFLAGS =/ NOLOGO / ALIGN: 32 / BASE:0x10000 / SUBSYSTEM:NATIVE / DRIVER / LIBPATH:$(LIB_PATH) / FORCE:UNRESOLVED / FORCE:MULTIPLE / ENTRY:DriverEntry
all : $(NAME).sys
$(NAME).sys : $(NAME).obj
omf2d $(NAME).obj / U_ *
link $(LINKFLAGS) $(LIBS) / out:$(NAME).sys $(NAME).obj ntutils.obj
$(NAME).obj : $(NAME).pas
$(DCC) $(DCCFLAGS) $(NAME).pas
clean :
del * .obj
del * .dcu
del * .sys
DCC = dcc32
INCLUDE = e:\mickeylan\KmdKit4D\include
LIB_PATH = e:\mickeylan\KmdKit4D\lib
DCCFLAGS =- U$(INCLUDE) - B - CG - JP - $A - ,C - ,D - ,G - ,H - ,I - ,L - ,P - ,V - ,W + ,Y -
LIBS = ntoskrnl.lib hal.lib win32k.lib ntdll.lib
LINKFLAGS =/ NOLOGO / ALIGN: 32 / BASE:0x10000 / SUBSYSTEM:NATIVE / DRIVER / LIBPATH:$(LIB_PATH) / FORCE:UNRESOLVED / FORCE:MULTIPLE / ENTRY:DriverEntry
all : $(NAME).sys
$(NAME).sys : $(NAME).obj
omf2d $(NAME).obj / U_ *
link $(LINKFLAGS) $(LIBS) / out:$(NAME).sys $(NAME).obj ntutils.obj
$(NAME).obj : $(NAME).pas
$(DCC) $(DCCFLAGS) $(NAME).pas
clean :
del * .obj
del * .dcu
del * .sys
PS:随本教程我发布了我的KmdKit4D 0.01预览版,目前只完成了万里长征的第一步,后面的路还很长,希望有兴趣的朋友能和我一起努力来走完剩下的路^_^
下载: KmdKit4D.rar
作 者: mickeylan 转至: http://bbs.pediy.com/showthread.php?t=58070