Joe1sn's Cabinet

【Win Pwn】HEVD-内核栈溢出(上)

开始做HEVD来熟悉windows的内核漏洞利用方式时,发现大多数的资料依旧基于windows7,但是目前主流的操作系统已经是win10,所以还是得更上时代潮流的

文章已在先知社区投稿:https://xz.aliyun.com/t/13363

0. 前置环境

更基础

我使用的Windows版本是

image-20240122113406037

I. 编程环境

如果你想快速搭建一个驱动开发环境可以参考B站上的一些资料,如:配置驱动开发环境
如果按照步骤vs没有KernelModDriver这一模板,找到vs目录的WDK.vsix双击即可

一段驱动的主要代码,在main.cpp中编写

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#include <ntifs.h>
#include "win10.h"
#include "x64.h"

VOID
DriverUnload(PDRIVER_OBJECT DriverObject)
{
UNREFERENCED_PARAMETER(DriverObject);
DbgPrint("Driver Stopping -> %wZ\n", &DriverObject->DriverName);
}

NTSTATUS
DriverEntry(PDRIVER_OBJECT DriverObject, PUNICODE_STRING RegistryPath)
{
UNREFERENCED_PARAMETER(RegistryPath);

DbgPrint("Driver Running -> %wZ\n", &DriverObject->DriverName);
DriverObject->DriverUnload = DriverUnload;
return STATUS_SUCCESS;
}

规定了驱动加载和卸载的两个函数,并在加载和卸载时打印调试信息

image-20240122115841033

II. 调试环境

传统串口调试

添加串口

image-20240115212926345

在虚拟机中Win+R召唤msconfig,打开允许串口调试

image-20240122114955532

然后在(记得用管理员打开)windbg中按照图中配置即可,其他选项不变

image-20240122115049006

一直找不到管道的话可以点Break再等会儿就有了

img

VirtualKD法调试

项目链接:http://virtualkd.sysprogs.org/

安装后,vmmon64.exe就行了

但是用了就不能用串口调试了

image-20240115222104521

打开信息显示+DbgView

\HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Session Manager\注册表中添加Debug Print Filter,并设置一个DefaultDWORD值,你可以将其设置为0x8或者允许更多调试信息的0xf

image-20240122114737436

虽然说windbg确实能打印出DbgPrint,但是HEVD使用的是DbgPrintEx,接受不到,安装了DbgView后,他会把调试信息打印出了并且windbg也能收到

image-20240122113922891

调试指令

如果出现了以下情况且虚拟机卡顿,可以使用这两个指令关闭输出

image-20240122114457173

1
2
1: kd> ed nt!Kd_SXS_Mask 0
1: kd> ed nt!Kd_FUSION_Mask 0

关闭这两个函数的输出,莫名其妙变卡的话再用一下

III. 驱动加载

使用KmdManager.exe,毛子的黑科技,使用时需要管理员启动

也可以使用osLoader啥的

这里用KmdKit的KmdManager演示,运行HEVD

image-20240115234008160

image-20240117111218338

那么就算搭建成功

A. 编程基础

关于内核模式驱动程序:https://learn.microsoft.com/zh-cn/windows-hardware/drivers/kernel/

内核中交互是通过IRP请求进行交互的

IRP 结构是表示 I/O 请求数据包的部分不透明结构。 IRP 结构的未记录成员是保留的,仅由 I/O 管理器使用,在某些情况下,由文件系统驱动程序 (FSD) 使用。

MSDN:https://learn.microsoft.com/zh-cn/windows-hardware/drivers/ddi/wdm/ns-wdm-_irp

使用到的内存堆栈为I/O Stack

I/O 管理器为分层驱动程序链中的每个驱动程序提供其设置的每个 IRP 的 I/O 堆栈位置。 每个 I/O 堆栈位置都包含 一个IO_STACK_LOCATION结构。

MSDN:https://learn.microsoft.com/zh-cn/windows-hardware/drivers/kernel/i-o-stack-locations

说明 irp 中 i/o 堆栈位置的内容的示意图。

IRP通过iostack发送给设备,对应的是应用层的“消息”。设备可以存在(硬盘等),也可以不存在(QQ Protect驱动等)。

根据上图,应用层通过和设备对象(FDO)进行交互,设备(PDO)再和设备对象交互,实现交互。同时FDO向PDO的交互不是必要的。

HAL 硬件抽象层

HAL通常是一个独立的动态链接库,windows自身携带多种HAL,但是在系统安装的时候只会选择一种,名为hal.dll。涉及中断控制器、单处理器/多处理器硬件断点。

image-20240116133826071

B. 代码

图:driver-object 结构中的函数指针。

创建设备

MSDN:https://learn.microsoft.com/en-us/windows-hardware/drivers/kernel/device-tree

使用DeviceTree,可以找到:https://web.archive.org/web/20200519214156/http://www.osronline.com/OsrDown.cfm/devicetree_v230.zip

image-20240116141544394

1
2
3
4
5
6
7
8
UNICODE_STRING DeviceName = { 0 };          //设备名
PDEVICE_OBJECT pDevice = NULL; //设备对象
RtlInitUnicodeString(&DeviceName, DEVICE_NAME);
Status = IoCreateDevice(DriverObject, 0, &DeviceName, FILE_DEVICE_UNKNOWN, 0, TRUE, &pDevice);
if (!NT_SUCCESS(Status)) {
DbgPrint("Create Device Failed: %x\n", Status);
return Status;
}

创建符号链接

image-20240116141604421

image-20240116141556022

符号链接就是类似与Z:\之前的前缀

使用WinObj可以看到,我用的再第一章中下载的KdmKie中的SymLinks(太老了,建议换一个)

image-20240116141803859

1
2
3
4
5
6
7
8
UNICODE_STRING SymLink = { 0 };
RtlInitUnicodeString(&SymLink, SYM_NAME);
Status = IoCreateSymbolicLink(&SymLink, &DeviceName);
if (!NT_SUCCESS(Status)) {
DbgPrint("Create Symbol Link Failed: %x\n", Status);
IoDeleteDevice(pDevice);
return Status;
}

关联功能的交互

I. 创建“句柄”

image-20240116142649799

MSDN:https://learn.microsoft.com/zh-cn/windows-hardware/drivers/gettingstarted/minidrivers-and-driver-pairs

具体调试方法也在上面的文档中

每个内核模式驱动程序都必须实现名为 DriverEntry 的函数,该函数在加载驱动程序之后会立即得到调用。 DriverEntry 函数使用指向驱动程序实现的一些其他函数的指针来填充 DRIVER_OBJECT 结构的某些成员。 例如,DriverEntry 函数使用指向驱动程序的 Unload 函数的指针来填充 DRIVER_OBJECT 结构的 Unload 成员

image-20240116143040078

1
DriverObject->MajorFunction[IRP_MJ_CREATE] = MyCreate;

根据上面的文档,创建的函数和主函数Entry差不多,这里用的是设备对象,不是驱动对象

1
2
3
4
5
6
7
8
9
NTSTATUS 
MyCreate(PDEVICE_OBJECT pdevice, PIRP pIrp) {
NTSTATUS RET = STATUS_SUCCESS;
DbgPrint("My Device Has Opened\n");
pIrp->IoStatus.Status = RET;
pIrp->IoStatus.Information = 0;
IoCompleteRequest(pIrp, IO_NO_INCREMENT);
return RET;
}

II. 关闭“句柄”

1
2
DriverObject->MajorFunction[IRP_MJ_CLOSE] = MyClose;
DriverObject->MajorFunction[IRP_MJ_CLEANUP] = MyClean;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
NTSTATUS
MyClose(PDEVICE_OBJECT pdevice, PIRP pIrp) {
NTSTATUS RET = STATUS_SUCCESS;
DbgPrint("My Device Has Closed\n");
pIrp->IoStatus.Status = RET;
pIrp->IoStatus.Information = 0;
IoCompleteRequest(pIrp, IO_NO_INCREMENT);
return RET;
}

NTSTATUS
MyClean(PDEVICE_OBJECT pdevice, PIRP pIrp) {
NTSTATUS RET = STATUS_SUCCESS;
DbgPrint("My Device Has Clean\n");
pIrp->IoStatus.Status = RET;
pIrp->IoStatus.Information = 0;
IoCompleteRequest(pIrp, IO_NO_INCREMENT);
return RET;
}

image-20240116150816039

image-20240116150757589

成功加载

III. 在Ring3进行交互

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#include <iostream>
#include <windows.h>

int main()
{
HANDLE hDevice = NULL;
hDevice = CreateFileW(L"\\\\.\\My1DeviceLinker", GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
if (hDevice== INVALID_HANDLE_VALUE) {
std::cout << "Error Create File\n";
system("pause");
return 0;
}
std::cout << "Success open\n";
system("pause");

CloseHandle(hDevice);
std::cout << "Success close\n";
system("pause");
return 0;
}

image-20240116161601128

C. 二阶段

MSDN:https://learn.microsoft.com/zh-cn/windows-hardware/drivers/ddi/wdm/nf-wdm-iocreatedevice

关于创建设备的原型

1
2
3
4
5
6
7
8
9
NTSTATUS IoCreateDevice(
[in] PDRIVER_OBJECT DriverObject,
[in] ULONG DeviceExtensionSize,
[in, optional] PUNICODE_STRING DeviceName,
[in] DEVICE_TYPE DeviceType,
[in] ULONG DeviceCharacteristics,
[in] BOOLEAN Exclusive,
[out] PDEVICE_OBJECT *DeviceObject
);
1
[in] DeviceExtensionSize

指定要为 设备对象的设备扩展 分配的驱动程序确定的字节数。 设备扩展的内部结构是驱动程序定义的。

  • 维护设备状态信息。
  • 为驱动程序使用的任何内核定义对象或其他系统资源(如旋转锁)提供存储。
  • 保存驱动程序必须在系统空间中驻留的任何数据,以执行其 I/O 操作。

那么这就是一段描述要传输的数据的空间的大小的值。

从驱动中读取

驱动MyRead函数和DriverEntry

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
NTSTATUS
MyRead(PDEVICE_OBJECT pdevice, PIRP pIrp) {
UNREFERENCED_PARAMETER(pdevice);
NTSTATUS RET = STATUS_SUCCESS;
DbgPrint("My Device Has Read\n");

PIO_STACK_LOCATION pStack = IoGetCurrentIrpStackLocation(pIrp);
ULONG ReadSize = pStack->Parameters.Read.Length;
PCHAR Buffer = pIrp->AssociatedIrp.SystemBuffer;
DbgPrint("Ring3 Want Read %x\n", ReadSize);
RtlCopyMemory(Buffer, "Message From Driver", strlen("Message From Driver"));


pIrp->IoStatus.Status = RET;
pIrp->IoStatus.Information = strlen("Message From Driver");
IoCompleteRequest(pIrp, IO_NO_INCREMENT);
return RET;
}

R3

1
2
3
4
5
CHAR Test[0x40] = { 0 };
DWORD lpRead = 0;
ReadFile(hDevice, Test, 30, &lpRead, NULL);
printf("%p -%s--%d\n", Test, Test,lpRead);

  • 30是要读取的字节
  • lpRead是真实读取的字节

image-20240116163249545

每向下传递一层需要一个设备栈(可以试着从自己设计这样一个模式的角度想想)

SystemBufferpIrp->MdlAddress是同一块物理地址的两个不同虚拟地址(不同的映射)。

image-20240116165239380

需要设置读写方式

1
2
pDevice->Flags |= DO_BUFFERED_IO;
//设备创建成功,绑定符号链接

image-20240116171657645

向驱动中写入

R3

1
WriteFile(hDevice, "This is From Ring3.", strlen("This is From Ring3."), &lpRead, NULL);

驱动

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
NTSTATUS
MyWrite(PDEVICE_OBJECT pdevice, PIRP pIrp) {
UNREFERENCED_PARAMETER(pdevice);
NTSTATUS RET = STATUS_SUCCESS;
DbgPrint("My Device Has Wrtitten\n");

PIO_STACK_LOCATION pStack = IoGetCurrentIrpStackLocation(pIrp);
ULONG ReadSize = pStack->Parameters.Write.Length;
PCHAR Buffer = pIrp->AssociatedIrp.SystemBuffer;
DbgPrint("Ring3 Write Read %x\n", ReadSize);

RtlZeroMemory(pdevice->DeviceExtension, 200);
RtlCopyMemory(pdevice->DeviceExtension, Buffer, ReadSize);
DbgPrint("--%p-%s\n", Buffer, (PCHAR)pdevice->DeviceExtension);

pIrp->IoStatus.Status = RET;
pIrp->IoStatus.Information = strlen("Message From Driver");
IoCompleteRequest(pIrp, IO_NO_INCREMENT);
return RET;
}

image-20240116173208413

[IOCTL]自定义控制IO

IRP_MJ_DEVICE_CONTROL,定义IOCTL操作,很多内核的交互大多都是依靠此方式

这里程序接收一个数字返回值x2

驱动

定义操作标识

MSDN:https://learn.microsoft.com/zh-cn/windows-hardware/drivers/ddi/d4drvif/nf-d4drvif-ctl_code

1
2
#define IOCTL_MUL CTL_CODE(FILE_DEVICE_UNKNOWN, 0x9888, METHOD_BUFFERED, FILE_ANY_ACCESS)

  • 0x9888:标识符号

编写函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
NTSTATUS
MyControl(PDEVICE_OBJECT pdevice, PIRP pIrp) {
UNREFERENCED_PARAMETER(pdevice);
NTSTATUS RET = STATUS_SUCCESS;
DbgPrint("My Device Has IOCTL\n");

PIO_STACK_LOCATION pStack = IoGetCurrentIrpStackLocation(pIrp);
ULONG ioCode = pStack->Parameters.DeviceIoControl.IoControlCode; //功能码
ULONG inLen = pStack->Parameters.DeviceIoControl.InputBufferLength; //出入长度
//ULONG outLen = pStack->Parameters.DeviceIoControl.OutputBufferLength; //输出长度
ULONG ioInfo = 0;
switch (ioCode)
{
case IOCTL_MUL:
{
DWORDLONG inData = *(PDWORDLONG)pIrp->AssociatedIrp.SystemBuffer; //取出传输的数据
DbgPrint("Kernel Recive: %d, Len: %lld\n", inData, inLen);
inData *= 2;
DbgPrint("Kernel Data %d\n", inData);
*(PDWORDLONG)pIrp->AssociatedIrp.SystemBuffer = inData; //写回操作
ioInfo = 4;
break;
}

default:
RET = STATUS_UNSUCCESSFUL;
ioInfo = 0;
break;
}

pIrp->IoStatus.Status = RET;
pIrp->IoStatus.Information = ioInfo;
IoCompleteRequest(pIrp, IO_NO_INCREMENT);
return RET;
}

image-20240116185236732

一些关键函数

DriverEntry

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
NTSTATUS
DriverEntry(PDRIVER_OBJECT DriverObject, PUNICODE_STRING RegistryPath)
{
UNREFERENCED_PARAMETER(RegistryPath);
NTSTATUS Status = STATUS_SUCCESS;
DbgPrint("Driver Running -> %wZ\n", &DriverObject->DriverName);
DriverObject->DriverUnload = DriverUnload;


//创建设备
UNICODE_STRING DeviceName = { 0 }; //设备名
PDEVICE_OBJECT pDevice = NULL; //设备对象
RtlInitUnicodeString(&DeviceName, DEVICE_NAME);
Status = IoCreateDevice(DriverObject, 0x200, &DeviceName, FILE_DEVICE_UNKNOWN, 0, TRUE, &pDevice);
if (!NT_SUCCESS(Status)) {
DbgPrint("Create Device Failed: %x\n", Status);
return Status;
}

pDevice->Flags |= DO_BUFFERED_IO;
//设备创建成功,绑定符号链接

UNICODE_STRING SymLink = { 0 };
RtlInitUnicodeString(&SymLink, SYM_NAME);
Status = IoCreateSymbolicLink(&SymLink, &DeviceName);
if (!NT_SUCCESS(Status)) {
DbgPrint("Create Symbol Link Failed: %x\n", Status);
IoDeleteDevice(pDevice);
return Status;
}

DbgPrint("Device & Symbolic Link Created\n");
DriverObject->MajorFunction[IRP_MJ_CREATE] = MyCreate;
DriverObject->MajorFunction[IRP_MJ_CLOSE] = MyClose;
DriverObject->MajorFunction[IRP_MJ_CLEANUP] = MyClean;
DriverObject->MajorFunction[IRP_MJ_READ] = MyRead;
DriverObject->MajorFunction[IRP_MJ_WRITE] = MyWrite;
DriverObject->MajorFunction[IRP_MJ_DEVICE_CONTROL] = MyControl;
DbgPrint("Function Settal Done\n");

return Status;
}

DriverUnload

1
2
3
4
5
6
7
8
9
10
11
12
13
14
VOID
DriverUnload(PDRIVER_OBJECT DriverObject)
{
UNREFERENCED_PARAMETER(DriverObject);
DbgPrint("Driver Stopping -> %wZ\n", &DriverObject->DriverName);
DbgPrint("Device Stopping\n");
if (DriverObject->DeviceObject) {
IoDeleteDevice(DriverObject->DeviceObject);

UNICODE_STRING symname = { 0 };
RtlInitUnicodeString(&symname, SYM_NAME);
IoDeleteSymbolicLink(&symname);
}
}

用于在Ring3交互的项目的主函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
// R3Control.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
//

#include <iostream>
#include <windows.h>
#define IOCTL_MUL CTL_CODE(FILE_DEVICE_UNKNOWN, 0x9888, METHOD_BUFFERED, FILE_ANY_ACCESS)

int main()
{
HANDLE hDevice = NULL;
hDevice = CreateFileW(L"\\\\.\\My1DeviceLinker", GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
if (hDevice== INVALID_HANDLE_VALUE) {
std::cout << "Error Create File\n";
return 0;
}
std::cout << "Success open\n";
system("pause");

std::cout << "now IOCTL\n";
DWORDLONG a = 64;
DWORDLONG b = 0;
DWORD info = 0;
DeviceIoControl(hDevice, IOCTL_MUL, &a, sizeof(DWORDLONG), &b, sizeof(DWORDLONG), &info, NULL);
printf("value a: %lld, b: %lld\nreal info %d\n", a, b, info);

CloseHandle(hDevice);
std::cout << "Success close\n";
return 0;
}

参考

https://www.bilibili.com/video/BV1QJ411A7kR

https://space.bilibili.com/1992190180/

https://learn.microsoft.com/zh-cn/windows-hardware/drivers/kernel/xianzh