其他
X86内核笔记_2_驱动开发
看雪论坛作者ID:SSH山水画
1
创建驱动项目
创建项目
#include <ntifs.h> //这个头文件内包含了大量驱动相关的头文件,一次性包含,省时省力。
驱动分类简述
NT式驱动:
WDM式驱动:
WDF式驱动:
2
编写驱动程序
驱动入口函数(DriverEntry)
#include "ntifs.h"
NTSTATUS DriverEntry(PDRIVER_OBJECT DriverObject,PUNICODE_STRING RegistryPath){
//代码
return STATUS_UNSUCCESSFUL;
}
DriverEntry是我们写代码时的入口函数。其编译生成的sys文件真正的入口点并不是DriverEntry。在IDA中可以看到驱动真正的入口点函数是GsDriverEntry。其内部调用了我们的DriverEntry函数。
指定入口函数:
编写代码
#include "ntddk.h"
void UnloadDriver(PDRIVER_OBJECT driver);
NTSTATUS DriverEntry(PDRIVER_OBJECT DriverObject, PUNICODE_STRING RegistryPath) {
DbgBreakPoint(); //相当于 __asm{int 3}
DbgPrint("驱动加载了。\r\n"); //驱动的打印函数,相当于3环的printf
DriverObject->DriverUnload = UnloadDriver; //为驱动指定卸载函数
return STATUS_SUCCESS;
}
//驱动卸载函数
void UnloadDriver(PDRIVER_OBJECT driver) {
DbgPrint("驱动停止了。\r\n");
}
打印字符串对象
NTSTATUS DriverEntry(PDRIVER_OBJECT DriverObject, PUNICODE_STRING pReg) {
DbgPrint("-------%wZ--------",pReg);//传入字符串对象指针。
return STATUS_SUCCESS;
}
生成驱动
加载驱动(部署-启动-停止-卸载)
使用InstDrv.exe加载驱动。使用DbgView.exe查看输出(必须选中监视核心,否则无法监视驱动层输出)。
调试驱动
解决刷屏
kd> ed nt!Kd_SXS_Mask 0;ed nt!Kd_FUSION_Mask 0
3
驱动对象PDRIVER_OBJECT初识
Type:驱动对象类型。
Size:驱动对象大小
DeviceObject:设备对象,我们这里没添加设备,因此是null
DriverStart:驱动文件基址,也就是PE格式中的ImageBase。通过db命令可以看到4D 5A。
DriverSize:驱动模块大小,也就是PE格式中的SizeOfImage。
DriverExtension:驱动扩展对象。使用dt命令查看该对象。
kd> dt _DRIVER_EXTENSION 0x8831b790ntdll!_DRIVER_EXTENSION +0x000 DriverObject : 0x8831b6e8 _DRIVER_OBJECT +0x004 AddDevice : (null) +0x008 Count : 0 +0x00c ServiceKeyName : _UNICODE_STRING "hellodriver" +0x014 ClientDriverExtension : (null) +0x018 FsFilterCallbacks : (null)
DriverObject:指向当前驱动对象首地址。 ServiceKeyName:驱动服务注册表文件夹名。
DriverName:驱动名,也就是驱动的文件名前面加个\Driver\。这个名字是个字符串结构体。查看该字符串结构:
kd> dt _UNICODE_STRING 8831b6e8 +1c
ntdll!_UNICODE_STRING
"\Driver\hellodriver"
+0x000 Length : 0x26 //字符串长度
+0x002 MaximumLength : 0x26 //字符串最大长度
+0x004 Buffer : 0x87fc36c8 "\Driver\hellodriver" //字符串内容
DisplayName:驱动名 ErrorControl:当驱动加载失败时会设置这个值。 ImagePath:驱动文件路径。\??\是设备路径,我们平时访问各种文件夹其实都带这个\??\,只是windows底层帮我们补充了。 Start:驱动加载类型。手动启动为3,开机自启为2,BIOS自启为1。 Type:服务类型。1为驱动。
DriverInit :驱动入口点,也就是PE文件的AddressOfEntryPoint。
DriverUnload:驱动卸载函数地址。
4
驱动加载方法
服务加载
直接加载
5
第一个练习
#include <ntifs.h>
void UnloadDriver(PDRIVER_OBJECT driver);
UINT32 i = 100;
NTSTATUS DriverEntry(PDRIVER_OBJECT DriverObject, PUNICODE_STRING RegistryPath) {
DbgPrint("i addr = %08x\r\n", &i);
DriverObject->DriverUnload = UnloadDriver;
return STATUS_SUCCESS;
}
//驱动卸载函数
void UnloadDriver(PDRIVER_OBJECT driver) {
DbgPrint("驱动停止了。\r\n");
}
#include <ntifs.h>
void UnloadDriver(PDRIVER_OBJECT driver);
NTSTATUS DriverEntry(PDRIVER_OBJECT DriverObject, PUNICODE_STRING RegistryPath) {
PUINT32 p = (PUINT32)0x8e315000;
DbgPrint("i value = %d\r\n", *p);
DriverObject->DriverUnload = UnloadDriver;
return STATUS_SUCCESS;
}
//驱动卸载函数
void UnloadDriver(PDRIVER_OBJECT driver) {
DbgPrint("驱动停止了。\r\n");
}
6
驱动常用类型及API
基本数据类型
UINT8,PUINT8 -> unsigned char
UINT16,PUINT16 -> unsigned short
UINT32,PUINT32 -> unsigned int
UINT64,PUINT64 -> unsigned __int64
INT8,PINT8 -> char
INT16,PINT16 -> short
INT32,PINT32 -> int
INT64,PINT64 -> __int64
LONG32,PLONG32 -> int
ULONG32,PULONG32 -> unsigned int
DWORD32,PDWRD32 -> int
错误码返回值
STATUS_SEVERITY_SUCCESS 0x0
STATUS_SEVERITY_INFORMATIONAL 0x1
STATUS_SEVERITY_WARNING 0x2
STATUS_SEVERITY_ERROR 0x3
STATUS_UNSUCCESSFUL 0xC0000001
NT_SUCCESS(NTSATUS类型参数) //#define NT_SUCCESS(Status) (((NTSTATUS)(Status)) >= 0
字符串相关
定义字符串:
UNICODE_STRING uStr = {0}; //定义一个unicode字符串,类型为UNICODE_STRING
STRING aStr = {0}; //定义一个ascii字符串,类型为STRING
ANSI_STRING aStr = {0}; //所有ANSI与直接STRING 作用相同
初始化字符串:
RtlInitUnicodeString(&uStr,L"unicode string");//初始化unicode字符串,为其赋值。不会申请内存。
RtlInitString(&aStr,"ascii string"); //初始化ascii字符串,为其赋值,不会申请内存。
RtlInitAnsiString(&aStr,"ascii string"); //初始化ascii字符串,为其赋值,不会申请内存。
字符串转化:
RtlAnsiStringToUnicodeString(&uStr,&aStr,true);//将ascii字符串转为unicode字符串,无需为unicode字符串做初始化,第三个参数为true则自动申请内存。为false则不申请,仅修改unicode现有空间。若为true,则需要手动释放字符串内存。
RtlUnicodeStringToAnsiString();//将unicode字符串转为ascii字符串,用法与上面相同。
释放字符串:
RtlFreeUnicodeString();//释放unicode字符串内存,当字符串初始化中为其分配了内存时,需要释放内存。
RtlFreeAnsiString(); //释放ascii字符串内存,当字符串初始化中为其分配了内存时,需要释放内存。
字符串格式化:
#include <ntstrsafe.h> //使用格式化API需要引入此头文件
char aStr[0x1000]= {0};
RtlStringCbPrintfA(aStr, 0x1000, "%d---%s", 123, "test");//参数1:Ascii字符串指针
wchar uStr[0x1000] = {0};
RtlStringCbPrintfW(uStr, 0x1000, L"%d---%s", 123, L"test");//参数1:Unicode字符串指针
字符串比较:
RtlCompareUnicodeString(&uStr1,&uStr2,TRUE);//比较两个unicode字符串是否相等,true忽略大小写
RtlCompareString //比较两个ascii字符串是否相等
内存相关
申请内存:
ExAllocatePool(type,size);//type:内存类型,PagePool和NonPagePool,分别为分页内存和非分页内存。
//分页内存:后面章节会详细说,暂时理解为不可执行的内存
//非分页内存:后面章节会详细说,暂时理解为可执行的内存 通常填NonPagePool,对应属性为PTE的XD/NX位。
ExAllocatePoolWithTag(type,size,tag);//tag:内存标志,四个字节最多,如'test',为申请的内存起个名字。用单引号包含,内部最终转为16进制数据。
拷贝、设置、比较内存:
RtlFillMemory(pointer,length,value);//相当于memset
RtlEqualMemory(pointer,Source,Length)//相当于memcmp结果取反
RtlMoveMemory(pointer,Source,Length) //相当于memmove
RtlCopyMemory(pointer,Source,Length) //相当于memcpy
RtlZeroMemory(pointer,Length) //相当于memset第二参数为0.
释放内存:
ExFreePool(pointer);//释放内存
延迟
//驱动代码中的延迟不可以使用Sleep,而是KeDelayExecutionThread
LARGE_INTEGER li = { 0 }; //时长结构。
li.QuadPart = -10000 * 5000; //时间单位 负数代表相对时间 正数代表绝对时间。5000代表5秒。
KeDelayExecutionThread(KernelMode,FALSE,&li);
//第一个参数:延迟模式,我们这里选内核模式
//第二个参数:强制唤醒。如果为FALSE,那么休眠时间未结束前,不会被唤醒。
//第三个参数:延迟时长。
创建线程
//线程函数
VOID myThreadFun(_In_ PVOID StartContext) {
//线程函数代码
}
HANDLE tHandle = NULL;
NTSTATUS tRet = PsCreateSystemThread(&tHandle,THREAD_ALL_ACCESS,NULL,NULL,NULL, myThreadFun,NULL);
//最后一个参数是线程函数启动参数。
if(NT_SUCCESS(tRet)){
ZwClose(tHandle);//相当于CloseHandle
}
内核链表API
typedef struct _Monster { //定义一个结构体,成员包含节点结构。这样该结构体也可以作为节点。
UINT32 ID;
LIST_ENTRY node;
UINT32 hp;
UINT32 level;
UNICODE_STRING name;
}Monster,*PMonster;
Monster m1 = { 0 };
InitializeListHead(&m1.node); //初始化链表,防止出现垃圾数据作为指针的情况。
IsListEmpty(&m1.node); //判断整条链表是否为空,传入整条链表中任意一个节点即可。
Monster m2 = { 0 };
InsertHeadList(&m1.node, &m2.node);//将m2节点插入至链表头部。
Monster m3 = { 0 };
InsertTailList(&m1.node, &m3.node);//将m3节点插入至链表尾部。
RemoveHeadList(&m2.node);//将整条链表的头部节点移除,传入任一节点即可。
RemoveTailList(&m2.node);//将整条链表的尾部节点移除,传入任一节点即可。
RemoveEntryList(&m3.node);//将指定节点移除,断链。
//通过FLINK找到Monster,架设要找到m2下一个节点
PMonster pm = (PMonster)((UCHAR)m2.node.Flink - ((UCHAR)(&m2.node) - (UCHAR)&m2));
//计算出ListEntry结构相对于Monster结构的偏移,用Flink减去该偏移得到m3的地址。
内核二叉树API
typedef struct _Monster {
UINT32 id;
UINT32 hp;
UINT32 level;
UNICODE_STRING name;
}Monster,*PMonster;
//树节点比较函数
RTL_GENERIC_COMPARE_RESULTS NTAPI myCmpFunc(_In_ struct _RTL_GENERIC_TABLE *Table,_In_ PVOID FirstStruct,_In_ PVOID SecondStruct) {
PMonster m1 = (PMonster)FirstStruct; //内部强转为自己需要的结构体
PMonster m2 = (PMonster)SecondStruct;
if (m1->id == m2->id) { //判断方法自己指定,我这里按照ID判断两个节点的大小关系
return GenericEqual;
}
return m1->id > m2->id ? GenericGreaterThan : GenericLessThan;
}
//节点新建函数
VOID NTAPI myAllocFunc( _In_ struct _RTL_GENERIC_TABLE *Table, _In_ CLONG ByteSize ) {
ExAllocatePool(NonPagedPool,ByteSize);//申请内存
}
//节点删除函数
VOID NTAPI myFreeFunc( _In_ struct _RTL_GENERIC_TABLE *Table, _In_ __drv_freesMem(Mem) _Post_invalid_ PVOID Buffer ) {
ExFreePool(Buffer);//释放该节点申请出来的内存
}
Monster m1 = {0,100,10,L"monster 1"};
Monster m2 = {1,100,10,L"monster 2"};
Monster m3 = {2,100,10,L"monster 3"};
RTL_GENERIC_TABLE table = {0};
//初始化二叉树
RtlInitializeGenericTable(&table, myCmpFunc, myAllocFunc, myFreeFunc,NULL);
BOOLEAN isNewEle = FALSE;
//插入/更新节点,将节点强转为void*,isNewEle接收该节点是否为新加加点或已存在节点。通过myCmpFunc来判断两个节点是否为同一个节点。在插入节点时,会调用myAllocFunc为节点重新分配一个内存并将数据拷贝过去。
RtlInsertElementGenericTable(&table, (PVOID)&m1,sizeof(m1),&isNewEle);
RtlInsertElementGenericTable(&table, (PVOID)&m2,sizeof(m1),&isNewEle);
RtlInsertElementGenericTable(&table, (PVOID)&m3,sizeof(m1),&isNewEle);
//查找节点,lookupM只需要赋值id属性,查找也会根据这个id去对比是否相同。返回查找到的结点指针
Monster lookupM = { 0,0,0,0 };
PMonster lookupResult = (PMonster)RtlLookupElementGenericTable(&table,&lookupM);
//删除节点,也是根据id删除。删除后会自动调用myFreeFunc释放内存。
RtlDeleteElementGenericTable(&table, &lookupM);
//取节点个数
ULONG nodeNum = RtlNumberGenericTableElements(&table);
ULONG nodeNum = RtlNumberGenericTableElementsAvl(&table);//安全函数,防止一边加节点一边读节点
//遍历节点,key用于取下一个节点,key为null时,取第一个节点。返回值为下一个节点,同时自动更新key指向返回值所属节点。返回值为null说明遍历结束。
PVOID key = NULL;
PMonster pm = (PMonster)RtlEnumerateGenericTableWithoutSplaying(&table, &key);
while (pm!=NULL) {
DbgPrint(pm->name.Buffer);
pm = (PMonster)RtlEnumerateGenericTableWithoutSplaying(&table, &key);
}
//二叉树用完(如驱动卸载),要将二叉树内所有节点销毁掉,防止内存泄漏。
7
驱动对象-DriverSection
typedef struct _KLDR_DATA_TABLE_ENTRY {
LIST_ENTRY InLoadOrderLinks;
ULONG __Undefined1;
ULONG __Undefined2;
ULONG __Undefined3;
ULONG NonPagedDebugInfo;
ULONG DllBase;
ULONG EntryPoint;
ULONG SizeOfImage;
UNICODE_STRING FullDllName;
UNICODE_STRING BaseDllName;
ULONG Flags;
USHORT LoadCount;
USHORT __Undefined5;
ULONG __Undefined6;
ULONG CheckSum;
ULONG TimeDateStamp;
} KLDR_DATA_TABLE_ENTRY, *PKLDR_DATA_TABLE_ENTRY;
驱动模块遍历-手动
typedef struct _KLDR_DATA_TABLE_ENTRY {
LIST_ENTRY InLoadOrderLinks;
ULONG __Undefined1;
ULONG __Undefined2;
ULONG __Undefined3;
ULONG NonPagedDebugInfo;
ULONG DllBase;
ULONG EntryPoint;
ULONG SizeOfImage;
UNICODE_STRING FullDllName;
UNICODE_STRING BaseDllName;
ULONG Flags;
USHORT LoadCount;
USHORT __Undefined5;
ULONG __Undefined6;
ULONG CheckSum;
ULONG TimeDateStamp;
} KLDR_DATA_TABLE_ENTRY, *PKLDR_DATA_TABLE_ENTRY;
NTSTATUS DriverEntry(PDRIVER_OBJECT DriverObject, PUNICODE_STRING RegistryPath) {
DbgBreakPoint();
KLDR_DATA_TABLE_ENTRY * ldr = DriverObject->DriverSection;
DriverObject->DriverUnload = UnloadDriver;
return STATUS_SUCCESS;
}
驱动模块遍历-代码
NTSTATUS DriverEntry(PDRIVER_OBJECT DriverObject, PUNICODE_STRING RegistryPath) {
PKLDR_DATA_TABLE_ENTRY selfNode = DriverObject->DriverSection;
PKLDR_DATA_TABLE_ENTRY preNode = selfNode;
UINT32 index = 1;
do {
DbgPrint("[db] %d driver name = %wZ \r\n", index++,&preNode->BaseDllName);
preNode = preNode->InLoadOrderLinks.Flink;
} while (preNode != selfNode);
DriverObject->DriverUnload = UnloadDriver;
return STATUS_SUCCESS;
}
8
驱动模块隐藏-断链
断链1-HTTP.sys
NTSTATUS DriverEntry(PDRIVER_OBJECT DriverObject, PUNICODE_STRING RegistryPath) {
PKLDR_DATA_TABLE_ENTRY selfNode = DriverObject->DriverSection;
PKLDR_DATA_TABLE_ENTRY preNode = selfNode;
UNICODE_STRING httpName = { 0 };
RtlInitUnicodeString(&httpName,L"HTTP.sys");
do {
if (preNode->BaseDllName.Length != 0 //过滤空字符串
&& RtlCompareUnicodeString(&preNode->BaseDllName,&httpName,TRUE) == 0) {
DbgPrint("%wZ\r\n", &preNode->BaseDllName);
RemoveEntryList(preNode);
break;
}
preNode = preNode->InLoadOrderLinks.Flink;
} while (preNode != selfNode);
DriverObject->DriverUnload = UnloadDriver;
return STATUS_SUCCESS;
}
获取驱动对象指针
//NTKERNELAPI是一个宏,用于指定内核模块中的导出函数。
NTKERNELAPI NTSTATUS ObReferenceObjectByName(
__in PUNICODE_STRING ObjectName, //驱动对象名,如HTTP.sys的驱动对象名就是\Driver\HTTP
__in ULONG Attributes, //权限,给一个FILE_ALL_ACCESS即可。
__in_opt PACCESS_STATE AccessState, //opt为可选参数,直接写NULL
__in_opt ACCESS_MASK DesiredAccess,//opt为可选参数,直接写NULL
__in POBJECT_TYPE ObjectType, //对象类型
__in KPROCESSOR_MODE AccessMode, //访问模式,有个枚举是_MODE,里面有个值是KernelMode,填写即可。
__inout_opt PVOID ParseContext, //opt为可选参数,直接写NULL
__out PVOID *Object //驱动对象二级指针
);
extern POBJECT_TYPE * IoDriverObjectType;
断链2-HTTP.sys增强
extern POBJECT_TYPE * IoDriverObjectType;
NTSTATUS DriverEntry(PDRIVER_OBJECT DriverObject, PUNICODE_STRING RegistryPath) {
PKLDR_DATA_TABLE_ENTRY selfNode = DriverObject->DriverSection;
PKLDR_DATA_TABLE_ENTRY preNode = selfNode;
UNICODE_STRING httpName = { 0 };
RtlInitUnicodeString(&httpName,L"HTTP.sys");
UNICODE_STRING httpObjName = { 0 };
RtlInitUnicodeString(&httpObjName, L"\\Driver\\HTTP");
do {
if (preNode->BaseDllName.Length != 0
&& RtlCompareUnicodeString(&preNode->BaseDllName,&httpName,TRUE) == 0) {
DbgPrint("%wZ\r\n", &preNode->BaseDllName);
PDRIVER_OBJECT pHttpObj = NULL;
ObReferenceObjectByName(&httpObjName,FILE_ALL_ACCESS,NULL,NULL, *IoDriverObjectType, KernelMode,NULL, &pHttpObj); //取驱动对象指针
pHttpObj->Flags = 0; //清除几个属性,防止被搜索到。
pHttpObj->DriverSection = 0;
pHttpObj->DriverInit = 0;
RemoveEntryList(preNode);
break;
}
preNode = preNode->InLoadOrderLinks.Flink;
} while (preNode != selfNode);
DriverObject->DriverUnload = UnloadDriver;
return STATUS_SUCCESS;
}
断链3-自身驱动
NTSTATUS DriverEntry(PDRIVER_OBJECT DriverObject, PUNICODE_STRING RegistryPath) {
PKLDR_DATA_TABLE_ENTRY selfNode = DriverObject->DriverSection;
DriverObject->Flags = 0;
DriverObject->DriverSection = 0;
DriverObject->DriverInit = 0;
RemoveEntryList(selfNode);
DriverObject->DriverUnload = UnloadDriver;
return STATUS_SUCCESS;
}
加载驱动,观察效果:
VOID hideSelf(PVOID pDriverObj) {
LARGE_INTEGER li = { 0 };
li.QuadPart = -10000 * 10000;
KeDelayExecutionThread(KernelMode,FALSE,&li); //延迟函数,详见 5-延迟
PDRIVER_OBJECT DriverObject = (PDRIVER_OBJECT)pDriverObj;
PKLDR_DATA_TABLE_ENTRY selfNode = DriverObject->DriverSection;
DriverObject->Flags = 0;
DriverObject->DriverSection = 0;
DriverObject->DriverInit = 0;
RemoveEntryList(selfNode);
}
NTSTATUS DriverEntry(PDRIVER_OBJECT DriverObject, PUNICODE_STRING RegistryPath) {
HANDLE tHandle = NULL;
NTSTATUS ret = PsCreateSystemThread(&tHandle,THREAD_ALL_ACCESS,NULL,NULL,NULL, hideSelf, DriverObject);
if (NT_SUCCESS(ret)) {
ZwClose(ret);
}
DriverObject->DriverUnload = UnloadDriver;
return STATUS_SUCCESS;
}
9
驱动通信-常规
0环代码-创建设备
UNICODE_STRING deviceName = { 0 };
RtlInitUnicodeString(&deviceName,L"\\Device\\MyDevice");
DEVICE_OBJECT devObj = {0};
NTSTATUS retStatus = IoCreateDevice(pDriverObj,NULL,&deviceName,FILE_DEVICE_UNKNOWN,FILE_DEVICE_SECURE_OPEN,FALSE,&devObj);
//参数1:驱动对象指针,用于将创建出来的设备绑定到某个驱动上
//参数2:设备扩展大小,我们这里写NULL就好。不需要扩展。
//参数3:设备名,UNICODE字符串。固定名字格式:\\Device\\名字
//参数4:设备类型,按F12可以看到很多类型宏,有鼠标、键盘之类的,我们这里选未知设备。
//参数5:设备权限,为了让3环可以打开我们的设备进行通信,我们这里选择FILE_DEVICE_SECURE_OPEN权限。其他权限可以在MSDN上找到。
//参数6:是否独占,填FALSE。如果独占的话3环无法打开该设备。
//参数7:设备对象指针,传出创建好的设备对象。
0环代码-设置数据交互方式
pDeviceObj->Flags |= DO_BUFFERED_IO;
//缓冲区方式读写(DO_BUFFERED_IO):将3环缓冲区内的数据复制一份到0环的缓冲区。方便,但性能不好。
//直接方式读写(DO_DIRECT_IO):首先将3环缓冲区锁住,然后在将对应的物理地址映射一份0环的线性地址。适合大量数据传输。两个线性地址对应同一个物理地址。
//其它方式读写(不设置值):0环直接读取3环的线性地址,不建议。当进程切换,CR3改变,会读取到其他进程的内存数据。
pDeviceObj->Flags &= DO_DEVICE_INITIALIZING;
//将DO_DEVICE_INITIALIZING初始化标志位清空,如果不清空这个位,那么3环可能无法打开设备。
0环代码-创建符号链接
UNICODE_STRING symName = { 0 };
RtlInitUnicodeString(&symName, L"\\??\\MyDeviceSymbol");
retStatus = IoCreateSymbolicLink(&symName,&deviceName);
//参数1:符号链接名。固定格式:\\??\\名字
//参数2:想要绑定的设备名。
IRP消息
CreateFile -》 IRP_MJ_CREATE
ReadFile -》 IRP_MJ_READ
WriteFile -》 IRP_MJ_WRITE
CloseHandle -》 IRP_MJ_CLOSE
DeviceControl -》 IRP_MJ_DEVICE_CONTROL //此API比上面的API更加灵活方便,因此内核编程中常使用该API进行消息的传递
派遣函数
NTSTATUS MyDispatchFunction(PDEVICE_OBJECT pDevObj,PIRP pIrp){
//业务代码
...
//设置返回状态
pIrp->IoStatus.Status = STATUS_SUCCESS; //3环的GetLastError得到的就是这个值
pIrp->IoStatus.Information = 0; //返回数据的字节数 没有写0
IoCompleteRequest(pIrp,IO_NO_INCREMENT); //当前处理完成,继续向下传递IRP消息
return STATUS_SUCCESS;
}
0环代码-处理IRP消息1
NTSTATUS NullFunc(DEVICE_OBJECT *DeviceObject, IRP *Irp) {
Irp->IoStatus.Status = STATUS_SUCCESS;
Irp->IoStatus.Information = 0;
IoCompleteRequest(Irp, IO_NO_INCREMENT);
return STATUS_SUCCESS;
}
NTSTATUS DeviceControlFunc(DEVICE_OBJECT *DeviceObject, IRP *Irp) {
//通信逻辑,后面补充。
Irp->IoStatus.Status = STATUS_SUCCESS;
Irp->IoStatus.Information = 0;
IoCompleteRequest(Irp, IO_NO_INCREMENT);
return STATUS_SUCCESS;
}
//Create和Close如果不想做处理就直接给个空函数,直接返回成功。如果不设置这两个派遣函数,3环则根本无法打开我们的设备。
NTSTATUS DriverEntry(PDRIVER_OBJECT pDriverObj,PUNICODE_STRING pReg) {
//...
pDriverObj->MajorFunction[IRP_MJ_DEVICE_CONTROL] = DeviceControlFunc;
pDriverObj->MajorFunction[IRP_MJ_CREATE] = NullFunc;
pDriverObj->MajorFunction[IRP_MJ_CLOSE] = NullFunc;
//....
}
3环代码-发送IRP消息
#include <iostream>
#include <Windows.h>
#include <winioctl.h> //防止下面的宏识别不到
//这个宏用于组装IRP控制码,其中用户自定义的控制码从0x800开始。
#define code1 CTL_CODE(FILE_DEVICE_UNKNOWN,0x800,METHOD_BUFFERED,FILE_ANY_ACCESS)
#define code2 CTL_CODE(FILE_DEVICE_UNKNOWN,0x900,METHOD_BUFFERED,FILE_ANY_ACCESS)
int main()
{
CHAR* devName = (CHAR*)"\\\\.\\MyDeviceSymbol";
HANDLE devHandle = CreateFileA(devName,GENERIC_READ|GENERIC_WRITE, FILE_SHARE_READ| FILE_SHARE_WRITE,NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL,NULL);
DWORD str = 100;
DWORD back = 0;
DWORD backLen = 0; //实际返回的数据长度,不接收就会崩溃
DeviceIoControl(devHandle, code1, &str,0x4,&back,0x4,&backLen,NULL);
printf("back = %d\r\n", back);
str = 200;
DeviceIoControl(devHandle, code2, &str, 0x4, &back, 0x4, &backLen, NULL);
printf("back = %d\r\n", back);
getchar();
CloseHandle(devHandle);
return 0;
}
0环代码-处理IRP消息-扩展
#define code1 CTL_CODE(FILE_DEVICE_UNKNOWN,0x800,METHOD_BUFFERED,FILE_ANY_ACCESS)
#define code2 CTL_CODE(FILE_DEVICE_UNKNOWN,0x900,METHOD_BUFFERED,FILE_ANY_ACCESS)
//由于3环使用DeviceIoControl进行IRP的发送,所以我们在DeviceControlFunc中补充逻辑。
NTSTATUS DeviceControlFunc(DEVICE_OBJECT *DeviceObject, IRP *Irp) {
//取设备堆栈,控制码在设备堆栈里。
PIO_STACK_LOCATION ioStack = IoGetCurrentIrpStackLocation(Irp);
//取控制码,Parameters结构内部有很多联合体,我们使用的是DeviceIoControl的方式通信,所以这里使用DeviceIoControl成员取控制码。
ULONG code = ioStack->Parameters.DeviceIoControl.IoControlCode;
//取3环传进来的参数。
PVOID buffer = Irp->AssociatedIrp.SystemBuffer;
//不同的控制码执行不同的分支,使用switch case语句。
switch (code)
{
case code1:
//打印3环传进来的值
DbgPrint("param = %d\r\n",*(PUINT32)buffer);
//返回给3环的值,直接写入到buffer里就行。
*(PUINT32)buffer = 800;
//设置返回去多少数据,因为我们的数据交互方式是METHOD_BUFFERED,会拷贝buffer里的数据给3环地址,如果不指定Information,就无法拷贝数据,3环得到的就是空的数据。
Irp->IoStatus.Information = 4;
break;
case code2:
DbgPrint("param = %d\r\n", *(PUINT32)buffer);
*(PUINT32)buffer = 900;
Irp->IoStatus.Information = 4;
break;
default:
break;
}
Irp->IoStatus.Status = STATUS_SUCCESS;
IoCompleteRequest(Irp, IO_NO_INCREMENT);
return STATUS_SUCCESS;
}
0环代码-卸载设备和符号链接
VOID UnloadDriver( DRIVER_OBJECT *DriverObject ) {
IoDeleteSymbolicLink(&symName);
IoDeleteDevice(DriverObject->DeviceObject);
}
执行效果
看雪ID:SSH山水画
https://bbs.pediy.com/user-home-867232.htm
*本文由看雪论坛 SSH山水画 原创,转载请注明来自看雪社区
# 往期推荐
1. 新的漏洞分析体验:CVE-2010-3333 RTF栈缓冲区溢出漏洞
4. angr学习(三)一道自己模仿着出的简单题和angr-ctf符号化输入相关题目
球分享
球点赞
球在看
点击“阅读原文”,了解更多!