Masimaro's blog - 内核编程
type: channel
link: https://www.masimaro.top/tag/%E5%86%85%E6%A0%B8%E7%BC%96%E7%A8%8B/
items: hook键盘驱动中的分发函数实现键盘输入数据的拦截
items: 遍历系统中加载的驱动程序以及通过设备对象指针获取设备对象名称
items: 驱动开发中的常用操作
items: 定时器的实现
items: 派遣函数
items: IRP的同步
items: 驱动程序的同步处理
items: WFP在包含fwpmu.h头的时候出错
items: 自己写的驱动用CreateFile打开时错误码返回1的问题
items: Windows内核函数

hook键盘驱动中的分发函数实现键盘输入数据的拦截
type: item
date: 2017-03-08T22:12:00+08:00
description: 我自己在看《寒江独钓》这本书的时候，书中除了给出了利用过滤的方式来拦截键盘数据之外，也提到了另外一种方法，就是hook键盘分发函数，将它替换成我们自己的，然后再自己的分发函数中获取这个数据的方式，但是书中并没有明确给出代码，我结合书中所说的一些知识加上网上找到的相关资料，自己编写了相关代码，并且试验成功了，现在给出详细的方法和代码。用这种方式时首先根据ObReferenceObjectByName函数来根据对应的驱动名称获取驱动的驱动对象指针。该函数是一个未导出函数，在使用时只需要先声明即可，函数原型如下:NTSTATUS ObReferenceObjectByName( PUNICODE_STRING ObjectName, //对应对象的名称 ULONG Attributes, //相关属性，一般给OBJ_CASE_INSENSITIVE PACCESS_STATE AccessState, //描述信息的一个结构体指针，一般给NULL ACCESS_MASK DesiredAccess, //以何种权限打开，一般给0如果或者FILL_ALL_ACCESS给它所有权限 POBJECT_TYPE ObjectType, //该指针是什么类型的指针，如果是设备对象给IoDeviceObjectType如果是驱动对象则给IoDriverObjectType KPROCESSOR_MODE AccessMode, //一般给NULL PVOID ParseContext, //附加参数，一般给NULL PVOID *pObject //用来接收相关指针的输出参数 );IoDeviceObjectType或者IoDriverObjectType也是未导出的，在使用之前需要先申明他们，例如extern POBJECT_TYPE IoDriverObjectType; extern POBJECT_TYPE IoDeviceObjectType;然后将该驱动对象中原始的分发函数保存起来，以便在hook之后调用或者在驱动卸载时恢复接下来hook相关函数，要截取键盘的数据，一般采用的是hook read函数在read函数中设置IRP的完成例程，然后调用原始的分发函数，一定要注意调用原始的分发函数，否则自己很难实现类似的功能，一旦实现不了，那么Windows上的键盘功能将瘫痪。在完成例程中解析穿回来的IRP就可得到对应键盘的信息。下面是具体的实现代码#define KDB_DRIVER_NAME L"\\Driver\\KbdClass" //键盘驱动的名称为KbdClass NTSTATUS ObReferenceObjectByName( PUNICODE_STRING ObjectName, ULONG Attributes, PACCESS_STATE AccessState, ACCESS_MASK DesiredAccess, POBJECT_TYPE ObjectType, KPROCESSOR_MODE AccessMode, PVOID ParseContext, PVOID *pObject); extern POBJECT_TYPE IoDriverObjectType; PDRIVER_OBJECT g_pKdbDriverObj; //键盘的驱动对象，保存这个是为了在卸载时还原它的分发函数 PDRIVER_DISPATCH g_oldDispatch[IRP_MJ_MAXIMUM_FUNCTION+1]; int g_KeyCount = 0; //记录键盘IRP的数量，当键盘的请求没有被处理完成时不能卸载这个驱动 VOID DriverUnload(PDRIVER_OBJECT DriverObject) { LARGE_INTEGER WaitTime; int i = 0; DbgPrint("KBD HOOK: Entry DriverUnload\n"); //等待5s WaitTime = RtlConvertLongToLargeInteger(-5 * 1000000000 / 100); //如果IRP没有被处理完成，等待5s再检测是否处理完成 while(0 != g_KeyCount) { KeDelayExecutionThread(KernelMode, FALSE, &WaitTime); } for(i = 0; i < IRP_MJ_MAXIMUM_FUNCTION + 1; i++) { //还原对应的分发函数 g_pKdbDriverObj->MajorFunction[i] = g_oldDispatch[i]; } } NTSTATUS c2cReadComplete( IN …

遍历系统中加载的驱动程序以及通过设备对象指针获取设备对象名称
type: item
date: 2017-03-01T22:15:00+08:00
description: 遍历系统中加载的驱动可以在R3层完成，通过几个未导出的函数：ZwOpenDirectoryObject、ZwQueryDirectoryObject，下面是具体的代码。//在这定义些基本的数据结构，这些本身是在R0层用的比较多的 typedef struct _UNICODE_STRING { USHORT Length; USHORT MaximumLength; PWSTR Buffer; } UNICODE_STRING, *PUNICODE_STRING; typedef ULONG NTSTATUS; // 对象属性定义 typedef struct _OBJECT_ATTRIBUTES { ULONG Length; HANDLE RootDirectory; UNICODE_STRING *ObjectName; ULONG Attributes; PSECURITY_DESCRIPTOR SecurityDescriptor; PSECURITY_QUALITY_OF_SERVICE SecurityQualityOfService; } OBJECT_ATTRIBUTES, *POBJECT_ATTRIBUTES; // 基本信息定义 typedef struct _DIRECTORY_BASIC_INFORMATION { UNICODE_STRING ObjectName; UNICODE_STRING ObjectTypeName; } DIRECTORY_BASIC_INFORMATION, *PDIRECTORY_BASIC_INFORMATION; // 返回值或状态类型定义 #define OBJ_CASE_INSENSITIVE 0x00000040L #define DIRECTORY_QUERY (0x0001) #define STATUS_SUCCESS ((NTSTATUS)0x00000000L) // ntsubauth #define STATUS_MORE_ENTRIES ((NTSTATUS)0x00000105L) #define STATUS_BUFFER_TOO_SMALL ((NTSTATUS)0xC0000023L) // 初始化对象属性宏定义 #define InitializeObjectAttributes( p, n, a, r, s ) { \ (p)->Length = sizeof(OBJECT_ATTRIBUTES); \ (p)->RootDirectory = r; \ (p)->Attributes = a; \ (p)->ObjectName = n; \ (p)->SecurityDescriptor = s; \ (p)->SecurityQualityOfService = NULL; \ } // 字符串初始化 //用来存储设备驱动对象名称的链表 extern vector<CString> g_DriverNameList; vector<DRIVER_INFO> g_DriverNameList; typedef VOID(CALLBACK* RTLINITUNICODESTRING)(PUNICODE_STRING, PCWSTR); RTLINITUNICODESTRING RtlInitUnicodeString; // 打开对象 typedef NTSTATUS(WINAPI *ZWOPENDIRECTORYOBJECT)( OUT PHANDLE DirectoryHandle, IN ACCESS_MASK DesiredAccess, IN POBJECT_ATTRIBUTES ObjectAttributes ); ZWOPENDIRECTORYOBJECT ZwOpenDirectoryObject; // 查询对象 typedef NTSTATUS (WINAPI *ZWQUERYDIRECTORYOBJECT)( IN HANDLE DirectoryHandle, OUT PVOID Buffer, IN ULONG BufferLength, IN BOOLEAN ReturnSingleEntry, IN BOOLEAN RestartScan, IN OUT PULONG Context, OUT PULONG ReturnLength OPTIONAL ); ZWQUERYDIRECTORYOBJECT ZwQueryDirectoryObject; // 关闭已经打开的对象 typedef NTSTATUS (WINAPI *ZWCLOSE)(IN HANDLE Handle); ZWCLOSE…

驱动开发中的常用操作
type: item
date: 2017-02-16T22:23:00+08:00
description: 这篇文章会持续更新，由于在驱动中，有许多常用的操作代码几乎不变，而我自己有时候长时间不用经常忘记，所以希望在这把一些常用的操作记录下来，当自己遗忘的时候，有个参考创建设备对象创建设备对象使用函数IoCreateDevice，它的参数如下：NTSTATUS IoCreateDevice( IN PDRIVER_OBJECT DriverObject, IN ULONG DeviceExtensionSize, IN PUNICODE_STRING DeviceName OPTIONAL, IN DEVICE_TYPE DeviceType, IN ULONG DeviceCharacteristics, IN BOOLEAN Exclusive, OUT PDEVICE_OBJECT *DeviceObject );第一个参数是驱动对象第二个参数是设备对象扩展的大小，它会自动根据大小生成一个内存空间，与对应设备绑定第三个参数是驱动名称第四个参数是驱动的类型，一般用作过滤设备的驱动类型为FILE_DEVICE_UNKNOWN第五个参数一般给FILE_DEVICE_SECURE_OPEN第六个参数表示设备是否为独占模式，一般给FALSE第七个参数是设备驱动的二级指针，用来返回生成的设备驱动的指针创建一个过滤设备的代码如下：//创建设备对象 status = IoCreateDevice(pDriverObject, sizeof(LIST_ENTRY), &uDeviceName, FILE_DEVICE_UNKNOWN, FILE_DEVICE_SECURE_OPEN, FALSE, &pDeviceObject); //为设备对象设置相关标识 pDeviceObject->Flags |= DO_BUFFERED_IO;IRP的完成在某些我们不需要进行特殊处理，但是又得需要对这个IRP进行处理的时候，一般采用完成处理的方式，这种方式主要使用函数IoCompleteRequest，使用例子如下：Irp->IoStatus.Information = 0; //设置返回给应用层的缓冲区的大小 Irp->IoStatus.Status = STATUS_SUCCESS;//给应用层当前IO操作返回成功 IoCompleteRequest(Irp, IO_NO_INCREMENT);//结束IRP在派遣函数中拿IRP的主功能号IRP中保存了它的主功能号和副功能号，他们都被存储在IRP的栈中，下面是基本的代码pStack = IoGetCurrentIrpStackLocation(Irp); //获取IRP栈 IrpCode = pStack->MajorFunction;在MJ_DEVICE_CONTROL类型的IRP中得到对应的控制码CtrlCode = pStack->Parameters.DeviceIoControl.IoControlCode;获取驱动所在的进程这个方法目前只在XP上实验过，win7或者更高版本可能有些不同。获取当前进程主要在EPROCESS结构找到名为ProcessName的项，由于这个结构微软并没有公开，所以可能以后会根据系统版本的不同它的偏移可能也有些许不同。下面是具体的代码pCurrProcess = IoGetCurrentProcess(); RtlInitUnicodeString(&uProcessName, (PTSTR)((ULONG)pCurrProcess + 0x174)); //这个偏移量是在xp上有效，是通过WinDbg获取到的，如果有变化，也可以通过windbg重新得到数据 代码所处内存的划分在驱动程序中，一定要非常小心的为每个函数，数据划分内存块，否则会出现蓝屏现象，比如处在DISPATCH_LEVEL的代码，只能位于非分页内存，因为这个IRQL下的代码不能被打断，如果发生缺页中断，那么只会造成蓝屏现象。而PASSIVE_LEVLE的代码则没有这个限制。下面是定义函数和数据在不同内存页的写法#define PAGEDCODE code_seg("PAGE") //分页内存 #define LOCKEDCODE code_seg()//非分页内存 #define INITCODE code_seg("INIT")//处在这种类型的代码，当函数执行完成后，系统会立马回收它所在的内存页 #define PAGEDDATA data_seg("PAGE") #define LOCKEDDATA data_seg() #define INITDATA data_seg("INIT") //下面是使用这些宏的例子，使用时只需要在函数或者数据前加上对应的宏 LOCKEDCODE void …

定时器的实现
type: item
date: 2017-02-16T19:38:00+08:00
description: 使用IO定时器IO定时器每隔1s就会触发一次，从而进入到定时器例程中，如果某个操作是每n秒执行一次（n为正整数）可以考虑在定时器例程中记录一个计数器大小就为n，每次进入定时器例程中时将计数器减一，当计数器为0时，表示到达n秒，这个时候可以执行操作。IO定时器只适合处理整数秒的情况在使用IO定时器之前需要对定时器进行初始化，初始化函数为IoInitializeTimer，定义如下：NTSTATUS IoInitializeTimer( IN PDEVICE_OBJECT DeviceObject, //设备对象指针 IN PIO_TIMER_ROUTINE TimerRoutine,//定时器例程 IN PVOID Context//传给定时器例程的函数 );初始化完成后可以使用IoStartTimer来启动定时器，使用IoStopTimer来停止定时器，下面是一个例子#define PAGEDCODE code_seg("PAGE") #define LOCKEDCODE code_seg() #define INITCODE code_seg("INIT") #define PAGEDDATA data_seg("PAGE") #define LOCKEDDATA data_seg() #define INITDATA data_seg("INIT") typedef struct _tag_DEVICE_EXTENSION { PDEVICE_OBJECT DeviceObject; UNICODE_STRING uDeviceName; UNICODE_STRING uSymbolickName; LONG lTimerCount; //定时器触发时间，以秒为单位 }DEVICE_EXTENSION, *PDEVICE_EXTENSION; NTSTATUS DriverEntry(DRIVER_OBJECT *DriverObject, PUNICODE_STRING RegistryPath) { NTSTATUS status; LONG i; PDEVICE_OBJECT pDeviceObject; UNREFERENCED_PARAMETER(RegistryPath); DriverObject->DriverUnload = DriverUnload; //设置派遣函数，这些代码在这就省略了 status = CreateDevice(DriverEntry, &pDeviceObject); IoStartTimer(pDeviceObject); return status; } #pragma LOCKEDCODE VOID IoTimer(DEVICE_OBJECT *DeviceObject,PVOID Context) { LONG ret; PDEVICE_EXTENSION pDeviceExtension; UNICODE_STRING uProcessName; PEPROCESS pCurrProcess; UNREFERENCED_PARAMETER(Context); pDeviceExtension = (PDEVICE_EXTENSION)(DeviceObject->DeviceExtension); ASSERT(NULL != pDeviceExtension); //采用互锁操作将定时器数减一 InterlockedDecrement(&pDeviceExtension->lTimerCount); //判断当前时间是否到达3秒 ret = InterlockedCompareExchange(&pDeviceExtension->lTimerCount, TIME_OUT, 0); if(0 == ret) { DbgPrint("3s time out\n"); } pCurrProcess = IoGetCurrentProcess(); RtlInitUnicodeString(&uProcessName, (PTSTR)((ULONG)pCurrProcess + 0x174)); DbgPrint("the current process %wZ", uProcessName); } #pragma INITCODE NTSTATUS CreateDevice(PDRIVER_OBJECT pDriverObject,PDEVICE_OBJECT *ppDeviceObject) { NTSTATUS status; UNICODE_STRING uDeviceName; UNICODE_STRING uSymbolickName; PDEVICE_EXT…

派遣函数
type: item
date: 2017-02-15T22:29:00+08:00
description: 驱动程序的主要功能是用来处理IO请求，而大部分的IO请求是在派遣函数中完成的，用户模式下所有的IO请求都会被IO管理器封装为一个IRP结构，类似于Windows窗口程序中的消息，不同的IRP被发送到不同的派遣函数中处理IRP与派遣函数IRPIRP（I/O Request Package）输入输出请求包，IRP的两个最基本的结构是MajorFunction和MinorFunction，分别记录IRP的主要类型和子类型，它们是一组函数指针数组，不同的项纪录的是处理当前请求的回调函数，可以在这些派遣函数中继续通过MinorFunction来判断每个驱动都有一个唯一的DRIVER_OBJECT结构，这个结构中有一个MajorFunction数组，通过这个数组可以将IRP与处理它的派遣函数关联起来，当应用层有一个针对于某个设备对象的I/O请求时，会根据这个设备对象所在驱动找到对应的MajorFunction结构，再根据请求类型来找到它对应的处理函数。IRP类型与应用层中有不同的消息类型，系统会根据消息类型调用具体消息处理函数类似，IRP也有不同的类型，在应用层调用不同的函数时会产生不同的IRP类型，例如调用应用层函数CreateFile或者内核函数ZwCreateFile会产生IRP_MJ_CREATE类型的IRP。下面是不同操作所对应产生的IRP请求列表IRP类型来源IRP_MJ_CREATE创建设备，CreateFile会产生此IRPIRP_MJ_CLOSE关闭设备，CloseHandle会产生此IRPIRP_MJ_CLEANUP清除工作，CloseHandle会产生此IRPIRP_MJ_DEVICE_CONTROLDeviceIoControl函数会产生此IRPIRP_MJ_PNP即插即用消息，NT驱动不支持此中IRP，只有WDM驱动才支持此中驱动IRP_MJ_POWER在操作系统处理电源消息时会产生此IRPIRP_MJ_QUERY_INFORMATION获取文件长度，GetFileSize会产生此IRPIRP_MJ_READ读取设备内容，ReadFile会产生此IRPIRP_MJ_SET_INFORMATION设置文件长度，SetFileSize会产生此IRPIRP_MJ_SHUTDOWN关闭系统前会产生此IRPIRP_MJ_SYSTEM_CONTROL系统内部产生控制信息，蕾西与调用DeviceIoControl函数IRP_MJ_WRITE对设备进行WriteFile时会产生此IRP对派遣函数的简单处理大部分的I/O请求都来自于应用层调用相应的API对设备进行I/O操作类似于CreateFile、ReadFile等函数产生，最简单的做法是将IRP设置为成功，然后结束IRP请求，并让派遣函数返回成功，结束这个IRP调用函数IoCompleteRequest。VOID IoCompleteRequest( IN PIRP Irp, //代表要结束的IRP IN CCHAR PriorityBoost//代笔线程恢复时的优先级别 );其实当应用层调用相关函数进行I/O操作时，会陷入睡眠或者阻塞状态，等待派遣函数成功返回，当派遣函数返回时会唤醒之前的等待线程，而第二个参数就是制定这个被唤醒的线程以何种优先级别运行。一般设置为IO_NO_INCREMENT表示不增加优先级，对于键盘，或者鼠标一类的需要更快的相应，这个时候可以设置为IO_MOUSE_INCREMENT 或者IO_KEYBOARD_INCREMENT下面是完成优先级的一个表在应用层打开设备在应用层一般通过设备名称打开驱动中的设备对象，设备名称一般只能在内核层使用，应用层能看到的是设备的符号链接名，符号链接名一般以"??\"开头，在应用层的写法有些不同，应用层设备的符号链接名称以“\.\开头”，因此在内核层符号链接为:"??\HelloDevice"到了应用层则应该写为"\.\HelloDevice"。设备栈驱动对象会创建多个设备对象，并将这些设备对象叠成一个垂直的结构，这种垂直结构被称作设备栈，IRP请求首先被发往设备栈上的顶层设备上，如果这个设备不处理可以将它下发到下层的设备对象中，直到某个设备结束这个IRP请求。为了记录IRP在每层设备中做的操作，IRP中有一个IO_STACK_LOCATION数组，这个数组对应于设备栈中各个设备对IRP所做的操作。在本层的设备中可以使用函数IoGetCurrentIrpStackLocation得到本层设备对应的IO_STACK_LOCATION结构，下面是它对应的结构图缓冲区方式读写操作在调用IoCreateDeivce函数完成设备对象的创建之后，需要设置该设备对象的缓冲区读写方式，这个值是由DEVICE_OBJECT中的Flag来…

IRP的同步
type: item
date: 2017-02-07T17:34:00+08:00
description: 应用层对设备的同步与异步操作以WriteFile为例，一般的同步操作是调用WriteFile完成后，并不会返回，应用程序会在此处暂停，一直等到函数将数据写入文件中并正常返回，而异步操作则是调用WriteFile后会马上返回，但是操作系统有另一线程在继续执行写的操作，这段时间并不影响应用程序的代码往下执行，一般异步操作都有一个事件用来通知应用程序，异步操作的完成，以下图分别来表示同步和异步操作:在调用这些函数时可以看做操作系统提供一个专门的线程来处理，然后如果选择同步，那么应用层线程会等待底层的线程处理完成后接着执行才执行后面的操作，而异步则是应用层线程接着执行后面的代码，而由操作系统来通知，因此一般来说异步相比较与同步少去了等待操作返回的过程，效率更高一些，但是选择同步还是异步，应该具体问题具体分析同步操作设备如果需要对设备进行同步操作，那么在使用CreateFile时就需要以同步的方式打开，这个函数的第六个参数dwFlagsAndAttributes是同步和异步操作的关键，如果给定了参数FILE_FLAG_OVERLAPPED则是异步的，否则是同步的。一旦用这个函数指定了操作方式，那么以后在使用这个函数返回的句柄进行操作时就是该中操作方式，但是这个函数本身不存在异步操作方式，一来这个函数没有什么耗时的操作，二来，如果它不正常返回，那么针对这个设备的操作也不能进行。一般像WriteFile、ReadFile、DeviceIoControl函数最后一个参数lpOverlapped，是一个OVERLAPPED类型的指针，如果是同步操作，需要给这个参数赋值为NULL异步操作方式设置Overlapped参数实现同步一般能够异步操作的函数都设置一个OVERLAPPED类型的参数，它的定义如下typedef struct _OVERLAPPED { ULONG_PTR Internal; ULONG_PTR InternalHigh; DWORD Offset; DWORD OffsetHigh; HANDLE hEvent; } OVERLAPPED; 对于这个参数在使用时，其余的我们都不需要关心，一般只使用最后一个hEvent成员，这个成员是一个事件对象的句柄，在使用时，先创建一个事件对象，并设置事件对象无信号，并将句柄赋值给这个成员，一旦异步操作完成，那么系统会将这个事件设置为有信号，在需要同步的地方使用Wait系列的函数进行等待即可。int main() { HANDLE hDevice = CreateFile("test.dat", GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL|FILE_FLAG_OVERLAPPED,//此处设置FILE_FLAG_OVERLAPPED NULL ); if (hDevice == INVALID_HANDLE_VALUE) { printf("Read Error\n"); return 1; } UCHAR buffer[BUFFER_SIZE]; DWORD dwRead; //初始化overlap使其内部全部为零 OVERLAPPED overlap={0}; //创建overlap事件 //设置事件采用自动赋值的方式，且初始化为无信号，这样操作系统才能在异步操作完成时自动给其赋值为有信号 overlap.hEvent = CreateEvent(NULL,FALSE,FALSE,NULL); //这里没有设置OVERLAP参数，因此是异步操作 ReadFile(hDevice,buffer,BUFFER_SIZE,&dwRead,&overlap); //做一些其他操作，这些操作会与读设备并行执行 //等待读设备结束 WaitForSingleObject(overlap.hEvent,INFINITE); CloseHandle(hDevice); return 0; }使用完成函数来实现异步操作异步函数是在异步操作完成时由操作系统调用的函数，所以我们可以在需要同步的地方等待一个同步对象，然后在异步函数中将这个对象设置为有信号。使用异步函数必须使用带有Ex的设备操作函数，像ReadFileEx,WriteFileEx等等，Ex系列的函数相比于不带Ex的函数来说，多了最后一个参数，LPOVERLAPPED_COMPLETION_ROUTINE 类型的回调函数，这个函数的原型如下：VOID CALLBACK FileIOCompletionRoutine( __in DWORD dwErrorCode, __in DWORD dwNumberOfBytesTransfered, …

驱动程序的同步处理
type: item
date: 2017-01-09T17:42:00+08:00
description: 驱动程序运行在系统的内核地址空间，而所有进程共享这2GB的虚拟地址空间，所以绝大多数驱动程序是运行在多线程环境中，有的时候需要对程序进行同步处理，使某些操作是严格串行化的，这就要用到同步的相关内容。异步是指两个线程各自运行互不干扰，而当某个线程运行取决与另一个线程，也就是要在线程之间进行串行化处理时就需要同步机制。中断请求级别在进行I/O操作时会产生中断，以便告知CPU当前I/O操作已完成，此时CPU会停下手头的工作，来处理这个中断请求，在Windows操作系统中，分为硬件中断和软件中断。并且将这些中断映射为不同级别的中断请求级。硬件中断是由硬件产生的中断，软件中断是由int指令产生的。在传统的PC中，一般可以接收16种中断信号，每个信号对应一个中断号。硬件中断分为可屏蔽中断和不可屏蔽中断。可屏蔽中断是由可编程中断控制器（PIC）产生，这是一个硬件设备。在后面的PC机中采用了高级可编程中断控制器（APIC）代替。在APIC中将中断扩展为24个，每个都有对应的优先级，一般正在运行的线程可以被中断打断，进入中断处理程序，当优先级高的中断来临时处在低优先级的中断也会被打断。在Windows中中断请求级别有32个，但是在编程或者在MSDN上只需要关心两类级别，PASSIVE_LEVEL：用户级别，这个中断级别最低。DISPATCH_LEVEL：级别相对较高。在运用内核函数时可以查看MSDN，运行在低优先级的函数不能进行高优先级的一些操作。下面是一些常用函数的优先级函数优先级DriverEntry AddDevice DriverUnload等函数PASSIVE_LEVEL各种分发派遣函数PASSIVE_LEVEL完成函数DISPATCH_LEVELNDIS回调函数DISPATCH_LEVEL在内核模式中可以调用KeGetCurrentIrql得到当前的IRQL需要注意的是，线程优先级只针对于应用程序，只有在IRQL处于PASSIVE_LEVEL级别才有意义。当线程运行在PASSIVE_LEVEL级别的时候可以进行线程切换，而当IRQL提升到DISPATCH_LEVEL，就不再出现线程切换PASSIVE_LEVEL是应用层的中断级别，可以有线程切换，处在这个IRQL下的程序是位于进程上下文，可以进行线程的切换休眠等操作，而处于DISPACTH_LEVEL的程序属于中断上下文，CPU会一直执行这个环境下的代码，没有线程切换，不能进行线程的休眠操作，否则，一旦休眠则没有线程能够唤醒。在内存的使用上，PASSIVE_LEVEL级别的程序可以使用分页内存，一旦发生缺页中断，系统可以进行线程切换，切换到其他进程，将缺页装载在内存，但是在DISPATCH_LEVEL没有线程切换，一旦发生缺页中断就会导致系统崩溃，所以DISPATCH_LEVEL只能使用非分页内存。我们可以在程序中手动提升和降低当前的IRQL。VOID KeRaiseIrql( IN KIRQL NewIrql, //新IRQL OUT PKIRQL OldIrql//当前的IRQL ); VOID KeLowerIrql( IN KIRQL NewIrql //新IRQL );自旋锁自旋锁是一种同步机制，他能保证某个资源只被一个线程所拥有。在初始化自旋锁的时候，处于解锁状态，这个时候线程可以获取自旋锁并访问同步资源，一旦有一个线程获取到自旋锁，必须等到它释放以后，才能被其他线程获取。自旋锁被锁上之后当切换到另外的线程时，线程会不停的询问是否可以获取自旋锁。此时线程处于空转的情况，白白浪费了CPU资源，所以一般要慎用自旋锁使用方法自旋锁用结构体KSPIN_LOCK来表示使用自旋锁的时候需要对其进行初始化，初始化可以使用函数KeInitializeSpinLock，一般在驱动加载函数DriverEntry或者AddDevice函数中初始化自旋锁。VOID KeInitializeSpinLock( IN PKSPIN_LOCK SpinLock );申请自旋锁可以使用函数KeAcquireSpinLock。VOID KeAcquireSpinLock( IN PKSPIN_LOCK SpinLock, OUT PKIRQL OldIrql //自旋锁以前所处的IRQL );释放自旋锁可以使用函数KeReleaseSpinLock内核模式下线程的创建在内核模式中线程使用PsCreateSystemThread;该函数的原型如下:NTSTATUS PsCreateSystemThread( OUT PHANDLE ThreadHandle, //线程的句柄指针，这个参数作为一个输出参数 IN ULONG DesiredAccess,//新线程的权限，在驱动中这个值一般给0 IN POBJE…

WFP在包含fwpmu.h头的时候出错
type: item
date: 2016-12-26T17:46:00+08:00
description: 最近在学WFP驱动框架，在使用VS2013写代码调用WFP的函数时会包含fwpmu.h这个头，但是在包含这个头的时候会报错，就像下面这个图这样：我百度了一下，然后在这个网站上面找到了解决方案：https://social.msdn.microsoft.com/Forums/windowsdesktop/en-US/8fd93a3d-a794-4233-9ff7-09b89eed6b1f/compiling-with-wfp?forum=wfp虽然我不太看得懂英文，但是根据上面的说明，我们点击第一个错误，然后定位到这个网上说的位置：在FwpTypes.h的第275行，343行出现这样的错误：从上面的错误来看，很明显是在定义宏的时候明明有换行的标志，但是它居然新换了一行，不知道是安装WDK的时候出现的还是什么原因，总之我们把这些换行去掉，然后再次编译就OK了。

自己写的驱动用CreateFile打开时错误码返回1的问题
type: item
date: 2016-12-01T17:48:00+08:00
description: 就像题目上说的，今天在写一个例子代码时遇到了这个问题，下面是当时驱动层和应用层的代码：#include <ntddk.h> #define BASE_CODE 0x800 #define CREATE_THREAD_COMMAND CTL_CODE(FILE_DEVICE_UNKNOWN, BASE_CODE + 1, METHOD_BUFFERED, FILE_ANY_ACCESS) #define DEVICE_NAME L"\\Device\\ThreadDevice" #define LINK_NAME L"\\??\\ControlDevice" VOID DriverUnload(PDRIVER_OBJECT pDriverObject) { UNICODE_STRING uLinkName; PDEVICE_OBJECT pDev = pDriverObject->DeviceObject; RtlInitUnicodeString(&uLinkName, LINK_NAME); IoDeleteSymbolicLink(&uLinkName); IoDeleteDevice(pDev); DbgPrint("Goodbye world!\n"); } VOID MyProcessThread(PVOID pContext) { //获取当前发送IRP请求的线程名 PEPROCESS pCurrProcess = IoGetCurrentProcess(); PTSTR pProcessName = (PTSTR)((CHAR*)pCurrProcess + 0x174); // UNREFERENCED_PARAMETER(pContext); DbgPrint("MyProcessThread Current Process %s\n", pProcessName); PsTerminateSystemThread(0); } VOID SystemThread(PVOID pContext) { //获取系统进程名 PEPROCESS pCurrProcess = IoGetCurrentProcess(); PTSTR pProcessName = (PTSTR)((CHAR*)pCurrProcess + 0x174); // UNREFERENCED_PARAMETER(pContext); DbgPrint("MyProcessThread Current Process %s\n", pProcessName); PsTerminateSystemThread(0); } VOID CreateThread_Test() { HANDLE hSysThread = NULL; HANDLE hMyProcThread = NULL; NTSTATUS status; //创建系统进程 status = PsCreateSystemThread(&hSysThread, 0, NULL, NULL, NULL, SystemThread, NULL); //创建用户进程 status = PsCreateSystemThread(&hMyProcThread, 0, NULL, NtCurrentProcess(), NULL, MyProcessThread, NULL); } NTSTATUS IoControlDispatch( IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp ) { NTSTATUS status = STATUS_SUCCESS; PIO_STACK_LOCATION pIrps = IoGetCurrentIrpStackLocation(Irp); if(CREATE_THREAD_COMMAND == pIrps->Parameters.DeviceIoControl.IoControlCode) { //创建线程 CreateThread_Test(); } Irp->IoStatus.Status = status; Irp->IoStatus.Information = 0; IoCompleteRequest(Irp, IO_NO_INCREMENT); return STATUS_SUCCESS; } NTSTATUS DriverEntry( IN PDRIVER_OBJECT pDriverObject, IN PUNICODE_STRING pRegistryPath) { UNICODE_STRING uDeviceName; UNICODE_STRING uSymbolicName; PDEVICE_OBJE…

Windows内核函数
type: item
date: 2016-11-30T17:52:00+08:00
description: 字符串处理在驱动中一般使用的是ANSI字符串和宽字节字符串，在驱动中我们仍然可以使用C中提供的字符串操作函数，但是在DDK中不提倡这样做，由于C函数容易导致缓冲区溢出漏洞，针对字符串的操作它提供了一组函数分别用来处理ANSI字符串和UNICODE字符串。针对两种字符串，首先定义了它们的结构体typedef struct _STRING { USHORT Length;//字符串的长度 USHORT MaximumLength;//字符缓冲的长度 PCHAR Buffer;//字符缓冲的地址 } ANSI_STRING, *PANSI_STRING; typedef struct _UNICODE_STRING { USHORT Length; USHORT MaximumLength; PWSTR Buffer; } UNICODE_STRING, *PUNICODE_STRING;对于这两个字符串的打印，可以使用%wZ打印UNICODE_STRING用%Z打印ANSI_STRING字符串的初始化VOID RtlInitAnsiString( IN OUT PANSI_STRING DestinationString, IN PCSZ SourceString ); VOID RtlInitUnicodeString( IN OUT PUNICODE_STRING DestinationString, IN PCWSTR SourceString );这两个函数只是简单的将SourceString 的首地址赋值给Buffer成员，并初始化相关的长度，所以在使用时需要考虑缓冲的生命周期，权限，同时如果我们改变SourceString 里面存储的字符串，那么对应的UNICODE_STRING 或者ANSI_STRING中的值也会改变，比如下面的代码RtlInitUnicodeString(&uTest, L"Hello World"); RtlCopyMemory(uTest.Buffer, L"Test");由于Buffer指向的是不可修改的常量内存部分，所以后面试图修改它的时候会造成程序崩溃。void InitString(&pUnicodeString) { WCHAR szBuf[255] = L"Hello world"; RtlInitUnicodeString(pUnicodeString, szBuffer); } void test() { UNICODE_STRING uTest; InitString(&uTest); //后面的操作 }我们在另外一个函数中利用局部变量来初始化这个字符串的时候由于当函数调用完成，函数中局部变量被销毁，这个时候指向的那块内存可能已经被其他函数所占用，而我们后面通过操作UNICODE_STRING，又要操作这段内存，这个时候一定会出现问题，所以一般如果要在多个函数中使用这个UNICODE_STRING时一般申请一段堆内存，但是在使用完成后一定要记得自己回收这段内存，否则会造成内存泄露，对此DDK专门提供了一组函数来销毁字符串中的堆内存VOID RtlFreeAnsiString( IN PANSI_STRING AnsiString ); VOID RtlFreeUnicodeString( IN PUNICODE_STRING UnicodeString );字符串拷贝：VOID RtlCopyString( IN OUT PSTRING DestinationString, IN PSTRING SourceString OPTIONAL ); VOID RtlCopyUnicodeString( IN OUT PUNICODE_STRING DestinationString, IN PUNICODE_STRING SourceString );字符串比较LONG RtlCompareString( IN PSTRING String1, IN PSTRING String2, BOOLEAN CaseInSensitive//是否忽略大小写 ); LONG RtlCompareUnicodeString( IN PUNICODE_STRING String1, IN PUNICODE_STRING String2, IN BOOLEAN CaseInSensitive );字符串转化为大写VOID RtlUpperString( IN OUT PSTRING DestinationString, IN PSTRING SourceString ); NTSTATUS RtlUpcaseUnicodeString( IN OUT PUNICODE_STRING Destin…