CPU的分级
我们都知道,CPU有环的概念。我们的所有可视化界面与任何的操作都在用户层进行,也就是口中的"三环"。在我们看不见的地方,比如驱动、硬件数据交互等操作,都在内核层执行,也就是"零环"。环的层数越低,权限相对越高。至于一环与二环,并未使用。
零环与三环的数据交互
与内核层交互,无非也就是收发数据。但我们要提供的缓冲区是用户态地址,所以不能在内核态随意使用。故而提供了三种设备对象的读写方式。
分别为缓冲区设备读写、直接读写和非前两者方式。
以下对三种方式做简单介绍(下图为自己画的,有问题或有错误欢迎指出)
缓冲区读写
将用户态缓冲区拷贝到内核态,在内核态使用完毕,再拷贝回用户态。
不追求效率且图简单的情况下可以使用该方式。
直接读写方式
对用户态地址进行重新映射,映射到内核空间中。
这也是最安全又快速的一种方式,在我们使用时,尽量使用直接方式
而我们要演示的也是该种方式
第三种,即两者都不方式
这种方式,零环得到的缓冲区的地址是用户层的地址,这样是极度不安全的。一旦发生了进程切换,地址将发生错误。
以下演示直接方式
零环(内核层)遍历驱动,将遍历到的所有数据上传到三环程序上显示。
驱动代码
#include <ntddk.h>
VOID DriverUnload(PDRIVER_OBJECT pDriver);
//定义控制码
#define IOCTL_GET_DRIVERINFO CTL_CODE(FILE_DEVICE_UNKNOWN,0x801,METHOD_IN_DIRECT ,FILE_ANY_ACCESS)//获取驱动信息
//LDR_DATA_TABLE_ENTRY结构体
typedef struct _LDR_DATA_TABLE_ENTRY {
LIST_ENTRY InLoadOrderLinks; //双向链表
LIST_ENTRY InMemoryOrderLinks;
LIST_ENTRY InInitializationOrderLinks;
PVOID DllBase;
PVOID EntryPoint;
ULONG SizeOfImage;
UNICODE_STRING FullDllName;
UNICODE_STRING BaseDllName;
ULONG Flags;
USHORT LoadCount;
USHORT TlsIndex;
union U1 {
LIST_ENTRY HashLinks;
struct S1 {
PVOID SectionPointer;
ULONG CheckSum;
}s1;
}u1;
union U2 {
struct S2 {
ULONG TimeDateStamp;
}s2;
struct S3 {
PVOID LoadedImports;
}s3;
}u2;
}LDR_DATA_TABLE_ENTRY, * PLDR_DATA_TABLE_ENTRY;
//获取驱动信息
void GetDriveINFO(PDEVICE_OBJECT pDeviceObject, PCHAR pOutBuf)
{
//获取所有驱动信息
ULONG dwInfo = 0;
PDRIVER_OBJECT pDriver = pDeviceObject->DriverObject;
PLDR_DATA_TABLE_ENTRY pLdr = (PLDR_DATA_TABLE_ENTRY)pDriver->DriverSection;
LIST_ENTRY* pTemp = &pLdr->InLoadOrderLinks; //将第一个驱动暂存,因为驱动信息是一个链表
PCHAR pTempBuf = pOutBuf;
ULONG n = 0;
do {
PLDR_DATA_TABLE_ENTRY pDriverInfo = (PLDR_DATA_TABLE_ENTRY)pTemp;
//构造结构体
/*
pOutBuf 结构体大小
pOutBuf + 4 驱动名字
*/
ULONG Size = pDriverInfo->BaseDllName.Length + 4 + 2; //结构体大小 = 驱动名称长度 + 4(保存缓冲区大小的四个字节) + 2(空出两个字节)
RtlCopyMemory(pOutBuf,&Size,4); //将大小拷贝到缓冲区
RtlCopyMemory(pOutBuf + 4,pDriverInfo->BaseDllName.Buffer,pDriverInfo->BaseDllName.Length);//将驱动名字拷贝到缓冲区+4的位置
pOutBuf += Size; //缓冲区 + 结构体大小 = 指向下一个结构体
pTemp = pTemp->Blink;//指向下一个
} while (pTemp != &pLdr->InLoadOrderLinks);
}
//IRP默认处理方式,即什么都不做
NTSTATUS DefaultIRPHandle(PDEVICE_OBJECT pDeviceObject, PIRP pIrp) {
UNREFERENCED_PARAMETER(pDeviceObject);
//1.设置IRP完成状态
pIrp->IoStatus.Status = STATUS_SUCCESS;
//2.设置IRP操作了多少字节
pIrp->IoStatus.Information = 0;
//3.处理IRP
IoCompleteRequest(pIrp, IO_NO_INCREMENT);
return STATUS_SUCCESS;
}
//IRP处理方式,通过控制码匹配处理函数
NTSTATUS CtrlCodeHandle(PDEVICE_OBJECT pDeviceObject, PIRP pIrp) {
UNREFERENCED_PARAMETER(pIrp);
//设置IRP完成状态
pIrp->IoStatus.Status = STATUS_SUCCESS;
//获取IRP栈
PIO_STACK_LOCATION pIrpStack = IoGetCurrentIrpStackLocation(pIrp);
//获取控制码
ULONG dwCtrlCode = pIrpStack->Parameters.DeviceIoControl.IoControlCode;
//获取传入缓存区和大小
PCHAR pInBuf = pIrp->AssociatedIrp.SystemBuffer;
ULONG dwInLength = pIrpStack->Parameters.DeviceIoControl.InputBufferLength;
//获取传出缓冲区和大小
PCHAR pOutBuf = MmGetSystemAddressForMdlSafe(pIrp->MdlAddress, NormalPagePriority);
ULONG dwOutLength = pIrpStack->Parameters.DeviceIoControl.OutputBufferLength;
//记录IRP操作了多少字节
ULONG dwInfo = 0;
switch (dwCtrlCode)
{
case IOCTL_GET_DRIVERINFO:
GetDriveINFO(pDeviceObject, pOutBuf);//调用获取驱动信息函数
break;
}
//1.设置IRP完成状态
pIrp->IoStatus.Status = STATUS_SUCCESS;
//2.设置IRP操作了多少字节
pIrp->IoStatus.Information = dwInfo;
//3.处理IRP
IoCompleteRequest(pIrp, IO_NO_INCREMENT);
return STATUS_SUCCESS;
}
//入口函数
NTSTATUS DriverEntry(
PDRIVER_OBJECT pDriver,
PUNICODE_STRING pPath)
{
//_asm int 3;
UNREFERENCED_PARAMETER(pPath);
//定义设备名
UNICODE_STRING strDeviceName = { 0 }; //仅供驱动对象使用
UNICODE_STRING strSymbolLinkName = { 0 };//符号连接名
PDEVICE_OBJECT pDevice_object = NULL;
//设备名必须在Device路径下
RtlInitUnicodeString(&strDeviceName, L"\\Device\\MyDevice");
//符号名必须在DosDevice路径下
RtlInitUnicodeString(&strSymbolLinkName, L"\\DosDevices\\MyDriverSymbol");
//创建设备对象(创建后需要释放)
NTSTATUS nStatus = IoCreateDevice(
pDriver, //驱动对象
0, //设备扩展对象大小
&strDeviceName, //设备名称
FILE_DEVICE_UNKNOWN, //设备类型(所有类型)
0, //设备特征信息
FALSE, //设备是否为独占的
&pDevice_object); //创建完成的设备对象指针
if (NT_SUCCESS(nStatus) == FALSE) {
KdPrint(("CreateDevice Error"));
return nStatus;
}
//设置通讯方式
pDevice_object->Flags |= DO_DIRECT_IO;//直接读写方式,MDL方式
//绑定符号连接
nStatus = IoCreateSymbolicLink(&strSymbolLinkName, &strDeviceName);
if (NT_SUCCESS(nStatus) == FALSE) {
KdPrint(("CreateSymbolicLink Error"));
return nStatus;
}
//设置驱动对象IRP派遣函数
for (int i = 0; i < IRP_MJ_MAXIMUM_FUNCTION; i++) {
pDriver->MajorFunction[i] = DefaultIRPHandle; // 设置一个默认派遣函数,该函数可以什么都不做
}
//控制码
pDriver->MajorFunction[IRP_MJ_DEVICE_CONTROL] = CtrlCodeHandle;
DbgPrint("驱动加载成功\n");
//驱动卸载回调函数
pDriver->DriverUnload = DriverUnload;
return STATUS_SUCCESS;
}
//驱动卸载函数
VOID DriverUnload(PDRIVER_OBJECT pDriver)
{
UNREFERENCED_PARAMETER(pDriver);
//一个驱动对象可以创建很多设备对象
//创建的所有设备对象形成一个链表,链表的头节点在驱动对象的DeviceObject字段上
PDEVICE_OBJECT objDevice = pDriver->DeviceObject;
PDEVICE_OBJECT NextDevice = NULL;
// 删除符号链接
UNICODE_STRING DosSymName;
RtlInitUnicodeString(&DosSymName, L"\\DosDevices\\MyDriverSymbol");
IoDeleteSymbolicLink(&DosSymName);
while (objDevice != NULL) {
//设备对象中存在NextDevice字段指向同驱动对象的下一个设备对象
NextDevice = objDevice->NextDevice;
IoDeleteDevice(objDevice);
objDevice = NextDevice;
}
DbgPrint("驱动成功卸载\n");
}
内存情况
用户层代码
#include <stdio.h>
#include <Windows.h>
//定义控制码
#define IOCTL_GET_DRIVERINFO CTL_CODE(FILE_DEVICE_UNKNOWN,0x801,METHOD_IN_DIRECT ,FILE_ANY_ACCESS)//获取驱动信息
int main()
{
//1.打开设备(CreateFile第一个参数传入设备路径打开的就是设备)
HANDLE hDevice = CreateFile(
L"\\\\.\\MyDriverSymbol", //设备符号连接名
GENERIC_ALL, NULL, NULL,
OPEN_EXISTING, //打开模式
FILE_ATTRIBUTE_NORMAL,
NULL
);
if (hDevice == INVALID_HANDLE_VALUE) {
MessageBox(NULL,L"设备打开失败", L"Test",NULL);
return 0;
}
char Inputbuf[100] = "Input_CTRL";//发送到内核层的信息
PCHAR DirverINFO = new CHAR[20000]{};//接收零环传进的信息缓冲区 ps:不一定非得20000,随意多少,只要够用
DWORD dwRealSize = 0;
DeviceIoControl(hDevice, IOCTL_GET_DRIVERINFO, Inputbuf, 500, DirverINFO, 20000, &dwRealSize, NULL); //向内核层发送控制码,将返回的信息存储到DirverINFO中
/*
* 结构体需要与内核层定义的偏移相同
pOutBuf 结构体大小
pOutBuf + 4 驱动名字
*/
system("pause");
while (true)//循环遍历内核层传过来的信息
{
DWORD szSize = *PDWORD(DirverINFO);//结构体块的大小
CHAR* szDriverName = (DirverINFO + 0x4);//驱动名字
if (szSize == 0) //循环结束的条件,块大小等于0时结束循环
{
break;
}
printf("%S \n",szDriverName);
DirverINFO += szSize; //得到下一个驱动信息的位置
}
system("pause");
return 0;
}
运行效果如下
驱动安装
三环程序运行
结尾
到这我们就完美实现了零环与三环之间的数据交互。
看懂该篇文章你可能需要有C语言基础、驱动开发基础。以及内核相关知识
(有问题欢迎提出)