
Aplique uma abordagem de dividir para conquistar para contornar EDRs
Dividir para conquistar é um algoritmo comumente aplicado em programação para resolver um problema complexo dividindo-o em muitos subproblemas mais simples. Podemos aplicar essa abordagem à segurança ofensiva com um objetivo diferente: confundir os EDRs para que percam o rastro das nossas atividades, impedindo-os de gerar qualquer alerta. Isso é algo semelhante ao que se vê atualmente em quase qualquer campanha de phishing na natureza: longas cadeias de infecção, executando vários arquivos passo a passo (por exemplo, .url -> .one -> .js -> .bat -> .dll) em vez de executar diretamente o payload final. Cada um dos arquivos executados realiza uma tarefa simples (baixar outro arquivo, fazer qualquer alteração no registro, mover arquivos entre diretórios ou alterar seus nomes/extensões e assim por diante) que é difícil de ser classificada como maliciosa por si só, preparando o ambiente para a execução final.
Decidi testar essa ideia simples, mas aplicada a algo diferente: neste caso, uma injeção de processo remoto. O código apresentado neste repositório não é nada novo; pelo contrário, é provavelmente uma das maneiras mais comuns e diretas de injetar um shellcode em um processo remoto: fazer uso de NtOpenProcess, NtAllocateVirtualMemory, NtWriteVirtualMemory, NtProtectVirtualMemory e NtCreateThreadEx. A única diferença é que estou fazendo fork do processo usando NtCreateUserProcess após cada uma dessas chamadas. Como o processo bifurcado continua a execução a partir de RIP + 1 e a memória é totalmente copiada do pai, podemos realizar a injeção de processo remoto usando 5 processos diferentes; só precisamos garantir que qualquer handle necessário para as chamadas de API subsequentes seja devidamente herdado.
Dessa forma, dividimos o procedimento de injeção de shellcode em tarefas mais simples e executamos cada uma delas em um contexto (processo) separado.
Testei este PoC contra três dos EDRs mais comuns atualmente: MDE, CrowdStrike e SentinelOne. Os resultados falam por si: 2 de 3 EDRs geraram um alerta de Remote Process Injection ao executar o PoC sem os forks; pelo contrário, nenhum deles gerou qualquer alerta depois que introduzi o mecanismo de fork.
Claro que, mesmo com o mecanismo de fork, podemos ver na telemetria bruta os eventos correspondentes à criação de processos, criação de threads e também todo o comportamento entre processos (cross process), mas parece que isso não é suficiente para os EDRs classificarem a atividade como maliciosa, comprovando o objetivo deste PoC. Ao dividir o comportamento malicioso em tarefas mais simples e executar cada uma delas a partir de um processo diferente, podemos confundir e impedir os EDRs de gerar qualquer alerta.
Esse mesmo resultado poderia ser alcançado de outras maneiras; eu apenas usei o mecanismo de fork para simplificar meu código e reduzir a atividade entre processos.
Se você quiser testar isso por conta própria, compile o código com e sem as chamadas à função fork() e então execute ambos os payloads em um ambiente com o EDR desejado.
Como estamos usando o plugin LITCRYPT para ofuscar literais de string (apenas para o código Dinvoke_rs), é necessário definir a variável de ambiente LITCRYPT_ENCRYPT_KEY antes de compilar o código:
C:\Users\User\Desktop\Split> set LITCRYPT_ENCRYPT_KEY="yoursupersecretkey"
Depois disso, basta compilar o código e executar a ferramenta:
C:\Users\User\Desktop\Split> cargo build --release
C:\Users\User\Desktop\Split\target\release> split.exe -h
Essa técnica por si só não é suficiente para burlar um EDR; se o seu código não tiver nenhuma opsec, é muito provável que você seja detectado de qualquer forma. Isso não é uma bala de prata, apenas mais uma camada de evasão que você pode adicionar às suas ferramentas. No entanto, o código apresentado neste repositório não tem opsec alguma, pelos seguintes motivos, entre outros:
Por outro lado, só testei essa abordagem contra os EDRs mencionados, e não sei se outros EDRs também serão burlados. Você pode testar e me contar como foi ;)