引入
对于PCHUNTER这类软件,通常都是在0环下进行遍历,在3环下进行打印,在0环中没有图形化界面,所以我们需要在0环与3环之间建立通信
Windows系统调用流程
用户空间流程:API(Kernel32.dll)->ntdll.dll->调用API的NT/ZW版本
用户层到内核层通过IRP通信
内核空间流程:ntos->下发到驱动
下面以ReadFile为例,详细介绍。
1.ReadFile调用ntdll中的NTReadFile。其中ReadFile函数是Win32 API,而NtReadFile函数是Native API。
2.ntdll中的NTReadFile进入内核模式,并调用系统服务中的NTReadFile函数。
3.系统服务函数NTReadFile创建IRP_MJ_WRITE类型的IRP,然后将这个IRP函数发送到对应驱动程序的派遣函数中。
4.在对应的派遣函数中一般会通过IoCompleteRequest函数将IRP请求结束。
设备
硬件设备、软件设备
通常设备对象都把自己的名字放到\Device目录中。所以我们的设备对象名称为 #define DEVICE_NAME L"\device\Helloworld"
在Windows 2000中,设备的名称有两个用途。
第一个用途,设备命名后,其它内核模式部件可以通过调用IoGetDeviceObjectPointer函数找到该设备,找到设备对象后,就可以向该设备的驱动程序发送IRP。
另一个用途,允许应用程序打开命名设备的句柄,这样它们就可以向驱动程序发送IRP。应用程序可以使用标准的CreateFile API打开命名设备句柄,然后用ReadFile、WriteFile,和DeviceIoControl向驱动程序发出请求
驱动
软件驱动:火绒、360等使用的驱动
硬件驱动:键盘、鼠标 、显卡等使用的驱动
IRP
IRP(I/O Request Package)在windows内核中,有一种系统组件——IRP,即输入输出请求包。
当上层应用程序需要访问底层输入输出设备时,发出I/O请求,系统会把这些请求转化为IRP数据,不同的IRP会启动I/O设备驱动中对应的派遣函数。
一共有28种IRP,最常用的有四种
符号连接
C盘、D盘等其实就是符号连接,磁盘设备
符号连接有点像桌面上的快捷方式,符号连接在Windows NT中的主要用途是把处于列表前面的DOS形式的名称连接到设备上。
符号连接可以使对象管理器在分析一个名称时能跳到命名空间的某个地方。例如,如果我用CreateFile打开名称为“C:\MYFILE.CPP”的对象,对象管理器将以下面过程打开该文件:
内核模式代码最开始看到的名称是\??\C:\MYFILE.CPP。对象管理器在根目录中查找“??”。
找到\??目录后,对象管理器在其中查找“C:”。它发现找到的对象是一个符号连接,所以它就用这个符号连接组成一个新的内核模式路径名:\Device\HarddiskVolume1\MYFILE.CPP,然后析取它。
使用新路径名后,对象管理器重新在根目录中查找“Device”。
找到\Device目录后,对象管理器在其中查找“HarddiskVolume1”,最后它找到一个以该名字命名的设备。
控制IRP
Device_IO_Control 设备IO控制
下发IRP_MJ_DEVICE_CONTROL和控制码
控制码
R3和R0都可以访问控制码,R3下发,R0利用switch实现具体操作
实现
#include<ntifs.h>
//设备对象名称
#define DEVICE_NAME L"\\device\\Helloworld"
//符号链接名称
#define LINK_NAME L"\\dosdevices\\whitebird"
VOID DriverUnload(PDRIVER_OBJECT pDriver) {
//符号链接名称
UNICODE_STRING uLinkName = { 0 };
//初始化符号链接名称
RtlInitUnicodeString(&uLinkName, LINK_NAME);
//删除符号链接
IoDeleteSymbolicLink(&uLinkName);
//删除设备对象
IoDeleteDevice(pDriver->DeviceObject);
DbgPrint("Unload success!\n");
}
//IRP派遣函数-默认处理
NTSTATUS DispatchCommon (PDEVICE_OBJECT pDeviceObject,PIRP pIrp){
DbgPrint("SUCCESS");
//设置IRP处理成功,告诉3环
pIrp->IoStatus.Status = STATUS_SUCCESS;
//设置返回的字节数
pIrp->IoStatus.Information = 0;
//结束IRP处理流程
IoCompleteRequest(pIrp, IO_NO_INCREMENT);
return STATUS_SUCCESS;
}
NTSTATUS DriverEntry(PDRIVER_OBJECT pDriverObject, PUNICODE_STRING pRegPath) {//驱动对象指针,注册表路径 DriverEntry不能改
//注册卸载函数,作用是指定用哪个函数来完成卸载
pDriverObject->DriverUnload = DriverUnload;
//返回状态
NTSTATUS ntStatus = 0;
//设备对象
UNICODE_STRING uDevicename = { 0 };
//符号链接名称
UNICODE_STRING uLinkName = { 0 };
//初始化设备对象
RtlInitUnicodeString(&uDevicename, DEVICE_NAME);
//初始化符号链接名称
RtlInitUnicodeString(&uLinkName, LINK_NAME);
//设备对象
PDEVICE_OBJECT pDeviceObject = NULL;
//创建设备对象
ntStatus=IoCreateDevice(pDriverObject, 0, &uDevicename, FILE_DEVICE_UNKNOWN, 0, TRUE, &pDeviceObject);
//判断设备对象是否创建成功
if (!NT_SUCCESS(ntStatus))
{
DbgPrint("IoCreateDevice failed:%x", ntStatus);
return ntStatus;
}
//通讯方式
// 1.DO_BUFFERED_IO基于缓存
// 2.DO_DIRECT_IO 直接读写
// 3.DO_FORCE_NEITHER_IO 两者介不的通信方式 R0直接读R3
pDeviceObject->Flags |= DO_BUFFERED_IO;
//创建符号链接
ntStatus = IoCreateSymbolicLink(&uLinkName, &uDevicename);
//判断符号链接是否创建成功
if (!NT_SUCCESS(ntStatus))
{ //删除设备对象
IoDeleteDevice(&pDeviceObject);
DbgPrint("IoCreateSymbolicLink failed:%x", ntStatus);
return ntStatus;
}
//将所有IRP派遣处理函数,设置为默认处理函数
for (size_t i = 0; i < IRP_MJ_MAXIMUM_FUNCTION; i++)
{
pDriverObject->MajorFunction[i] = DispatchCommon;
}
return STATUS_SUCCESS;//状态成功0,一定要有返回值
}
IofCompleteRequest(
_In_ PIRP Irp,
_In_ CCHAR PriorityBoost
);
3环代码
#include<stdio.h>
#include<Windows.h>
#define LINK_NAME L"\\\\.\\whitebird"
int main() {
//打开符号链接
HANDLE hDeviceHandle=CreateFile(LINK_NAME, GENERIC_ALL, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
//判断符号链接是否打开成功
if (hDeviceHandle==INVALID_HANDLE_VALUE)
{
printf("Error:%d\n",GetLastError);
system("pause");
return 0;
}
ULONG uRetWriteLength = 0;
ULONG uLength = wcslen(L"hello world") * sizeof(WCHAR)+1;
WriteFile(hDeviceHandle, L"hello world", uLength,&uRetWriteLength,NULL);
system("pause");
return 0;
}
我们的四个SUCCESS,分别是CreateFile、WriteFile、CloseFile、CleanUp完成的