
Technical Write-Up on and PoC Exploit for CVE-2020-11519 and CVE-2020-11520
Date: June 2020
Author: Dennis Elser (code: github)
In reference to its web representation, Winmagic SecureDoc "allows businesses to deal with the security of their IT environment efficiently leveraging features including: Full Disk Encryption (FDE), Multi-Factor Authentication, Removable Media Container Encryption (RMCE) and File and Folder Encryption (FFE). These features help businesses increase security, mitigate business risk and meet government and regulatory requirements for hard drive encryption."
The Winmagic SecureDoc product, which is available in standalone and enterprise editions, is affected by two local privilege escalation vulnerabilities (CVE-2020-11519 and CVE-2020-11520) in versions 8.3 and 8.5. After the vulnerabilities had been reported to Winmagic in late March, the vendor released a patch (version 8.5SR2) in mid June 2020. However, this patch was found to address the vulnerabilities insufficiently, which also made version 8.5SR2 vulnerable to the reported flaws. Although technical details about the vulnerabilities had been held back for this reason, the flaws have to be considered public since then. According to the vendor, another patch is still in the pipeline, roughly 106 days after the initial vulnerability report to Winmagic. On July 15th, 111 days after the initial vulnerability report to the vendor, Winmagic released SecureDoc v8.5 SR2 HF1 to customers, which reportedly fixes CVE-2020-11519 and CVE-2020-11520. Versions of SecureDoc older than 8.3 have not been tested but can be assumed to be affected as well, based on the affected component's code
Successful exploitation of any of the vulnerabilities will lead to escalation of privileges to SYSTEM for locally authenticated attackers.
Both vulnerabilities affect the component "SDDisk2k.sys", a kernel driver that comes with the Winmagic SecureDoc product. The security flaws were identified using manual static analysis with the help of the Hex-Rays IDA Pro disassembler and decompiler. In retrospective, the weaknesses could have been discovered with significantly less effort if dynamic testing approaches such as fuzzing had been applied instead. This is because the driver can be interfaced with from limited user-mode applications and because it assumes their input to be well-formed by default.
Due to the "SDDisk2k.sys" driver's unsafe creation of a "SecureDocDevice" device object and missing code that'd set up an appropriate security descriptor, even limited user accounts are given the ability to acquire a handle to the device using the CreateFile() API function. With the driver granting a user mode application a handle to its device object, it hereby opens up a direct path to its attack surface in kernel land.
RtlInitUnicodeString(&DestinationString, L"\\Device\\SecureDocDevice");
RtlInitUnicodeString(&SymbolicLinkName, L"\\DosDevices\\SecureDocDevice");
if ( IoCreateDevice(v1, 0xDD8u, &DestinationString, 0x8D1Fu, 0, 0, &DeviceObject) >= 0 ) // <--- unsafe
{
memset(DeviceObject->DeviceExtension, 0, 0xDD8ui64);
DeviceObject->Flags |= 4u;
DeviceObject->AlignmentRequirement = 0;
if ( IoCreateSymbolicLink(&SymbolicLinkName, &DestinationString) < 0 )
IoDeleteDevice(DeviceObject);
IoObject = DeviceObject;
}
By having reverse engineered a number of the "SDDisk2k.sys" driver's IOCTL service handlers, it was found that one of it exposes critical functionality to user mode, in that it allows read and write operations of an arbitrary drive's raw disk sectors - by design. Adding to this, by interfacing with this very code it was noticed that the driver ignores any exclusive locks that might have been set earlier on a drive. As a consequence, concurrent read/write operations are made possible, which facilitates race conditions and risks loss of data.
The following shows the driver's decompiled IOCTL service handler that is responsible for handling read requests of raw disk sectors. It calls a function sub_29CD4() with an argument "controlled_buf", which is a pointer to a buffer whose content can be chosen arbitrarily by any calling user mode application:
if ( ioctlcode == 0x8D1F2824 ) // <--- I/O control code for raw disk reading functionality
{
controlled_buf = (unsigned __int8 *)controlled_addr;
mode = 0;
temp_result = sub_29CD4((char *)controlled_buf, v3, mode); // <--- call to raw disk read function
Actually, this attacker-controlled buffer is a structure whose fields "offset", "length" and "ptr_buf" are entirely unchecked function arguments passed to a call to IoBuildSynchronousFsdRequest(). The latter function prepares an IRP_MJ_READ I/O request packet (IRP) that it sends to the underlying file system driver using a call to IofCallDriver():
__int64 __fastcall sub_29CD4(char *controlled_addr, PIRP a2, char mode)
{
//[...snip...]
// extract drive number and type (floppy/hd) from offset 0
devicetype_and_num = (unsigned __int8)*controlled_buf // <--- controllable from user mode
// advance pointer
p = controlled_buf + 1;
// build device name
if ( (devicetype_and_num & 0x80u) == 0 )
v11 = vsnprintf_wrapper(&device_name, 0x3Fui64, L"\\Device\\Floppy%d", devicetype_and_num);
else
v11 = vsnprintf_wrapper(&device_name, 0x3Fui64, L"\\Device\\Harddisk%d\\Partition0", devicetype_and_num & 0x7F);
v12 = v11;
if ( v11 >= 0 )
{
RtlInitUnicodeString(&DestinationString, &device_name);
// get object pointer of drive
if ( IoGetDeviceObjectPointer(&DestinationString, 0x80u, &FileObject, &DeviceObject) >= 0
|| (v12 = sub_2C5D4(&DestinationString, &DeviceObject), v12 >= 0) )
{
// extract further fields from structure
offset = *(_QWORD *)(p + 0x4E); // <--- where to start reading from
length = *(_DWORD *)(p + 0x56); // <--- number of bytes to read
ptr_buf = *(void **)(p + 0x5A); // <--- ptr to destination buffer
devobj = DeviceObject;
StartingOffset.QuadPart = offset << 9;
KeInitializeEvent(&Event, NotificationEvent, 0);
// build request
v17 = IoBuildSynchronousFsdRequest(
(unsigned int)(mode != 0) + IRP_MJ_READ, // <--- issue read request
devobj,
ptr_buf,
length << 9,
&StartingOffset,
&Event,
&IoStatusBlock);
v18 = v17;
if ( v17 )
{
v19 = v17->Tail.Overlay.CurrentStackLocation;
if ( mode )
v19[0xFFFFFFFF].Flags |= 0x10u;