Skip to content
KitploitKITPLOIT
FerramentasBlog
Enviar
FerramentasBlog
Enviar

Ferramentas de Hacking, PenTest e Cibersegurança para o seu Arsenal de Segurança!

Kitploit é um diretório de ferramentas de hacking, cibersegurança e pentesting. Descubra as últimas atualizações de projetos para encontrar vulnerabilidades, analisar sistemas, automatizar testes e fortalecer sua segurança.

··Feeds·Contato·Privacidade·© 2026 Kitploit

Diretório de Ferramentas

Categorias

Ver todas as categorias
Loading categories
CVE-2020-15368 — CVE-2020-15368, também conhecido como "Como explorar um driver vulnerável" | Kitploit
Ferramentas/GitHubGitHub/stong/cve-2020-15368
Escalada de PrivilégiosAnálise de VulnerabilidadesExploraçãoShellcodeAprendizado e EducaçãoDesenvolvimento de PayloadsExploração de Binários
GitHubstong/cve-2020-15368

CVE-2020-15368

CVE-2020-15368, também conhecido como "Como explorar um driver vulnerável"

Ver Repositório
51549há 4 anosRevisado pelo Kitploit

Mais Populares

Ver todos →

Descubra as ferramentas mais usadas pela nossa comunidade.

Explore todas as ferramentas

Navegue pela nossa coleção de ferramentas

Ver todas as ferramentas →
Compartilhar

Como explorar um driver vulnerável do Windows

Exploit e Prova de Conceito (PoC) para CVE-2020-15368. A Asrock reempacotou o driver rweverything para sua ferramenta de configuração do controlador RGB e o assinou. Eles o "protegem" criptografando seus ioctls...lol. Encontramos essa CVE por acidente no verão passado, e até onde sei o driver ainda não foi corrigido. O impacto é, claro, execução arbitrária de código no kernel, etc. Então aproveitem esse "0day" lol.

Se você quiser discutir comigo se isso é uma CVE REAL DE BOA-FÉ, sinta-se à vontade para me contatar no Twitter, podemos ter uma briga enorme nas redes sociais públicas e será realmente emocionante para todos os envolvidos! Até comprarei um domínio para esse bug se você estiver tão inclinado. É tudo sobre marketing!!!!

De qualquer forma, esse bug é bem ruim, então vou usá-lo como um tutorial sobre como derrotar seu driver vulnerável típico. Portanto, este post é voltado para iniciantes. Você aprenderá a explorar um driver vulnerável. Existem toneladas de outros drivers ruins por aí assim. O mundo é a sua ostra. Divirta-se

AVISO LEGAL: Esta publicação é fornecida apenas para fins educacionais. É responsabilidade do leitor obedecer a todas as leis locais, estaduais e federais aplicáveis. O(s) autor(es) desta publicação não assume(m) nenhuma responsabilidade e não se responsabiliza(m) por qualquer uso indevido ou dano causado pelo software contido nesta publicação.

História

Presos em quarentena, meus colegas de quarto (Pear0, Codetector) e eu estávamos brincando na nova placa-mãe Asrock do Pear0. Os LEDs vermelhos brilhantes eram extremamente irritantes e não era possível configurá-los no Linux. Assim, nosso plano era fazer engenharia reversa do driver do Windows que o controlava e replicar as operações de I/O no Linux.

Em resumo, não demorou muito até percebermos que o driver é literalmente apenas um driver genérico que concede acesso arbitrário de leitura/escrita a qualquer coisa. Isso inclui registradores de controle como CR3, CR4, memória física, etc. Drivers como este são destinados ao uso como ferramenta de depuração e o site do fornecedor afirma claramente isso.

docs/lol.png

Achamos isso extremamente engraçado. É bem empolgante a primeira vez que você faz um computador dar triple fault e reiniciar bruscamente a partir do userspace. (Talvez menos empolgante na vigésima vez.) De qualquer forma, reportamos o bug e depois o esquecemos por um ano.

Configuração

Como novato em kernel, eu me perguntava como realmente carregar e interagir com o driver. Acontece que é extremamente fácil.

Você pode simplesmente criar um serviço para o driver no Process Hacker (obviamente, exige admin para carregar drivers). Então você pode apenas clicar com o botão direito e iniciá-lo. Sim, é realmente assim tão simples.

docs/processhacker.png

Podemos ver nosso objeto de dispositivo no WinObjEx64.

docs/processhacker.png

Podemos até brincar com o dispositivo no FileTest.

docs/filetest.png

docs/filetest2.png

Todas essas 3 ferramentas são incríveis, especialmente PH e FileTest. Elas são como um canivete suíço e deveriam estar na caixa de ferramentas de todo reverser de Windows. Por exemplo, pelo que entendi, Jonas L encontrou inúmeras vulnerabilidades LPE no Windows apenas mexendo no FileTest. Então o Windows realmente tem ótimas ferramentas para bagunçar. Eu queria que houvesse essa merda no Linux.

Bypass de "segurança"

Rweverything tem um ioctl que recebe outro ioctl como parâmetro, o que controla qual operação será executada (ler mem, escrever mem, ler msr, etc.), e uma união de alguns parâmetros específicos da operação, como endereço src, endereço dst, etc. Quando comparamos o código dos dois drivers:

docs/comparison.png

🤔🤔🤔🤔🤔🤔🤔🤔🤔

No entanto, o driver faz uma tentativa tosca de segurança por obscuridade ao exigir que todas as chamadas ioctl sejam devidamente criptografadas com uma chave AES fixa no código. O código (após alguma limpeza) é assim:

root@kitploit:~
if ( IoControlCode == 0x22EC00 )
{

  char enc_key[32];
  memset(enc_key, 0, sizeof(enc_key));
  memmove(enc_key, "C110DD4FE9434147B92A5A1E3FDBF29A", 32ui64);
  memcpy(enc_key + 13, ioctl_args->key, 16);
  
  size_t cb_decrypted = 0;
  my_decrypted_cmd* decryptedCmd = NULL;
  DWORD iv_size = ioctl_args->iv_size;
  DWORD input_size = *(DWORD*)(ioctl_args + bufferLen - 6);

  // really just calls BCrypt API to get an AES implementation
  if ( (unsigned int)decrypt_ioctl_params(enc_key, 32, ioctl_args->iv, iv_size, ioctl_args + bufferLen - input_size - 6, input_size, &decryptedCmd, &cb_decrypted) )
  {
    // Decryption failed
    if ( decryptedCmd )
      ExFreePoolWithTag(decryptedCmd, 0);
    irp->IoStatus.Status = 0xC000000D;
    goto Fail_Out;
  }
  IoControlCode = decryptedCmd->opcode;
  Rweverything_Args = &decryptedCmd->args;
}
else // not 0x22EC00
{
  if ( IoControlCode != 0x22E858 &&
       IoControlCode != 0x22E860 &&
       IoControlCode != 0x22E800 &&
       IoControlCode != 0x22E804 )// whitelisted control codes
    IoControlCode = 0; // block everything else
}

O driver permite algumas operações chatas que fazem um pouco de PMIO, mas todos os códigos de controle "divertidos" são bloqueados por essa rotina de descriptografia. Apesar de ter essa lista de permissões explícita, ela ainda inclui todas as funcionalidades perigosas do Rweverything. Em vez de esconder esses recursos perigosos, eles provavelmente deveriam ter sido simplesmente removidos por completo.

Além disso, curiosamente, ele também permite que o usuário especifique parte da chave (???), e eu não faço ideia para quê. O código é simplesmente muito mal escrito.

De qualquer forma, é relativamente fácil escrever o código do cliente para usar essa API criptografada estranha e passar a ela chamadas ioctl arbitrárias que quisermos. Não vou entediá-lo com os detalhes disso.

Conversando com o driver

Abrimos um handle para o driver e usamos DeviceIoControl para chamar o ioctl, coisa bem padrão.

root@kitploit:~
HANDLE hDevice = CreateFileA("\\\\.\\GlobalRoot\\Device\\AsrDrv104", GENERIC_READ | GENERIC_WRITE | SYNCHRONIZE, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);

// ... set up the encrypted ioctl data

BOOL result = DeviceIoControl(hDevice, 0x22EC00, ioctl_data, in_buf_size, out_buf, sizeof(out_buf), &bytes_returned, NULL);

Agora que conseguimos conversar com a parte oculta do Rweverything no driver, a primeira coisa que quis fazer foi provocar uma falha para saber se meu cliente do driver está funcionando.

A maneira mais direta de fazer isso é sobrescrever o CR3 com lixo. Sei que alguns de vocês que estão lendo são novatos, e tudo bem, então explicarei em detalhes. Eu também sou burro, então talvez isso ajude você a aprender. Se você sabe o que está fazendo, pode pular esta parte.

Em x86, quando a paginação está habilitada (quase o tempo todo em qualquer sistema operacional moderno), o CR3 aponta para o endereço base físico do diretório de tabela de páginas de nível superior. Se você não sabe o que isso significa, vá ler o artigo da Wikipedia sobre Memória virtual.

Quando sobrescrevemos o CR3 com lixo, digamos 0x0000000000000000, o TLB é limpo, e ao tentar executar a próxima instrução, o processador (especificamente a MMU) tentará traduzir o ponteiro de instrução para um endereço físico. A tradução de endereços pode ser pensada como essencialmente uma série de buscas na tabela de páginas começando a partir do CR3. O CR3 agora aponta para a memória física em 0, que existe e é acessível; no entanto, é extremamente improvável que seja uma tabela de páginas válida. (Entradas de tabela de páginas, ou PTEs, para abreviar, têm uma estrutura específica que devem seguir.)

Quando isso acontece, obtemos uma page fault na tradução de endereços. Normalmente, a CPU nos levaria ao endereço do handler de page fault. Mas como ela sabe onde está a função do handler de page fault? Isso é armazenado em uma estrutura de dados na memória conhecida como Tabela de Descritores de Interrupção (IDT). O processador tem um registrador (lido/escrito pelas instruções sidt e lidt) que contém o endereço virtual da IDT. Você vê o problema agora? Para tratar a page fault, precisamos primeiro fazer outro acesso à memória virtual e, portanto, mais uma tradução de endereço.

Claro, nossa segunda tradução de endereço também falhará. Agora temos uma Double Fault: uma falha que ocorre durante o tratamento da primeira page fault. Isso é bastante sério, mas ainda recuperável---o processador nos dará uma última chance de recuperação. Claro, essa tentativa também é interrompida de forma brutal com uma terceira e última page fault, uma Triple Fault. Nesse ponto, a CPU simplesmente desiste e reinicia a máquina de forma brusca. Se você executasse esse procedimento em uma máquina física, provavelmente veria a tela de splash da BIOS agora.

Agora, se você tiver alguma pergunta, darei a mesma resposta que sempre me deram. Que é ler o Intel Manual Volume 3A. (também conhecido como a Bíblia).

Explorando o driver

OK, agora como realmente exploramos o driver? Olhando ao redor, vemos uma primitiva livre de leitura/escrita arbitrária de memória física. Ela basicamente mapeia qualquer endereço físico que você quiser usando MmMapIoSpace, copia seu buffer para lá (ou vice-versa) e desmapeia o endereço.

Nota pequena: quando você tenta chamar MmMapIoSpace com alguns argumentos estúpidos como nós, enquanto o depurador de kernel está anexado, você terá um bugcheck. Você pode contornar isso escrevendo um byte mágico no WinDbg. Procure um comentário referenciando MiShowBadMapper no exploit.cpp para ver mais. Eu realmente não sei o que essa merda é e não quero saber.

Como podemos aproveitar essa primitiva para obter execução de código no kernel? O principal problema com essa primitiva é que ela opera em memória física. Como um programa no user mode, praticamente não temos ideia de como é o layout da memória física---o sistema operacional lida com tudo isso por nós. Mesmo se conseguirmos endereços virtuais de algumas estruturas de dados do kernel ou ponteiros de função do kernel, não temos ideia de onde eles estão no espaço de endereço físico.

Uma ideia é ler o CR3, ler as tabelas de páginas e realizar a tradução de endereço virtual nós mesmos. Essa é uma ótima ideia. Ela não funciona. Isso porque o Windows não permite mais que você mapeie tabelas de páginas com MmMapIoSpace. Então precisamos ser mais espertos.

Usei a técnica do xeroxz do VDM. É bem simples, mas a técnica é bastante engenhosa. Embora não saibamos o layout da memória física, ainda podemos escanear toda a memória física até encontrar o que procuramos. Uma coisa que podemos aproveitar é que o conteúdo das páginas é sempre o mesmo tanto física quanto virtualmente: quaisquer offsets relativos aos limites de página são sempre preservados. Por exemplo, se eu tiver a página 0x7fff000000000XXX mapeada para o frame físico 0x0000000123456XXX, os XXX de todos os endereços são iguais tanto no endereço físico quanto no virtual. Toda a estrutura dentro da página é preservada; assim, podemos escanear em busca de alguma página interessante que gostaríamos de sobrescrever.

A coisa mais fácil que podemos sobrescrever é provavelmente algum handler de syscall ou ioctl fácil de alcançar. No Windows, há uma função padrão Beep() que faz seu computador emitir um bipe. Acredite ou não, isso é implementado em um driver, Beep.sys, que fornece o dispositivo Beep. (Na verdade, você pode vê-lo na captura de tela do WinObjEx64 mais acima.) Qualquer um pode usar o dispositivo Beep, e ele raramente é chamado. Então vamos sobrescrever o handler de ioctl do Beep.

Podemos abrir o Beep.sys no IDA e verificar o handler de DeviceIoControl.

docs/beep.png

No offset 0x270 da página, temos este código com os bytes 40 53 48 .... Nenhum desses bytes é relocado, então escanear essa função é muito fácil. Se houvesse bytes relocados, precisaríamos usar curingas neles. É a mesma ideia da varredura de assinaturas quando você está escrevendo um hack de jogo.

Então, depois de escanear a memória física para localizar este código, podemos simplesmente sobrescrevê-lo com nosso próprio shellcode. Você também precisa ter cuidado, pois pode haver várias cópias dessa página espalhadas pela memória física (!), então vá encontrar todas as cópias.

Neste ponto, podemos escalar privilégios facilmente trocando o token de segurança do nosso processo pelo de um processo do sistema para obter permissões nt authority\system. Infelizmente, o driver asrock exige permissões de administrador para abrir de qualquer forma, então isso não é muito interessante.

Para nós, escrevemos um shellcode básico que aloca e copia um payload de estágio 2 e, em seguida, gera uma nova thread do kernel. Não podemos fazer tudo no handler do Beep sobrescrito porque 1) estamos limitados a 1 página e 2) o sistema travará quando tentarmos fechar o handle para o dispositivo Beep, já que também destruímos o resto do código no dispositivo Beep. Quanto a obter ponteiros do kernel, isso é fácil porque o NtQuerySystemInformation nos os dará de graça se pedirmos educadamente.

root@kitploit:~
__int64 __declspec(dllexport) __fastcall MyIRPHandler(struct _DEVICE_OBJECT* a1, IRP* irp)
{
    MyIrpStruct* user_data = (MyIrpStruct*)irp->AssociatedIrp.SystemBuffer;

    void* my_rwx = user_data->nt_ExAllocatePoolWithTag(NonPagedPoolExecute, user_data->payload_size, 'lmao');

    user_data->nt_memcpy(my_rwx, user_data->payload, user_data->payload_size);

    HANDLE hThread;
    user_data->nt_PsCreateSystemThread(&hThread, THREAD_ALL_ACCESS, NULL, NULL, NULL, (PKSTART_ROUTINE)my_rwx, NULL);

    user_data->nt_IofCompleteRequest(irp, 0);

    return 0;
}

Então, rapidamente aplicamos patch no Beep, chamamos o handler de ioctl sobrescrito e removemos o patch do Beep. Agora criamos com segurança uma thread do kernel executando nosso código sem destruir mais nada no sistema. Neste ponto, podemos mapear nossos próprios drivers ou o que quisermos.

Conclusão

Sou um péssimo pesquisador de segurança e só encontro bugs inúteis, por acidente. Obrigado a todos por lerem. Por favor, assinem meu OnlyFans

Baixar ferramenta