22.1 利用实验之 exploitme.sys
define DEVICE_NAME L”\Device\ExploitMe”
define DEVICE_LINK L”\DosDevices\DRIECTX1”
define FILE_DEVICE_EXPLOIT_ME 0x00008888
define IOCTL_EXPLOIT_ME (ULONG)CTL_CODE(FILE_DEVICE_EXPLOIT_ME,0x800,METHOD_NEITHER,FILE_WRITE_ACCESS)
//创建的设备对象指针 PDEVICE_OBJECT g_DeviceObject;
/** 驱动派遣例程函数 输入:驱动对象的指针,Irp指针 输出:NTSTATUS类型的结果 **/ NTSTATUS DrvDispatch(IN PDEVICE_OBJECT driverObject,IN PIRP pIrp) { PIO_STACK_LOCATION pIrpStack;//当前的pIrp栈 PVOID Type3InputBuffer;//用户态输入地址 PVOID UserBuffer;//用户态输出地址 ULONG inputBufferLength;//输入缓冲区的大小 ULONG outputBufferLength;//输出缓冲区的大小 ULONG ioControlCode;//DeviceIoControl的控制号 PIO_STATUS_BLOCK IoStatus;//pIrp的IO状态指针 NTSTATUS ntStatus=STATUS_SUCCESS;//函数返回值
//获取数据
pIrpStack = IoGetCurrentIrpStackLocation(pIrp);
Type3InputBuffer = pIrpStack->Parameters.DeviceIoControl.Type3InputBuffer;
UserBuffer = pIrp->UserBuffer;
inputBufferLength = pIrpStack->Parameters.DeviceIoControl.InputBufferLength;
outputBufferLength = pIrpStack->Parameters.DeviceIoControl.OutputBufferLength;
ioControlCode = pIrpStack->Parameters.DeviceIoControl.IoControlCode;
IoStatus=&pIrp->IoStatus;
IoStatus->Status = STATUS_SUCCESS;// Assume success
IoStatus->Information = 0;// Assume nothing returned
//根据 ioControlCode 完成对应的任务
switch(ioControlCode)
{
case IOCTL_EXPLOIT_ME:
if ( inputBufferLength >= 4 && outputBufferLength >= 4 )
{
*(ULONG *)UserBuffer = *(ULONG *)Type3InputBuffer;
IoStatus->Information = sizeof(ULONG);
}
break;
}
//返回
IoStatus->Status = ntStatus;
IoCompleteRequest(pIrp,IO_NO_INCREMENT);
return ntStatus;
}
/**
驱动卸载函数
输入:驱动对象的指针
输出:无
**/
VOID DriverUnload( IN PDRIVER_OBJECT driverObject )
{
UNICODE_STRING symLinkName;
KdPrint((“DriverUnload: 88!\n”));
RtlInitUnicodeString(&symLinkName,DEVICE_LINK);
IoDeleteSymbolicLink(&symLinkName);
IoDeleteDevice( g_DeviceObject );
}
/*
驱动入口函数(相当于main函数)
输入:驱动对象的指针,服务程序对应的注册表路径
输出:NTSTATUS类型的结果
**/
NTSTATUS DriverEntry( IN PDRIVER_OBJECT driverObject, IN PUNICODE_STRING registryPath )
{
NTSTATUS ntStatus;
UNICODE_STRING devName;
UNICODE_STRING symLinkName;
int i=0;
//打印一句调试信息
KdPrint((“DriverEntry: Exploit me driver demo!\n”));
//创建设备
RtlInitUnicodeString(&devName,DEVICE_NAME);
ntStatus = IoCreateDevice( driverObject,
0,
&devName,
FILE_DEVICE_UNKNOWN,
0, TRUE,
&g_DeviceObject );
if (!NT_SUCCESS(ntStatus))
{
return ntStatus;
}
//创建符号链接
RtlInitUnicodeString(&symLinkName,DEVICE_LINK);
ntStatus = IoCreateSymbolicLink( &symLinkName,&devName );
if (!NT_SUCCESS(ntStatus))
{
IoDeleteDevice( g_DeviceObject );
return ntStatus;
}
//设置该驱动对象的卸载函数
driverObject->DriverUnload = DriverUnload;
//设置该驱动对象的派遣例程函数
for (i = 0; i < IRP_MJ_MAXIMUM_FUNCTION; i++)
{
driverObject->MajorFunction[i] = DrvDispatch;
}
//返回成功结果
return STATUS_SUCCESS;
}
```
- 在派遣例程DrvDispatch函数中:
- IOCTL_EXPLOIT_ME 这个 IoControlCode 指定的 Ring3/Ring0 内存访问为METHOD_NEITHER 方式。
- 对“IOCTL_EXPLOIT_ME”的处理,实际上就是将 Ring3 输入缓冲区中的第一个 ULONG数据写入 Ring3 输出缓冲区的第一个 ULONG 数据中。
- Type3InputBuffer 表示 Ring3 输入缓冲区指针。
- UserBuffer 表示 Ring3 输出缓冲区指针。
- inputBufferLength 表示 Ring3 输入缓冲区的大小(字节数)。
- outputBufferLength 表示 Ring3 输出缓冲区的大小(字节数)。
使用METHOD_NEITHER方式来进行Ring3/Ring0间的通信的脆弱点:输入、输出地址都是由 Ring3 程序来指定的,读写却是在 Ring0 完成的。因此 Ring3 可以将输出缓冲区地址指定为内核高端地址,这相当于篡改了内核中任意地址的数据,而且可以篡改为任何数值。
22.2 内核漏洞利用思路
22.3 内核漏洞利用方法
目前常见的内核漏洞利用方法主要有两种:一是篡改内核内存数据;二是执行 Ring0 Shellcode。
- 执行Ring0 Shellcode的思路是:Ring0 中是有很多内核 API函数,这些函数地址大多保存于一些表中,并且这个表也是由内核导出的。如果能修改这些表中的内核 API 函数地址为事先准备好的 ShellCode 存放的地址(本进程空间内存地址),然后在本进程中调用这个内核 API函数,这样便实现了在 Ring0 权限下执行 Shellcode 的目的。
- 对 22.1 节中 exploitme.sys 的漏洞进行利用。该漏洞能够使得向任意地址写入任意数据。利用的思路是:首先在当前进程(exploit.exe)的 0x0 地址处申请内存,并存放 Ring0 Shellcode 代码,然后利用漏洞将 HalDispatchTable 中的HalQuerySystemInformation 函数地址改写为 0x0,最后再调用该函数的上层封装函数NtQueryIntervalProfile,于是事先准备好的 Ring0 Shellcode 将会被执行。
- 要修改HalQuerySystemInformation函数的入口地址为0x0,首先需要知道:
- HalQuerySystemInformation函数是HalDispatchTable表中的第一个函数。
- HalDispatchTable表是内核模块 hal.dll 导出的一个函数表
- 要想调用HalQuerySystemInformation函数,需要知道:
- 需要通过HalQuerySystemInformation函数的上层封装函数NtQueryIntervalProfile去调用它。
- NtQueryIntervalProfile函数和HalQuerySystemInformation函数之间的关系:
- NtQueryIntervalProfile 函数有两个参数,一个输入参数 ProfileSource 和一个输出参数 Interval。ProfileSource 是一个 KPROFILE_SOURCE 枚举类型变量,如下图所示:
- KeQueryIntervalProfile 函数的处理流程:如果输入的 ProfileSource 参数既不等于ProfileTime,也不等于 ProfileAlignmentFixup,那么就会调用HalQuerySystemInformation 函数来获取结果。
- 所以只要调用 NtQueryIntervalProfile 函数,输入的第一个参数 ProfileSource不等于 ProfileTime,也不等于 ProfileAlignmentFixup,那么最终会调用到HalQuerySystemInformation 函数。
- Ring0 Shellcode 常见的用法:
- 提权到 SYSTEM:修改当前进程的 token 为 SYSTEM 进程的 token,这样当前进程便具备了系统最高权限,可以完全控制整个系统。
- 恢复内核 Hook/Inline Hook:安全软件也大部分是通过Hook/Inline Hook 系统内核函数来实现防御的。因此可以通过恢复这些内核 Hook/Inline Hook来突破安全软件,甚至瓦解其整个防御体系。
- 添加调用门/中断门/任务门/陷阱门:四门机制是出入 Ring0/Ring3 的重要手段。若能在系统中成功添加一个门,就能在后续代码中,自由出入 Ring0 和 Ring3。