三环和内核进行通信
驱动层通过接受到应用层通信传输过来的PID,操作此PID,完成杀死进程的逻辑。从而领会三环和内核通信的驱动对象IRP机制。
驱动层
//设备的名称 必须和你的.sys文件名同名!
#define _DEVICE_NAME L"\\Device\\MyDriver1"
#define _SYM_NAME L"\\??\\MyDriver1"
#include <ntddk.h>
#include<wdmsec.h> //这个头,在linker需要附加依赖项wdmsec.lib
#include <ntstrsafe.h>
//操作码
#define CMD_CODE CTL_CODE(FILE_DEVICE_UNKNOWN /*确定是什么设备类型,未知*/, 0x855/*操作码*/, METHOD_BUFFERED /*数据通信的方式,BUFFER_IO*/, FILE_ANY_ACCESS /*权限,所有权限*/)
//第四步编写派遣函数
NTSTATUS CreateDispatch(
_In_ PDEVICE_OBJECT pDeviceObject,
_In_ PIRP Irp
)
{
UNREFERENCED_PARAMETER(pDeviceObject);
Irp->IoStatus.Status = STATUS_SUCCESS; //告知3环,该IRP请求处理成功
Irp->IoStatus.Information = 0; //回传给3环的数据:无
DbgPrintEx(DPFLTR_IHVDRIVER_ID, 0, "CreateDispatch函数执行了!\n");
IoCompleteRequest(Irp, IO_NO_INCREMENT); //告知操作系统,次IRP请求处理成功!
return STATUS_SUCCESS;
}
NTSTATUS CloseDispatch(
_In_ PDEVICE_OBJECT pDeviceObject,
_In_ PIRP Irp
)
{
UNREFERENCED_PARAMETER(pDeviceObject);
Irp->IoStatus.Status = STATUS_SUCCESS;
Irp->IoStatus.Information = 0;
DbgPrintEx(DPFLTR_IHVDRIVER_ID, 0, "CloseDispatch函数执行了\n");
IoCompleteRequest(Irp, IO_NO_INCREMENT);
return STATUS_SUCCESS;
}
NTSTATUS DeviceControlDispatch(
_In_ PDEVICE_OBJECT pDeviceObject,
_In_ PIRP Irp
)
{
UNREFERENCED_PARAMETER(pDeviceObject);
//DbgBreakPoint();
//获取IRP的栈
PIO_STACK_LOCATION pIrpStack = IoGetCurrentIrpStackLocation(Irp);
ULONG64 pid;
DbgPrintEx(DPFLTR_IHVDRIVER_ID, 0, "[db]:MajorFunction = %x\r\n", pIrpStack->MajorFunction);
DbgPrintEx(DPFLTR_IHVDRIVER_ID, 0, "[db]:InputBufferLength = %x\r\n", pIrpStack->Parameters.DeviceIoControl.InputBufferLength);
DbgPrintEx(DPFLTR_IHVDRIVER_ID, 0, "[db]:OutputBufferLength = %x\r\n", pIrpStack->Parameters.DeviceIoControl.OutputBufferLength);
DbgPrintEx(DPFLTR_IHVDRIVER_ID, 0, "[db]:IoControlCode = %x\r\n", pIrpStack->Parameters.DeviceIoControl.IoControlCode);
DbgPrintEx(DPFLTR_IHVDRIVER_ID, 0, "[db]:SystemBuffer = %p\r\n", Irp->AssociatedIrp.SystemBuffer);
//先比对操作码 和输入缓存区长度
if (pIrpStack->Parameters.DeviceIoControl.IoControlCode == CMD_CODE && pIrpStack->Parameters.DeviceIoControl.InputBufferLength == sizeof(ULONG64)) {
if (Irp->AssociatedIrp.SystemBuffer)
{
//获取到进程ID
pid = *(PULONG64)Irp->AssociatedIrp.SystemBuffer;
NTSTATUS status;
OBJECT_ATTRIBUTES objAttr;
CLIENT_ID clientId;
HANDLE hProcess;
InitializeObjectAttributes(&objAttr, NULL, OBJ_CASE_INSENSITIVE, NULL, NULL); //初始化对象属性
//通过PID 打开进程,得到进程的句柄
clientId.UniqueProcess = (HANDLE)pid;
clientId.UniqueThread = NULL;
status = ZwOpenProcess(&hProcess, PROCESS_ALL_ACCESS, &objAttr, &clientId);
if (NT_SUCCESS(status))
{
DbgPrintEx(77, 0, "恭喜,进程打开成功,成功得到进程句柄,即将杀死进程.\n");
status = ZwTerminateProcess(hProcess, STATUS_SUCCESS); //杀死进程,并释放进程句柄资源
ZwClose(hProcess);
}
else
{
DbgPrintEx(77, 0, "进程打开失败,未能获取到进程句柄!,错误! 终止!\n");
}
if (NT_SUCCESS(status))
{
DbgPrintEx(77, 0, "成功,进程已杀死!\n");
Irp->IoStatus.Status = STATUS_SUCCESS;
}
else
{
Irp->IoStatus.Status = STATUS_UNSUCCESSFUL;
}
}
else
{
DbgPrintEx(77, 0, "IRP请求的系统缓存区不存在!\n");
Irp->IoStatus.Status = STATUS_UNSUCCESSFUL;
}
}
else
{
DbgPrintEx(77, 0, "3环操作码输入错误,或输入缓存区长度不符.引发异常!");
Irp->IoStatus.Status = STATUS_INVALID_PARAMETER;
}
IoCompleteRequest(Irp, IO_NO_INCREMENT);
return STATUS_SUCCESS;
}
extern "C" VOID DriverUnload(PDRIVER_OBJECT DriverObject)
{
UNREFERENCED_PARAMETER(DriverObject);
DbgPrintEx(DPFLTR_IHVDRIVER_ID,0,"驱动卸载成功!\n");
}
extern "C" NTSTATUS DriverEntry(PDRIVER_OBJECT pDriverObject,
PUNICODE_STRING pRegistryPath)
{
if (pDriverObject)
pDriverObject->DriverUnload = DriverUnload;
//应用层与驱动IO通信
//我们已经掌握了编写驱动程序的技巧,但我们希望这些驱动程序能够根据三环程序的指令执行相应的动作。这要求我们需要解决三环程序与0环程序之间数据传递的问题。
//需要注意的是,通过驱动实现的功能往往无法直接呈现给用户。因此,有些功能需要通过应用层的界面等方式与用户进行交互,以使用户能够看到和使用这些功能。
//设备对象简介
//在内核开发中,如果想向内核程序发送消息,必须将消息发送给设备对象,因为驱动本身不能直接接收消息。
//三环程序只能将消息传递给设备对象,而不能直接传递给驱动程序。这通常通过另一个结构体称为IRP(输入输出请求包)来实现。IRP中的"I"代表输入,"O"代表输出,而"P"则代表请求包。
//总之,IRP只能发送给设备对象。因此,在常规方式下,我们无法绕过设备对象来交互数据,因为没有设备对象,就无法接收数据。
//创建设备对象
//WdmlibIoCreateDeviceSecure,函数允许你创建具有指定安全属性的设备对象,以便更灵活地管理设备对象的访问权限。.
//设置数据的交互方式
//创建设备后,我们需要明确指定与三环之间进行数据交互的具体方式和机制,以确保设备与操作系统之间的有效通信。这通常涉及定义和配置适当的I/O请求处理程序,确保数据的正确流动和处理。
// pDevice->Flags |= DO_BUFFERED_IO
// 缓冲区方式读写(DO_BUFFERED_IO):操作系统将应用程序提供缓冲区的数据复制到内核模式下的地址中
// 直接方式读写(DO_DIRECT_IO):操作系统会将用户模式下的缓冲区锁住,然后操作系统将这段缓冲区在内核模式地址再次映射一遍,这样,用户模式的缓冲区和内核模式的缓冲区指向的是同一区域的物理内存.
// 缺点,就是要单独占用物理页面
// 其他方式读写: 在调用IoCreateDevice()后,对pDevice->Flags 即不设置DO_BUFFERED_IO 也不设置 DO_DIRECT_IO,此时就是其他方式.
// 在使用其他方法读写设备时,派遣函数直接读写应用程序提供的缓冲区地址,在驱动程序中,直接操作应用程序缓冲区地址是十分危险的,只有驱动程序在应用程序运行在相同线程上下文的情况下,才能使用这种方式.
// 缓冲区方式读写是一种用于数据传递的方式,其中有以下关键点:
// 三环与0环的概念:在操作系统内核开发中,常常涉及到"三环"和"0环"的概念,分别代表用户态和内核态。。这是为了控制不同权限级别的代码执行。
// 数据复制:在缓冲区方式中,数据实际上是从三环的一个缓冲区的地址复制到0环。这种方式可以确保数据的正确性,因为直接将数据复制到0环可以避免地址切换引起的问题。
// 地址空间:应用程序通常占用操作系统地址空间的低2GB,而内核程序占用高2GB。因此,采用缓冲区方式不会涉及地址切换,从而保证了数据的正确传输。
// 性能:虽然缓冲区方式保证了数据的正确性和易用性,但性能不是特别好。这种方式适合于小规模数据传输,但对于大规模数据传输可能会影响性能
// 总之,缓冲区方式读写是一种保证数据正确性和便于使用的方式,但在性能方面可能有一定的限制,特别适用于小规模数据传输的场景。
//直接方式读写是另一种数据传递方式,其关键点如下:
// 简单性:这种方式对于已经实现了段页机制的操作系统而言非常简单。在这种方式下,数据在三环传输,0环在映射相同的线性地址,两个线性地址映射的是同一份物理页。
// 适用于大数据传输:直接方式适用于传输大量的数据,因为它允许两个环境共享同一份物理内存,而不需要复制数据.
// 物理页浪费:然而,这种方式的缺点在于,如果两个线性地址映射的是同一份物理页,就需要锁定这个物理页,以防止并发写入和读取。此外,如果数据存储在文件中,读取可能会受到限制,因为映射的物理页可能无法直接读取文件数据。
// 省事但浪费物理页:总体而言,直接方式传输数据省去了复制数据的步骤,适用于大规模数据传输,但需要注意浪费物理页的问题。
// 综上所述,直接方式读写适合大规模数据传输,但需要小心处理物理页锁定和文件读取的问题。
// 其他方式读写:这是一种不推荐使用的方式,其中0环直接读写三环的线性地址。在这种情况下,如果三环的地址在操作期间发生变化,例如由进程A切换到进程B,那么数据可能会变得不正确,因为0环无法跟踪这种变化。
// 总的来说,虽然存在其他方式来读写数据,但这些方式不稳定且不可靠,容易导致数据一致性和正确性的问题,因此不建议使用。最好使用缓冲区方式或直接方式,根据具体情况选择合适的方式来进行数据传输。
//创建符号链接
//RtlInitUnicodeString(&UnDeviceName,_DEVICE_NAME); //创建符号链接名称
//status = IoCreateSymbolicLink(&UnSymName, &UnDeviceName); // 创建符号链接
//特别说明:
//1、设备名称的作用是给内核对象用的,如果要在Ring3访问,必须要有符号链接其实就是一个别名,没有这个别名,在Ring3不可见。
//2、内核模式下,符号链接是以"\??\"开头的,如C盘就是"\??\C:"
//3、而在用户模式下,则是以"\\.\"开头的,如C盘就是"\\.\C:"
//IRP与派遣函数
//在图形界面开发中,当用户执行操作(例如点击鼠标)时,操作系统会将与操作相关的信息封装到消息结构体中,然后将这些信息传递给相应的窗口对象。
//这些消息通常包含了鼠标点击的位置等信息。窗口对象会根据消息的类型调用适当的回调函数,例如单击回调函数或双击回调函数,以响应用户的操作。
//在内核函数中,三环可以向设备对象发送消息。消息传递的方式不同于用户界面,而是通过调用不同的函数来实现的。
//例如,在三环调用CreateFile时,需要将所需的参数信息封装成一个结构体IRP(输入输出请求包),然后将其传递给设备对象。设备对象会根据IRP的类型调用不同的派遣函数来处理请求。
//因此,我们的任务是为设备对象提供这些派遣函数,并在适当的位置注册它们。当设备对象需要使用它们时,就会调用这些回调函数来处理相应的操作请求。这样可以实现内核层面的消息传递和响应。
//总之,在图形界面开发中,窗口对象根据消息类型调用回调函数,而在内核开发中,设备对象根据IRP的类型调用派遣函数。我们的任务是准备好这些派遣函数并在适当的位置注册它们,以便在需要时被调用。
//IRP类型
//在图形界面开发中,用户的各种操作,如双击、单击鼠标、键盘输入等,都会产生不同类型的消息。而在内核开发中,用于与设备通信的IRP(输入输出请求包)也有多种不同的类型。
//例如,在应用层调用CreateFile函数时,会产生一种IRP,其类型通常是IRP_MJ_CREATE。这个IRP用于设备的创建,而不是文件的创建。在内核层,通过调用这个函数打开设备时,会在0环产生相应的IRP。
//如果你想从设备中读取数据,通常会使用ReadFile函数,在这种情况下,会产生一种新的IRP,用于读取操作。不同的设备操作对应着不同的IRP类型,例如读取、写入、控制等。
//总之,IRP类型取决于操作的性质,不同的设备操作会产生不同类型的IRP,而在内核中,你需要根据这些IRP类型来处理设备的不同操作请求。
//注册派遣函数
/*
NTSTATUS DriverEntry(。。。。)
{
//设置卸载函数
pDriverObject->DriverUnload = 卸载函数;
//设置派遣函数
pDriverObject->MajorFunction[IRP_MJ_CREATE] = 派遣函数1;
pDriverObject->MajorFunction[IRP_MJ_CLOSE] = 派遣函数2;
pDriverObject->MajorFunction[IRP_MJ_WRITE] = 派遣函数3;
pDriverObject->MajorFunction[IRP_MJ_READ] = 派遣函数4;
pDriverObject->MajorFunction[IRP_MJ_CLEANUP] = 派遣函数5;
pDriverObject->MajorFunction[IRP_MJ_SET_INFORMATION] = 派遣函数6;
pDriverObject->MajorFunction[IRP_MJ_DEVICE_CONTROL] = 派遣函数7;
pDriverObject->MajorFunction[IRP_MJ_SHUTDOWN] = 派遣函数8;
pDriverObject->MajorFunction[lRP_MJ_sYSTEM_CONTROL] = 派遣函数9;
lRP_MJ_MAXIMUM_FUNCTION 派造函数的最大值
*/
//派遣函数的格式
/*
NTSTATUS MyDispatchFunction(PDEVICE_OBJECT pDevObj, PIRP plrp)
{
//处理自己的业务...
//设置返回状态
plrp->loStatus.Status = STATUS_suCCESS; //getLasterror()得到的就是这个值
plrp->loStatus.Information = 0; //返回给3环多少数据没有填0
loCompleteRequest(plrp, lO_NO_INCREMENT);
return STATUs_suCCESS;
}*/
// DbgBreakPoint();
// 第一步创建设备对象
NTSTATUS status = STATUS_SUCCESS;
PDEVICE_OBJECT pDevice = NULL;
UNICODE_STRING UnDeviceName;
UNICODE_STRING UnSymName;
RtlInitUnicodeString(&UnDeviceName, _DEVICE_NAME);
RtlInitUnicodeString(&UnSymName, _SYM_NAME);
UNICODE_STRING DefaultSDDLString;
RtlInitUnicodeString(&DefaultSDDLString, L"D:P(A;;GA;;;SY)(A;;GA;;;BA)(A;;GRGWGX;;;WD)");
GUID DeviceClassGuid = { 0x12345678, 0x1234, 0x5678, { 0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0 } };
if (!pDriverObject || !pRegistryPath)
{
DbgPrintEx(77, 0, "驱动环境初始化异常!");
return STATUS_SUCCESS;
}
DbgPrintEx(77, 0, "注册表路径 RegisterPath:%wZ\n", pRegistryPath);
DbgPrintEx(77, 0, "驱动对象地址 Driver Object Address :%p\n", pDriverObject);
do
{
// 使用WdmlibIoCreateDeviceSecure创建设备
status = WdmlibIoCreateDeviceSecure
(
pDriverObject, 0,
&UnDeviceName,
FILE_DEVICE_UNKNOWN,
FILE_DEVICE_SECURE_OPEN,
FALSE,
&DefaultSDDLString,
&DeviceClassGuid,
&pDevice );
if (!NT_SUCCESS(status))
{
DbgPrintEx(77, 0, "驱动对象创建失败!\n");
break;
}
// 第二步使用数据交互方式
pDevice->Flags |= DO_BUFFERED_IO;
// 第三步设置设备标志 表示设备初始化完成
pDevice->Flags &= ~DO_DEVICE_INITIALIZING;
// 第三步创建符号链接
status = IoCreateSymbolicLink(&UnSymName, &UnDeviceName);
if (!NT_SUCCESS(status))
{
// 失败删除设备
DbgPrintEx(77, 0, "符号链接创建失败,并操作卸载设备对象,流程终止!\n");
IoDeleteDevice(pDevice); break;
}
// 第五步设置派遣回调
pDriverObject->MajorFunction[IRP_MJ_CREATE] = CreateDispatch;
pDriverObject->MajorFunction[IRP_MJ_CLOSE] = CloseDispatch;
pDriverObject->MajorFunction[IRP_MJ_DEVICE_CONTROL] = DeviceControlDispatch;
} while (false);
return status;
}
应用层
// KillProcessDlg.cpp : 实现文件
//
#include "stdafx.h"
#include "KillProcess.h"
#include "KillProcessDlg.h"
#include "afxdialogex.h"
#include <winioctl.h>
#ifdef _DEBUG
#define new DEBUG_NEW
#endif
// 用于应用程序“关于”菜单项的 CAboutDlg 对话框
class CAboutDlg : public CDialogEx
{
public:
CAboutDlg();
// 对话框数据
enum { IDD = IDD_ABOUTBOX };
protected:
virtual void DoDataExchange(CDataExchange* pDX); // DDX/DDV 支持
// 实现
protected:
DECLARE_MESSAGE_MAP()
};
CAboutDlg::CAboutDlg() : CDialogEx(CAboutDlg::IDD)
{
}
void CAboutDlg::DoDataExchange(CDataExchange* pDX)
{
CDialogEx::DoDataExchange(pDX);
}
BEGIN_MESSAGE_MAP(CAboutDlg, CDialogEx)
END_MESSAGE_MAP()
// CKillProcessDlg 对话框
CKillProcessDlg::CKillProcessDlg(CWnd* pParent /*=NULL*/)
: CDialogEx(CKillProcessDlg::IDD, pParent)
{
m_hIcon = AfxGetApp()->LoadIcon(IDR_MAINFRAME);
}
void CKillProcessDlg::DoDataExchange(CDataExchange* pDX)
{
CDialogEx::DoDataExchange(pDX);
}
BEGIN_MESSAGE_MAP(CKillProcessDlg, CDialogEx)
ON_WM_SYSCOMMAND()
ON_WM_PAINT()
ON_WM_QUERYDRAGICON()
ON_BN_CLICKED(IDC_BUTTON1, &CKillProcessDlg::OnBnClickedButton1)
ON_BN_CLICKED(IDC_BUTTON2, &CKillProcessDlg::OnBnClickedButton2)
END_MESSAGE_MAP()
// CKillProcessDlg 消息处理程序
BOOL CKillProcessDlg::OnInitDialog()
{
CDialogEx::OnInitDialog();
// 将“关于...”菜单项添加到系统菜单中。
// IDM_ABOUTBOX 必须在系统命令范围内。
ASSERT((IDM_ABOUTBOX & 0xFFF0) == IDM_ABOUTBOX);
ASSERT(IDM_ABOUTBOX < 0xF000);
CMenu* pSysMenu = GetSystemMenu(FALSE);
if (pSysMenu != NULL)
{
BOOL bNameValid;
CString strAboutMenu;
bNameValid = strAboutMenu.LoadString(IDS_ABOUTBOX);
ASSERT(bNameValid);
if (!strAboutMenu.IsEmpty())
{
pSysMenu->AppendMenu(MF_SEPARATOR);
pSysMenu->AppendMenu(MF_STRING, IDM_ABOUTBOX, strAboutMenu);
}
}
// 设置此对话框的图标。当应用程序主窗口不是对话框时,框架将自动
// 执行此操作
SetIcon(m_hIcon, TRUE); // 设置大图标
SetIcon(m_hIcon, FALSE); // 设置小图标
// TODO: 在此添加额外的初始化代码
return TRUE; // 除非将焦点设置到控件,否则返回 TRUE
}
void CKillProcessDlg::OnSysCommand(UINT nID, LPARAM lParam)
{
if ((nID & 0xFFF0) == IDM_ABOUTBOX)
{
CAboutDlg dlgAbout;
dlgAbout.DoModal();
}
else
{
CDialogEx::OnSysCommand(nID, lParam);
}
}
// 如果向对话框添加最小化按钮,则需要下面的代码
// 来绘制该图标。对于使用文档/视图模型的 MFC 应用程序,
// 这将由框架自动完成。
void CKillProcessDlg::OnPaint()
{
if (IsIconic())
{
CPaintDC dc(this); // 用于绘制的设备上下文
SendMessage(WM_ICONERASEBKGND, reinterpret_cast<WPARAM>(dc.GetSafeHdc()), 0);
// 使图标在工作区矩形中居中
int cxIcon = GetSystemMetrics(SM_CXICON);
int cyIcon = GetSystemMetrics(SM_CYICON);
CRect rect;
GetClientRect(&rect);
int x = (rect.Width() - cxIcon + 1) / 2;
int y = (rect.Height() - cyIcon + 1) / 2;
// 绘制图标
dc.DrawIcon(x, y, m_hIcon);
}
else
{
CDialogEx::OnPaint();
}
}
//当用户拖动最小化窗口时系统调用此函数取得光标
//显示。
HCURSOR CKillProcessDlg::OnQueryDragIcon()
{
return static_cast<HCURSOR>(m_hIcon);
}
#define CMD_CODE CTL_CODE(FILE_DEVICE_UNKNOWN, 0x855, METHOD_BUFFERED, FILE_ANY_ACCESS)
bool isDeviceLinked = false;
HANDLE hDeivce = INVALID_HANDLE_VALUE;
//链接设备对象
void CKillProcessDlg::OnBnClickedButton1()
{
// TODO: 在此添加控件通知处理程序代码
//获取输入的设备名称
CString deviceName;
GetDlgItem(IDC_EDIT1)->GetWindowText(deviceName);
if (deviceName.IsEmpty())
{
AfxMessageBox(_T("请输入设备名称!"));
return;
}
if (!isDeviceLinked)
{
//拼接设备路径
CString devicePath = _T("\\\\.\\") + deviceName;
//打开设备
hDeivce = CreateFile(devicePath,GENERIC_READ| GENERIC_WRITE,0,NULL,OPEN_EXISTING,FILE_ATTRIBUTE_NORMAL,NULL);
if (INVALID_HANDLE_VALUE == hDeivce)
{
AfxMessageBox(_T("无法打开设备!"));
return;
}
isDeviceLinked = true;
AfxMessageBox(_T("设备链接成功!"));
}
else
{
//断开连接,并关闭设备句柄
CloseHandle(hDeivce);
hDeivce = INVALID_HANDLE_VALUE;
isDeviceLinked = false;
AfxMessageBox(_T("设备已断开连接!"));
}
}
//杀死进程
void CKillProcessDlg::OnBnClickedButton2()
{
// TODO: 在此添加控件通知处理程序代码
CString pidText;
this->GetDlgItem(IDC_EDIT2)->GetWindowText(pidText);
if (pidText.IsEmpty())
{
AfxMessageBox(_T("请输入PID!"));
return;
}
//将文本转为ULONG
ULONG pid;
if (!pidText.IsEmpty() && _stscanf_s(pidText, _T("%u"), &pid) == 1)
{
if (isDeviceLinked && hDeivce != INVALID_HANDLE_VALUE)
{
ULONG64 inputBuffer = pid;
ULONG outputBuffer;
DWORD byteRetruned;
CString message;
message.Format(_T("PID:%I64u"), inputBuffer);
AfxMessageBox(message);
//向内核驱动发送命令和PID
BOOL result = ::DeviceIoControl
(
hDeivce, //设备对象句柄
CMD_CODE,//操作码
&inputBuffer,sizeof(inputBuffer),//发送给内核对象的数据,这里是PID
NULL,NULL,NULL,NULL);
if (result)
{
CString message;
message.Format(_T("进程已终止,PID %I64u"), inputBuffer);
AfxMessageBox(message);
}
else
{
AfxMessageBox(_T("异常! 不能访问内核,故无法终止进程!"));
}
}
else
{
AfxMessageBox(_T("设备没有连接!"));
}
}
}