首页
社区
课程
招聘
[原创]内核漏洞学习[1]-HEVD-StackOverflow
发表于: 2021-10-30 20:45 25132

[原创]内核漏洞学习[1]-HEVD-StackOverflow

2021-10-30 20:45
25132

HEVD:漏洞靶场,包含各种Windows内核漏洞的驱动程序项目,在Github上就可以找到该项目,进行相关的学习

Releases · hacksysteam/HackSysExtremeVulnerableDriver · GitHub

环境准备:

Windows 7 X86 sp1 虚拟机

使用VirtualKD和windbg双机调试

HEVD 3.0+KmdManager+DubugView

网上找的堆栈图,

栈溢出指的是程序向栈中某个变量中写入的字节数超过了这个变量本身所申请的字节数,因而导致与其相邻的栈中的变量的值被改变。

发生栈溢出的基本前提是

程序必须向栈上写入数据。

写入的数据大小没有被良好地控制

例如图中,可以写入大于buffer长度的数据,溢出到返回地址

Windows内核采用面向对象的编程方式,但是使用的却是C语言。所以说所谓的“Windows内核对象”,并不是一个C++类的对象,而是Windows的内核程序员使用C语言对面向对象编程方式的一种模拟。首先,Windows内核认为许多东西都是“对象”,比如一个驱动、一个设备、一个文件,甚至其他的一些东西。“对象”相当于一个基类。
在 Windows启动之后,这些内核对象都在内存中。如果我们在内核中写代码,则可以随意访问它们。每个种类的内核对象都用一个结构体来表示。
一个驱动对象代表了一个驱动程序,或者说一个内核模块。驱动对象的结构如下

实际上,如果写一个驱动程序,或者说编写一个内核模块,要在 Windows中加载,则必须填写这样一个结构(_Driver_Object),来告诉Windows程序提供哪些功能。与编写一个应用程序不同。内核模块并不生成一个进程,只是填写一组回调函数让Windows来调用,而且这组回调函数必须符合 Windows内核规定。
这一组回调函数包括上面的“普通分发函数”和“快速IO分发函数”。这些函数用来处理发送给这个内核模块的请求。一个内核模块所有的功能都由它们提供给 Windows。

Windows窗口应用程序开发,窗口是唯一可以接收消息的东西,任何消息都是发送给一个窗口的。而在内核世界里,大部分“消息”都以请求(IRP)的方式传递。而设备对象(DEVICE OBJECT)是唯一可以接收请求的实体,任何一个“请求”(IRP)都是发送给某个设备对象的。

因为我们总是在内核程序中生成一个DEVICE OBJECT,而一个内核程序是用一个驱动对象表示的,所以一个设备对象总是属于一个驱动对象。

_Device_Object结构,看出驱动对象与设备对象的联系,驱动对象生成多个设备对象。而Windows向设备对象发送请求,这些请求是被驱动对象的分发函数所捕获的。当 Windows内核向一个设备发送一个请求时,驱动对象的分发函数中的某一个会被调用。分发函数原型如下:

大部分请求以IRP的形式发送。IRP也是一个内核数据结构

内核方面的编程

1.生成控制设备

2.控制设备的名字和符号链接

3.控制设备的删除(依次删除符号链接和控制设备)

4.分发函数

5.请求的处理

应用层面的编程

1.在应用程序中打开与关闭设备

2.设备控制请求

总结:通过用户层与内核层的通信,可以让用户程序在需要时调用驱动的特定功能.驱动通信类似于Windows消息机制,在内核中,消息被封装在IRP结构体中(I/O Request Packae :IO请求包),设备对象接受IRP实现用户程序与驱动程序的通信。

通过DeviceIoControl函数来使应用程序与驱动程序通信

这种通信方式,就是驱动程序和应用程序自定义一种IO控制码,然后应用程序调用DeviceIoControl函数,DeviceIoControl函数会产生此IRPIRP_MJ_DEVICE_CONTROL,系统就调用相应的处理IRP_MJ_DEVICE_CONTROL的分发函数,在分发函数中判断,是自定义的控制码你就进行相应的处理。

编写 Hello World Windows 驱动程序 (KMDF) - Windows drivers | Microsoft Docs

Windows 驱动程序示例 - Windows drivers | Microsoft Docs

(可以借助这个HEVD项目更加加深对驱动通信的理解)

漏洞原理:栈溢出漏洞,使用过程中使用危险的函数,没有对

(1)加载驱动

安装驱动程序,使用kmdManager 加载驱动程序,DebugView检测内核,可以看到加载驱动程序成功。

windbg:

(2)分析漏洞点

对BufferOverflowStack.c源码进行分析,漏洞函数TriggerBufferOverflowStack,存在明显的栈溢出漏洞,RtlCopyMemory函数没有对KernelBuffer大小进行验证,直接将size大小的userbuffer传入缓冲区,没有进行大小的限制,(例如安全版本的sizeof(KernelBuffer)),因此造成栈溢出,攻击返回地址,使之指向构造的payload。

利用驱动与R3通信,能够直接读取R3地址,构造exp

1.获取栈中KernelBuffer到返回地址的偏移

ida中打开HEVD.sys

KernelBuffer 距离ebp 81Ch,想覆盖到返回地址需要81C+4+4 =824h

也可以使用windbg结合断点,在TriggerBufferOverflowStack,RtlCopyMemory函数处下断点,判断偏移。

2.构造exp(官方exp)

HACKSYS_EVD_IOCTL_STACK_OVERFLOW控制码对应的派遣函数BufferOverflowStackIoctlHandler

BufferOverflowStackIoctlHandler函数调用TriggerBufferOverflowStack触发漏洞函数

payload功能:遍历进程,得到系统进程的token,把当前进程的token替换,达到提权目的。

相关内核结构体:

在内核模式下,fs:[0]指向KPCR结构体

payload:

运行exp,windbg:

提权成功:

对RtlCopyMemory的参数进行严格的设置,使用sizeof(kernelbuffer)

 
 
 
 
kd> dt _Driver_Object
ntdll!_DRIVER_OBJECT
   +0x000 Type             : Int2B  //结构的类型
   +0x002 Size             : Int2B //结构的大小
   +0x004 DeviceObject     : Ptr32 _DEVICE_OBJECT //设备对象,这里实际上是一个设备对象的链表的开始
   +0x008 Flags            : Uint4B
   +0x00c DriverStart      : Ptr32 Void //驱动模块在内核空间中的开始地址
   +0x010 DriverSize       : Uint4B//驱动模块在内核空间中的大小
   +0x014 DriverSection    : Ptr32 Void
   +0x018 DriverExtension  : Ptr32 _DRIVER_EXTENSION //驱动扩展对象
   +0x01c DriverName       : _UNICODE_STRING //驱动名字,这个名字是个字符串结构体
   +0x024 HardwareDatabase : Ptr32 _UNICODE_STRING //驱动服务注册表路径
   +0x028 FastIoDispatch   : Ptr32 _FAST_IO_DISPATCH// 快速IO分发函数
   +0x02c DriverInit       : Ptr32     long //驱动入口点
   +0x030 DriverStartIo    : Ptr32     void
   +0x034 DriverUnload     : Ptr32     void //驱动的卸载函数
   +0x038 MajorFunction    : [28] Ptr32     long  //普通分发函数
kd> dt _Driver_Object
ntdll!_DRIVER_OBJECT
   +0x000 Type             : Int2B  //结构的类型
   +0x002 Size             : Int2B //结构的大小
   +0x004 DeviceObject     : Ptr32 _DEVICE_OBJECT //设备对象,这里实际上是一个设备对象的链表的开始
   +0x008 Flags            : Uint4B
   +0x00c DriverStart      : Ptr32 Void //驱动模块在内核空间中的开始地址
   +0x010 DriverSize       : Uint4B//驱动模块在内核空间中的大小
   +0x014 DriverSection    : Ptr32 Void
   +0x018 DriverExtension  : Ptr32 _DRIVER_EXTENSION //驱动扩展对象
   +0x01c DriverName       : _UNICODE_STRING //驱动名字,这个名字是个字符串结构体
   +0x024 HardwareDatabase : Ptr32 _UNICODE_STRING //驱动服务注册表路径
   +0x028 FastIoDispatch   : Ptr32 _FAST_IO_DISPATCH// 快速IO分发函数
   +0x02c DriverInit       : Ptr32     long //驱动入口点
   +0x030 DriverStartIo    : Ptr32     void
   +0x034 DriverUnload     : Ptr32     void //驱动的卸载函数
   +0x038 MajorFunction    : [28] Ptr32     long  //普通分发函数
 
kd> dt _Device_Object
ntdll!_DEVICE_OBJECT
   +0x000 Type             : Int2B
   +0x002 Size             : Uint2B
   +0x004 ReferenceCount   : Int4B //引用计数
   +0x008 DriverObject     : Ptr32 _DRIVER_OBJECT //这个设备所属的驱动对象
   +0x00c NextDevice       : Ptr32 _DEVICE_OBJECT //下一个设备对象,在一个驱动中有n个设备,这些设备用这个指针链接起来(单项链表)
   +0x010 AttachedDevice   : Ptr32 _DEVICE_OBJECT
   +0x014 CurrentIrp       : Ptr32 _IRP
   +0x018 Timer            : Ptr32 _IO_TIMER
   +0x01c Flags            : Uint4B
   +0x020 Characteristics  : Uint4B
   +0x024 Vpb              : Ptr32 _VPB
   +0x028 DeviceExtension  : Ptr32 Void
   +0x02c DeviceType       : Uint4B//设备类型
   +0x030 StackSize        : Char //IRp栈大小
   +0x034 Queue            : <unnamed-tag>
   +0x05c AlignmentRequirement : Uint4B
   +0x060 DeviceQueue      : _KDEVICE_QUEUE
   +0x074 Dpc              : _KDPC
   +0x094 ActiveThreadCount : Uint4B
   +0x098 SecurityDescriptor : Ptr32 Void
   +0x09c DeviceLock       : _KEVENT
   +0x0ac SectorSize       : Uint2B
   +0x0ae Spare1           : Uint2B
   +0x0b0 DeviceObjectExtension : Ptr32 _DEVOBJ_EXTENSION
   +0x0b4 Reserved         : Ptr32 Void
kd> dt _Device_Object
ntdll!_DEVICE_OBJECT
   +0x000 Type             : Int2B
   +0x002 Size             : Uint2B
   +0x004 ReferenceCount   : Int4B //引用计数
   +0x008 DriverObject     : Ptr32 _DRIVER_OBJECT //这个设备所属的驱动对象
   +0x00c NextDevice       : Ptr32 _DEVICE_OBJECT //下一个设备对象,在一个驱动中有n个设备,这些设备用这个指针链接起来(单项链表)
   +0x010 AttachedDevice   : Ptr32 _DEVICE_OBJECT
   +0x014 CurrentIrp       : Ptr32 _IRP
   +0x018 Timer            : Ptr32 _IO_TIMER
   +0x01c Flags            : Uint4B
   +0x020 Characteristics  : Uint4B
   +0x024 Vpb              : Ptr32 _VPB
   +0x028 DeviceExtension  : Ptr32 Void
   +0x02c DeviceType       : Uint4B//设备类型
   +0x030 StackSize        : Char //IRp栈大小
   +0x034 Queue            : <unnamed-tag>
   +0x05c AlignmentRequirement : Uint4B
   +0x060 DeviceQueue      : _KDEVICE_QUEUE
   +0x074 Dpc              : _KDPC
   +0x094 ActiveThreadCount : Uint4B
   +0x098 SecurityDescriptor : Ptr32 Void
   +0x09c DeviceLock       : _KEVENT
   +0x0ac SectorSize       : Uint2B
   +0x0ae Spare1           : Uint2B
   +0x0b0 DeviceObjectExtension : Ptr32 _DEVOBJ_EXTENSION
   +0x0b4 Reserved         : Ptr32 Void
NTSTATUS MyDispatch(PDEVICE_OBECT deivce,PIRP irp);
//参数device是请求的目标设备,第二个参数irp是请求的指针
NTSTATUS MyDispatch(PDEVICE_OBECT deivce,PIRP irp);
//参数device是请求的目标设备,第二个参数irp是请求的指针
kd> dt _IRP
ntdll!_IRP
   +0x000 Type             : Int2B //类型和大小
   +0x002 Size             : Uint2B
   +0x004 MdlAddress       : Ptr32 _MDL ////内存描述符链表指针。实际上,这里用来描述一个缓冲区。可以想象/一个内核请求一般都需要一个缓冲区(如读硬盘需要有读出缓冲区)
 
   +0x008 Flags            : Uint4B
   +0x00c AssociatedIrp    : <unnamed-tag>
   +0x010 ThreadListEntry  : _LIST_ENTRY
   +0x018 IoStatus         : _IO_STATUS_BLOCK ///l1O状态。一般请求完成之后的返回值放在这里
   +0x020 RequestorMode    : Char
   +0x021 PendingReturned  : UChar
   +0x022 StackCount       : Char//IRP 栈空间大小
   +0x023 CurrentLocation  : Char // IRP当前栈空间
   +0x024 Cancel           : UChar
   +0x025 CancelIrql       : UChar
   +0x026 ApcEnvironment   : Char
   +0x027 AllocationFlags  : UChar
   +0x028 UserIosb         : Ptr32 _IO_STATUS_BLOCK
   +0x02c UserEvent        : Ptr32 _KEVENT
   +0x030 Overlay          : <unnamed-tag>
   +0x038 CancelRoutine    : Ptr32     void //用来取消一个未决请求的函数
   +0x03c UserBuffer       : Ptr32 Void
   +0x040 Tail             : <unnamed-tag>
kd> dt _IRP
ntdll!_IRP
   +0x000 Type             : Int2B //类型和大小
   +0x002 Size             : Uint2B
   +0x004 MdlAddress       : Ptr32 _MDL ////内存描述符链表指针。实际上,这里用来描述一个缓冲区。可以想象/一个内核请求一般都需要一个缓冲区(如读硬盘需要有读出缓冲区)
 
   +0x008 Flags            : Uint4B
   +0x00c AssociatedIrp    : <unnamed-tag>
   +0x010 ThreadListEntry  : _LIST_ENTRY
   +0x018 IoStatus         : _IO_STATUS_BLOCK ///l1O状态。一般请求完成之后的返回值放在这里
   +0x020 RequestorMode    : Char
   +0x021 PendingReturned  : UChar
   +0x022 StackCount       : Char//IRP 栈空间大小
   +0x023 CurrentLocation  : Char // IRP当前栈空间
   +0x024 Cancel           : UChar
   +0x025 CancelIrql       : UChar
   +0x026 ApcEnvironment   : Char
   +0x027 AllocationFlags  : UChar
   +0x028 UserIosb         : Ptr32 _IO_STATUS_BLOCK
   +0x02c UserEvent        : Ptr32 _KEVENT
   +0x030 Overlay          : <unnamed-tag>
   +0x038 CancelRoutine    : Ptr32     void //用来取消一个未决请求的函数
   +0x03c UserBuffer       : Ptr32 Void
   +0x040 Tail             : <unnamed-tag>
// Sample "Hello World" driver
// creates a HelloDev, that expects one IOCTL
 
#include <ntddk.h>
 
#define HELLO_DRV_IOCTL CTL_CODE(FILE_DEVICE_UNKNOWN, 0x800, METHOD_NEITHER, FILE_ANY_ACCESS)   //#define CTL_CODE(DeviceType, Function, Method, Access) (  ((DeviceType) << 16) | ((Access) << 14) | ((Function) << 2) | (Method))
//这里的CTL CODE是一个宏,是SDK里的头文件提供的。读者要做的是直接利用这个宏来生成一个自己的设备控制请求功能号。CTL_CODE有4个参数,其中第一个参数是设备类型。笔者生成的这种控制设备和任何硬件都没有关系,所以直接定义成未知类型(FILE_DEVICE_UNKNOWN)即可。第二个参数是生成这个功能号的核心数字,这个数字直接用来和其他参数“合成”功能号。OxO~Ox7ff已经被微软预留,所以笔者只能使用大于0x7ff的数字。同时,这个数字不能大于0xfmf。如果要定义超过一个的功能号,那么不同的功能号就靠这个数字进行区分。第三个参数METHOD_BUFFERED是说用缓冲方式2。用缓冲方式的话,输入/输出缓冲会在用户和内核之间拷贝。这是比较简单和安全的一种方式。最后一个参数是这个操作需要的权限。当笔者需要将数据发送到设备时,相当于往设备上写入数据,所以标志为拥有写数据权限(FILE_WRITE_DATA)。
 
 
#define DOS_DEV_NAME L"\\DosDevices\\HelloDev"
#define DEV_NAME L"\\Device\\HelloDev"
 
/// <summary>
/// IRP Not Implemented Handler
/// </summary>
/// <param name="DeviceObject">The pointer to DEVICE_OBJECT</param>
/// <param name="Irp">The pointer to IRP</param>
/// <returns>NTSTATUS</returns>、
 
 
/*
以下是分发函数
 
分发函数是一组用来处理发送给设备对象(当然也包括控制设备)的请求的函数。这些数由内核驱动的开发者编写,以便处理这些请求并返回给Windows。分发函数是设置在驱动对象(Driver Object)上的。也就是说,每个驱动都有一组自己的分发函数。Windows的IO管理器在收到请求时,会根据请求发送的目标,也就是一个设备对象,来调用这个设备对象所从的驱动对象上对应的分发函数。
 
不同的分发函数处理不同的请求
打开(Create'):在试图访问一个设备对象之前,必须先用打开请求“打开”它。只有得到成功的返回,才可以发送其他的请求。
关闭(Close):在结束访问一个设备对象之后,发送关闭请求将它关闭。关闭之后,就必须再次打开才能访问。
设备控制(Device Control):设备控制请求是一种既可以用来输入(从应用到内核),又可以用来输出(从内核到应用)的请求。
NTSTATUS cwkDispatch(IN_PDEVICE_OB3ECT dev,IN PIRp irp)
 
其中的dev就是请求要发送给的目标对象;irp则是代表请求内容的数据结构的指针。无论如何,分发函数必须首先设置给驱动对象,这个工作一般在 DriverEntry中完成。
 
------------------------------------------------------------------------------------------------------------------
*/
 
 
NTSTATUS IrpNotImplementedHandler(IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp) {
    Irp->IoStatus.Information = 0;
    Irp->IoStatus.Status = STATUS_NOT_SUPPORTED;
 
    UNREFERENCED_PARAMETER(DeviceObject);
    PAGED_CODE();
 
    // Complete the request
    IoCompleteRequest(Irp, IO_NO_INCREMENT);
 
    return STATUS_NOT_SUPPORTED;
}
 
/// <summary>
/// IRP Create Close Handler
/// </summary>
/// <param name="DeviceObject">The pointer to DEVICE_OBJECT</param>
/// <param name="Irp">The pointer to IRP</param>
/// <returns>NTSTATUS</returns>
NTSTATUS IrpCreateCloseHandler(IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp) {
    Irp->IoStatus.Information = 0;
    Irp->IoStatus.Status = STATUS_SUCCESS;
 
    UNREFERENCED_PARAMETER(DeviceObject);
    PAGED_CODE();
 
    // Complete the request
    IoCompleteRequest(Irp, IO_NO_INCREMENT);
 
    return STATUS_SUCCESS;
}
 
/// <summary>
/// IRP Unload Handler
/// </summary>
/// <param name="DeviceObject">The pointer to DEVICE_OBJECT</param>
/// <returns>NTSTATUS</returns>
VOID IrpUnloadHandler(IN PDRIVER_OBJECT DriverObject) {
    UNICODE_STRING DosDeviceName = { 0 };
 
    PAGED_CODE();
 
    RtlInitUnicodeString(&DosDeviceName, DOS_DEV_NAME);
 
    // Delete the symbolic link
    IoDeleteSymbolicLink(&DosDeviceName);
 
    // Delete the device
    IoDeleteDevice(DriverObject->DeviceObject);
 
    DbgPrint("[!] Hello Driver Unloaded\n");
}
 
/// <summary>
/// IRP Device IoCtl Handler
/// </summary>
/// <param name="DeviceObject">The pointer to DEVICE_OBJECT</param>
/// <param name="Irp">The pointer to IRP</param>
/// <returns>NTSTATUS</returns>
NTSTATUS IrpDeviceIoCtlHandler(IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp) {
    ULONG IoControlCode = 0;
    PIO_STACK_LOCATION IrpSp = NULL;
    NTSTATUS Status = STATUS_NOT_SUPPORTED;
 
    UNREFERENCED_PARAMETER(DeviceObject);
    PAGED_CODE();
 
    IrpSp = IoGetCurrentIrpStackLocation(Irp);
    IoControlCode = IrpSp->Parameters.DeviceIoControl.IoControlCode;
 //根据不同的控制码作出不同的处理
    if (IrpSp) {
        switch (IoControlCode) {
        case HELLO_DRV_IOCTL:
            DbgPrint("[< HelloDriver >] Hello from the Driver!\n");
            break;
        default:
            DbgPrint("[-] Invalid IOCTL Code: 0x%X\n", IoControlCode);
            Status = STATUS_INVALID_DEVICE_REQUEST;
            break;
        }
    }
 
    Irp->IoStatus.Status = Status;//3环的GetLastError得到的就是这个值
    Irp->IoStatus.Information = 0;//返回数据的字节数  没有写0
 
    // Complete the request
    IoCompleteRequest(Irp, IO_NO_INCREMENT);//用于结束这个请求
 
    return Status;
}
-------------------------------------------------------------------------------------------------------------------
以上都是分发函数
 
-------------------------------------------------------------------------------------------------------------------
 
 
 // 驱动入口函数
NTSTATUS DriverEntry(IN PDRIVER_OBJECT DriverObject, IN PUNICODE_STRING RegistryPath) {
    UINT32 i = 0;
    PDEVICE_OBJECT DeviceObject = NULL;
    NTSTATUS Status = STATUS_UNSUCCESSFUL; //绝大多数内核函数都会有一个返回值,类型为NTSTATUS。该类型本质就是一个LONG
    UNICODE_STRING DeviceName, DosDeviceName = { 0 };
 
    UNREFERENCED_PARAMETER(RegistryPath);
    PAGED_CODE();
 
    RtlInitUnicodeString(&DeviceName, DEV_NAME);//初始化unicode字符串,为其赋值。不会申请内存。
    RtlInitUnicodeString(&DosDeviceName, DOS_DEV_NAME);
 
    DbgPrint("[*] In DriverEntry\n");//驱动的打印函数
 
    //1.创建设备,指定设备的名字
    Status = IoCreateDevice(DriverObject,
        0,
        &DeviceName,
        FILE_DEVICE_UNKNOWN,
        FILE_DEVICE_SECURE_OPEN,
        FALSE,
        &DeviceObject);
 /*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); 用来返回结果,如果函数执行成功(返回值为STATUS_SUCCESS),*DeviceObject就是生成的设备对象对的指针
生成设备对象DriverObject,设备对象可以在内核中暴露出来给应用层,应用层可以像操作文件一样操作它。这
 
*/
 
 
    if (!NT_SUCCESS(Status)) {
        if (DeviceObject) {
            // Delete the device
            IoDeleteDevice(DeviceObject);
        }
 
        DbgPrint("[-] Error Initializing HelloDriver\n");
        return Status;
    }
/*
分发函数必须首先设置给驱动对象
注意,下面的片段中将所有的分发函数(实际上MajorFunction是一个函数指针数组,保存所有分发函数的指针>都设置成同一个函数,这是一种简单的处理方案。
 
 
*/
    // Assign the IRP handlers
    for (i = 0; i <= IRP_MJ_MAXIMUM_FUNCTION; i++) {
        // Disable the Compiler Warning: 28169
#pragma warning(push)
#pragma warning(disable : 28169)
        DriverObject->MajorFunction[i] = IrpNotImplementedHandler;
#pragma warning(pop)
    }
 // 5.请求的处理
 
    // Assign the IRP handlers for Create, Close and Device Control
    /*
    在用户层,我们每次调用CreateFile、OpenFIle、DeleteFile、CloseHandle等API时,都会向0环发送一个消息,这个消息成为IRP数据包。这些API称为设备操作API。如:当调用CreateFile时,会向内核层发送一个名为IRP_MJ_CREATE的打开设备的IRP消息。其他常用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进行消息的传递
    */
 
 
    DriverObject->MajorFunction[IRP_MJ_CREATE] = IrpCreateCloseHandler;
    DriverObject->MajorFunction[IRP_MJ_CLOSE] = IrpCreateCloseHandler;
    DriverObject->MajorFunction[IRP_MJ_DEVICE_CONTROL] = IrpDeviceIoCtlHandler;
 
    // Assign the driver Unload routine
    DriverObject->DriverUnload = IrpUnloadHandler;//为驱动指定卸载函数
 
    // Set the flags
    /*
    设置数据交互方式
pDeviceObj->Flags
//缓冲区方式读写(DO_BUFFERED_IO):将3环缓冲区内的数据复制一份到0环的缓冲区。  方便,但性能不好。
//直接方式读写(DO_DIRECT_IO):首先将3环缓冲区锁住,然后在将对应的物理地址映射一份0环的线性地址。适合大量数据传输。两个线性地址对应同一个物理地址。
//其它方式读写(不设置值):0环直接读取3环的线性地址,不建议。当进程切换,CR3改变,会读取到其他进程的内存数据。
pDeviceObj->Flags &= DO_DEVICE_INITIALIZING;
//将DO_DEVICE_INITIALIZING初始化标志位清空,如果不清空这个位,那么3环可能无法打开设备。
    */
    DeviceObject->Flags |= DO_DIRECT_IO;
    DeviceObject->Flags &= ~DO_DEVICE_INITIALIZING;
 
    // 2.创建符号链接
    //控制设备需要名字,暴露出来,供其他程序打开与之通信
   /*
  设备的名字可以在调用IoCreateDevice或IoCreateDeviceSecure时指定。此外,应用层是无法直接通过设备的名字来打开对象的,为此必须要建立一个暴露给应用层的符号链接。符号链接就是记录一个字符串对应到另一个字符串的一种简单结构。生成符号链接的函数是:
 NTSTATUS
IoCreatesymbolicLink(
IN PUNICODE_STRING symbolicLinkName, 符号链接,名
IN PUNICODE_STRING DeviceName  设备名
);
 
   */
//IoCreateSymbolicLink  3.控制设备的删除,依次删除符号链接和控制设备
    Status = IoCreateSymbolicLink(&DosDeviceName, &DeviceName);
 
    // Show the banner
    DbgPrint("[!] HelloDriver Loaded\n");
 
    return Status;
}
// Sample "Hello World" driver
// creates a HelloDev, that expects one IOCTL
 
#include <ntddk.h>
 
#define HELLO_DRV_IOCTL CTL_CODE(FILE_DEVICE_UNKNOWN, 0x800, METHOD_NEITHER, FILE_ANY_ACCESS)   //#define CTL_CODE(DeviceType, Function, Method, Access) (  ((DeviceType) << 16) | ((Access) << 14) | ((Function) << 2) | (Method))
//这里的CTL CODE是一个宏,是SDK里的头文件提供的。读者要做的是直接利用这个宏来生成一个自己的设备控制请求功能号。CTL_CODE有4个参数,其中第一个参数是设备类型。笔者生成的这种控制设备和任何硬件都没有关系,所以直接定义成未知类型(FILE_DEVICE_UNKNOWN)即可。第二个参数是生成这个功能号的核心数字,这个数字直接用来和其他参数“合成”功能号。OxO~Ox7ff已经被微软预留,所以笔者只能使用大于0x7ff的数字。同时,这个数字不能大于0xfmf。如果要定义超过一个的功能号,那么不同的功能号就靠这个数字进行区分。第三个参数METHOD_BUFFERED是说用缓冲方式2。用缓冲方式的话,输入/输出缓冲会在用户和内核之间拷贝。这是比较简单和安全的一种方式。最后一个参数是这个操作需要的权限。当笔者需要将数据发送到设备时,相当于往设备上写入数据,所以标志为拥有写数据权限(FILE_WRITE_DATA)。
 
 
#define DOS_DEV_NAME L"\\DosDevices\\HelloDev"
#define DEV_NAME L"\\Device\\HelloDev"
 
/// <summary>
/// IRP Not Implemented Handler
/// </summary>
/// <param name="DeviceObject">The pointer to DEVICE_OBJECT</param>
/// <param name="Irp">The pointer to IRP</param>
/// <returns>NTSTATUS</returns>、
 
 
/*
以下是分发函数
 
分发函数是一组用来处理发送给设备对象(当然也包括控制设备)的请求的函数。这些数由内核驱动的开发者编写,以便处理这些请求并返回给Windows。分发函数是设置在驱动对象(Driver Object)上的。也就是说,每个驱动都有一组自己的分发函数。Windows的IO管理器在收到请求时,会根据请求发送的目标,也就是一个设备对象,来调用这个设备对象所从的驱动对象上对应的分发函数。
 
不同的分发函数处理不同的请求
打开(Create'):在试图访问一个设备对象之前,必须先用打开请求“打开”它。只有得到成功的返回,才可以发送其他的请求。
关闭(Close):在结束访问一个设备对象之后,发送关闭请求将它关闭。关闭之后,就必须再次打开才能访问。
设备控制(Device Control):设备控制请求是一种既可以用来输入(从应用到内核),又可以用来输出(从内核到应用)的请求。
NTSTATUS cwkDispatch(IN_PDEVICE_OB3ECT dev,IN PIRp irp)
 
其中的dev就是请求要发送给的目标对象;irp则是代表请求内容的数据结构的指针。无论如何,分发函数必须首先设置给驱动对象,这个工作一般在 DriverEntry中完成。
 
------------------------------------------------------------------------------------------------------------------
*/
 
 
NTSTATUS IrpNotImplementedHandler(IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp) {
    Irp->IoStatus.Information = 0;
    Irp->IoStatus.Status = STATUS_NOT_SUPPORTED;
 
    UNREFERENCED_PARAMETER(DeviceObject);
    PAGED_CODE();
 
    // Complete the request
    IoCompleteRequest(Irp, IO_NO_INCREMENT);
 
    return STATUS_NOT_SUPPORTED;
}
 
/// <summary>
/// IRP Create Close Handler
/// </summary>
/// <param name="DeviceObject">The pointer to DEVICE_OBJECT</param>
/// <param name="Irp">The pointer to IRP</param>
/// <returns>NTSTATUS</returns>
NTSTATUS IrpCreateCloseHandler(IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp) {
    Irp->IoStatus.Information = 0;
    Irp->IoStatus.Status = STATUS_SUCCESS;
 
    UNREFERENCED_PARAMETER(DeviceObject);
    PAGED_CODE();
 
    // Complete the request
    IoCompleteRequest(Irp, IO_NO_INCREMENT);
 
    return STATUS_SUCCESS;
}
 
/// <summary>
/// IRP Unload Handler
/// </summary>
/// <param name="DeviceObject">The pointer to DEVICE_OBJECT</param>
/// <returns>NTSTATUS</returns>
VOID IrpUnloadHandler(IN PDRIVER_OBJECT DriverObject) {
    UNICODE_STRING DosDeviceName = { 0 };
 
    PAGED_CODE();
 
    RtlInitUnicodeString(&DosDeviceName, DOS_DEV_NAME);
 
    // Delete the symbolic link
    IoDeleteSymbolicLink(&DosDeviceName);
 
    // Delete the device
    IoDeleteDevice(DriverObject->DeviceObject);
 
    DbgPrint("[!] Hello Driver Unloaded\n");
}
 
/// <summary>
/// IRP Device IoCtl Handler
/// </summary>
/// <param name="DeviceObject">The pointer to DEVICE_OBJECT</param>
/// <param name="Irp">The pointer to IRP</param>
/// <returns>NTSTATUS</returns>
NTSTATUS IrpDeviceIoCtlHandler(IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp) {
    ULONG IoControlCode = 0;
    PIO_STACK_LOCATION IrpSp = NULL;
    NTSTATUS Status = STATUS_NOT_SUPPORTED;
 
    UNREFERENCED_PARAMETER(DeviceObject);
    PAGED_CODE();
 
    IrpSp = IoGetCurrentIrpStackLocation(Irp);
    IoControlCode = IrpSp->Parameters.DeviceIoControl.IoControlCode;
 //根据不同的控制码作出不同的处理
    if (IrpSp) {
        switch (IoControlCode) {
        case HELLO_DRV_IOCTL:
            DbgPrint("[< HelloDriver >] Hello from the Driver!\n");
            break;
        default:
            DbgPrint("[-] Invalid IOCTL Code: 0x%X\n", IoControlCode);
            Status = STATUS_INVALID_DEVICE_REQUEST;
            break;
        }
    }
 
    Irp->IoStatus.Status = Status;//3环的GetLastError得到的就是这个值
    Irp->IoStatus.Information = 0;//返回数据的字节数  没有写0
 
    // Complete the request
    IoCompleteRequest(Irp, IO_NO_INCREMENT);//用于结束这个请求
 
    return Status;
}
-------------------------------------------------------------------------------------------------------------------
以上都是分发函数
 
-------------------------------------------------------------------------------------------------------------------
 
 
 // 驱动入口函数
NTSTATUS DriverEntry(IN PDRIVER_OBJECT DriverObject, IN PUNICODE_STRING RegistryPath) {
    UINT32 i = 0;
    PDEVICE_OBJECT DeviceObject = NULL;
    NTSTATUS Status = STATUS_UNSUCCESSFUL; //绝大多数内核函数都会有一个返回值,类型为NTSTATUS。该类型本质就是一个LONG
    UNICODE_STRING DeviceName, DosDeviceName = { 0 };
 
    UNREFERENCED_PARAMETER(RegistryPath);
    PAGED_CODE();
 
    RtlInitUnicodeString(&DeviceName, DEV_NAME);//初始化unicode字符串,为其赋值。不会申请内存。
    RtlInitUnicodeString(&DosDeviceName, DOS_DEV_NAME);
 
    DbgPrint("[*] In DriverEntry\n");//驱动的打印函数
 
    //1.创建设备,指定设备的名字
    Status = IoCreateDevice(DriverObject,
        0,
        &DeviceName,
        FILE_DEVICE_UNKNOWN,
        FILE_DEVICE_SECURE_OPEN,
        FALSE,
        &DeviceObject);
 /*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); 用来返回结果,如果函数执行成功(返回值为STATUS_SUCCESS),*DeviceObject就是生成的设备对象对的指针
生成设备对象DriverObject,设备对象可以在内核中暴露出来给应用层,应用层可以像操作文件一样操作它。这
 
*/
 
 
    if (!NT_SUCCESS(Status)) {
        if (DeviceObject) {
            // Delete the device
            IoDeleteDevice(DeviceObject);
        }
 
        DbgPrint("[-] Error Initializing HelloDriver\n");
        return Status;
    }
/*
分发函数必须首先设置给驱动对象
注意,下面的片段中将所有的分发函数(实际上MajorFunction是一个函数指针数组,保存所有分发函数的指针>都设置成同一个函数,这是一种简单的处理方案。
 
 
*/
    // Assign the IRP handlers
    for (i = 0; i <= IRP_MJ_MAXIMUM_FUNCTION; i++) {
        // Disable the Compiler Warning: 28169
#pragma warning(push)
#pragma warning(disable : 28169)
        DriverObject->MajorFunction[i] = IrpNotImplementedHandler;
#pragma warning(pop)
    }
 // 5.请求的处理
 
    // Assign the IRP handlers for Create, Close and Device Control
    /*
    在用户层,我们每次调用CreateFile、OpenFIle、DeleteFile、CloseHandle等API时,都会向0环发送一个消息,这个消息成为IRP数据包。这些API称为设备操作API。如:当调用CreateFile时,会向内核层发送一个名为IRP_MJ_CREATE的打开设备的IRP消息。其他常用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进行消息的传递
    */
 
 
    DriverObject->MajorFunction[IRP_MJ_CREATE] = IrpCreateCloseHandler;
    DriverObject->MajorFunction[IRP_MJ_CLOSE] = IrpCreateCloseHandler;
    DriverObject->MajorFunction[IRP_MJ_DEVICE_CONTROL] = IrpDeviceIoCtlHandler;
 
    // Assign the driver Unload routine

[招生]科锐逆向工程师培训(2024年11月15日实地,远程教学同时开班, 第51期)

收藏
免费 4
支持
分享
最新回复 (2)
雪    币: 14303
活跃值: (10776)
能力值: ( LV12,RANK:360 )
在线值:
发帖
回帖
粉丝
2
感谢分享
2021-11-13 16:38
0
雪    币: 732
活跃值: (192)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
3
看到一本天书。感谢分享。
2022-1-17 20:24
0
游客
登录 | 注册 方可回帖
返回
//