Por Gabriel Landau da Elastic Security. Modificação do EDRSandblast - veja o README original abaixo.
Integra GodFault ao EDR Sandblast, obtendo o mesmo resultado sem o uso de drivers vulneráveis.
C:UsersuserDesktopOffsets>EDRSandblast.exe --kernelmode cmd
______ _____ _____ _____ _ _ _ _
| ____| __ | __ / ____| | | | | | | |
| |__ | | | | |__) | (___ __ _ _ __ __| | |__ | | __ _ ___| |_
| __| | | | | _ / ___ / _` | '_ / _` | '_ | |/ _` / __| __|
| |____| |__| | | ____) | (_| | | | | (_| | |_) | | (_| __ | |_
|______|_____/|_| _|_____/ __,_|_| |_|__,_|_.__/|_|__,_|___/__|
D3FC0N 30 Edition | Thomas DIOT (@_Qazeer) & Maxime MEIGNAN (@th3m4ks)
[!] If kernel mode bypass is enabled, it is recommended to enable usermode bypass as well (e.g. to unhook the NtLoadDriver API call)
[===== KERNEL MODE =====]
[+] Setting up prerequisites for the kernel read/write primitives...
[+] Loading kernel related offsets from the CSV file
[*] System's ntoskrnl.exe file version is: ntoskrnl_22621-1702.exe
[+] Offsets are available for this version of ntoskrnl.exe (ntoskrnl_22621-1702.exe)!
[+] Checking if any EDR kernel notify rountines are set for image loading, process and thread creations...
[+] [NotifyRountines] Enumerating process creation callbacks
[+] Running command: GodFault.exe -t 2684
[?] Server does not appear to be running. Attempting to install it...
[+] CSRSS PID is 748
[+] Testing initial ability to acquire PROCESS_ALL_ACCESS to System: Failure
[+] Ready. Spawning WinTcb.
[+] SpawnPPL: Waiting for child process to finish.
[+] Thread 2684 (KTHREAD FFFF910961E4C080) has been blessed by GodFault
[+] [NotifyRountines] fffff8034df25500 [cng.sys + 0x5500]
[+] [NotifyRountines] fffff8034e9efdc0 [WdFilter.sys + 0x4fdc0]
[+] [NotifyRountines] Found callback belonging to EDR driver WdFilter.sys
[+] [NotifyRountines] fffff803487bc460 [ksecdd.sys + 0x1c460]
[+] [NotifyRountines] fffff8034eff3fd0 [tcpip.sys + 0x13fd0]
[+] [NotifyRountines] fffff8034f5ed980 [iorate.sys + 0xd980]
[+] [NotifyRountines] fffff8034dea8890 [CI.dll + 0x88890]
[+] [NotifyRountines] fffff803525079f0 [dxgkrnl.sys + 0x179f0]
[+] [NotifyRountines] fffff80352be0a70 [vm3dmp.sys + 0x10a70]
[+] [NotifyRountines] fffff8036ebccd00 [peauth.sys + 0x3cd00]
[+] [NotifyRountines] fffff8036eda1550 [wtd.sys + 0x1550]
[+] [NotifyRountines] Found a total of 1 EDR / security products driver(s)
[+] [NotifyRountines] Enumerating thread creation callbacks
[+] [NotifyRountines] fffff8034e9f15c0 [WdFilter.sys + 0x515c0]
[+] [NotifyRountines] Found callback belonging to EDR driver WdFilter.sys
[+] [NotifyRountines] fffff8034e9f1350 [WdFilter.sys + 0x51350]
[+] [NotifyRountines] Found callback belonging to EDR driver WdFilter.sys
[+] [NotifyRountines] fffff8036eb71010 [mmcss.sys + 0x1010]
[+] [NotifyRountines] Found a total of 2 EDR / security products driver(s)
[+] [NotifyRountines] Enumerating image loading callbacks
[+] [NotifyRountines] fffff8034e9f0820 [WdFilter.sys + 0x50820]
[+] [NotifyRountines] Found callback belonging to EDR driver WdFilter.sys
[+] [NotifyRountines] fffff80352ab5710 [ahcache.sys + 0x25710]
[+] [NotifyRountines] Found a total of 1 EDR / security products driver(s)
[+] Checking if EDR callbacks are registered on processes and threads handle creation/duplication...
[+] [ObjectCallblacks] Enumerating Process object callbacks :
[+] [ObjectCallblacks] Callback at FFFF800C5E2F2940 for handle creations & duplications:
[+] [ObjectCallblacks] Status: Enabled
[+] [ObjectCallblacks] Preoperation at 0xfffff8034e9eda30 [WdFilter.sys + 0x4da30]
[+] [ObjectCallblacks] Callback belongs to an EDR and is enabled!
[+] [ObjectCallblacks] Enumerating Thread object callbacks :
[+] [ObjectCallblacks] Object callbacks are present !
[+] [ETWTI] Checking the ETW Threat Intelligence Provider state...
[+] [ETWTI] ETW Threat Intelligence Provider is ENABLED!
[+] Process is NOT "safe" to launch our payload, removing monitoring and starting another process...
[+] [ETWTI] Disabling the ETW Threat Intel provider by patching ProviderEnableInfo at 0xffff91095ce8c430 with 0x00.
[+] [ETWTI] The ETW Threat Intel provider was successfully disabled!
[+] Removing kernel callbacks registered by EDR for process creation, thread creation and image loading...
[+] [NotifyRountines] Removing process creation callbacks
[+] [NotifyRountines] Removing callback of EDR driver "WdFilter.sys" [callback addr: 0xfffff8034970c2a8 | callback struct: 0xffff91095dbf3a5f | callback function: 0xfffff8034e9efdc0]
[+] [NotifyRountines] Removing thread creation callbacks
[+] [NotifyRountines] Removing callback of EDR driver "WdFilter.sys" [callback addr: 0xfffff8034970c4a0 | callback struct: 0xffff91095dbf3b1f | callback function: 0xfffff8034e9f15c0]
[+] [NotifyRountines] Removing callback of EDR driver "WdFilter.sys" [callback addr: 0xfffff8034970c4a8 | callback struct: 0xffff91095dbf3b4f | callback function: 0xfffff8034e9f1350]
[+] [NotifyRountines] Removing image loading callbacks
[+] [NotifyRountines] Removing callback of EDR driver "WdFilter.sys" [callback addr: 0xfffff8034970c6a0 | callback struct: 0xffff91095dbf3e4f | callback function: 0xfffff8034e9f0820]
[+] Disabling kernel callbacks registered by EDR for process and thread opening or handle duplication...
[+] [ObjectCallblacks] Disabling WdFilter.sys callback...
[+] All EDR drivers were successfully removed from Kernel callbacks!
==================================================
Starting a new unmonitored process...
==================================================
[!] If kernel mode bypass is enabled, it is recommended to enable usermode bypass as well (e.g. to unhook the NtLoadDriver API call)
[===== KERNEL MODE =====]
[+] Setting up prerequisites for the kernel read/write primitives...
[+] Loading kernel related offsets from the CSV file
[*] System's ntoskrnl.exe file version is: ntoskrnl_22621-1702.exe
[+] Offsets are available for this version of ntoskrnl.exe (ntoskrnl_22621-1702.exe)!
[+] Checking if any EDR kernel notify rountines are set for image loading, process and thread creations...
[+] [NotifyRountines] Enumerating process creation callbacks
[+] Running command: GodFault.exe -t 8344
[+] Thread 8344 (KTHREAD FFFF91096169F080) has been blessed by GodFault
[+] Initial blessing successful
[+] [NotifyRountines] fffff8034df25500 [cng.sys + 0x5500]
[+] [NotifyRountines] fffff803487bc460 [ksecdd.sys + 0x1c460]
[+] [NotifyRountines] fffff8034eff3fd0 [tcpip.sys + 0x13fd0]
[+] [NotifyRountines] fffff8034f5ed980 [iorate.sys + 0xd980]
[+] [NotifyRountines] fffff8034dea8890 [CI.dll + 0x88890]
[+] [NotifyRountines] fffff803525079f0 [dxgkrnl.sys + 0x179f0]
[+] [NotifyRountines] fffff80352be0a70 [vm3dmp.sys + 0x10a70]
[+] [NotifyRountines] fffff8036ebccd00 [peauth.sys + 0x3cd00]
[+] [NotifyRountines] fffff8036eda1550 [wtd.sys + 0x1550]
[+] [NotifyRountines] No EDR driver(s) found!
[+] [NotifyRountines] Enumerating thread creation callbacks
[+] [NotifyRountines] fffff8036eb71010 [mmcss.sys + 0x1010]
[+] [NotifyRountines] No EDR driver(s) found!
[+] [NotifyRountines] Enumerating image loading callbacks
[+] [NotifyRountines] fffff80352ab5710 [ahcache.sys + 0x25710]
[+] [NotifyRountines] No EDR driver(s) found!
[+] Checking if EDR callbacks are registered on processes and threads handle creation/duplication...
[+] [ObjectCallblacks] Enumerating Process object callbacks :
[+] [ObjectCallblacks] Callback at FFFF800C5E2F2940 for handle creations & duplications:
[+] [ObjectCallblacks] Status: Disabled
[+] [ObjectCallblacks] Preoperation at 0xfffff8034e9eda30 [WdFilter.sys + 0x4da30]
[+] [ObjectCallblacks] Callback belongs to an EDR but is disabled.
[+] [ObjectCallblacks] Enumerating Thread object callbacks :
[+] [ObjectCallblacks] Object callbacks are not found !
[+] [ETWTI] Checking the ETW Threat Intelligence Provider state...
[+] [ETWTI] ETW Threat Intelligence Provider is DISABLED!
[+] Process is "safe" to launch our payload
[+] Kernel callbacks have normally been removed, starting cmd.exe
WARNING: EDR kernel callbacks will be restored after exiting the cmd prompt (by typing exit)
WARNING: While unlikely, the longer the callbacks are removed, the higher the chance of being detected / causing a BSoD upon restore is!
Microsoft Windows [Version 10.0.22621.1702]
(c) Microsoft Corporation. All rights reserved.
C:UsersuserDesktopOffsets>
? README original abaixo?
EDRSandBlast
é uma ferramenta escrita em C
que transforma um driver assinado vulnerável em uma arma para ignorar detecções de EDR (retornos de chamada de rotina de notificação, retornos de chamada de objeto e provedor ETW TI
) e proteções LSASS
. Múltiplas técnicas de desengate do userland também são implementadas para evitar o monitoramento do userland.
A partir do lançamento, a combinação das técnicas userland ( --usermode
) e Kernel-land ( --kernelmode
) foi usada para despejar a memória LSASS
sob escrutínio EDR, sem ser bloqueado nem gerar eventos relacionados a "OS Credential Dumping" no produto (nuvem ) console. Os testes foram realizados em 3 produtos EDR distintos e foram bem-sucedidos em cada caso.
Os produtos EDR usam retornos de chamada "Notify Routines" do Kernel no Windows para serem notificados pelo kernel sobre atividades do sistema, como criação de processos e threads e carregamento de imagens ( exe
/ DLL
).
Esses retornos de chamada do kernel são definidos a partir do kernel, geralmente a partir do driver que implementa os retornos de chamada, usando uma série de APIs documentadas ( nt!PsSetCreateProcessNotifyRoutine
, nt!PsSetCreateThreadNotifyRoutine
, etc.). Essas APIs adicionam rotinas de retorno de chamada fornecidas pelo driver a matrizes de rotinas não documentadas no espaço do kernel:
PspCreateProcessNotifyRoutine
para criação de processosPspCreateThreadNotifyRoutine
para criação de threadPspLoadImageNotifyRoutine
para carregamento de imagens EDRSandBlast
enumera as rotinas definidas nesses arrays e remove qualquer rotina de retorno de chamada 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 driver EDR. A enumeração e remoção são possíveis através da exploração de um Primitiva arbitrária de leitura/gravação da memória do kernel fornecida pela exploração de um driver vulnerável (consulte a seção Drivers vulneráveis).
Os deslocamentos dos arrays acima mencionados são recuperados usando múltiplas técnicas, consulte a seção Compensações.
Os produtos EDR (e até EPP) geralmente registram "retornos de chamada de objeto" por meio do uso da API do kernel nt!ObRegisterCallbacks
. Esses retornos de chamada permitem que o produto de segurança seja notificado a cada geração de identificador em tipos de objetos específicos (os retornos de chamada de objetos relacionados a processos, threads e desktops agora são suportados pelo Windows). Uma geração de identificador pode ocorrer na abertura do objeto (chamada para OpenProcess
, OpenThread
, etc.), bem como na duplicação de identificador (chamada para DuplicateHandle
, etc.).
Ao ser notificado pelo kernel sobre cada uma dessas operações, um produto de segurança pode analisar a legitimidade da criação do identificador ( por exemplo, um processo desconhecido está tentando abrir o LSASS ) e até mesmo bloqueá-lo se uma ameaça for detectada.
A cada registro de retorno de chamada usando ObRegisterCallbacks
, um novo item é adicionado à lista duplamente vinculada CallbackList
presente no objeto _OBJECT_TYPE
descrevendo o tipo de objeto afetado pelo retorno de chamada (seja um Processo, um Thread ou um Desktop). Infelizmente, esses itens são descritos por uma estrutura que não está documentada nem publicada em arquivos de símbolos pela Microsoft. No entanto, estudá-lo em várias versões ntoskrnl.exe
parece indicar que a estrutura não mudou entre (pelo menos) as versões 10240 e 22000 do Windows 10 (de 2015 a 2022).
A estrutura mencionada, representando um registro de callback de objeto, é a seguinte:
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 está documentada e é definida pelo seguinte:
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 ;
Para desabilitar retornos de chamada de objetos registrados em EDR, três técnicas são implementadas em EDRSandblast
; no entanto, apenas um está ativado no momento.
Enabled
de OB_CALLBACK_ENTRY
Esta é a técnica padrão habilitada no EDRSandblast
. Para detectar e desabilitar retornos de chamada de objetos relacionados ao EDR, a lista CallbackList
localizada nos objetos _OBJECT_TYPE
vinculados aos tipos Process e Thread é navegada. Ambos _OBJECT_TYPE
s são apontados por símbolos globais públicos no kernel, PsProcessType
e PsThreadType
.
Presume-se que cada item da lista se ajuste à estrutura OB_CALLBACK_ENTRY
descrita acima (suposição que parece ser válida pelo menos em todas as compilações do Windows 10 no momento da escrita). As funções definidas nos campos PreOperation
e PostOperation
estão localizadas para verificar se pertencem a um driver EDR e, em caso afirmativo, os callbacks são simplesmente desabilitados 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 se alguns campos possuem os valores esperados:
Enabled
é TRUE
ou FALSE
( não ria, um BOOL
é um int
, então pode ser qualquer coisa diferente de 1
ou 0
);Operations
são OB_OPERATION_HANDLE_CREATE
, OB_OPERATION_HANDLE_DUPLICATE
ou ambas;ObjectType
aponta em PsProcessType
ou PsThreadType
. CallbackList
de threads e processos Outra estratégia que não depende de uma estrutura não documentada (e é, portanto, teoricamente mais robusta contra alterações no kernel do NT) é a desvinculação de todo o CallbackList
para processos e threads. O objeto _OBJECT_TYPE
é o seguinte:
struct _OBJECT_TYPE {
LIST_ENTRY TypeList ;
UNICODE_STRING Name ;
[...]
_OBJECT_TYPE_INITIALIZER TypeInfo ;
[...]
LIST_ENTRY CallbackList ;
}
Fazer com que os ponteiros Flink
e Blink
do CallbackList
LIST_ENTRY
apontem para o próprio LIST_ENTRY
efetivamente torna a lista vazia. Como a estrutura _OBJECT_TYPE
é publicada nos símbolos do kernel, a técnica não depende de compensações/estruturas codificadas. No entanto, tem algumas desvantagens.
O primeiro é não poder desabilitar apenas retornos de chamada do EDR; na verdade, a técnica afeta todos os retornos de chamada de objetos que poderiam ter sido registrados por software "legítimo". No entanto, deve-se notar que os retornos de chamada de objetos não são usados por nenhum componente pré-instalado no Windows 10 (no momento em que este artigo 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 processamento ou thread são muito frequentes (quase contínuas) no funcionamento normal do sistema operacional. Como tal, se a primitiva de gravação do kernel usada não puder executar uma gravação QWORD
"atomicamente", há uma boa chance de que o ponteiro _OBJECT_TYPE.CallbackList.Flink
seja acessado pelo kernel no meio de sua substituição. Por exemplo, o driver vulnerável MSI RTCore64.sys
só pode executar uma gravação DWORD
por vez, portanto, 2 IOCTLs distintos serão necessários para substituir o ponteiro, entre os quais o kernel tem uma alta probabilidade de usá-lo (resultando em uma falha). Por outro lado, o driver DELL vulnerável DBUtil_2_3.sys
pode executar gravações de tamanhos arbitrários em um IOCTL, portanto, usar esse método com ele não corre o risco de causar uma falha.
Uma última técnica que encontramos foi desabilitar totalmente o suporte de retornos de chamada 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 bit ObjectTypeFlags
, cujo sinalizador SupportsObjectCallbacks
determina se o tipo de objeto descrito (Process, Thread, Desktop, Token, File, etc.) suporta ou não o registro de retorno de chamada do objeto. Conforme declarado anteriormente, apenas os tipos de objeto Process, Thread e Desktop suportam esses retornos de chamada em uma instalação do Windows no momento da escrita.
Como o bit SupportsObjectCallbacks
é verificado por ObpCreateHandle
ou ObDuplicateObject
antes mesmo de ler CallbackList
(e antes de executar retornos de chamada, é claro), inverter o bit no tempo de execução do kernel desabilita efetivamente a execução de todos os retornos de chamada 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
com o parâmetro 4 sendo igual a 0x8
, significando que uma estrutura de tipo de objeto foi alterada.
No entanto, executar a desativação/reativação (e a ação "malicioso" intermediária) com rapidez suficiente deve ser suficiente para "competir" com o PatchGuard (a menos que você tenha azar e uma verificação periódica seja realizada no momento errado).
O provedor ETW Microsoft-Windows-Threat-Intelligence
registra dados sobre os usos 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 LSASS
) e monitorada pela função nt!EtwTiLogReadWriteVm
.
Os produtos EDR podem consumir os logs produzidos pelo provedor ETW TI
por meio de serviços ou processos executados como, respectivamente, SERVICE_LAUNCH_PROTECTED_ANTIMALWARE_LIGHT
ou PS_PROTECTED_ANTIMALWARE_LIGHT
e associados a um driver Early Launch Anti Malware (ELAM)
.
Conforme publicado por slaeryan
em uma postagem no blog CNO Development Labs
, o provedor ETW TI
pode ser totalmente desabilitado corrigindo, na memória do kernel, seu atributo ProviderEnableInfo
para 0x0
. Consulte a excelente postagem do blog mencionada acima para obter mais informações sobre a técnica.
Da mesma forma que a remoção de retornos de chamada do Kernel, os deslocamentos ntoskrnl.exe
necessários ( nt!EtwThreatIntProvRegHandleOffset
, _ETW_REG_ENTRY
's GuidEntry
e _ETW_GUID_ENTRY
's ProviderEnableInfo
) são calculados no arquivo NtoskrnlOffsets.csv
para várias versões do Kernel do Windows.
Para monitorar facilmente as ações executadas pelos processos, os produtos EDR geralmente implantam um mecanismo chamado userland hooking . Primeiro, os produtos EDR registram um retorno de chamada do kernel (geralmente retornos de chamada de carregamento de imagem ou criação de processo , veja acima) que permite que eles sejam notificados no início de cada processo.
Quando um processo é carregado pelo Windows, e antes de realmente ser iniciado, 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, esta DLL injeta " ganchos " no início de cada função que será monitorada pelo EDR. Em tempo de execução, quando as funções monitoradas são chamadas pelo processo sob vigilância, esses ganchos redirecionam o fluxo de controle para algum código de supervisão presente na DLL do EDR, que permite inspecionar argumentos e retornar valores 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
. A interceptação de chamadas para funções Nt*
permite que os produtos estejam o mais próximo possível do limite da área do usuário/kernel (enquanto permanecem na área do 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 fisgado pelo produto EDR:
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
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
Os ganchos do userland têm a "fraqueza" de estarem localizados na memória do userland, o que significa que eles são diretamente observáveis e modificáveis pelo processo sob escrutínio. Para detectar automaticamente ganchos no espaço de endereço do processo, a ideia principal é comparar as diferenças entre a DLL original no disco e a biblioteca residente na memória, que foi potencialmente alterada por um EDR. Para realizar esta comparação, os seguintes passos são seguidos pelo EDRSandblast:
InLoadOrderModuleList
localizado no PEB
(para evitar chamar qualquer API que possa ser monitorada e suspeita) Nota: O processo pode ser generalizado para encontrar diferenças em qualquer lugar nas seções não graváveis e não apenas no início das funções exportadas, por exemplo, se os produtos EDR começarem a aplicar ganchos no meio da função :) Portanto, não é usado pela ferramenta, este foi implementado em findDiffsInNonWritableSections
.
Para contornar o monitoramento realizado por esses ganchos, múltiplas técnicas são possíveis, e cada uma tem vantagens e desvantagens.
O método mais intuitivo para contornar o monitoramento baseado em ganchos é removê-los. Como os ganchos estão presentes na memória que pode ser acessada pelo próprio processo, para remover um gancho, o processo pode simplesmente:
Esta abordagem é bastante simples e pode ser usada para remover todos os ganchos detectados de uma só vez. Realizado por uma ferramenta ofensiva em seu início, permite que o restante do código fique completamente inconsciente do mecanismo de hooking e funcione normalmente sem ser monitorado.
No entanto, tem duas desvantagens principais. O EDR provavelmente está monitorando o uso de NtProtectVirtualMemory
, portanto, usá-lo para alterar as permissões da página onde os ganchos foram instalados é (pelo menos conceitualmente) uma má ideia. Além disso, se um thread for executado pelo EDR e verificar periodicamente a integridade dos ganchos, isso também poderá acionar alguma detecção.
Para obter detalhes de implementação, verifique o caminho do código da função unhook()
quando unhook_method
for UNHOOK_WITH_NTPROTECTVIRTUALMEMORY
.
Nota importante: para simplificar, esta técnica é implementada no EDRSandblast como técnica base usada para demonstrar as outras técnicas de bypass; cada um deles demonstra como obter uma versão não monitorada de NtProtectVirtualMemory
, mas executa a mesma operação posteriormente (desconectando um gancho específico).
Para contornar um gancho específico, é possível simplesmente "pular" e executar o resto da função como está. Primeiro, os bytes originais da função monitorada, que foram sobrescritos pelo EDR para instalar o gancho, devem ser recuperados do arquivo DLL. Em nosso exemplo de código anterior, seriam os bytes correspondentes às seguintes instruções:
mov r10 , rcx
mov eax , 50h
Identificar esses bytes é uma tarefa simples, pois podemos realizar uma comparação limpa das versões de memória e disco da biblioteca, conforme descrito anteriormente. Em seguida, montamos uma instrução de salto que é construída para redirecionar o fluxo de controle para o código seguindo imediatamente o gancho, no endereço NtProtectVirtualMemory + sizeof(overwritten_instructions)
jmp NtProtectVirtualMemory + 8
Finalmente, concatenamos esses opcodes, armazenamos-os na memória (recentemente) executável e mantemos um ponteiro para eles. Este objeto é chamado de " trampolim " e pode então ser usado como um ponteiro de função, estritamente equivalente à função NtProtectVirtualMemory
original.
O principal benefício desta técnica, como de todas as técnicas abaixo, é que o gancho nunca é apagado, portanto, qualquer verificação de integridade realizada nos ganchos pelo EDR deve passar. No entanto, é necessário 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 obter detalhes de implementação, verifique o caminho do código da função unhook()
quando unhook_method
for UNHOOK_WITH_INHOUSE_NTPROTECTVIRTUALMEMORY_TRAMPOLINE
. Lembre-se que a técnica só é mostrada em nossa implementação e é, no final, usada para remover ganchos da memória, como todas as técnicas abaixo.
O produto EDR, para que seu gancho funcione, deve salvar em algum lugar da memória os opcodes que ele removeu. Pior ( ou "melhor", do ponto de vista do invasor ), para usar efetivamente as instruções originais, o EDR provavelmente alocou um trampolim em algum lugar para executar a função original após ter interceptado a chamada.
Este trampolim pode ser procurado e usado como um substituto para a função hooked, 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 trampolim na memória, navegamos em todo o espaço de endereço usando VirtualQuery
em busca de memória comprometida e executável. Para cada região da memória, nós a examinamos em busca de uma instrução de salto que tenha como alvo o endereço seguindo as instruções substituídas ( NtProtectVirtualMemory+8
em nosso exemplo anterior). O trampolim pode então ser usado para chamar a função de gancho sem acionar o gancho.
Esta técnica funciona surpreendentemente bem, pois recupera quase todos os trampolins em EDR testados. Para obter detalhes de implementação, verifique o caminho do código da função unhook()
quando unhook_method
for UNHOOK_WITH_EDR_NTPROTECTVIRTUALMEMORY_TRAMPOLINE
.
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ço do processo. Como duas DLLs idênticas podem ser carregadas no mesmo processo, desde que tenham nomes diferentes, podemos simplesmente copiar o arquivo ntdll.dll
legítimo 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 boas chances de sucesso, já que a maioria dos produtos EDR não reinstala ganchos em DLLs recém-carregadas quando o processo está em execução. No entanto, a principal desvantagem é que copiar binários assinados pela Microsoft com um nome diferente é muitas vezes considerado suspeito tanto pelos produtos EDR quanto pelos próprios.
No entanto, esta técnica é implementada no EDRSandblast
. Para obter detalhes de implementação, verifique o caminho do código da função unhook()
quando unhook_method
for UNHOOK_WITH_DUPLICATE_NTPROTECTVIRTUALMEMORY
.
Para usar funções relacionadas às chamadas do sistema, um programa pode reimplementar syscalls (em assembly) para chamar os recursos correspondentes do sistema operacional sem realmente tocar no código em ntdll.dll
, que pode ser monitorado pelo EDR. Isso ignora completamente qualquer conexão de usuário feita nas funções syscall em ntdll.dll
.
No entanto, isto tem algumas desvantagens. Em primeiro lugar, isto implica poder conhecer a lista de números de syscall de funções que o programa necessita, que muda para cada versão do Windows. No entanto, isso é mitigado pela implementação de várias heurísticas que funcionam em todas as versões anteriores do Windows NT (classificando as exportações Zw*
de ntdll
, pesquisando por mov rax, #syscall_number
na função ntdll
associada, etc.) e verificar se todos retornam o mesmo resultado (consulte Syscalls.c
para obter mais detalhes).
Além disso, funções que não são tecnicamente syscalls (por exemplo, LoadLibraryX
/ LdrLoadDLL
) também podem ser monitoradas e não podem simplesmente ser reimplementadas usando um syscall.
A técnica direta de syscalls é implementada no EDRSandblast. Conforme afirmado anteriormente, ele é usado apenas para executar NtProtectVirtualMemory
com segurança e remover todos os ganchos detectados.
Para obter detalhes de implementação, verifique o caminho do código da função unhook()
quando unhook_method
for UNHOOK_WITH_DIRECT_SYSCALL
.
Como afirmado anteriormente, toda ação que precisa de leitura ou gravação na memória do kernel depende de um driver vulnerável para fornecer essa primitiva. No EDRSanblast, adicionar suporte para um novo driver fornecendo a primitiva de leitura/gravação pode ser feito "facilmente", apenas três funções precisam ser implementadas:
ReadMemoryPrimitive_DRIVERNAME(SIZE_T Size, DWORD64 Address, PVOID Buffer)
, que copia Size
bytes do Address
do kernel Address para o buffer do usuário Buffer
;WriteMemoryPrimitive_DRIVERNAME(SIZE_T Size, DWORD64 Address, PVOID Buffer)
, que copia Size
bytes do buffer da terra do usuário Buffer
para Address
do kernel Address ;CloseDriverHandle_DRIVERNAME()
que garante que todos os identificadores do driver sejam fechados (necessário antes da operação de desinstalação, que é independente do driver, no momento). Por exemplo, dois drivers são atualmente suportados pelo EDRSandblast, RTCore64.sys
(SHA256: 01AA278B07B58DC46C84BD0B1B5C8E9EE4E62EA0BF7A695862444AF32E87F1FD
) e DBUtils_2_3.sys
(SHA256: 0296e2ce999e67c76352613a718e11516fe1b0efc3ffdb8918fc999dd76a73a5
). O código a seguir em KernelMemoryPrimitives.h
deve ser atualizado se o driver vulnerável usado precisar ser alterado ou se um novo for implementado.
#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
Atualmente, diversas técnicas são usadas para determinar se um driver ou processo específico pertence ou não a um produto EDR.
Primeiro, o nome do driver pode simplesmente ser usado para esse fim. Na verdade, a Microsoft aloca números específicos chamados “Altitudes” para todos os drivers que precisam inserir retornos de chamada no kernel. Isto permite uma ordem determinística na execução dos callbacks, independente da ordem de registro, mas apenas baseada na utilização do driver. Uma lista de (fornecedores de) drivers que reservaram altitudes específicas pode ser encontrada no MSDN. Como consequência, uma lista quase abrangente de nomes de drivers de segurança vinculados 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 incorporadas no EDRSandblast, bem como contribuições adicionais.
Além disso, os executáveis EDR e DLL são 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 a identificação rápida de produtos EDR.
Além disso, os drivers precisam ser assinados diretamente pela Microsoft para poderem ser carregados no espaço do kernel. Embora o fornecedor do driver não seja diretamente o signatário do próprio driver, parece que o nome do fornecedor ainda está incluído em um atributo da assinatura; esta técnica de detecção ainda precisa ser investigada e implementada.
Finalmente, ao enfrentar um EDR desconhecido pelo EDRSandblast, a melhor abordagem é executar a ferramenta em modo "auditoria" e verificar a lista de drivers com retornos de chamada de kernel registrados; então o nome do driver pode ser adicionado à lista, a ferramenta recompilada e executada novamente.
O mecanismo Local Security Authority (LSA) Protection
, introduzido pela primeira vez no Windows 8.1 e no Windows Server 2012 R2, aproveita 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 despejo de memória de processos protegidos, mesmo de um processo que detém o privilégio SeDebugPrivilege
. No modelo de proteção de processos, apenas processos executados com níveis de proteção mais elevados 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 por meio de seus atributos Type
( _PS_PROTECTED_TYPE
) e Signer
( _PS_PROTECTED_SIGNER
).
Ao gravar na memória do kernel, o processo EDRSandblast é capaz de atualizar seu próprio nível de proteção para PsProtectedSignerWinTcb-Light
. Este nível é suficiente para despejar a memória do processo LSASS
, uma vez que "domina" para PsProtectedSignerLsa-Light
, o nível de proteção do processo LSASS
em execução com o mecanismo RunAsPPL
.
EDRSandBlast
implementa a autoproteção da seguinte forma:
NtQuerySystemInformation
para localizar o identificador aberto no processo atual e o endereço da estrutura EPROCESS
do processo atual na memória do kernel.Micro-Star MSI Afterburner
para substituir o campo _PS_PROTECTION
do processo atual na memória do kernel. Os deslocamentos do campo _PS_PROTECTION
relativos à estrutura EPROCESS
(definida pela versão ntoskrnl
em uso) são calculados no arquivo NtoskrnlOffsets.csv
. Microsoft Credential Guard
é uma tecnologia de isolamento baseada em virtualização, introduzida no Windows 10 (Enterprise edition)
que impede o acesso direto às credenciais armazenadas no processo LSASS
.
Quando Credentials Guard
é ativado, um processo LSAIso
( LSA Isolated ) é criado no Virtual Secure Mode
, um recurso que aproveita as extensões de virtualização da CPU para fornecer segurança adicional aos dados na memória. O acesso ao processo LSAIso
é restrito mesmo para um acesso com o contexto de segurança NT AUTHORITYSYSTEM
. Ao processar um hash, o processo LSA
realiza uma chamada RPC
para o processo LSAIso
e aguarda que o resultado LSAIso
continue. Assim, o processo LSASS
não conterá nenhum segredo e armazenará LSA Isolated Data
.
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 de texto não criptografado na memória LSASS
para quaisquer novos logons interativos (sem exigir a reinicialização do sistema). Consulte a postagem original do blog de pesquisa para obter mais detalhes sobre esta técnica.
EDRSandBlast
simplesmente torna o PoC original um pouco mais amigável ao opsec e fornece suporte para uma série de versões wdigest.dll
(por meio de compensações computadas para g_fParameter_useLogonCredential
e g_IsCredGuardEnabled
).
Para realizar operações de desvio de monitoramento do kernel de maneira confiável, o EDRSandblast precisa saber exatamente onde ler e gravar a memória do kernel. Isso é feito usando deslocamentos de variáveis globais dentro da imagem de destino (ntoskrnl.exe, wdigest.dll), bem como deslocamento de campos específicos em estruturas cujas definições são publicadas pela Microsoft em arquivos de símbolos. Essas compensações são específicas para cada construção das imagens alvo e devem ser coletadas pelo menos uma vez para uma versão específica da plataforma.
A escolha de usar offsets "hardcoded" em vez de buscas de padrões para localizar as estruturas e variáveis utilizadas 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 alterações e que qualquer tentativa de leitura ou escrita do Kernel memória no endereço errado pode (e muitas vezes resultará) em uma Bug Check
( Blue Screen of Death
). Uma falha de máquina não é aceitável tanto em cenários de red-team quanto de teste de penetração normal, uma vez que uma máquina que falha é altamente visível pelos defensores e perderá todas as credenciais que ainda estavam na memória no momento do ataque.
Para recuperar compensações para cada versão específica do Windows, duas abordagens são implementadas.
Os deslocamentos ntoskrnl.exe
e wdigest.dll
necessários podem ser extraídos usando o script Python ExtractOffsets.py
fornecido, que depende de radare2
e r2pipe
para baixar e analisar símbolos de arquivos PDB e extrair deles os deslocamentos necessários. Os deslocamentos são então armazenados em arquivos CSV para uso posterior pelo EDRSandblast.
Para oferecer suporte imediato a uma ampla variedade de compilações do Windows, muitas versões dos binários ntoskrnl.exe
e wdigest.dll
são referenciadas por Winbindex e podem ser baixadas automaticamente (e seus deslocamentos extraídos) pelo ExtractOffsets.py
. Isso permite extrair compensações de quase todos os arquivos que já foram publicados em pacotes de atualização do Windows (até o momento, mais de 450 versões ntoskrnl.exe
e mais de 30 versões wdigest.dll
estão disponíveis e pré-computadas).
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é mesmo 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, uma vez que um arquivo .pdb
é baixado e colocado 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 do PDB na memória poderá ser implementada no futuro para eliminar esse requisito e reduzir o espaço ocupado pela ferramenta.
O driver RTCore64.sys
vulnerável pode ser recuperado em:
http://download-eu2.guru3d.com/afterburner/%5BGuru3D.com%5D-MSIAfterburnerSetup462Beta2.zip