
Shellcode de modo kernel artesanal para roubo de token em Windows x64
Windows x64 shellcode artesanal em modo kernel para substituir o token de acesso primário do processo em execução pelo token de processo SYSTEM para Elevation of Privilege(EoP).
Windows 7/Windows Server 2008 R2 Build 7601Windows 8/Windows Server 2012 Build 9200Windows 8.1/Windows Server 2012 R2 Build 9600Windows 10 1507/TS1 Build 10240Windows 10 1511/TS2 Build 10586Windows 10 1607/RS1/Windows Server 2016 Build 14393Windows 10 1703/RS2 Build 15063Windows 10 1709/RS3 Build 16299Windows 10 1803/RS4 Build 17134Windows 10 1809/RS5/Windows Server 2019 Build 17763Windows 10 1903/19H1 Build 18362Windows 10 1909/19H2 Build 18363Windows 10 2004/20H1 Build 19041Windows 10 2009/20H2 Build 19042Windows 10 2104/21H1 Build 19043Windows 10 2110/21H2 Build 19044Os pré-requisitos para compilar este projeto são:
Visual Studio 2019(any edition will do fine)Windows 10 SDK, version 2004Windows 10 WDK, version 2004Python3Deve-se notar aqui que você pode se virar apenas tendo um assembler (este projeto está usando MASM) porque tecnicamente é tudo que você precisa.
Depois de instalar o acima, deve ser tão fácil quanto abrir a solução com Visual Studio e compilar para o alvo x64.
Após uma compilação bem-sucedida, os binários podem ser encontrados dentro do diretório Bin no subdiretório de arquitetura apropriado.
Alternativamente, você pode baixar shellcode independente de posição pronto para implantação a partir de Releases.
Por favor, NÃO tente implantar o payload em uma máquina da qual você depende para trabalhar se você não tiver certeza de como ele funciona.
Consulte a documentação da Microsoft para qualquer informação adicional.
Para fins de teste, eu recomendo fortemente usar flare-kscldr para implantar o shellcode em modo kernel em uma VM de teste e o guia de configuração do CodeMachine System para desenvolvimento e depuração de kernel para configurar uma VM convidada do Hyper-V com suporte completo a depuração de kernel.
Opcionalmente, você também pode considerar automatizar o processo com kdbg-driver-vagrant para criar rapidamente uma VM de teste com depuração completa de kernel usando Vagrant.

Como apontado para mim por Dmytro Oleksiuk(@d_olex), existem algumas condições de corrida não tão sutis no código, especificamente relacionadas a:
nt!_EPROCESS ligadas entre si por uma lista duplamente encadeada circular sem usar algum tipo de primitiva de sincronização/mecanismo de bloqueioEles atualmente não possuem nenhuma proteção contra alterações feitas neles enquanto trabalhamos neles.
Isso é um problema? Sim, condições de corrida são sempre problemáticas e podem causar todos os tipos de comportamento indefinido/bugchecks desagradáveis.
Usar este payload afetará a estabilidade do meu exploit? Pode ser.
Bem, qual é a correção? A correção tem duas etapas.
A Parte 1 envolve adquirir um bloqueio do tipo espera, como Pushlocks - nt!PspActiveProcessLock(ponteiro de pushlock) para acesso exclusivo usando nt!ExAcquirePushLockExclusive antes de percorrer a lista de processos(entrega normal de APC do kernel precisa ser desabilitada previamente) e nt!ExReleasePushLockExclusive para liberar o bloqueio assim que terminarmos de usar a lista, ponto em que a entrega normal de APC do kernel deve ser reabilitada.
No entanto, como essa variável global não é exportada pelo kernel nt, uma abordagem muito mais adequada e segura seria usar a API nt!ZwQuerySystemInformation com SYSTEM_INFORMATION_CLASS == SystemProcessInformation para encontrar o PID a partir do ImageName e nt!PsLookupProcessByProcessId para obter nt!_EPROCESS VA a partir do PID.
Se você está curioso, no entanto, sobre como o kernel faz o primeiro, eu pediria que você olhasse para nt!PsGetNextProcess em um desmontador.
A Parte 2 envolve referenciar objetos com segurança usando a família de APIs nt!ObReferenceObject para aumentar a contagem de referência no objeto de processo, para que ele não possa ser excluído até que o decrementemos explicitamente no final, quando terminarmos de usá-lo, com nt!ObDereferenceObject.
Observe que aumentar a contagem de referência manualmente é redundante, pois uma chamada para nt!PsLookupProcessByProcessId, se bem-sucedida, faz isso por nós.
Implementar essas correções, no entanto, exigiria encontrar o endereço base do ntoskrnl.exe e resolver símbolos dentro dele percorrendo o EAT para encontrar os ponteiros de função usando algum algoritmo de hash de string, tudo o que aumentaria drasticamente o tamanho do payload.
Posso decidir implementar isso algum dia ou apenas escrever em C e inundar a saída do compilador :)
Agradecimentos a Dmytro Oleksiuk(@d_olex) e Paul L.(@am0nsec) por apontar o(s) erro(s) e também sugerir a correção.