CVE-2021-40425,CVE-2021-40424
An out-of-bounds read vulnerability exists in the IOCTL GetProcessCommand and B_03 of Webroot Secure Anywhere 21.4. A specially-crafted executable can lead to denial of service. An attacker can issue an ioctl to trigger this vulnerability.
Webroot Secure Anywhere 21.4
Secure Anywhere - https://www.webroot.com/us/en/home/products/av
7.1 - CVSS:3.0/AV:L/AC:L/PR:N/UI:N/S:C/C:N/I:N/A:H
CWE-125 - Out-of-bounds Read
A windows device driver is almost like a kernel DLL that, once loaded, provides additional features. In order to communicate with these device drivers, Windows has a major component named Windows I/O Manager.
The Windows IO Manager is responsible for the interface between user applications and device drivers. It implements I/O Request Packets (IRP) to enable the communication with the devices drivers, answering to all I/O requests.
For more information see the Microsoft website https://docs.microsoft.com/en-us/windows-hardware/drivers/kernel/example-i-o-request—an-overview.
The driver is responsible for creating a device interface with different functions to answer to specific codes, named major code function.
If the designer wants to implement customized functions into a driver, there is one major function code named IRP_MJ_DEVICE_CONTROL
.
Handling such major code function, device drivers will support specific I/O Control Code (IOCTL) through a dispatch routine.
The Windows I/O Manager provides three different methods to enable the shared memory: - Buffered I/O - Direct I/O - Neither I/O
Without getting into the details of the IO Manager mechanisms, the method Buffered I/O is often the easiest one for handling memory user buffers from a device perspective.
The I/O Manager is providing all features to enable device drivers sharing buffers between userspace and kernelspace. It will be responsible for copying data back and forth.
Let’s see some examples of routines (which you should not copy as is) that explain how things work.
When creating a driver, you’ll have several functions to implement, and you’ll find some dispatcher routines to handle different IRP as follows:
extern "C"
NTSTATUS DriverEntry(_In_ PDRIVER_OBJECT pDriverObject, _In_ PUNICODE_STRING RegistryPath)
{
[...]
pDriverObject->DriverUnload = DriverUnload;
pDriverObject->MajorFunction[IRP_MJ_DEVICE_CONTROL] = DriverIOctl;
pDriverObject->MajorFunction[IRP_MJ_CREATE] = DriverCreate;
pDriverObject->MajorFunction[IRP_MJ_CLOSE] = DriverClose;
[...]
}
The DriverEntry is the function main
for a driver. This is the place where initializations start.
We can see for example the pDriverObject
which is a PDRIVER_OBJECT
object given by the system to associate different routines, to be called against specific codes, into the Majorfunction
table IRP_MJ_DEVICE_CONTROL
for DriverIOctl
etc.
Then later inside the driver you’ll see the implementation of the DriverIOctl
routine responsible for handling the IOCTL code. It can be something like below:
NTSTATUS DriverIOctl(PDEVICE_OBJECT pDevObject, PIRP pIrp)
{
[...]
auto pIrpSp = IoGetCurrentIrpStackLocation(pIrp);
switch (pIrpSp->Parameters.DeviceIoControl.IoControlCode)
{
case IO_CREATE_EXAMPLE:
ioctl_inbuffer_data = (ioctl_inbuffer*)pIrp->AssociatedIrp.SystemBuffer;
auto InputBufferLength = pIrpSp->Parameters.DeviceIoControl.InputBufferLength;
auto OutputBufferLength = pIrpSp->Parameters.DeviceIoControl.OutputBufferLength;
[...] some code
pIrp->IoStatus.Information = 0;
pIrp->IoStatus.Status = status;
break;
}
pIrp->IoStatus.Information = some value;
pIrp->IoStatus.Status = status;
return status;
}
First the we can see the pIrp
pointer to an IRP structure (the description would be out of the scope of this document). Keep in mind this pointer will be useful for accessing data.
So here for example we can observe some switch-case implementation depending on the IoControlCode
IOCTL.
When the device driver gets an IRP with code value IO_CREATE_EXAMPLE
, it performs the operations below the case.
To get into the buffer data exchanged between userspace and kernelspace and vice-versa, we’ll look into SystemBuffer
passed as an argument through the pIrp
pointer.
On the device side, the pointer inside an IRP represents the user buffer, usually a field named Irp->AssociatedIrp.SystemBuffer
, when the buffered I/O mechanism is chosen. The specification of the method is indicated by the code itself.
On the userspace side, an application would need to gain access to the device driver symbolic link if it exists, then send some ioctl requests as follows:
success = ::DeviceIoControl(
ghDevice,
IO_CREATE_EXAMPLE, // control code
&gpIoctl, // input buffer
sizeof(struct ioctl_inbuffer), // input buffer length
&gpIoctl, // output buffer
sizeof(struct ioctl_inbuffer), // output buffer length
&returned,
nullptr
);
Such a call will result in an IRP with a major code IRP_MJ_DEVICE_CONTROL
and a control code to IO_CREATE_EXAMPLE
. The buffer passed from userspace here as input gpIoctl
, and output will be accessible from the device driver in the kernelspace via pIrp->AssociatedIrp.SystemBuffer
. The lengths specified on the DeviceIoControl
parameters will be used to build the IRP, and the device would be able to get them into the InputBufferLength
and the OutputBufferLength
respectively.
Now below we’ll see two examples of out-of-bounds read, which can lead to different behaviors and more frequently a local denial of service and blue screen of death through the usage of the device driver WRCore_x64
.
The GetProcessCommandLine
IOCTL request could cause an out-of-bounds read in the device driver WRCore_x64
, as shown below:
WRCore_x64+0x190b:
fffff80f`ad60190b 8b4024 mov eax,dword ptr [rax+24h] ds:ffff8488`e3a0b004=????????
When inspecting the memory, we see the out-of-bounds read in non-mapped memory
3: kd> dq ffff8488e3a0afe0
ffff8488`e3a0afe0 00000000`00000000 00000000`00000000
ffff8488`e3a0aff0 00000000`00000000 55555555`55555555
ffff8488`e3a0b000 ????????`???????? ????????`????????
ffff8488`e3a0b010 ????????`???????? ????????`????????
ffff8488`e3a0b020 ????????`???????? ????????`????????
ffff8488`e3a0b030 ????????`???????? ????????`????????
ffff8488`e3a0b040 ????????`???????? ????????`????????
ffff8488`e3a0b050 ????????`???????? ????????`????????
The call stack is the following, clearly indicating the out-of-bounds happening in WRCore_x64+0x190b
3: kd> k
# Child-SP RetAddr Call Site
00 ffffe781`793f6c18 fffff802`1946046f nt!KeBugCheckEx
01 ffffe781`793f6c20 fffff802`192b5500 nt!MiSystemFault+0x18d0bf
02 ffffe781`793f6d20 fffff802`1941b35e nt!MmAccessFault+0x400
03 ffffe781`793f6ec0 fffff80f`ad60190b nt!KiPageFault+0x35e
04 ffffe781`793f7050 fffff80f`ad61decf WRCore_x64+0x190b
05 ffffe781`793f7080 fffff80f`ad61c6d3 WRCore_x64+0x1decf
06 ffffe781`793f7110 fffff802`193830b7 WRCore_x64+0x1c6d3
07 ffffe781`793f7140 fffff802`199d7f1a nt!IopfCallDriver+0x53
08 ffffe781`793f7180 fffff802`19454db1 nt!IovCallDriver+0x266
09 ffffe781`793f71c0 fffff802`1968b308 nt!IofCallDriver+0x1af5a1
0a ffffe781`793f7200 fffff802`1968abd5 nt!IopSynchronousServiceTail+0x1a8
0b ffffe781`793f72a0 fffff802`1968a5d6 nt!IopXxxControlFile+0x5e5
0c ffffe781`793f73e0 fffff802`1941ebb5 nt!NtDeviceIoControlFile+0x56
0d ffffe781`793f7450 00007ffa`a450ce54 nt!KiSystemServiceCopyEnd+0x25
0e 00000026`34d4fbf8 00000000`00000000 0x00007ffa`a450ce54
This corresponds to the following pseudo-code named get_hprocess_from_webroot_irp
. The function get_hprocess_from_webroot_irp
has an argument named here webroot_irp
; we’ll see later where it’s coming from.
LINE1 __int64 __fastcall get_hprocess_from_webroot_irp(webroot_irp *webroot_irp)
LINE2 {
LINE3 if ( webroot_irp->possible_size_of_self <= 32ui64 )
LINE4 j_rtl_failfast_wrappoer((__int64)webroot_irp);
LINE5 return (unsigned int)webroot_irp->getProcessInfo->hprocess;
LINE6 }
The out-of-bounds read happens at LINE5 while attempting to read what should be the hprocess
.
The function get_hprocess_from_webroot_irp
is called at LINE28 from a function named GetProcessCommandLine
. The function GetProcessCommandLine
is an IOCTL handler which is responsible for returning information against some specific process ID.
Here below, the pseudo-code corresponds to GetProcessCommandLine
:
LINE8 MACRO_STATUS __fastcall GetProcessCommandLine(IRP *pIrp, _IO_STACK_LOCATION *io_stack_loc)
LINE9 {
[...]
LINE20
LINE21 if ( io_stack_loc->Parameters.DeviceIoControl.InputBufferLength < 0x18
LINE22 || io_stack_loc->Parameters.DeviceIoControl.OutputBufferLength < 0x18 )
LINE23 {
LINE24 return STATUS_SINGLE_STEP|STATUS_OBJECT_NAME_EXISTS;
LINE25 }
LINE26 build_webroot_irp(&webroot_irp, (getProcessInfo *)pIrp->AssociatedIrp.SystemBuffer);
LINE27 process_list_related_0 = (process_related *)get_process_list_related_0();
LINE28 p_systemBuffer = get_hprocess_from_webroot_irp(&webroot_irp);
LINE29 sub_14000F8EC(process_list_related_0, &_result, p_systemBuffer);
LINE30 nt_status = 0;
LINE31 if ( !_result.some_ptr )
LINE32 {
LINE33 nt_status = STATUS_UNSUCCESSFUL;
LINE34 LABEL_11:
LINE35 sub_14000159C(&_result);
LINE36 sub_14001C190(&webroot_irp);
LINE37 return (unsigned __int64)nt_status;
LINE38 }
LINE39 field_28_of_irp = get_field_28_of_irp(&webroot_irp);
LINE40 size_to_check = (unsigned __int64)*(unsigned __int16 *)sub_14000D5E8(_result.some_ptr) >> 1;
LINE41 v8 = *(_WORD **)sub_140001A8C(&webroot_irp);
LINE42 if ( !v8 || field_28_of_irp < size_to_check )
LINE43 {
LINE44 // set some length
LINE45 sub_14000199C(&webroot_irp, size_to_check);
LINE46 pIrp->IoStatus.Information = 24i64;
LINE47 goto LABEL_11;
LINE48 }
LINE49 v9 = sub_14000D5E8(_result.some_ptr);
LINE50 v10 = &word_140028480;
LINE51 if ( *(_QWORD *)(v9 + 8) )
LINE52 v10 = *(const wchar_t **)(v9 + 8);
LINE53 _mm_lfence();
LINE54 ProbeForWrite(v8, size_to_check, 1u);
LINE55 perform_write_into_dest(v8, field_28_of_irp, (__int64)v10, size_to_check);
LINE56 _mm_lfence();
LINE57 sub_140001810(&webroot_irp, size_to_check);
LINE58 pIrp->IoStatus.Information = 24i64;
LINE59 sub_14000159C(&_result);
LINE60 sub_14001C190(&webroot_irp);
LINE61 return 0i64;
LINE62 }
The function build_webroot_irp
, called at LINE26, is responsible for building the webroot_irp
object, which will be used as an argument in the culprit function.
LINE63 webroot_irp *__fastcall build_webroot_irp(webroot_irp *webroot_irp, getProcessInfo *getProcessInfoData)
LINE64 {
LINE65 webroot_irp->table_function = &off_14002AAA0;
LINE66 webroot_irp->byte8 = 0;
LINE67 webroot_irp->qword10 = 0i64;
LINE68 webroot_irp->PERESOURCE = 0i64;
LINE69 webroot_irp->qword20 = 0i64;
LINE70 webroot_irp->possible_size_of_self = 56i64;
LINE71 webroot_irp->getProcessInfo = getProcessInfoData;
LINE72 if ( !getProcessInfoData )
LINE73 j_rtl_failfast_wrappoer((__int64)webroot_irp);
LINE74 webroot_irp->table_function = &off_14002AAA0;
LINE75 return webroot_irp;
LINE76 }
When looking at LINE26, we can see our buffer AssociatedIrp.SystemBuffer
is corresponding to the parameter named here getProcessInfoData
.
The function build_webroot_irp
is associating the buffer getProcessInfoData
to the variable webroot_irp->getProcessInfo
as we can see at LINE71.
When reversing a bit more the functions around this spot, we can deduce the expected structure getProcessInfo
format should correspond to something like:
00000000 getProcessInfo struc ; (sizeof=0x38, mappedto_561)
00000000 qword_1 dq ?
00000008 qword_2 dq ?
00000010 qword_3 dq ?
00000018 qword_4 dq ?
00000020 dword_1 dd ?
00000024 hprocess dd ?
00000028 length dq ?
00000030 output_buffer dq ?
00000038 getProcessInfo ends
The getProcessInfoData
corresponds to the user buffer we can control from userspace as input buffer.
We can see here the hprocess
at offset 0x24. And this is the hprocess
field from the structure getProcessInfo
which corresponds to the read value in the culprit function get_hprocess_from_webroot_irp
without precaution, causing the out-of-bounds read. The issue is the checks done in LINE21 and LINE22 are not preventing the out-of-bounds read happening, as they check only for valid input up to input size of 0x18 bytes.
3: kd> !analyze -v
*******************************************************************************
* *
* 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: ffff8488e3a0b004, memory referenced.
Arg2: 0000000000000000, value 0 = read operation, 1 = write operation.
Arg3: fffff80fad60190b, If non-zero, the instruction address which referenced the bad memory
address.
Arg4: 0000000000000002, (reserved)
Debugging Details:
------------------
Unable to load image \??\C:\Program Files\Webroot\Core\WRCore.x64.sys, Win32 error 0n2
KEY_VALUES_STRING: 1
Key : Analysis.CPU.mSec
Value: 3078
Key : Analysis.DebugAnalysisManager
Value: Create
Key : Analysis.Elapsed.mSec
Value: 4492
Key : Analysis.Init.CPU.mSec
Value: 7921
Key : Analysis.Init.Elapsed.mSec
Value: 22374
Key : Analysis.Memory.CommitPeak.Mb
Value: 83
Key : WER.OS.Branch
Value: vb_release
Key : WER.OS.Timestamp
Value: 2019-12-06T14:06:00Z
Key : WER.OS.Version
Value: 10.0.19041.1
VIRTUAL_MACHINE: HyperV
BUGCHECK_CODE: 50
BUGCHECK_P1: ffff8488e3a0b004
BUGCHECK_P2: 0
BUGCHECK_P3: fffff80fad60190b
BUGCHECK_P4: 2
READ_ADDRESS: ffff8488e3a0b004 Special pool
MM_INTERNAL_CODE: 2
IMAGE_NAME: WRCore.x64.sys
MODULE_NAME: WRCore.x64
FAULTING_MODULE: 0000000000000000
BLACKBOXBSD: 1 (!blackboxbsd)
BLACKBOXNTFS: 1 (!blackboxntfs)
BLACKBOXWINLOGON: 1
PROCESS_NAME: webroot_ioctl_a01.exe
TRAP_FRAME: ffffe781793f6ec0 -- (.trap 0xffffe781793f6ec0)
NOTE: The trap frame does not contain all registers.
Some register values may be zeroed or incorrect.
rax=ffff8488e3a0afe0 rbx=0000000000000000 rcx=ffffe781793f70b8
rdx=ffff8488e3a0afe0 rsi=0000000000000000 rdi=0000000000000000
rip=fffff80fad60190b rsp=ffffe781793f7050 rbp=ffff8488df571d80
r8=ffff8488df571d80 r9=000000000000000e r10=fffff80fad61c554
r11=0000000000000000 r12=0000000000000000 r13=0000000000000000
r14=0000000000000000 r15=0000000000000000
iopl=0 nv up ei pl nz na po nc
WRCore_x64+0x190b:
fffff80f`ad60190b 8b4024 mov eax,dword ptr [rax+24h] ds:ffff8488`e3a0b004=????????
Resetting default scope
STACK_TEXT:
ffffe781`793f6c18 fffff802`1946046f : 00000000`00000050 ffff8488`e3a0b004 00000000`00000000 ffffe781`793f6ec0 : nt!KeBugCheckEx
ffffe781`793f6c20 fffff802`192b5500 : 00000000`00001000 00000000`00000000 ffffe781`793f6f40 00000000`00000000 : nt!MiSystemFault+0x18d0bf
ffffe781`793f6d20 fffff802`1941b35e : 00000000`00000001 00000000`00001000 00000000`00000000 00000000`00000000 : nt!MmAccessFault+0x400
ffffe781`793f6ec0 fffff80f`ad60190b : fffff802`1968b308 fffff802`199eaf31 ffff8488`e377d0d8 fffff802`199f3a48 : nt!KiPageFault+0x35e
ffffe781`793f7050 fffff80f`ad61decf : ffff8488`e0205650 ffff8488`e2afe9a0 ffff8488`e377d0d8 fffff802`199e311a : WRCore_x64+0x190b
ffffe781`793f7080 fffff80f`ad61c6d3 : 00000000`00000000 ffff8488`df5196a0 ffff8488`df5195d0 ffff8488`e0205650 : WRCore_x64+0x1decf
ffffe781`793f7110 fffff802`193830b7 : ffff8488`df5195d0 00000000`00000000 ffff8488`00000001 ffff8488`df571d80 : WRCore_x64+0x1c6d3
ffffe781`793f7140 fffff802`199d7f1a : ffff8488`df5195d0 ffff8488`df571d80 00000000`20206f49 00000000`00000000 : nt!IopfCallDriver+0x53
ffffe781`793f7180 fffff802`19454db1 : ffff8488`df5195d0 00000000`00000002 00000000`00000028 ffff8488`e0205650 : nt!IovCallDriver+0x266
ffffe781`793f71c0 fffff802`1968b308 : ffffe781`793f7540 ffff8488`df5195d0 00000000`00000001 ffffe781`793f7540 : nt!IofCallDriver+0x1af5a1
ffffe781`793f7200 fffff802`1968abd5 : 00000000`9c412804 ffffe781`793f7540 00000000`00000000 ffffe781`793f7540 : nt!IopSynchronousServiceTail+0x1a8
ffffe781`793f72a0 fffff802`1968a5d6 : 00000000`00000000 00000000`00000000 00000000`00000000 00000000`00000000 : nt!IopXxxControlFile+0x5e5
ffffe781`793f73e0 fffff802`1941ebb5 : 00000000`000000e8 00000000`00000000 00000000`77566d4d ffffe781`793f74a8 : nt!NtDeviceIoControlFile+0x56
ffffe781`793f7450 00007ffa`a450ce54 : 00000000`00000000 00000000`00000000 00000000`00000000 00000000`00000000 : nt!KiSystemServiceCopyEnd+0x25
00000026`34d4fbf8 00000000`00000000 : 00000000`00000000 00000000`00000000 00000000`00000000 00000000`00000000 : 0x00007ffa`a450ce54
STACK_COMMAND: .thread ; .cxr ; kb
FAILURE_BUCKET_ID: AV_VRF_R_INVALID_IMAGE_WRCore.x64.sys
OS_VERSION: 10.0.19041.1
BUILDLAB_STR: vb_release
OSPLATFORM_TYPE: x64
OSNAME: Windows 10
FAILURE_ID_HASH: {4487bf4b-fcaa-ab42-3f1b-e48f350aad47}
Followup: MachineOwner
---------
Another IOCTL request with specific invalid data causes a similar issue in the device driver WRCore_x64
, as shown below:
WRCore_x64+0x192b:
fffff803`8a87192b 8b4020 mov eax,dword ptr [rax+20h] ds:ffffe48f`d8063000=????????
Investigating the call stack should lead us to the culprit routine :
3: kd> k
# Child-SP RetAddr Call Site
00 ffff810a`4aae4478 fffff801`03d24b12 nt!DbgBreakPointWithStatus
01 ffff810a`4aae4480 fffff801`03d240f6 nt!KiBugCheckDebugBreak+0x12
02 ffff810a`4aae44e0 fffff801`03c092b7 nt!KeBugCheck2+0x946
03 ffff810a`4aae4bf0 fffff801`03c5c46f nt!KeBugCheckEx+0x107
04 ffff810a`4aae4c30 fffff801`03ab1500 nt!MiSystemFault+0x18d0bf
05 ffff810a`4aae4d30 fffff801`03c1735e nt!MmAccessFault+0x400
06 ffff810a`4aae4ed0 fffff803`8a87192b nt!KiPageFault+0x35e
07 ffff810a`4aae5060 fffff803`8a88e079 WRCore_x64+0x192b
08 ffff810a`4aae5090 fffff803`8a88c760 WRCore_x64+0x1e079
09 ffff810a`4aae5110 fffff801`03b7f0b7 WRCore_x64+0x1c760
0a ffff810a`4aae5140 fffff801`041d3f1a nt!IopfCallDriver+0x53
0b ffff810a`4aae5180 fffff801`03c50db1 nt!IovCallDriver+0x266
0c ffff810a`4aae51c0 fffff801`03e87308 nt!IofCallDriver+0x1af5a1
0d ffff810a`4aae5200 fffff801`03e86bd5 nt!IopSynchronousServiceTail+0x1a8
0e ffff810a`4aae52a0 fffff801`03e865d6 nt!IopXxxControlFile+0x5e5
0f ffff810a`4aae53e0 fffff801`03c1abb5 nt!NtDeviceIoControlFile+0x56
10 ffff810a`4aae5450 00007ff9`b24ece54 nt!KiSystemServiceCopyEnd+0x25
11 00000045`4e31fb28 00007ff9`aff5b07b ntdll!NtDeviceIoControlFile+0x14
12 00000045`4e31fb30 00007ff9`b09a5611 KERNELBASE!DeviceIoControl+0x6b
13 00000045`4e31fba0 00007ff7`e9271142 KERNEL32!DeviceIoControlImplementation+0x81
14 00000045`4e31fbf0 00000000`00000000 poc+0x1142
So it turns out the WRCore_x64+0x192b
looks to be where the out-of-bounds is occurring. This corresponds to the following pseudo-code:
LINE77 __int64 __fastcall sub_14000191C(webroot_irp *iob)
LINE78 {
LINE79 if ( iob->self_size <= 0x20ui64 )
LINE80 j_rtl_failfast_wrappoer((__int64)iob);
LINE81 return (unsigned int)iob->SystemBuffer->field_20;
LINE82 }
We can see at LINE81 a variable here named field_20
from iob->SystemBuffer
.
The subroutine sub_14000191C
is called directly from the ioctl dispatcher routine ioctl_2
associated with following pseudo code:
LINE84 __int64 __fastcall ioctl_2(PIRP pIrp, _IO_STACK_LOCATION *psStackLocation)
LINE85 {
[...]
LINE93
LINE94 if ( psStackLocation->Parameters.DeviceIoControl.InputBufferLength < 32
LINE95 || psStackLocation->Parameters.DeviceIoControl.OutputBufferLength < 32 )
LINE96 {
LINE97 return 0xC0000004i64;
LINE98 }
LINE99 SystemBuffer = pIrp->AssociatedIrp.SystemBuffer;
LINE100 memset(&iob.qword10, 0, 24);
LINE101 iob.byte8 = 0;
LINE102 iob.self_size = 64i64;
LINE103 iob.SystemBuffer = SystemBuffer;
LINE104 if ( !SystemBuffer )
LINE105 goto LABEL_11;
LINE106 iob.table_function = &off_14002B920;
LINE107 process_list_related_0 = get_process_list_related_0();
LINE108 v5 = sub_14000191C(&iob);
LINE109 sub_14000F8EC(process_list_related_0, &a2, v5);
LINE110 some_ptr = a2.some_ptr;
LINE111 if ( a2.some_ptr )
LINE112 {
LINE113 pIrp->IoStatus.Information = 64i64;
LINE114 v7 = sub_14000D850(some_ptr, &iob);
LINE115 }
LINE116 else
LINE117 {
LINE118 v7 = STATUS_UNSUCCESSFUL;
LINE119 }
LINE120 sub_14000159C(&a2);
LINE121 iob.table_function = &off_14002B920;
LINE122 if ( iob.PERESOURCE )
LINE123 {
LINE124 if ( iob.self_size <= 0x20ui64 )
LINE125 LABEL_11:
LINE126 j_rtl_failfast_wrappoer((__int64)pIrp);
LINE127 }
LINE128 sub_1400076C8(&iob.byte8);
LINE129 return v7;
LINE130 }
At LINE108 we can see the call directly to the culprit subroutine using the variable iob
.
The iob.SystemBuffer
is directly derived from SystemBuffer
which is in fact the IOCTL input SystemBuffer
as indicated at LINE99.
When doing some reverse around function the attended SystemBuffer
is structured like below:
00000000 ioctl_03 struc ; (sizeof=0x40, mappedto_625)
00000000 vftable dq ?
00000008 field_8 dq ?
00000010 field_10 dq ?
00000018 field_18 dq ?
00000020 hprocess dd ? ; XREF: sub_14000191C+F/r
00000024 field_24 dd ?
00000028 self_size dq ?
00000030 SystemBuffer dq ?
00000038 field_38 dq ?
00000040 ioctl_03 ends
We can see here the hprocess
field at offset 0x20, which again checks the size length for input buffer and output buffer done at LINE94 and at LINE95. They are not big enough, as they allow an out-of-bounds read to happen when the function sub_14000191C
is called.
3: kd> !analyze -v
Connected to Windows 10 19041 x64 target at (Thu Dec 2 18:11:33.195 2021 (UTC + 1:00)), ptr64 TRUE
Loading Kernel 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: ffffe48fd8063000, memory referenced.
Arg2: 0000000000000000, value 0 = read operation, 1 = write operation.
Arg3: fffff8038a87192b, If non-zero, the instruction address which referenced the bad memory
address.
Arg4: 0000000000000002, (reserved)
Debugging Details:
------------------
*** WARNING: Unable to verify checksum for poc.exe
KEY_VALUES_STRING: 1
Key : Analysis.CPU.mSec
Value: 7952
Key : Analysis.DebugAnalysisManager
Value: Create
Key : Analysis.Elapsed.mSec
Value: 11787
Key : Analysis.Init.CPU.mSec
Value: 172905
Key : Analysis.Init.Elapsed.mSec
Value: 24533381
Key : Analysis.Memory.CommitPeak.Mb
Value: 92
Key : WER.OS.Branch
Value: vb_release
Key : WER.OS.Timestamp
Value: 2019-12-06T14:06:00Z
Key : WER.OS.Version
Value: 10.0.19041.1
BUGCHECK_CODE: 50
BUGCHECK_P1: ffffe48fd8063000
BUGCHECK_P2: 0
BUGCHECK_P3: fffff8038a87192b
BUGCHECK_P4: 2
READ_ADDRESS: ffffe48fd8063000 Special pool
MM_INTERNAL_CODE: 2
IMAGE_NAME: WRCore.x64.sys
MODULE_NAME: WRCore.x64
FAULTING_MODULE: 0000000000000000
PROCESS_NAME: poc.exe
TRAP_FRAME: ffff810a4aae4ed0 -- (.trap 0xffff810a4aae4ed0)
NOTE: The trap frame does not contain all registers.
Some register values may be zeroed or incorrect.
rax=ffffe48fd8062fe0 rbx=0000000000000000 rcx=ffff810a4aae50c8
rdx=ffffe48fd6ab8f70 rsi=0000000000000000 rdi=0000000000000000
rip=fffff8038a87192b rsp=ffff810a4aae5060 rbp=ffff810a4aae5100
r8=ffff8383152450b2 r9=0000000000000002 r10=fffff80103f14940
r11=0000000000000000 r12=0000000000000000 r13=0000000000000000
r14=0000000000000000 r15=0000000000000000
iopl=0 nv up ei pl nz na pe nc
WRCore_x64+0x192b:
fffff803`8a87192b 8b4020 mov eax,dword ptr [rax+20h] ds:ffffe48f`d8063000=????????
Resetting default scope
STACK_TEXT:
ffff810a`4aae4478 fffff801`03d24b12 : ffff810a`4aae45e0 fffff801`03b8f200 fffff803`8a870000 00000000`00000000 : nt!DbgBreakPointWithStatus
ffff810a`4aae4480 fffff801`03d240f6 : fffff803`00000003 ffff810a`4aae45e0 fffff801`03c1e110 ffff810a`4aae4b30 : nt!KiBugCheckDebugBreak+0x12
ffff810a`4aae44e0 fffff801`03c092b7 : 00000000`00000000 00000000`00000000 ffffe48f`d8063000 ffffe48f`d8063000 : nt!KeBugCheck2+0x946
ffff810a`4aae4bf0 fffff801`03c5c46f : 00000000`00000050 ffffe48f`d8063000 00000000`00000000 ffff810a`4aae4ed0 : nt!KeBugCheckEx+0x107
ffff810a`4aae4c30 fffff801`03ab1500 : 00000000`00000000 00000000`00000000 ffff810a`4aae4f50 00000000`00000000 : nt!MiSystemFault+0x18d0bf
ffff810a`4aae4d30 fffff801`03c1735e : ffff8383`0d000100 fffff801`03a9b422 ffff8383`0d000340 00000000`000000ff : nt!MmAccessFault+0x400
ffff810a`4aae4ed0 fffff803`8a87192b : ffff8383`15774060 fffff803`8a88f986 00000000`00000000 ffffe48f`00000000 : nt!KiPageFault+0x35e
ffff810a`4aae5060 fffff803`8a88e079 : ffffe48f`d240b0d8 fffff801`041df11a ffff8383`15245e30 fffff801`03e87308 : WRCore_x64+0x192b
ffff810a`4aae5090 fffff803`8a88c760 : 00000000`00000000 ffffe48f`d6ab8ea0 00000000`00000000 ffffe48f`d5c48440 : WRCore_x64+0x1e079
ffff810a`4aae5110 fffff801`03b7f0b7 : ffffe48f`d6ab8ea0 00000000`00000000 ffffe48f`00000001 ffffe48f`d0f16290 : WRCore_x64+0x1c760
ffff810a`4aae5140 fffff801`041d3f1a : ffffe48f`d6ab8ea0 ffffe48f`d0f16290 00000000`20206f49 00000000`00000000 : nt!IopfCallDriver+0x53
ffff810a`4aae5180 fffff801`03c50db1 : ffffe48f`d6ab8ea0 00000000`00000002 00000000`00000028 ffffe48f`d5c48440 : nt!IovCallDriver+0x266
ffff810a`4aae51c0 fffff801`03e87308 : ffff810a`4aae5540 ffffe48f`d6ab8ea0 00000000`00000001 ffff810a`4aae5540 : nt!IofCallDriver+0x1af5a1
ffff810a`4aae5200 fffff801`03e86bd5 : 00000000`9c412c0c ffff810a`4aae5540 00000000`00000000 ffff810a`4aae5540 : nt!IopSynchronousServiceTail+0x1a8
ffff810a`4aae52a0 fffff801`03e865d6 : 00007ff9`b046d246 00000000`00000000 00000000`00000000 00000000`00000000 : nt!IopXxxControlFile+0x5e5
ffff810a`4aae53e0 fffff801`03c1abb5 : 00000000`00000000 00000000`00000000 00000000`00000000 00000000`00000000 : nt!NtDeviceIoControlFile+0x56
ffff810a`4aae5450 00007ff9`b24ece54 : 00007ff9`aff5b07b 00007ff9`b049f4e8 00000002`0000000c 000001c9`c66c0101 : nt!KiSystemServiceCopyEnd+0x25
00000045`4e31fb28 00007ff9`aff5b07b : 00007ff9`b049f4e8 00000002`0000000c 000001c9`c66c0101 00000045`4e31fb50 : ntdll!NtDeviceIoControlFile+0x14
00000045`4e31fb30 00007ff9`b09a5611 : 00000000`9c412c0c 00000000`00000000 00000000`00000000 00007ff9`b03bdd3e : KERNELBASE!DeviceIoControl+0x6b
00000045`4e31fba0 00007ff7`e9271142 : 00000000`00000000 00000000`00000000 00000000`00000000 00000000`00000000 : KERNEL32!DeviceIoControlImplementation+0x81
00000045`4e31fbf0 00000000`00000000 : 00000000`00000000 00000000`00000000 00000000`00000000 000001c9`c66d15c0 : poc+0x1142
STACK_COMMAND: .thread ; .cxr ; kb
FAILURE_BUCKET_ID: AV_VRF_R_INVALID_IMAGE_WRCore.x64.sys
OS_VERSION: 10.0.19041.1
BUILDLAB_STR: vb_release
OSPLATFORM_TYPE: x64
OSNAME: Windows 10
FAILURE_ID_HASH: {4487bf4b-fcaa-ab42-3f1b-e48f350aad47}
Followup: MachineOwner
---------
2021-12-16 - Vendor disclosure
2022-03-15 - Public Release
Discovered by Emmanuel Tacheau of Cisco Talos.