在进入主题之前,先来简单地看一下结构化异常处理(Structured Exception Handling, SEH),本篇的程序需要这个东东。
结构化异常处理
这里我并不打算详细讲结构化异常处理,关于SEH,在网上你能找到相关的内容,SHE能用于所有的异常处理,也就是说,SEH既能用于用户模式又能用于内核模式。但这两种模式下的异常处理有一个本质上的差别:
在内核模式下,借助于seh,并非所有的异常都能得到处理!比如说,即使使用了seh,用零作除数作除法也会使系统崩溃。最为可怕的是,引用未定义的内核内存也会导致蓝屏死机BSOD。而对未定义的用户模式内存的引用异常,seh却可以轻松处理。因此避免系统崩溃的唯一办法就是所编写的代码不要导致无法处理的异常。
以下是个使用结构化异常的例子:
安装SHE-Frame
由于在内核模式下我们无法直接使用Delphi自身的异常处理机制,因为在驱动程序中我们要自己手工安装SHE,这里我们使用Delphi的BASM来做这件事情,了解SHE的朋友都知道,做这件事情是非常简单的。
为了在异常处理之后我们的处理程序能恢复线程的执行,我们应该保存esp、ebp寄存器的内容以及线程继续执行的地址。我们将这三项信息保存在seh结构体中并调用函数BuggyReader。BuggyReader函数试图从地址00000000读取一个DWORD。
nil指针引用是一个十分常见的错误,微软在00000000-0000FFFFh划出了64k字节的内存区,并使此区域无法访问。访问此区域中的任何一个字节都会导致EXCEPTION_ACCESS_VIOLATION类型的异常。
异常处理
函数BuggyReader从地址00000000读取引发了异常,我们就进入了我们指定的处理程序。
我们处理的第一件事就是输出相应的调试信息,如果发生的是EXCEPTION_ACCESS_VIOLATION类型的异常,还要输出一些额外的信息。之后开始真正的异常处理(这里了使用了BASM,你可以从中体会到BASM的强大功能)。
在本例中异常处理只是简单地恢复esp、ebp寄存器的值并将eip寄存器的值置为一个能使线程安全地继续执行的地址。这些处理信息都是由系统从我们预先填充的seh结构体中取出并保存在了CONTEXT结构体中,CONTEXT结构体的指针又传递给了系统。
最后异常处理函数返回ExceptionContinueExecution,该值为零就是告诉系统应该恢复线程的上下文并继续执行。即eip的值等于标记SafePlace的地址,而esp和ebp寄存器的值恢复为原值,线程从标记SafePlace处继续其执行。还有一点要注意,异常处理程序是C调用约定的。
有了结构化异常处理的知识后,让我们继续我们的内核之旅。接下来的例子中我们要从驱动程序中转到用户模式内存里。这个转换最好能包含在SEH-frame里。
内存共享
Windows提供了许多机制来进行进程间通讯(Interprocess Communications, IPC):通讯缓冲、DDE、通讯窗口(WM_COPYDATA就在这里)、邮槽(mailslot)、sockets等等。所有这些机制都是基于文件映射对象(file-mapping object)的,该对象本身是一块两个或多个进程可以访问的内存区,用DDK的术语,映射文件就是section对象,不要把它和PE文件中的section混淆起来。
section对象是最底层的通讯机制,这种对象被系统用来将可执行映象加载到内存,而缓存调度程序用它来访问缓存文件中的数据。section对象还能将磁盘上的文件映射到进程的地址空间中,而且用起来不像是在用文件,而是在用内存块。
借助于section对象来共享数据的情形如下:一个进程调用函数CreateFileMapping创建了一个内存映射文件。之后调用函数MapViewOfFile(如果层次更低就调用NtMapViewOfSection)将其视图(view)映射到自己的地址空间中,而另一个进程通过OpenFileMapping打开这个映射文件,并将其映射到自己的地址空间中。结果同一组物理内存页变为由两个进程访问,这就使得它们能通过这个区域轻松地传递较大量的数据,一个进程对这些页内容的修改会反映到另一个进程中。
共享section这种通讯方法不止可以用在用户进程间,还可以用在驱动程序里。在下面的例子里我们用命名section来在用户进程和驱动程序之间进行通讯。
老规矩,先来看看驱动程序。
整个驱动程序非常简单,但它却是个完整意义上的驱动程序,包含了一个驱动程序所必须的各个部分。程序很简单,大家一看就明白,所以也没加什么注释,只捡几个重要的地方说明一下。
使用共享资源通常情况下需要考虑同步问题,即读写线程不能同时访问共享资源。在本例中总是只有一个线程,所以不需要同步。
取得控制代码 IOCTL_SHARE_MY_SECTION,驱动程序尝试打开section对象,其名字定义为变量g_usSectionName。
如果取得了section的句柄,我们就来映射它的视图。这里与应用程序中的映射视图部分几乎完全相同。但是在调用了ZwMapViewOfSection之后,变量pSectionBaseAddress保存的值位于用户地址区域中,而不是核心区域中的地址,这点我们可以预料到。这可是个原则上的问题,即对这个地址的使用将只能在这个进程的上下文中,在映射section的地址空间中。由于SharedSection驱动程序是单层的(您还记得IRP_MJ_DEVICE_CONTROL类型的IRP处理要经过驱动程序进入发出该操作的线程上下文中),所以我们位于我们的应用程序的上下文中。
这里视图的虚拟地址与应用程序中视图的地址将有所不同,但共享section所在的物理页是同一个。我们这里有一个内存页,其中还保存着倒着写的一行文字。
以上建立SEH-frame,并调用_strrev函数将内存里的字符串反转过来。
下面来看看在用户模式下如何加载和调用这个驱动程序。
这是个控制台应用程序,我们也只讲几个关键的地方,其他地方很容易理解。
建立section需要指明其大小,对于大小值使用LARGE_INTEGER类型的变量的程序来说,这个值可以超过4GB。我们将这个值初始化为一个内存页的大小即4KB。
对于宏InitializeObjectAttributes我们已经很熟悉了。后面的ZwCreateSection调用需要填充好的OBJECT_ATTRIBUTES结构体,填充工作我们用这些宏来完成。
我们准备使用的section应该取个名字,这样就可以用名字来打开它。section的名字必须就unicode字符串,通过RtlInitUnicodeString(g_usSectionName, '\BaseNamedObjects\UserKernelSharedSection');来创建。
section对象在对象管理器名字空间的BaseNamedObjects目录下,用户进程创建的命名对象一般都在这个目录下。
调用函数ZwCreateSection来创建命名section对象,其大小为SECTION_SIZE,访问方式为可读写。如果section创建成功,则可以从变量hSection中得到其句柄。
这样就将section的所有的视图都映射到内存中。这个函数的参数够多的——所有的参数在DDK中都有详细介绍。参数pSectionBaseAddress初始化为NULL,为更方便她映射section,系统自己定义了映射section的虚拟地址,并将这个地址返给此参数。参数liViewSize初始化为零,这就定义了section将被全部映射。
我们将一行倒着写的文字拷贝到所得的视图中。驱动程序的任务就是将这行文字翻转,使其变为可读的形式。
CallDriver的返回值为TRUE表示驱动程序已成功完成自己的任务。我们来检查一下其工作的结果。在函数CallDriver中,我们完成通常的注册和启动驱动程序的操作并向驱动程序发送控制代码IOCTL_SHARE_MY_SECTION。
最后ZwUnmapViewOfSection(HANDLE(NtCurrentProcess), pSectionBaseAddress);把系统恢复成初始的样子。
结构化异常处理
这里我并不打算详细讲结构化异常处理,关于SEH,在网上你能找到相关的内容,SHE能用于所有的异常处理,也就是说,SEH既能用于用户模式又能用于内核模式。但这两种模式下的异常处理有一个本质上的差别:
在内核模式下,借助于seh,并非所有的异常都能得到处理!比如说,即使使用了seh,用零作除数作除法也会使系统崩溃。最为可怕的是,引用未定义的内核内存也会导致蓝屏死机BSOD。而对未定义的用户模式内存的引用异常,seh却可以轻松处理。因此避免系统崩溃的唯一办法就是所编写的代码不要导致无法处理的异常。
以下是个使用结构化异常的例子:
unit seh; interface uses nt_status; function _DriverEntry(pDriverObject:PDRIVER_OBJECT; pusRegistryPath:PUNICODE_STRING): NTSTATUS; stdcall; implementation uses ntoskrnl; const SEH_SafePlaceCounter = 0; SEH_INSTALLED = 0; type _SEH = record SafeEip: DWORD; { 线程继续执行的地方 } PrevEsp: DWORD; { 以前esp的值 } PrevEbp: DWORD; { 以前ebp的值 } end; var sseh: _SEH; function DefaultExceptionHandler(pExcept:PEXCEPTION_RECORD; pFrame:DWORD; pContext:PCONTEXT; pDispatch:DWORD): DWORD; cdecl; begin DbgPrint(#13#10'SEH: An exception %08X has occured'#13#10, pExcept^.ExceptionCode); if pExcept^.ExceptionCode = $0C0000005 then begin {如果发生了EXCEPTION_ACCESS_VIOLATION类型的异常,} {则输出以下信息.} DbgPrint(' Access violation at address: %08X'#13#10, pExcept^.ExceptionAddress); if pExcept^.ExceptionInformation[0] <> nil then {试图读还是写?} begin DbgPrint(' The code tried to write to address %08X'#13#10#13#10, DWORD(pExcept^.ExceptionInformation[4])); end else begin DbgPrint(' The code tried to read from address %08X'#13#10#13#10, DWORD(pExcept^.ExceptionInformation[4])); end; end; asm lea eax, sseh push (_SEH PTR [eax]).SafeEip push (_SEH PTR [eax]).PrevEsp push (_SEH PTR [eax]).PrevEbp mov eax, pContext pop (CONTEXT PTR [eax]).regEbp pop (CONTEXT PTR [eax]).regEsp pop (CONTEXT PTR [eax]).regEip end; result := 0; end; procedure BuggyReader; assembler; asm xor eax, eax mov eax, [eax] {!!! 没有SEH的话 - BSOD !!!} end; procedure BuggyWriter; assembler; asm mov eax, offset MmUserProbeAddress mov eax, [eax] mov eax, [eax] mov byte ptr [eax], 0 {!!!没有SEH的话 - BSOD !!!} end; function _DriverEntry(pDriverObject:PDRIVER_OBJECT; pusRegistryPath:PUNICODE_STRING): NTSTATUS; stdcall; label SafePlace; begin DbgPrint(#13#10'SEH: Entering DriverEntry'#13#10); { "手工"安装SEH } asm push offset DefaultExceptionHandler {我们的SEH程序} push fs:[0] mov fs:[0], esp mov sseh.SafeEip, offset SafePlace {SafePlace是处理完异常后继续执行的地方} mov sseh.PrevEbp, ebp mov sseh.PrevEsp, esp end; BuggyReader; BuggyWriter; SafePlace: asm pop fs:[0] add esp, 4 end; DbgPrint(#13#10'SEH: Leaving DriverEntry'#10#13); result := STATUS_DEVICE_CONFIGURATION_ERROR; end; end. |
安装SHE-Frame
由于在内核模式下我们无法直接使用Delphi自身的异常处理机制,因为在驱动程序中我们要自己手工安装SHE,这里我们使用Delphi的BASM来做这件事情,了解SHE的朋友都知道,做这件事情是非常简单的。
asm push offset DefaultExceptionHandler {我们的SEH程序} push fs:[0] mov fs:[0], esp mov sseh.SafeEip, offset SafePlace {SafePlace是处理完异常后继续执行的地方} mov sseh.PrevEbp, ebp mov sseh.PrevEsp, esp end; |
为了在异常处理之后我们的处理程序能恢复线程的执行,我们应该保存esp、ebp寄存器的内容以及线程继续执行的地址。我们将这三项信息保存在seh结构体中并调用函数BuggyReader。BuggyReader函数试图从地址00000000读取一个DWORD。
procedure BuggyReader; assembler; asm xor eax, eax mov eax, [eax] {!!! 没有SEH的话 - BSOD !!!} end; |
nil指针引用是一个十分常见的错误,微软在00000000-0000FFFFh划出了64k字节的内存区,并使此区域无法访问。访问此区域中的任何一个字节都会导致EXCEPTION_ACCESS_VIOLATION类型的异常。
异常处理
函数BuggyReader从地址00000000读取引发了异常,我们就进入了我们指定的处理程序。
function DefaultExceptionHandler(pExcept:PEXCEPTION_RECORD; pFrame:DWORD; pContext:PCONTEXT; pDispatch:DWORD): DWORD; cdecl; begin DbgPrint(#13#10'SEH: An exception %08X has occured'#13#10, pExcept^.ExceptionCode); if pExcept^.ExceptionCode = $0C0000005 then begin {如果发生了EXCEPTION_ACCESS_VIOLATION类型的异常,} {则输出以下信息.} DbgPrint(' Access violation at address: %08X'#13#10, pExcept^.ExceptionAddress); if pExcept^.ExceptionInformation[0] <> nil then {试图读还是写?} begin DbgPrint(' The code tried to write to address %08X'#13#10#13#10, DWORD(pExcept^.ExceptionInformation[4])); end else begin DbgPrint(' The code tried to read from address %08X'#13#10#13#10, DWORD(pExcept^.ExceptionInformation[4])); end; end; asm lea eax, sseh push (_SEH PTR [eax]).SafeEip push (_SEH PTR [eax]).PrevEsp push (_SEH PTR [eax]).PrevEbp mov eax, pContext pop (CONTEXT PTR [eax]).regEbp pop (CONTEXT PTR [eax]).regEsp pop (CONTEXT PTR [eax]).regEip end; result := 0; end; |
我们处理的第一件事就是输出相应的调试信息,如果发生的是EXCEPTION_ACCESS_VIOLATION类型的异常,还要输出一些额外的信息。之后开始真正的异常处理(这里了使用了BASM,你可以从中体会到BASM的强大功能)。
asm lea eax, sseh push (_SEH PTR [eax]).SafeEip push (_SEH PTR [eax]).PrevEsp push (_SEH PTR [eax]).PrevEbp mov eax, pContext pop (CONTEXT PTR [eax]).regEbp pop (CONTEXT PTR [eax]).regEsp pop (CONTEXT PTR [eax]).regEip end; |
在本例中异常处理只是简单地恢复esp、ebp寄存器的值并将eip寄存器的值置为一个能使线程安全地继续执行的地址。这些处理信息都是由系统从我们预先填充的seh结构体中取出并保存在了CONTEXT结构体中,CONTEXT结构体的指针又传递给了系统。
最后异常处理函数返回ExceptionContinueExecution,该值为零就是告诉系统应该恢复线程的上下文并继续执行。即eip的值等于标记SafePlace的地址,而esp和ebp寄存器的值恢复为原值,线程从标记SafePlace处继续其执行。还有一点要注意,异常处理程序是C调用约定的。
有了结构化异常处理的知识后,让我们继续我们的内核之旅。接下来的例子中我们要从驱动程序中转到用户模式内存里。这个转换最好能包含在SEH-frame里。
内存共享
Windows提供了许多机制来进行进程间通讯(Interprocess Communications, IPC):通讯缓冲、DDE、通讯窗口(WM_COPYDATA就在这里)、邮槽(mailslot)、sockets等等。所有这些机制都是基于文件映射对象(file-mapping object)的,该对象本身是一块两个或多个进程可以访问的内存区,用DDK的术语,映射文件就是section对象,不要把它和PE文件中的section混淆起来。
section对象是最底层的通讯机制,这种对象被系统用来将可执行映象加载到内存,而缓存调度程序用它来访问缓存文件中的数据。section对象还能将磁盘上的文件映射到进程的地址空间中,而且用起来不像是在用文件,而是在用内存块。
借助于section对象来共享数据的情形如下:一个进程调用函数CreateFileMapping创建了一个内存映射文件。之后调用函数MapViewOfFile(如果层次更低就调用NtMapViewOfSection)将其视图(view)映射到自己的地址空间中,而另一个进程通过OpenFileMapping打开这个映射文件,并将其映射到自己的地址空间中。结果同一组物理内存页变为由两个进程访问,这就使得它们能通过这个区域轻松地传递较大量的数据,一个进程对这些页内容的修改会反映到另一个进程中。
共享section这种通讯方法不止可以用在用户进程间,还可以用在驱动程序里。在下面的例子里我们用命名section来在用户进程和驱动程序之间进行通讯。
老规矩,先来看看驱动程序。
unit SharedSection; interface uses nt_status, ntoskrnl, native, winioctl, fcall, macros; function _DriverEntry(pDriverObject:PDRIVER_OBJECT; pusRegistryPath:PUNICODE_STRING): NTSTATUS; stdcall; implementation uses seh; const SECTION_SIZE = $1000; var g_usDeviceName, g_usSymbolicLinkName, g_usSectionName: UNICODE_STRING; function DispatchCreateClose(p_DeviceObject:PDEVICE_OBJECT; p_Irp:PIRP): NTSTATUS; stdcall; begin p_Irp^.IoStatus.Status := STATUS_SUCCESS; p_Irp^.IoStatus.Information := 0; IofCompleteRequest(p_Irp, IO_NO_INCREMENT); result := STATUS_SUCCESS; end; function DispatchControl(p_DeviceObject: PDEVICE_OBJECT; p_Irp:PIRP): NTSTATUS; stdcall; label SafePlace; var status:NTSTATUS; IOCTL_SHARE_MY_SECTION: DWORD; psl:PIO_STACK_LOCATION; oa:OBJECT_ATTRIBUTES; hSection:HANDLE; pSectionBaseAddress:PVOID; liViewSize:LARGE_INTEGER; begin IOCTL_SHARE_MY_SECTION := CTL_CODE(FILE_DEVICE_UNKNOWN, $800, 0, 0); psl := IoGetCurrentIrpStackLocation(p_Irp); {取IRP的stack location的指针} if psl^.Parameters.DeviceIoControl.IoControlCode = IOCTL_SHARE_MY_SECTION then begin {是我们控制码就开始处理} DbgPrint('SharedSection: Opening section object'#10#13); RtlInitUnicodeString(g_usSectionName, '\BaseNamedObjects\UserKernelSharedSection'); InitializeObjectAttributes(oa, @g_usSectionName, OBJ_CASE_INSENSITIVE, 0, nil); status := ZwOpenSection(@hSection, SECTION_MAP_WRITE or SECTION_MAP_READ, @oa); if status = STATUS_SUCCESS then begin DbgPrint('SharedSection: Section object opened'#13#10); pSectionBaseAddress := nil; liViewSize.HighPart := 0; liViewSize.LowPart := 0; status := ZwMapViewOfSection(hSection, HANDLE(NtCurrentProcess), pSectionBaseAddress, 0, SECTION_SIZE, nil, @liViewSize, ViewShare, 0, PAGE_READWRITE); if status = STATUS_SUCCESS then begin DbgPrint('SharedSection: Section mapped at address %08X'#13#10, pSectionBaseAddress); {安装SEH} asm push offset DefaultExceptionHandler push fs:[0] mov fs:[0], esp mov sseh.SafeEip, offset SafePlace mov sseh.PrevEbp, ebp mov sseh.PrevEsp, esp end; _strrev(pSectionBaseAddress); p_Irp^.IoStatus.Status := STATUS_SUCCESS; DbgPrint('SharedSection: String reversed'#13#10); SafePlace: asm pop fs:[0] add esp, 4 end; ZwUnmapViewOfSection(HANDLE(NtCurrentProcess), pSectionBaseAddress); DbgPrint('SharedSection: Section at address %08X unmapped '#13#10, pSectionBaseAddress); end else begin DbgPrint('SharedSection: Couldn''t map view of section. Status: %08X'#13#10, status); end; ZwClose(hSection); DbgPrint('SharedSection: Section object handle closed'#13#10); end else begin DbgPrint('SharedSection: Couldn''t open section. Status: %08X'#13#10, status); end; end else begin status := STATUS_INVALID_DEVICE_REQUEST; end; p_Irp^.IoStatus.Status := status; IofCompleteRequest(p_Irp, IO_NO_INCREMENT); DbgPrint('SharedSection: Leaving DispatchControl'#13#10); result := status; end; {卸载驱动} procedure DriverUnload(p_DriverObject:PDRIVER_OBJECT); stdcall; begin IoDeleteSymbolicLink(@g_usSymbolicLinkName); IoDeleteDevice(p_DriverObject^.DeviceObject); end; {驱动进入点} function _DriverEntry(pDriverObject:PDRIVER_OBJECT; pusRegistryPath:PUNICODE_STRING): NTSTATUS; var status: NTSTATUS; pDeviceObject: TDeviceObject; begin status := STATUS_DEVICE_CONFIGURATION_ERROR; RtlInitUnicodeString(@g_usDeviceName, '\Device\SharedSection'); RtlInitUnicodeString(@g_usSymbolicLinkName, '\DosDevices\SharedSection'); if IoCreateDevice(pDriverObject, 0, @g_usDeviceName, FILE_DEVICE_UNKNOWN, 0, TRUE, pDeviceObject) = STATUS_SUCCESS then begin if IoCreateSymbolicLink(@g_usSymbolicLinkName, @g_usDeviceName) = STATUS_SUCCESS then begin pDriverObject^.MajorFunction[IRP_MJ_CREATE] := @DispatchCreateClose; pDriverObject^.MajorFunction[IRP_MJ_CLOSE] := @DispatchCreateClose; pDriverObject^.MajorFunction[IRP_MJ_DEVICE_CONTROL] := @DispatchControl; pDriverObject^.DriverUnload := @DriverUnload; status := STATUS_SUCCESS; end else begin IoDeleteDevice(@pDeviceObject); end; end; result := status; end; end. |
整个驱动程序非常简单,但它却是个完整意义上的驱动程序,包含了一个驱动程序所必须的各个部分。程序很简单,大家一看就明白,所以也没加什么注释,只捡几个重要的地方说明一下。
使用共享资源通常情况下需要考虑同步问题,即读写线程不能同时访问共享资源。在本例中总是只有一个线程,所以不需要同步。
InitializeObjectAttributes(oa, @g_usSectionName, OBJ_CASE_INSENSITIVE, 0, nil); |
取得控制代码 IOCTL_SHARE_MY_SECTION,驱动程序尝试打开section对象,其名字定义为变量g_usSectionName。
status := ZwOpenSection(@hSection, SECTION_MAP_WRITE or SECTION_MAP_READ, @oa); if status = STATUS_SUCCESS then begin DbgPrint('SharedSection: Section object opened'#13#10); pSectionBaseAddress := nil; liViewSize.HighPart := 0; liViewSize.LowPart := 0; status := ZwMapViewOfSection(hSection, HANDLE(NtCurrentProcess), pSectionBaseAddress, 0, SECTION_SIZE, nil, @liViewSize, ViewShare, 0, PAGE_READWRITE); |
如果取得了section的句柄,我们就来映射它的视图。这里与应用程序中的映射视图部分几乎完全相同。但是在调用了ZwMapViewOfSection之后,变量pSectionBaseAddress保存的值位于用户地址区域中,而不是核心区域中的地址,这点我们可以预料到。这可是个原则上的问题,即对这个地址的使用将只能在这个进程的上下文中,在映射section的地址空间中。由于SharedSection驱动程序是单层的(您还记得IRP_MJ_DEVICE_CONTROL类型的IRP处理要经过驱动程序进入发出该操作的线程上下文中),所以我们位于我们的应用程序的上下文中。
这里视图的虚拟地址与应用程序中视图的地址将有所不同,但共享section所在的物理页是同一个。我们这里有一个内存页,其中还保存着倒着写的一行文字。
asm push offset DefaultExceptionHandler push fs:[0] mov fs:[0], esp mov sseh.SafeEip, offset SafePlace mov sseh.PrevEbp, ebp mov sseh.PrevEsp, esp end; _strrev(pSectionBaseAddress); p_Irp^.IoStatus.Status := STATUS_SUCCESS; DbgPrint('SharedSection: String reversed'#13#10); SafePlace: asm pop fs:[0] add esp, 4 end; |
以上建立SEH-frame,并调用_strrev函数将内存里的字符串反转过来。
下面来看看在用户模式下如何加载和调用这个驱动程序。
program SharedSection; {$APPTYPE CONSOLE} uses SysUtils, Windows, Dialogs, WinSvc, nt_status, native, macros, ntdll; const SECTION_SIZE = $1000; str = '.revird ecived a dna sessecorp resu neewteb yromem erahs ot euqinhcet emas eht esu nac uoy ,revewoH .sessecorp resu gnoma yromem gnirahs rof desu euqinhcet nommoc a si elif gnigap eht yb dekcab elif deppam-yromem A'; _DELETE = $10000; var hSection:HANDLE; liSectionSize: LARGE_INTEGER; oa:OBJECT_ATTRIBUTES; pSectionBaseAddress:PVOID; liViewSize: LARGE_INTEGER; g_usSectionName: UNICODE_STRING; status:NTSTATUS; sTemp: array[0..255] of char; function CallDriver: boolean; var fOk: boolean; hSCManager:HANDLE; hService:HANDLE; acModulePath: string; _ss:SERVICE_STATUS; hDevice:HANDLE; dwBytesReturned: DWORD; IOCTL_SHARE_MY_SECTION: DWORD; lpTemp: PChar; begin fOk := false; IOCTL_SHARE_MY_SECTION := CTL_CODE(FILE_DEVICE_UNKNOWN, $800, 0, 0); hSCManager := OpenSCManager(nil, nil, SC_MANAGER_ALL_ACCESS); if hSCManager <> 0 then begin acModulePath := GetCurrentDir + '\' + ExtractFileName('SharedSection.sys'); hService := CreateService(hSCManager, 'SharedSection', 'One way to share section', SERVICE_START or SERVICE_STOP or _DELETE, SERVICE_KERNEL_DRIVER, SERVICE_DEMAND_START, SERVICE_ERROR_IGNORE, PChar(acModulePath), nil, nil, nil, nil, nil); if hService <> 0 then begin if StartService(hService, 0, lpTemp) then begin; hDevice := CreateFile(PChar('\\.\SharedSection'), 0, 0, nil, OPEN_EXISTING, 0, 0); if hDevice <> INVALID_HANDLE_VALUE then begin if DeviceIoControl(hDevice, IOCTL_SHARE_MY_SECTION, nil, 0, nil, 0, dwBytesReturned, nil) then begin fOk := true; end else begin ShowMessage('Can''t send control code to device.'); end; CloseHandle(hDevice); end else begin ShowMessage('Device is not present.'); end; ControlService(hService, SERVICE_CONTROL_STOP, _ss); end else begin ShowMessage('Can''t start driver.'); end; DeleteService(hService); CloseServiceHandle(hService); end else begin ShowMessage('Can''t register driver.'); end; CloseServiceHandle(hSCManager); end else begin ShowMessage('Can''t connect to Service Control Manager.'); end; result := fOk; end; begin liSectionSize.HighPart := 0; liSectionSize.LowPart := SECTION_SIZE; RtlInitUnicodeString(g_usSectionName, '\BaseNamedObjects\UserKernelSharedSection'); InitializeObjectAttributes(oa, @g_usSectionName, OBJ_CASE_INSENSITIVE, 0, nil); status := ZwCreateSection(@hSection, SECTION_MAP_WRITE or SECTION_MAP_READ, @oa, @liSectionSize, PAGE_READWRITE, SEC_COMMIT, 0); if status = STATUS_SUCCESS then begin pSectionBaseAddress := nil; liViewSize.HighPart := 0; liViewSize.LowPart := 0; status := ZwMapViewOfSection(hSection, HANDLE(NtCurrentProcess), pSectionBaseAddress, 0, SECTION_SIZE, nil, @liViewSize, ViewShare, 0, PAGE_READWRITE); if status = STATUS_SUCCESS then begin //RtlInitUnicodeString(g_szStrToReverse, str); strcpy(pSectionBaseAddress, PChar(str)); if CallDriver then begin strcpy(sTemp, pSectionBaseAddress); ShowMessage(sTemp); ZwUnmapViewOfSection(HANDLE(NtCurrentProcess), pSectionBaseAddress); end; end else begin ShowMessage('Can''t map section.'); end; ZwClose(hSection); end else begin ShowMessage('Can''t create section.'); end; end. |
这是个控制台应用程序,我们也只讲几个关键的地方,其他地方很容易理解。
liSectionSize.HighPart := 0; liSectionSize.LowPart := SECTION_SIZE; |
建立section需要指明其大小,对于大小值使用LARGE_INTEGER类型的变量的程序来说,这个值可以超过4GB。我们将这个值初始化为一个内存页的大小即4KB。
InitializeObjectAttributes(oa, @g_usSectionName, OBJ_CASE_INSENSITIVE, 0, nil); |
对于宏InitializeObjectAttributes我们已经很熟悉了。后面的ZwCreateSection调用需要填充好的OBJECT_ATTRIBUTES结构体,填充工作我们用这些宏来完成。
我们准备使用的section应该取个名字,这样就可以用名字来打开它。section的名字必须就unicode字符串,通过RtlInitUnicodeString(g_usSectionName, '\BaseNamedObjects\UserKernelSharedSection');来创建。
section对象在对象管理器名字空间的BaseNamedObjects目录下,用户进程创建的命名对象一般都在这个目录下。
status := ZwCreateSection(@hSection, SECTION_MAP_WRITE or SECTION_MAP_READ, @oa, @liSectionSize, PAGE_READWRITE, SEC_COMMIT, 0); |
调用函数ZwCreateSection来创建命名section对象,其大小为SECTION_SIZE,访问方式为可读写。如果section创建成功,则可以从变量hSection中得到其句柄。
if status = STATUS_SUCCESS then begin pSectionBaseAddress := nil; liViewSize.HighPart := 0; liViewSize.LowPart := 0; status := ZwMapViewOfSection(hSection, HANDLE(NtCurrentProcess), pSectionBaseAddress, 0, SECTION_SIZE, nil, @liViewSize, ViewShare, 0, PAGE_READWRITE); |
这样就将section的所有的视图都映射到内存中。这个函数的参数够多的——所有的参数在DDK中都有详细介绍。参数pSectionBaseAddress初始化为NULL,为更方便她映射section,系统自己定义了映射section的虚拟地址,并将这个地址返给此参数。参数liViewSize初始化为零,这就定义了section将被全部映射。
.revird ecived a dna sessecorp resu neewteb yromem erahs ot euqinhcet emas eht esu nac uoy ,revewoH .sessecorp resu gnoma yromem gnirahs rof desu euqinhcet nommoc a si elif gnigap eht yb dekcab elif deppam-yromem A |
我们将一行倒着写的文字拷贝到所得的视图中。驱动程序的任务就是将这行文字翻转,使其变为可读的形式。
if status = STATUS_SUCCESS then begin strcpy(pSectionBaseAddress, PChar(str)); if CallDriver then begin strcpy(sTemp, pSectionBaseAddress); ShowMessage(sTemp); ZwUnmapViewOfSection(HANDLE(NtCurrentProcess), pSectionBaseAddress); end; |
CallDriver的返回值为TRUE表示驱动程序已成功完成自己的任务。我们来检查一下其工作的结果。在函数CallDriver中,我们完成通常的注册和启动驱动程序的操作并向驱动程序发送控制代码IOCTL_SHARE_MY_SECTION。
最后ZwUnmapViewOfSection(HANDLE(NtCurrentProcess), pSectionBaseAddress);把系统恢复成初始的样子。