驱动开发中的常用操作

这篇文章会持续更新,由于在驱动中,有许多常用的操作代码几乎不变,而我自己有时候长时间不用经常忘记,所以希望在这把一些常用的操作记录下来,当自己遗忘的时候,有个参考

创建设备对象

创建设备对象使用函数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 test()
{
}

给编译器提示,函数某些参数在函数中不使用

一般在编译驱动时,如果函数参数或者在函数内部定义了某些变量在函数中没有使用的话,编译器会报错,但是有的函数原型是系统规定,但是有些参数又确实用不到,这个时候有两种方式,一种是关掉相关的编译选项,另一种是使用宏告知编译器,这个变量在函数中不使用.

UNREFERENCED_PARAMETER(RegistryPath);

获取系统时间

这里的获取系统时间一般是指获取时间并将它转化我们熟悉的年月日、时分秒的格式,一般的步骤如下: 1. 利用函数KeGetSystemTime()获取系统时间,这个时间是格林尼治时间从1601年起至今经历的时间,单位为100ns 2. 利用ExSystemTimeToLocalTime()将上述的格林尼治时间转化为本时区的时间,这个值得含义和单位与上述的相同 3. 利用函数RtlTimeToTimeFields()将本地时间转化为带有年月日格式的时间函数的第二个参数是TIME_FIELDS结构,他的定义如下:

typedef struct TIME_FIELDS {
    CSHORT Year;
    CSHORT Month;
    CSHORT Day;
    CSHORT Hour;
    CSHORT Minute;
    CSHORT Second;
    CSHORT Milliseconds;
    CSHORT Weekday;
} TIME_FIELDS;

下面是一个时间转化的例子

    LARGE_INTEGER  current_system_time;
    TIME_FIELDS time_fields;
    LARGE_INTEGER current_local_time;
    KeQuerySystemTime(&current_system_time);
    ExSystemTimeToLocalTime(&current_system_time, &current_local_time);
    RtlTimeToTimeFields(&current_local_time, &time_fields);

    DbgPrint("Current Time: %d/%d/%d %d:%d:%d\n", time_fields.Year, time_fields.Month, time_fields.Day, time_fields.Hour, time_fields.Minute, time_fields.Second);

他们三个可以互相转化,下面是它们之间转化的一个示意图:

文件读写

文件读写一般需要进行这样几步 1. 使用InitializeObjectAttributes初始化一个OBJECT_ATTRIBUTES对象 2. 使用ZwCreateFile创建一个文件句柄 3. 调用ZwReadFile或者ZwWriteFile读写文件 这里面复杂的是InitializeObjectAttributes和ZwCreateFile传参的问题,好在这两个函数在调用时,一般传参都是固定的。

VOID 
  InitializeObjectAttributes(
    OUT POBJECT_ATTRIBUTES  InitializedAttributes,
    IN PUNICODE_STRING  ObjectName, //传希望打开的文件名称或者设备对象名称
    IN ULONG  Attributes, //权限一般给OBJ_CASE_INSENSITIVE
    IN HANDLE  RootDirectory, //根目录,一般给NULL
    IN PSECURITY_DESCRIPTOR  SecurityDescriptor//安全属性,一般给NULL
    );
NTSTATUS 
  ZwCreateFile(
    __out PHANDLE  FileHandle, //返回的文件句柄
    __in ACCESS_MASK  DesiredAccess, //权限,如果希望对文件进行同步操作,需要额外加上SYNCHRONIZE
    __in POBJECT_ATTRIBUTES  ObjectAttributes,
    __out PIO_STATUS_BLOCK  IoStatusBlock, //一般不怎么用这个输出参数,但是的给值
    __in_opt PLARGE_INTEGER  AllocationSize,//一般给NULL
    __in ULONG  FileAttributes,//文件属性,一般给FILE_ATTRIBUTE_NORMAL
    __in ULONG  ShareAccess,//共享属性一般给0
    __in ULONG  CreateDisposition,//创建的描述信息,根据MSDN很容易决定
    __in ULONG  CreateOptions, //如果是同步操作,一般加上FILE_SYNCHRONOUS_IO_NONALERT,如果是异步操作一般给0
    __in_opt PVOID  EaBuffer, //一般给NULL
    __in ULONG  EaLength//一般给0
    );

下面是读写不同设备的相关代码

//同步读取驱动的设备对象
    NTSTATUS status;
    HANDLE hDeviceA;
    OBJECT_ATTRIBUTES ObjAtrribute;
    UNICODE_STRING uDeviceName;
    IO_STATUS_BLOCK status_block;
    RtlInitUnicodeString(&uDeviceName, DEVICE_NAME);
    InitializeObjectAttributes(&ObjAtrribute, &uDeviceName, OBJ_CASE_INSENSITIVE, NULL, NULL);
    status = ZwCreateFile(&hDeviceA, SYNCHRONIZE | FILE_READ_ATTRIBUTES, &ObjAtrribute,&status_block, NULL, FILE_ATTRIBUTE_NORMAL, FILE_SHARE_READ, FILE_OPEN, FILE_SYNCHRONOUS_IO_NONALERT, NULL, 0);
    if(!NT_SUCCESS(status))
    {
        return status;
    }

    ZwReadFile(hDeviceA, NULL, NULL, NULL, &status_block, NULL, 0, NULL, NULL);
    ZwClose(hDeviceA);
    return STATUS_SUCCESS;
//同步读取文件
HANDLE hFile = NULL;
OBJECT_ATTRIBUTES ObjAttribute;
IO_STATUS_BLOCK IoStatusBlock;
UNICODE_STRING uFileName;
WCHAR wFname[] = L"\\??\\C:\\log.txt";
CHAR buf[] = "Hello World\r\n";
NTSTATUS status = STATUS_SUCCESS;
FILE_STANDARD_INFORMATION fsi = {0};
PCHAR pBuffer = NULL;
RtlInitUnicodeString(&uFileName, wFname);
InitializeObjectAttributes(&ObjAttribute, &uFileName, OBJ_CASE_INSENSITIVE, NULL, NULL);
//打开文件或者创建文件

status = ZwCreateFile(&hFile, GENERIC_WRITE | GENERIC_READ, &ObjAttribute, &IoStatusBlock, NULL, 0, 0, FILE_OPEN_IF, FILE_SYNCHRONOUS_IO_NONALERT, NULL, NULL);
if(!NT_SUCCESS(status))
{
DbgPrint("Create File Error\n");
return;
}

//写文件
status = ZwWriteFile(hFile, NULL, NULL, NULL, &IoStatusBlock, buf, sizeof(buf), NULL, NULL);
if(NT_SUCCESS(status))
{
DbgPrint("Write File Success%u", IoStatusBlock.Information);
}

//读取文件长度
status = ZwQueryInformationFile(hFile, &IoStatusBlock, &fsi, sizeof(fsi), FileStandardInformation);
if(NT_SUCCESS(status))
{
DbgPrint("file length:%u\n", fsi.EndOfFile.QuadPart);
}

//读文件
pBuffer = (PCHAR)ExAllocatePoolWithTag(PagedPool, fsi.EndOfFile.QuadPart * sizeof(CHAR), 'eliF');
if(NULL != pBuffer)
{
status = ZwReadFile(hFile, NULL, NULL, NULL, &IoStatusBlock, pBuffer, fsi.EndOfFile.QuadPart * sizeof(CHAR), NULL, NULL);
if(NT_SUCCESS(status))
{
DbgPrint("Read File %s lenth: %u", pBuffer, fsi.EndOfFile.QuadPart * sizeof(CHAR));
}
}
//关闭文件句柄
ZwClose(hFile);
ExFreePool(pBuffer);

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏Java帮帮-微信公众号-技术文章全总结

第三十天-加强2-多表查询&JDBC&连接池&DBUtils&综合案例【悟空教程】

第三十天-加强2-多表查询&JDBC&连接池&DBUtils&综合案例【悟空教程】

19540
来自专栏ASP.NET MVC5 后台权限管理系统

ASP.NET MVC5+EF6+EasyUI 后台管理系统(89)-EF执行SQL语句与存储过程

EF上下文 DbContext包含了DataBase属性,里面有很多方法,但是实际我们只需要用到个方法

40630
来自专栏WD学习记录

8-18 Android学习ing

getSharedPreferences("1234", Context.MODE_PRIVATE)  之中的名字不能带有后缀名,比如1234.xml

12740
来自专栏数据库

使用VBA创建Access数据表

导读: 本期介绍如何在Access数据库中创建一张空数据表。下期将介绍如何将工作表中的数据存入数据库对应的表中,随后还将介绍如何从数据库的表中取出数据输出到Ex...

32670
来自专栏后台架构

Sphinx源码学习笔记(一):索引创建

  因为项目开发需要在游戏内部实现玩家名称的模糊查找功能,本身直接使用Sphinx配置mysql可以直接搭建一套模糊匹配的即可支持功能的实现。

57370
来自专栏一枝花算不算浪漫

[Java面试三]JavaWeb基础知识总结.

824100
来自专栏小尘哥的专栏

要“鱼”也要“渔”-java手写代码生成器

思路:依然依赖mp组件,因为mp已经提供了对应的BaseService,BaseMapper等,但是本文重点不在于“怎么用”,而在于“怎么写”。“怎么写”属于自...

19620
来自专栏王小雷

SAS进阶《深入解析SAS》之Base SAS基础、读取外部数据到SAS数据集

SAS进阶《深入解析SAS》之Base SAS基础、读取外部数据到SAS数据集 前言:在学习完《SAS编程与商业案例》后,虽然能够接手公司的基本工作,但是为了更...

25570
来自专栏Web 开发

在SAE上开发遇到的问题~

添加一个escape_data()的函数,该函数已经会自动识别各种PHP配置环境~

12900
来自专栏大内老A

谈谈基于SQL Server 的Exception Handling[上篇]

对于所有的开发人员来说,Exception Handling是我们每天都要面对的事情。对于基于Source Code的Exception Handling,我想...

19350

扫码关注云+社区

领取腾讯云代金券