
Explora drivers assinados vulneráveis para contornar callbacks do kernel da EDR, callbacks de objetos, provedor ETW TI e hooks no espaço do usuário para despejo de memória do LSASS e extração de credenciais.
EDRSandBlast é uma ferramenta escrita em C que armamentiza um driver assinado vulnerável para contornar detecções de EDR (callbacks de rotinas de notificação, callbacks de objeto e provedor ETW TI) e proteções do LSASS. Múltiplas técnicas de unhooking em modo de usuário também são implementadas para evadir a monitoração em modo de usuário.
Na data do lançamento, a combinação de técnicas de modo de usuário (--usermode) e modo kernel (--kernelmode) foi usada para despejar a memória do LSASS sob escrutínio de EDR, sem ser bloqueada nem gerar eventos relacionados a "Despejo de Credenciais do SO" no console (em nuvem) do produto. Os testes foram realizados em 3 produtos EDR distintos e foram bem-sucedidos em cada caso.
Os produtos EDR usam callbacks de "Rotinas de Notificação" do kernel no Windows para serem notificados pelo kernel sobre atividade do sistema, como criação de processos e threads e carregamento de imagens (exe / DLL).
Esses callbacks do kernel são definidos a partir do modo kernel, geralmente pelo driver que implementa os callbacks, usando várias APIs documentadas (nt!PsSetCreateProcessNotifyRoutine, nt!PsSetCreateThreadNotifyRoutine, etc.). Essas APIs adicionam rotinas de callback fornecidas pelo driver a arrays não documentados de rotinas no espaço do kernel:
PspCreateProcessNotifyRoutine para criação de processosPspCreateThreadNotifyRoutine para criação de threadsPspLoadImageNotifyRoutine para carregamento de imagensEDRSandBlast enumera as rotinas definidas nesses arrays e remove qualquer rotina de callback vinculada a uma lista predefinida de drivers EDR (mais de 1000 drivers de produtos de segurança suportados, consulte a seção de detecção de drivers EDR). A enumeração e remoção são possíveis através da exploração de uma primitiva arbitrária de leitura/escrita de memória do kernel fornecida pela exploração de um driver vulnerável (consulte a seção de drivers vulneráveis).
Os offsets dos arrays mencionados são recuperados usando múltiplas técnicas; consulte a seção de Offsets.
Os produtos EDR (e até EPP) frequentemente registram "callbacks de objeto" através do uso da API do kernel nt!ObRegisterCallbacks. Esses callbacks permitem que o produto de segurança seja notificado a cada geração de handle em tipos específicos de objeto (callbacks de objeto relacionados a Processos, Threads e Desktops agora são suportados pelo Windows). Uma geração de handle pode ocorrer na abertura de objeto (chamada a OpenProcess, OpenThread, etc.), bem como na duplicação de handle (chamada a DuplicateHandle, etc.).
Ao ser notificado pelo kernel em cada uma dessas operações, um produto de segurança pode analisar a legitimidade da criação do handle (por exemplo, um processo desconhecido está tentando abrir o LSASS), e até bloqueá-lo se uma ameaça for detectada.
A cada registro de callback usando ObRegisterCallbacks, um novo item é adicionado à lista duplamente ligada CallbackList presente no objeto _OBJECT_TYPE que descreve o tipo de objeto afetado pelo callback (seja um Processo, uma Thread ou um Desktop). Infelizmente, esses itens são descritos por uma estrutura que não é documentada nem publicada em arquivos de símbolos pela Microsoft. No entanto, estudá-la a partir de várias versões do ntoskrnl.exe parece indicar que a estrutura não mudou entre (pelo menos) as compilações 10240 e 22000 do Windows 10 (de 2015 a 2022).
A estrutura mencionada, representando um registro de callback de objeto, é a seguinte:```C typedef struct OB_CALLBACK_ENTRY_t { LIST_ENTRY CallbackList; // linked element tied to _OBJECT_TYPE.CallbackList OB_OPERATION Operations; // bitfield : 1 for Creations, 2 for Duplications BOOL Enabled; // self-explanatory OB_CALLBACK* Entry; // points to the structure in which it is included POBJECT_TYPE ObjectType; // points to the object type affected by the callback POB_PRE_OPERATION_CALLBACK PreOperation; // callback function called before each handle operation POB_POST_OPERATION_CALLBACK PostOperation; // callback function called after each handle operation KSPIN_LOCK Lock; // lock object used for synchronization } OB_CALLBACK_ENTRY;
A estrutura `OB_CALLBACK` mencionada acima também não é documentada, e é definida pelo seguinte:```C
typedef struct OB_CALLBACK_t {
USHORT Version; // usually 0x100
USHORT OperationRegistrationCount; // number of registered callbacks
PVOID RegistrationContext; // arbitrary data passed at registration time
UNICODE_STRING AltitudeString; // used to determine callbacks order
struct OB_CALLBACK_ENTRY_t EntryItems[1]; // array of OperationRegistrationCount items
WCHAR AltitudeBuffer[1]; // is AltitudeString.MaximumLength bytes long, and pointed by AltitudeString.Buffer
} OB_CALLBACK;
De forma a desativar os callbacks de objeto registados pelo EDR, três técnicas estão implementadas no EDRSandblast; no entanto, apenas uma está ativa no momento.
Enabled de OB_CALLBACK_ENTRYEsta é a técnica padrão ativada no EDRSandblast. Para detetar e desativar callbacks de objeto relacionados com o EDR, é percorrida a lista CallbackList localizada nos objetos _OBJECT_TYPE associados aos tipos Process e Thread. Ambos os _OBJECT_TYPEs são apontados por símbolos globais públicos no kernel, PsProcessType e PsThreadType.
Cada item da lista é assumido como correspondendo à estrutura OB_CALLBACK_ENTRY descrita acima (suposição que parece ser válida pelo menos em todas as builds do Windows 10 à data da escrita). As funções definidas nos campos PreOperation e PostOperation são localizadas para verificar se pertencem a um driver de EDR e, em caso afirmativo, os callbacks são simplesmente desativados alternando a flag Enabled.
Embora seja uma técnica bastante segura, tem o inconveniente de depender de uma estrutura não documentada; para reduzir o risco de manipulação insegura desta estrutura, são realizadas verificações básicas para validar que alguns campos têm os valores esperados:
Enabled é TRUE ou FALSE (não ria, um BOOL é um int, por isso pode ser qualquer coisa menos 1 ou 0);Operations é OB_OPERATION_HANDLE_CREATE, OB_OPERATION_HANDLE_DUPLICATE ou ambos;ObjectType aponta para PsProcessType ou PsThreadType.CallbackList de threads e processosOutra estratégia que não depende de uma estrutura não documentada (sendo, portanto, teoricamente mais robusta contra alterações no kernel NT) é a desassociação de toda a CallbackList tanto para processos como para threads. O objeto _OBJECT_TYPE é o seguinte:```C
struct _OBJECT_TYPE {
LIST_ENTRY TypeList;
UNICODE_STRING Name;
[...]
_OBJECT_TYPE_INITIALIZER TypeInfo;
[...]
LIST_ENTRY CallbackList;
}
Fazer os ponteiros `Flink` e `Blink` do `CallbackList` `LIST_ENTRY` apontarem para
o próprio `LIST_ENTRY` torna efetivamente a lista vazia. Como a estrutura `_OBJECT_TYPE`
é publicada nos símbolos do kernel, a técnica não depende de offsets/estruturas fixas.
No entanto, tem algumas desvantagens.
A primeira é não conseguir desabilitar apenas callbacks do EDR; na verdade, a técnica afeta
todos os callbacks de objeto que poderiam ter sido registrados por software "legítimo". Deve-se
notar, no entanto, que os callbacks de objeto não são usados por nenhum componente pré-instalado
no Windows 10 (no momento em que este texto foi escrito), portanto desativá-los não deve afetar a
estabilidade da máquina (ainda mais se a desativação for apenas temporária).
A segunda desvantagem é que as operações de handle de processo ou thread são realmente frequentes (quase
contínuas) no funcionamento normal do SO. Como tal, se a primitiva de escrita no kernel usada
não conseguir realizar uma escrita `QWORD` "atomicamente", há uma boa chance de que o
ponteiro `_OBJECT_TYPE.CallbackList.Flink` seja acessado pelo kernel no meio
da sua sobrescrita. Por exemplo, o driver vulnerável MSI `RTCore64.sys` só consegue realizar
uma escrita `DWORD` por vez, então serão necessários 2 IOCTLs distintos para sobrescrever o ponteiro, entre
os quais o kernel tem alta probabilidade de usá-lo (resultando em um crash). Por outro lado,
o driver vulnerável da DELL `DBUtil_2_3.sys` pode realizar escritas de tamanhos arbitrários em um
único IOCTL, portanto usar este método com ele não corre o risco de causar um crash.
#### Desabilitar todos os callbacks de objeto
Uma última técnica que encontramos foi desabilitar completamente o suporte a callbacks de objeto para threads
e processos. Dentro da estrutura `_OBJECT_TYPE` correspondente aos tipos de processo e
thread reside um campo `TypeInfo`, seguindo a estrutura documentada `_OBJECT_TYPE_INITIALIZER`.
Este último contém um campo de bits `ObjectTypeFlags`, cuja flag `SupportsObjectCallbacks`
determina se o tipo de objeto descrito (Processo, Thread, Área de Trabalho, Token, Arquivo, etc.)
suporta ou não o registro de callbacks de objeto. Conforme afirmado anteriormente, apenas os tipos de objeto
Processo, Thread e Área de Trabalho suportam esses callbacks em uma instalação do Windows no momento da escrita.
Como o bit `SupportsObjectCallbacks` é verificado por `ObpCreateHandle` ou
`ObDuplicateObject` antes mesmo de ler o `CallbackList` (e antes de executar
callbacks, obviamente), inverter o bit em tempo de execução do kernel efetivamente desabilita toda execução
de callbacks de objeto.
A principal desvantagem do método é simplesmente que o *KPP* ("*PatchGuard*") monitora a integridade
de algumas (todas?) estruturas `_OBJECT_TYPE` e aciona uma [`0x109 Bug Check`](https://docs.microsoft.com/en-us/windows-hardware/drivers/debugger/bug-check-0x109---critical-structure-corruption)
com o parâmetro 4 sendo igual a `0x8`, significando que uma estrutura de tipo de objeto foi alterada.
No entanto, realizar a desativação / reativação (e a ação "maliciosa" no meio)
rapidamente deve ser suficiente para "correr" contra o *PatchGuard* (a menos que você tenha azar e uma verificação
periódica seja realizada exatamente no momento errado).
### Bypass de EDR através do desligamento de callbacks de minifilters
O sistema Windows Filter Manager permite que um EDR carregue um driver "minifilter" e
registre callbacks para ser notificado de operações de I/O, como abertura de arquivo,
leitura, escrita, etc.
Aqui está um breve resumo das diferentes estruturas internas usadas pelo filter manager:
- O Filter Manager estabelece um "frame" (`_FLTP_FRAME`) como sua estrutura raiz;
- Uma estrutura "volume" (`_FLT_VOLUME`) é instanciada para cada "disco" gerenciado pelo
Filter Manager (podem ser partições, cópias de sombra ou especiais correspondentes a
named pipes ou sistemas de arquivos remotos);
- Para cada driver minifilter registrado, corresponde uma estrutura "filter" (`_FLT_FILTER`),
descrevendo várias propriedades, como suas operações suportadas;
- Esses minifilters não estão todos anexados a cada volume; uma estrutura "instance" (`_FLT_INSTANCE`)
é criada para marcar cada uma das associações
filter<->volume;
- Os minifilters registram funções de callback que devem ser executadas antes e/ou depois de
operações específicas (abertura de arquivo, escrita, leitura, etc.). Esses callbacks são descritos em
estruturas `_CALLBACK_NODE` e podem ser acessados por diferentes maneiras:
- Um array de todos os `_CALLBACK_NODE`s implementados por uma instância de um minifilter
pode ser encontrado na estrutura `_FLT_INSTANCE`; o array é indexado pelo código "major function"
da IRP, uma constante que representa as operações tratadas pelos callbacks (`IRP_MJ_CREATE`, `IRP_MJ_READ`, etc.).
- Além disso, todos os `_CALLBACK_NODE`s implementados por instâncias ligadas a um volume específico
são agrupados em listas encadeadas, armazenadas no array `_FLT_VOLUME.Callbacks.OperationLists`
indexado por códigos de major function da IRP.
Essas diferentes estruturas são percorridas pelo `EDRSandblast` para detectar filtros que estão
associados a drivers relacionados a EDR, e os nós de callback contendo funções de monitoramento
são enumerados. Para desabilitar seu efeito, os nós são desvinculados de suas listas,
tornando-os temporariamente invisíveis para o filter manager.
Dessa forma, durante um período especificado, o EDR pode estar completamente alheio a qualquer operação
de arquivo. Um exemplo básico seria a criação de um arquivo de dump de memória do lsass no disco,
que não acionaria nenhuma análise do EDR e, portanto, nenhuma detecção baseada no próprio arquivo.
### Bypass de EDR através da desativação do provedor ETW Microsoft-Windows-Threat-Intelligence
O provedor `ETW Microsoft-Windows-Threat-Intelligence` registra dados sobre o
uso de algumas APIs do Windows comumente usadas de forma maliciosa. Isso inclui a
API `nt!MiReadWriteVirtualMemory`, chamada por `nt!NtReadVirtualMemory` (que é
usada para despejar a memória do `LSASS`) e monitorada pela função
`nt!EtwTiLogReadWriteVm`.
Produtos EDR podem consumir os logs produzidos pelo provedor `ETW TI` através
de serviços ou processos executando como, respectivamente,
`SERVICE_LAUNCH_PROTECTED_ANTIMALWARE_LIGHT` ou
`PS_PROTECTED_ANTIMALWARE_LIGHT`, e associados a um driver de
`Early Launch Anti Malware (ELAM)`.
Conforme publicado por
[`slaeryan` em um post do blog `CNO Development Labs`](https://public.cnotools.studio/bring-your-own-vulnerable-kernel-driver-byovkd/exploits/data-only-attack-neutralizing-etwti-provider),
o provedor `ETW TI` pode ser desabilitado completamente corrigindo, na memória do kernel,
seu atributo `ProviderEnableInfo` para `0x0`. Consulte o excelente post do blog mencionado
para mais informações sobre a técnica.
De forma semelhante à remoção de callbacks do kernel, os offsets necessários do `ntoskrnl.exe`
(`nt!EtwThreatIntProvRegHandleOffset`, `GuidEntry` de `_ETW_REG_ENTRY` e
`ProviderEnableInfo` de `_ETW_GUID_ENTRY`) são calculados no arquivo
`NtoskrnlOffsets.csv` para várias versões do kernel do Windows.
### Bypass de EDR através do bypass de hooking em modo de usuário
#### Como funciona o hooking em modo de usuário
Para monitorar facilmente as ações realizadas por processos, os produtos EDR frequentemente
implementam um mecanismo chamado *hooking em modo de usuário*. Primeiro, os produtos EDR registram um callback
do kernel (geralmente callbacks de *carregamento de imagem* ou *criação de processo*, veja acima) que permite
que sejam notificados a cada início de processo.
Quando um processo é carregado pelo Windows, e antes que ele realmente inicie, o EDR é capaz de
injetar alguma DLL personalizada no espaço de endereço do processo, que contém sua lógica
de monitoramento. Durante o carregamento, essa DLL injeta "*hooks*" no início de cada função que deve ser
monitorada pelo EDR. Em tempo de execução, quando as funções monitoradas são chamadas pelo
processo sob vigilância, esses hooks redirecionam o fluxo de controle para algum código de supervisão
presente na DLL do EDR, o que permite inspecionar argumentos e valores de retorno dessas
chamadas.
Na maioria das vezes, as funções monitoradas são chamadas de sistema (como `NtReadVirtualMemory`,
`NtOpenProcess`, etc.), cujas implementações residem em `ntdll.dll`. Interceptar chamadas para
funções `Nt*` permite que os produtos estejam o mais próximo possível do limite entre modo de usuário e modo kernel
(enquanto permanecem em modo de usuário), mas funções de algumas DLLs de nível superior também podem
ser monitoradas.
Abaixo estão exemplos da mesma função, antes e depois de ser hookada pelo produto EDR:```assembly
NtProtectVirtualMemory proc near
mov r10, rcx
mov eax, 50h
test byte ptr ds:7FFE0308h, 1
jnz short loc_18009D1E5
syscall
retn
loc_18009D1E5:
int 2Eh
retn
NtProtectVirtualMemory endp
Nada para traduzir, nenhum conteúdo fornecido.```assembly NtProtectVirtualMemory proc near jmp sub_7FFC74490298 ; --> "hook", jump to EDR analysis function int 3 ; overwritten instructions int 3 ; overwritten instructions int 3 ; overwritten instructions test byte_7FFE0308, 1 ; <-- execution resumes here after analysis jnz short loc_7FFCB44AD1E5 syscall retn loc_7FFCB44AD1E5: int 2Eh retn NtProtectVirtualMemory endp
#### Detecção de hooks
Os hooks em espaço de usuário têm a "fraqueza" de estarem localizados na memória do espaço de usuário, o que significa que são
diretamente observáveis e modificáveis pelo processo sob escrutínio. Para detectar automaticamente
hooks no espaço de endereço do processo, a ideia principal é comparar as diferenças entre
o DLL original no disco e a biblioteca residente na memória, que foi potencialmente
alterada por um EDR. Para realizar essa comparação, os seguintes passos são seguidos pelo
EDRSandblast:
* A lista de todos os DLLs carregados é enumerada graças à `InLoadOrderModuleList` localizada
no `PEB` (para evitar chamar qualquer API que possa ser monitorada e suspeita)
* Para cada DLL carregado, seu conteúdo no disco é lido e seus cabeçalhos são analisados. A
biblioteca correspondente, residente na memória, também é analisada para identificar seções, exportações,
etc.
* As realocações do DLL são analisadas e aplicadas, levando em conta o endereço base da
biblioteca carregada correspondente. Isso permite que o conteúdo tanto da biblioteca em memória
quanto do DLL originário do disco tenham exatamente o mesmo conteúdo (nas seções onde
as realocações são aplicadas), tornando a comparação confiável.
* As funções exportadas são enumeradas e os primeiros bytes das versões "em memória" e "em disco"
são comparados. Qualquer diferença indica uma alteração que foi feita após
o DLL ser carregado, e, portanto, é muito provavelmente um hook de EDR.
Nota: O processo pode ser generalizado para encontrar diferenças em qualquer lugar em seções não graváveis
e não apenas no início das funções exportadas, por exemplo, se produtos EDR começarem a
aplicar hooks no meio da função :) Assim, não usado pela ferramenta, isso foi
implementado em `findDiffsInNonWritableSections`.
Para contornar o monitoramento realizado por esses hooks, múltiplas técnicas são
possíveis, e cada uma tem benefícios e desvantagens.
#### Bypass de hook usando ... unhooking
O método mais intuitivo para contornar o monitoramento baseado em hooks é remover os
hooks. Como os hooks estão presentes na memória que é acessível pelo próprio processo, para
remover um hook, o processo pode simplesmente:
* Alterar as permissões na página onde o hook está localizado (RX -> RWX ou RW)
* Escrever os bytes originais que são conhecidos graças ao conteúdo do DLL em disco
* Reverter as permissões para RX
Esta abordagem é bastante simples e pode ser usada para remover todos os hooks detectados de uma só
vez. Realizada por uma ferramenta ofensiva no seu início, isso permite que o restante do código
seja completamente ignorante do mecanismo de hooking e execute normalmente sem ser
monitorado.
No entanto, tem duas desvantagens principais. O EDR provavelmente está monitorando o uso de
`NtProtectVirtualMemory`, então usá-lo para alterar as permissões da página onde os
hooks foram instalados é (pelo menos conceitualmente) uma má ideia. Além disso, se uma thread for
executada pelo EDR e verificar periodicamente a integridade dos hooks, isso também poderia
desencadear alguma detecção.
Para detalhes de implementação, verifique o caminho de código da função `unhook()` quando `unhook_method` for
`UNHOOK_WITH_NTPROTECTVIRTUALMEMORY`.
**Nota importante: por simplicidade, esta técnica está implementada no EDRSandblast como a
técnica base usada para *demonstrar* as outras técnicas de bypass; cada uma delas demonstra
como obter uma versão não monitorada de `NtProtectVirtualMemory`, mas realiza a mesma
operação posteriormente (unhooking de um hook específico).**
#### Bypass de hook usando um trampolim personalizado
Para contornar um hook específico, é possível simplesmente "pular por cima" e executar o restante da
função como está. Primeiro, os bytes originais da função monitorada, que foram
sobrescritos pelo EDR para instalar o hook, devem ser recuperados do arquivo DLL. Em nosso
exemplo de código anterior, isso seriam os bytes correspondentes às seguintes
instruções:```assembly
mov r10, rcx
mov eax, 50h
Identificar esses bytes é uma tarefa simples, pois somos capazes de realizar uma diff limpa das versões da biblioteca na memória e no disco, conforme descrito anteriormente. Em seguida, montamos uma instrução de salto que é construída para redirecionar o fluxo de controle para o código imediatamente após o hook, no endereço `NtProtectVirtualMemory + sizeof(overwritten_instructions)````assembly jmp NtProtectVirtualMemory+8
Finalmente, concatenamos esses opcodes, os armazenamos em memória (recém-)executável e mantemos um ponteiro para eles. Esse objeto é chamado de "*trampoline*" e pode então ser usado como um ponteiro de função, estritamente equivalente à função original `NtProtectVirtualMemory`.
O principal benefício desta técnica, assim como para todas as técnicas abaixo, é que o hook nunca é apagado, então qualquer verificação de integridade realizada nos hooks pelo EDR deve passar. No entanto, requer alocar memória gravável e depois executável, o que é típico de uma alocação de shellcode, atraindo assim o escrutínio do EDR.
Para detalhes de implementação, verifique o caminho de código da função `unhook()` quando `unhook_method` é `UNHOOK_WITH_INHOUSE_NTPROTECTVIRTUALMEMORY_TRAMPOLINE`. Lembre-se de que a técnica é apenas demonstrada em nossa implementação e é, no final, usada para **remover** hooks da memória, como em todas as técnicas abaixo.
#### Bypass de hook usando o próprio trampoline do EDR
O produto EDR, para que seu hook funcione, deve salvar em algum lugar na memória os opcodes que ele removeu. Pior (*ou "melhor", do ponto de vista do atacante*), para usar efetivamente as instruções originais, o EDR provavelmente alocou para si mesmo um *trampoline* em algum lugar para executar a função original após interceptar a chamada.
Esse trampoline pode ser pesquisado e usado como substituto para a função hookada, sem a necessidade de alocar memória executável ou chamar qualquer API, exceto `VirtualQuery`, que provavelmente não é monitorada por ser uma função inócua.
Para encontrar o trampoline na memória, percorremos todo o espaço de endereçamento usando `VirtualQuery` em busca de memória com commit e executável. Para cada região desse tipo de memória, a escaneamos em busca de uma instrução de salto que tenha como alvo o endereço após as instruções sobrescritas (`NtProtectVirtualMemory+8` em nosso exemplo anterior). O trampoline pode então ser usado para chamar a função hookada sem acionar o hook.
Essa técnica funciona surpreendentemente bem, pois recupera quase todos os trampolines nos EDRs testados. Para detalhes de implementação, verifique o caminho de código da função `unhook()` quando `unhook_method` é `UNHOOK_WITH_EDR_NTPROTECTVIRTUALMEMORY_TRAMPOLINE`.
#### Bypass de hook usando DLL duplicada
Outro método simples para obter acesso a uma versão não monitorada da função `NtProtectVirtualMemory` é carregar uma versão duplicada da biblioteca `ntdll.dll` no espaço de endereçamento do processo. Como duas DLLs idênticas podem ser carregadas no mesmo processo, desde que tenham nomes diferentes, podemos simplesmente copiar o arquivo legítimo `ntdll.dll` para outro local, carregá-lo usando `LoadLibrary` (ou reimplementar o processo de carregamento) e acessar a função usando `GetProcAddress`, por exemplo.
Essa técnica é muito simples de entender e implementar, e tem uma chance decente de sucesso, já que a maioria dos produtos EDR não reinstala hooks em DLLs recém-carregadas depois que o processo está em execução. No entanto, a principal desvantagem é que copiar binários assinados pela Microsoft com um nome diferente é frequentemente considerado suspeito pelos próprios produtos EDR.
Essa técnica está, no entanto, implementada no `EDRSandblast`. Para detalhes de implementação, verifique o caminho de código da função `unhook()` quando `unhook_method` é `UNHOOK_WITH_DUPLICATE_NTPROTECTVIRTUALMEMORY`.
#### Bypass de hook usando syscalls diretos
Para usar funções relacionadas a chamadas de sistema, um programa pode reimplementar syscalls (em assembly) para chamar os recursos correspondentes do SO sem realmente tocar no código em `ntdll.dll`, que pode estar monitorado pelo EDR. Isso contorna completamente qualquer hooking em modo de usuário feito em funções de syscall em `ntdll.dll`.
Isso, no entanto, tem algumas desvantagens. Primeiro, implica ser capaz de conhecer a lista de números de syscall das funções que o programa precisa, que mudam para cada versão do Windows. Isso é, no entanto, mitigado implementando múltiplas heurísticas que se sabe funcionarem em todas as versões passadas do Windows NT (ordenando as exportações `Zw*` do `ntdll`, procurando pela instrução `mov rax, #syscall_number` na função `ntdll` associada, etc.) e verificando se todas retornam o mesmo resultado (veja `Syscalls.c` para mais detalhes).
Além disso, funções que não são tecnicamente syscalls (por exemplo, `LoadLibraryX`/`LdrLoadDLL`) também podem estar monitoradas e não podem simplesmente ser reimplementadas usando um syscall.
A técnica de syscalls diretos está implementada no EDRSandblast. Como afirmado anteriormente, é usada apenas para executar `NtProtectVirtualMemory` com segurança e remover todos os hooks detectados.
Para detalhes de implementação, verifique o caminho de código da função `unhook()` quando `unhook_method` é `UNHOOK_WITH_DIRECT_SYSCALL`.
### Exploração de drivers vulneráveis
Como afirmado anteriormente, toda ação que precisa de uma leitura ou escrita na memória do kernel depende de um driver vulnerável para fornecer essa primitiva. No EDRSanblast, adicionar suporte para um novo driver que fornece a primitiva de leitura/escrita pode ser feito "facilmente", bastando implementar três funções:
* Uma função `ReadMemoryPrimitive_DRIVERNAME(SIZE_T Size, DWORD64 Address, PVOID Buffer)`, que copia `Size` bytes do endereço do kernel `Address` para o buffer do modo de usuário `Buffer`;
* Uma função `WriteMemoryPrimitive_DRIVERNAME(SIZE_T Size, DWORD64 Address, PVOID Buffer)`, que copia `Size` bytes do buffer do modo de usuário `Buffer` para o endereço do kernel `Address`;
* Uma função `CloseDriverHandle_DRIVERNAME()` que garante que todos os handles para o driver sejam fechados (necessário antes da operação de desinstalação, que é independente do driver, por enquanto).
Como exemplo, dois drivers são atualmente suportados pelo EDRSandblast, `RTCore64.sys` (SHA256: `01AA278B07B58DC46C84BD0B1B5C8E9EE4E62EA0BF7A695862444AF32E87F1FD`) e `DBUtils_2_3.sys` (SHA256: `0296e2ce999e67c76352613a718e11516fe1b0efc3ffdb8918fc999dd76a73a5`). O seguinte código em `KernelMemoryPrimitives.h` deve ser atualizado se o driver vulnerável utilizado precisar ser alterado, ou se um novo for implementado.```C
#define RTCore 0
#define DBUtil 1
// Select the driver to use with the following #define
#define VULN_DRIVER RTCore
#if VULN_DRIVER == RTCore
#define DEFAULT_DRIVER_FILE TEXT("RTCore64.sys")
#define CloseDriverHandle CloseDriverHandle_RTCore
#define ReadMemoryPrimitive ReadMemoryPrimitive_RTCore
#define WriteMemoryPrimitive WriteMemoryPrimitive_RTCore
#elif VULN_DRIVER == DBUtil
#define DEFAULT_DRIVER_FILE TEXT("DBUtil_2_3.sys")
#define CloseDriverHandle CloseDriverHandle_DBUtil
#define ReadMemoryPrimitive ReadMemoryPrimitive_DBUtil
#define WriteMemoryPrimitive WriteMemoryPrimitive_DBUtil
#endif
Múltiplas técnicas são atualmente usadas para determinar se um driver ou processo específico pertence ou não a um produto de EDR.
Primeiramente, o nome do driver pode ser simplesmente usado para esse propósito. De fato, a Microsoft aloca números específicos chamados "Altitudes" para todos os drivers que precisam inserir callbacks no kernel. Isso permite uma ordem determinística na execução dos callbacks, independente da ordem de registro, mas baseada apenas no uso do driver. Uma lista de (fornecedores de) drivers que reservaram altitude específica pode ser encontrada no MSDN. Como consequência, uma lista quase abrangente de nomes de drivers de segurança ligados a produtos de segurança é oferecida pela Microsoft, principalmente nas listas "FSFilter Anti-Virus" e "FSFilter Activity Monitor". Essas listas de nomes de drivers estão embutidas no EDRSandblast, assim como contribuições adicionais.
Além disso, executáveis e DLLs de EDR são mais frequentemente assinados digitalmente usando o certificado de assinatura do fornecedor. Assim, verificar o signatário de um executável ou DLL associado a um processo pode permitir identificar rapidamente produtos de EDR.
Além disso, os drivers precisam ser diretamente assinados pela Microsoft para serem carregados no espaço do kernel. Embora o fornecedor do driver não seja diretamente o signatário do driver em si, parece que o nome do fornecedor ainda está incluído dentro de um atributo da assinatura; essa técnica de detecção ainda está por ser investigada e implementada.
Finalmente, ao enfrentar um EDR desconhecido para o EDRSandblast, a melhor abordagem é executar a ferramenta no modo "audit" e verificar a lista de drivers que registraram callbacks do kernel; então o nome do driver pode ser adicionado à lista, a ferramenta recompilada e executada novamente.
O mecanismo de Proteção da Autoridade de Segurança Local (LSA) , introduzido pela primeira vez no Windows 8.1 e Windows Server 2012 R2, utiliza a tecnologia Protected Process Light (PPL) para restringir o acesso ao processo LSASS. A proteção PPL regula e restringe operações, como injeção de memória ou dumping de memória de processos protegidos, mesmo de um processo que detenha o privilégio SeDebugPrivilege. No modelo de proteção de processos, apenas processos executando com níveis de proteção mais altos podem realizar operações em processos protegidos.
A estrutura _EPROCESS, usada pelo kernel do Windows para representar um processo na memória do kernel, inclui um campo _PS_PROTECTION que define o nível de proteção de um processo através de seus atributos Type (_PS_PROTECTED_TYPE) e Signer (_PS_PROTECTED_SIGNER).
Ao escrever na memória do kernel, o processo EDRSandblast é capaz de atualizar seu próprio nível de proteção para PsProtectedSignerWinTcb-Light. Esse nível é suficiente para fazer o dump da memória do processo LSASS, pois ele "domina" o PsProtectedSignerLsa-Light, o nível de proteção do processo LSASS executando com o mecanismo RunAsPPL.
EDRSandBlast implementa a autoproteção da seguinte forma:
NtQuerySystemInformation para encontrar o handle aberto no processo atual e o endereço da estrutura EPROCESS do processo atual na memória do kernel._PS_PROTECTION do processo atual na memória do kernel. Os deslocamentos do campo _PS_PROTECTION relativos à estrutura EPROCESS (definidos pela versão do ntoskrnl em uso) são calculados no arquivo NtoskrnlOffsets.csv.O Credential Guard da Microsoft é uma tecnologia de isolamento baseada em virtualização, introduzida no Windows 10 (edição Enterprise) da Microsoft, que impede o acesso direto às credenciais armazenadas no processo LSASS.
Quando o Credential Guard é ativado, um processo LSAIso (LSA Isolated) é criado no Modo Seguro Virtual, um recurso que utiliza as extensões de virtualização da CPU para fornecer segurança adicional dos dados na memória. O acesso ao processo LSAIso é restrito mesmo para um acesso com o contexto de segurança NT AUTHORITY\SYSTEM. Ao processar um hash, o processo LSA realiza uma chamada RPC ao processo LSAIso e aguarda o resultado do LSAIso para continuar. Assim, o processo LSASS não conterá segredos e, em seu lugar, armazenará Dados Isolados da LSA.
Conforme declarado na pesquisa original conduzida por N4kedTurtle: "Wdigest pode ser habilitado em um sistema com Credential Guard corrigindo os valores de g_fParameter_useLogonCredential e g_IsCredGuardEnabled na memória". A ativação do Wdigest resultará no armazenamento de credenciais em texto claro na memória do LSASS para quaisquer novos logons interativos (sem exigir reinicialização do sistema). Consulte o post do blog da pesquisa original para mais detalhes sobre essa técnica.
EDRSandBlast simplesmente torna o PoC original um pouco mais amigável para opsec e fornece suporte para várias versões do wdigest.dll (através de deslocamentos calculados para g_fParameter_useLogonCredential e g_IsCredGuardEnabled).
Para realizar de forma confiável operações de bypass de monitoramento do kernel, o EDRSandblast precisa saber exatamente onde ler e escrever na memória do kernel. Isso é feito usando deslocamentos de variáveis globais dentro da imagem alvo (ntoskrnl.exe, wdigest.dll), bem como deslocamentos de campos específicos em estruturas cujas definições são publicadas pela Microsoft em arquivos de símbolo. Esses deslocamentos são específicos para cada compilação das imagens alvo e devem ser coletados pelo menos uma vez para uma versão específica da plataforma.
A escolha de usar deslocamentos "hardcoded" em vez de buscas de padrão para localizar as estruturas e variáveis usadas pelo EDRSandblast é justificada pelo fato de que as APIs não documentadas responsáveis pela adição/remoção de callbacks do kernel estão sujeitas a mudanças e que qualquer tentativa de ler ou escrever na memória do kernel no endereço errado pode (e frequentemente irá) resultar em um Bug Check (Tela Azul da Morte). Uma falha na máquina não é aceitável tanto em cenários de red team quanto em testes de penetração normais, pois uma máquina que falha é altamente visível para os defensores e perderá qualquer credencial que ainda estava na memória no momento do ataque.
Para recuperar deslocamentos para cada versão específica do Windows, duas abordagens são implementadas.
Os deslocamentos necessários do ntoskrnl.exe e wdigest.dll podem ser extraídos usando o script Python fornecido ExtractOffsets.py, que depende do radare2 e r2pipe para baixar e analisar símbolos de arquivos PDB, e extrai os deslocamentos necessários deles. Os deslocamentos são então armazenados em arquivos CSV para uso posterior pelo EDRSandblast.
Para suportar imediatamente uma ampla gama de compilações do Windows, muitas versões dos binários ntoskrnl.exe e wdigest.dll são referenciadas pelo Winbindex e podem ser baixadas automaticamente (e seus deslocamentos extraídos) pelo ExtractOffsets.py. Isso permite extrair deslocamentos de quase todos os arquivos que já foram publicados em pacotes de atualização do Windows (até o momento, mais de 450 versões do ntoskrnl.exe e mais de 30 do wdigest.dll estão disponíveis e pré-calculadas).
Uma opção adicional foi implementada no EDRSandBlast para permitir que o programa baixe os arquivos .pdb necessários do Microsoft Symbol Server, extraia os deslocamentos necessários e até atualize os arquivos .csv correspondentes, se presentes.
Usar a opção --internet torna a execução da ferramenta muito mais simples, ao mesmo tempo que introduz um risco adicional de OpSec, já que um arquivo .pdb é baixado e gravado no disco durante o processo. Isso é exigido pelas funções dbghelp.dll usadas para analisar o banco de dados de símbolos; no entanto, a análise completa de PDB em memória pode ser implementada no futuro para eliminar esse requisito e reduzir a pegada da ferramenta.
O EDRSandblast implementa publicamente o suporte de pelo menos 3 drivers vulneráveis: gdrv.sys (padrão), RTCore64.sys e DBUtil_2_3.sys. O driver realmente usado é decidido antes da compilação da ferramenta (veja #define VULN_DRIVER <nome do driver> em includes/KernelMemoryPrimitive.h). Uma cópia do driver vulnerável deve ser baixada e fornecida ao EDRSandblast para que sua operação no kernel funcione.
Os hashes dos drivers testados são mencionados no início de cada arquivo Driver<nome>.c que implementa as primitivas de leitura e escrita da memória do kernel usadas pelo EDRSanblast. Usando esses hashes, amostras de drivers podem ser facilmente encontradas na Internet, especialmente em https://www.loldrivers.io.
Aqui está a lista dos drivers vulneráveis suportados junto com links de download:
Usage: EDRSandblast.exe [-h | --help] [-v | --verbose] <audit | dump | cmd | credguard | firewall | load_unsigned_driver> [--usermode] [--unhook-method ] [--direct-syscalls] [--add-dll ]* [--kernelmode] [--dont-unload-driver] [--no-restore] [--nt-offsets <NtoskrnlOffsets.csv>] [--fltmgr-offsets <FltmgrOffsets.csv>] [--wdigest-offsets <WdigestOffsets.csv>] [--ci-offsets <CiOffsets.csv>] [--internet] [--vuln-driver <RTCore64.sys>] [--vuln-service <SERVICE_NAME>] [--unsigned-driver <evil.sys>] [--unsigned-service <SERVICE_NAME>] [--no-kdp] [-o | --dump-output <DUMP_FILE>]
### Opções```
-h | --help Show this help message and exit.
-v | --verbose Enable a more verbose output.
Actions mode:
audit Display the user-land hooks and / or Kernel callbacks without taking actions.
dump Dump the process specified by --process-name (LSASS process by default), as '<process_name>' in the current directory or at the
specified file using -o | --output <DUMP_FILE>.
cmd Open a cmd.exe prompt.
credguard Patch the LSASS process' memory to enable Wdigest cleartext passwords caching even if
Credential Guard is enabled on the host. No kernel-land actions required.
firewall Add Windows firewall rules to block network access for the EDR processes / services.
load_unsigned_driver Load the specified unsigned driver, bypassing Driver Signature Enforcement (DSE).
WARNING: currently an experimental feature, only works if KDP is not present and enabled.
--usermode Perform user-land operations (DLL unhooking).
--kernelmode Perform kernel-land operations (Kernel callbacks removal and ETW TI disabling).
Hooking-related options:
--add-dll <dll name or path> Loads arbitrary libraries into the process' address space, before starting
anything.This can be useful to audit userland hooking for DLL that are not
loaded by default by this program. Use this option multiple times to load
multiple DLLs all at once.
Example of interesting DLLs to look at: user32.dll, ole32.dll, crypt32.dll,
samcli.dll, winhttp.dll, urlmon.dll, secur32.dll, shell32.dll...
--unhook-method <N> Choose the userland un-hooking technique, from the following:
0 Do not perform any unhooking (used for direct syscalls operations).
1 (Default) Uses the (probably monitored) NtProtectVirtualMemory function in ntdll to remove all
present userland hooks.
2 Constructs a 'unhooked' (i.e. unmonitored) version of NtProtectVirtualMemory, by allocating an executable trampoline jumping over the hook, and remove all present
userland hooks.
3 Searches for an existing trampoline allocated by the EDR itself, to get an 'unhooked'
(i.e. unmonitored) version of NtProtectVirtualMemory, and remove all present userland
hooks.
4 Loads an additional version of ntdll library into memory, and use the (hopefully unmonitored) version of NtProtectVirtualMemory present in this library to remove all
present userland hooks.
5 Allocates a shellcode that uses a direct syscall to call NtProtectVirtualMemory, and uses it to remove all detected hooks
--direct-syscalls Use direct syscalls to dump the selected process memory without unhooking unserland hooks.
BYOVD options:
--dont-unload-driver Keep the vulnerable driver installed on the host
Default to automatically unsinstall the driver.
--no-restore Do not restore the EDR drivers' Kernel Callbacks that were removed.
Default to restore the callbacks.
--vuln-driver <gdrv.sys> Path to the vulnerable driver file.
Default to 'gdrv.sys' in the current directory.
--vuln-service <SERVICE_NAME> Name of the vulnerable service to intall / start.
Driver sideloading options:
--unsigned-driver <evil.sys> Path to the unsigned driver file.
Default to 'evil.sys' in the current directory.
--unsigned-service <SERVICE_NAME> Name of the unsigned driver's service to intall / start.
--no-kdp Switch to g_CiOptions patching method for disabling DSE (default is callback swapping).
Offset-related options:
--nt-offsets <NtoskrnlOffsets.csv> Path to the CSV file containing the required ntoskrnl.exe's offsets.
Default to 'NtoskrnlOffsets.csv' in the current directory.
--fltmgr-offsets <FltmgrOffsets.csv> Path to the CSV file containing the required fltmgr.sys's offsets
Default to 'FltmgrOffsets.csv' in the current directory.
--wdigest-offsets <WdigestOffsets.csv> Path to the CSV file containing the required wdigest.dll's offsets
(only for the 'credguard' mode).
Default to 'WdigestOffsets.csv' in the current directory.
--ci-offsets <CiOffsets.csv> Path to the CSV file containing the required ci.dll's offsets
(only for the 'load_unsigned_driver' mode).
Default to 'WdigestOffsets.csv' in the current directory.
-i | --internet Enables automatic symbols download from Microsoft Symbol Server
If a corresponding *Offsets.csv file exists, appends the downloaded offsets to the file for later use
OpSec warning: downloads and drops on disk a PDB file for the corresponding image
Dump options:
-o | --dump-output <DUMP_FILE> Output path to the dump file that will be generated by the 'dump' mode.
Default to 'process_name' in the current directory.
--process-name <NAME> File name of the process to dump (defaults to 'lsass.exe')
O EDRSandBlast (apenas x64) foi compilado no Visual Studio 2019 (Windows SDK Versão: 10.0.19041.0 e Conjunto de Ferramentas da Plataforma: Visual Studio 2019 (v142)).
Observe que o ExtractOffsets.py foi testado apenas no Windows.```
pip.exe install -m .\requirements.txt
ExtractOffsets.py [-h] -i INPUT [-o OUTPUT] [-d] mode
positional arguments: mode ntoskrnl or wdigest. Mode to download and extract offsets for either ntoskrnl or wdigest
optional arguments: -h, --help show this help message and exit -i INPUT, --input INPUT Single file or directory containing ntoskrnl.exe / wdigest.dll to extract offsets from. If in download mode, the PE downloaded from MS symbols servers will be placed in this folder. -o OUTPUT, --output OUTPUT CSV file to write offsets to. If the specified file already exists, only new ntoskrnl versions will be downloaded / analyzed. Defaults to NtoskrnlOffsets.csv / WdigestOffsets.csv in the current folder. -d, --download Flag to download the PE from Microsoft servers using list of versions from winbindex.m417z.com.
## Detecção
Do ponto de vista do defensor (fornecedor de EDR, Microsoft, analistas de SOC analisando a telemetria do EDR, ...), vários indicadores podem ser usados para detectar ou prevenir este tipo de técnicas.
### Lista de permissões de drivers
Como cada ação executada pela ferramenta na memória do modo kernel depende de um driver vulnerável para ler/escrever conteúdo arbitrário, os eventos de carregamento de driver devem ser examinados minuciosamente pelo produto EDR (ou analistas de SOC) e gerar um alerta para qualquer carregamento incomum de driver, ou até mesmo bloquear drivers vulneráveis conhecidos. Esta última abordagem é até [recomendada pela própria Microsoft](https://docs.microsoft.com/en-us/windows/security/threat-protection/windows-defender-application-control/microsoft-recommended-driver-block-rules): qualquer dispositivo Windows com HVCI (*Integridade do Código Protegida pelo Hipervisor*) ativada incorpora uma lista de bloqueio de drivers, e isso se tornará progressivamente um comportamento padrão no Windows (já é no Windows 11).
### Verificações de integridade da memória do kernel
Uma vez que um atacante ainda poderia usar um driver vulnerável desconhecido para executar as mesmas ações na memória, o driver do EDR poderia verificar periodicamente se seus callbacks do kernel ainda estão registrados, diretamente inspecionando a memória do kernel (como esta ferramenta faz), ou simplesmente acionando eventos (criação de processo, criação de thread, carregamento de imagem, etc.) e verificando se as funções de callback são de fato chamadas pelo kernel executivo.
Como nota adicional, este tipo de estrutura de dados poderia ser protegida através do recente mecanismo [Kernel Data Protection (KDP)](https://www.microsoft.com/security/blog/2020/07/08/introducing-kernel-data-protection-a-new-platform-security-technology-for-preventing-data-corruption/), que depende da Virtual Based Security, para tornar o array de callbacks do kernel não gravável sem chamar as APIs corretas.
A mesma lógica poderia ser aplicada a variáveis sensíveis do ETW, como `ProviderEnableInfo`, abusada por esta ferramenta para desabilitar a geração de eventos de Inteligência de Ameaças do ETW.
### Detecção no modo usuário
O primeiro indicador de que um processo está ativamente tentando evadir o hooking em modo usuário são os acessos a arquivos de cada DLL correspondente aos módulos carregados; em uma execução normal, um processo em modo usuário raramente precisa ler arquivos DLL fora de uma chamada `LoadLibrary`, especialmente `ntdll.dll`.
Para proteger o hooking de API de ser contornado, os produtos EDR poderiam verificar periodicamente se os hooks não foram alterados na memória, dentro de cada processo monitorado.
Finalmente, para detectar o bypass de hooking (abusando de um trampolim, usando syscalls diretas, etc.) que não implica a remoção dos hooks, os produtos EDR poderiam potencialmente confiar em callbacks do kernel associados às syscalls abusadas (ex. `PsCreateProcessNotifyRoutine` para a syscall `NtCreateProcess`, `ObRegisterCallbacks` para a syscall `NtOpenProcess`, etc.) e realizar análise da pilha de chamadas em modo usuário para determinar se a syscall foi acionada a partir de um caminho normal (`kernel32.dll` -> `ntdll.dll` -> syscall) ou anormal (ex. `program.exe` -> syscall direta).
## Agradecimentos
- Enumeração e remoção de callbacks do kernel:
https://github.com/br-sn/CheekyBlinder
- Primitivas de leitura/escrita na memória do kernel através do driver vulnerável
`Micro-Star MSI Afterburner`:
https://github.com/Barakat/CVE-2019-16098/
- Desativação do provedor de Inteligência de Ameaças do ETW:
https://public.cnotools.studio/bring-your-own-vulnerable-kernel-driver-byovkd/exploits/data-only-attack-neutralizing-etwti-provider
- Instalação/desinstalação de driver: https://github.com/gentilkiwi/mimikatz
- Lista inicial de nomes de drivers EDR:
https://github.com/SadProcessor/SomeStuff/blob/master/Invoke-EDRCheck.ps1
- Bypass do Credential Guard reativando o `Wdigest` através de patches na memória
do `LSASS`: https://teamhydra.blog/2020/08/25/bypassing-credential-guard/
## Autores
[Thomas DIOT (Qazeer)](https://github.com/Qazeer/)
[Maxime MEIGNAN (themaks)](https://github.com/themaks)
## Agradecimentos aos contribuidores
- [v1k1ngfr](https://github.com/v1k1ngfr): pelo bypass da Assinatura de Driver (via patching de `g_CiOptions`) e suporte ao driver GDRV.sys
- [Windy Bug](https://github.com/0mWindyBug): por um bypass da Assinatura de Driver compatível com KDP (via *troca de callback*) e sua grande contribuição no recurso de bypass de minifilter
## Licença
Licença CC BY 4.0 - https://creativecommons.org/licenses/by/4.0/
| Supported driver | Download link | SHA256 |
|---|
GDRV.sys | LOLDrivers link | 31f4cfb4c71da44120752721103a16512444c13c2ac2d857a7e6f13cb679b427 |
RTCore64.sys | LOLDrivers link | 01aa278b07b58dc46c84bd0b1b5c8e9ee4e62ea0bf7a695862444af32e87f1fd |
DBUtil_2_3.sys | LOLDrivers link | 0296e2ce999e67c76352613a718e11516fe1b0efc3ffdb8918fc999dd76a73a5 |