
Execute executáveis Windows não gerenciados em Beacons do CobaltStrike
Inline-Execute-PE é um conjunto de Beacon Object Files (BOFs) e um script Aggressor para CobaltStrike que permite aos Operadores carregar executáveis Windows não gerenciados na memória do Beacon e executá-los, recuperando a saída e exibindo-a no console do Beacon.
Isso permite que os Operadores utilizem muitas ferramentas de terceiros (Mimikatz, Dsquery, ferramentas Sysinternals, etc.) sem precisar gravá-las em disco, reformatá-las para código independente de posição usando uma ferramenta como Donut, ou criar um novo processo para executá-las.
Esses executáveis são mapeados na memória do Beacon para que possam ser executados repetidamente sem precisar enviá-los pela rede, alocar nova memória e criar um novo processo conhost.exe a cada vez.
Os executáveis carregados nos Beacons são acessíveis e podem ser executados por todos os Clientes CobaltStrike conectados ao Servidor de Equipe CobaltStrike.
O Inline-Execute-PE foi projetado para Beacons x64 e executáveis Windows x64 em C ou C++ compilados com Mingw ou Visual Studio. Este projeto não suporta executáveis x86 ou executáveis x64 escritos em outra linguagem ou compilados com outro compilador.

Clone o repositório e opcionalmente execute make para recompilar os BOFs.
Carregue o Inline-Execute-PE.cna no cliente CobaltStrike. Certifique-se de que o diretório de onde o CobaltStrike está sendo executado seja gravável pelo seu utilizador; o Inline-Execute-PE cria um ficheiro de texto lá (petable.txt) para garantir a disponibilidade dos dados necessários para o funcionamento do Inline-Execute-PE.
O Inline-Execute-PE compreende 3 comandos voltados para o alvo que executam BOFs e 3 comandos internos que manipulam a estrutura de dados do projeto:
Voltados para o alvo:
Estrutura de dados interna:
peload é o início do Inline-Execute-PE. Este comando é utilizado para carregar um PE na memória do Beacon. Ele realiza as seguintes ações principais:
perun é o segundo passo no Inline-Execute-PE. Ele realiza as seguintes ações principais:
peunload é chamado para remover o PE da memória do Beacon quando um Operador terminar com ele ou desejar carregar um PE diferente. Ele realiza as seguintes ações principais:
petable é usado para exibir informações sobre todos os PEs atualmente carregados nos Beacons.
Cada Cliente CobaltStrike tem seu próprio petable; o Inline-Execute-PE se esforça ao máximo para garantir a sincronicidade dos seus dados entre todos os Clientes CobaltStrike conectados, para que os PEs possam ser usados por todos os Operadores. Para mais informações, veja "Considerações de Design e Comentários".

peconfig é usado para configurar opções relacionadas ao funcionamento do Inline-Execute-PE. As duas opções atuais que podem ser alteradas são:
pebroadcast pode ser usado para transmitir manualmente o conteúdo do petable de um Cliente para todos os outros Clientes CobaltStrike conectados.
Cada outro Cliente CobaltStrike atualizará seu petable com os dados transmitidos. Isso nunca deve ser realmente necessário, mas a funcionalidade existe apenas por precaução.
Use peload para carregar um PE na memória do Beacon

Alternativamente, se houver um PE na máquina alvo que deseje usar sem criar um novo processo, forneça o caminho e a opção --local

Chame perun, passando quaisquer argumentos para o PE carregado

Aspas duplas em argumentos devem ser escapadas com barras invertidas

Se identificou que um PE causa problemas ao tentar libertar DLLs durante o descarregamento, use peconfig para definir unloadlibraries como falso

Depois de terminar de usar um PE, chame peunload para limpá-lo do Beacon

Agora, um PE diferente pode ser carregado no Beacon

É preciso ter cuidado com os argumentos de linha de comando que passa ao PE; alguns PEs falham completamente se receberem argumentos errados, enquanto outros executam infinitamente, fazendo com que o Beacon nunca retorne mesmo que o processo ainda esteja em execução.
Isso pode ser visto com o Mimikatz.exe quando 'exit' não é especificado no final da lista de argumentos

...

O Inline-Execute-PE terminará a thread do PE em execução após o valor de timeout especificado ter sido atingido. Isso permite que o Beacon retome as comunicações normais (o Beacon não retorna até que o BOF perun tenha concluído a execução). Embora comandos normais do CobaltStrike e outros BOFs ainda possam ser usados neste Beacon, o Inline-Execute-PE está agora desativado; quando um PE em execução é terminado desta forma, parece quebrar o stdout e stderr no processo Beacon, e PEs carregados subsequentemente não funcionam corretamente.
O PE pode (e deve) ainda ser descarregado da memória do Beacon, no entanto, olhar para o petable mostrará que este Beacon pode não ter mais PEs adicionais carregados nele. 
É imperativo que teste os PEs que deseja executar usando o Inline-Execute-PE e que tenha cuidado ao fornecer argumentos de linha de comando ao perun. Alguns PEs são mais tolerantes que outros.
Abaixo estão, sem ordem particular, algumas observações feitas durante os testes e desenvolvimento sobre determinados PEs que os utilizadores possam querer carregar no Beacon.
Os IOC's associados ao Inline-Execute-PE incluem, mas não se limitam a:
Não fiz um teste completo contra um EDR durante o desenvolvimento, em parte por preguiça e em parte por falta de disponibilidade de um ambiente de teste. No entanto, foi testado contra o Windows Defender com as últimas atualizações (que, na minha experiência, é um produto AV bastante bom).
O Mimikatz.exe é provavelmente o PE mais suspeito e conhecido que vem à mente como candidato ao uso com o Inline-Execute-PE. Descobri que a capacidade do Windows Defender de detetar o Mimikatz a executar usando o Inline-Execute-PE dependia do processo em que o Beacon estava a executar.
Um beacon a executar num executável autónomo (pense em beacon.exe com artifact kit para que consiga executar e funcionar normalmente ultrapassando o Defender) será detetado ao usar o Mimikatz.exe com o Inline-Execute-PE.
Um beacon a executar num processo Windows (injetado no Explorer.exe, notepad.exe, etc. ou DLL sideloaded num processo legítimo) NÃO será detetado ao usar o Mimikatz.exe com o Inline-Execute-PE.
Em relação a EDRs que fazem hooking no espaço do utilizador, como disse, não testei, mas tenho os seguintes pensamentos gerais:
Sendo que o PE está a executar dentro do processo Beacon, que presumivelmente já removeu o hook/atualizou o NTDLL internamente, penso que não deve ter muitos problemas com as chamadas de API feitas pelo PE a serem sinalizadas. Os mesmos problemas em relação ao que o PE realmente faz (tocar em processos, alterar chaves de registo, etc.) ainda se aplicam.
Há alguns meses deparei-me com RunPE-In-Memory e pensei em tentar convertê-lo num BOF para CobaltStrike. A jornada que se seguiu foi muito mais complexa e demorou muito mais do que o antecipado. Este projeto foi particularmente desafiante porque não é uma ferramenta independente por si só; é uma ferramenta usada para executar outras ferramentas. Isto requer uma grande flexibilidade e esforço para garantir compatibilidade com uma vasta gama de PEs e todas as diferentes formas como esses PEs podem realizar a mesma tarefa (obter argumentos, terminar, etc.).
No início, o Inline-Execute-PE foi concebido como um BOF tudo-em-um, responsável por carregar, executar e libertar um PE num Beacon. Cerca de 3 semanas no projeto, altura em que tinha um POC ~75% completo, encontrei o Pezor que foi lançado há ~1,5 anos e já fazia quase tudo o que eu estava a tentar fazer; a principal diferença é que o Pezor chamava o Donut internamente para transformar o PE em shellcode, em vez de mapear manualmente o PE original na memória.
Esta descoberta foi bem-vinda a certa medida e dececionante a outra; foi fantástico ter um projeto maduro do qual retirar inspiração e ajuda para ultrapassar alguns pontos problemáticos no meu código, mas desanimador por ter efetivamente reinventado a roda sem o saber. Depois de ler sobre o Pezor e pensar no seu design, em algumas questões relacionadas com a arte operacional e nas necessidades operacionais da minha organização, alterei o rumo do Inline-Execute-PE para o que vê hoje. Esta decisão foi impulsionada por vários fatores que serão discutidos abaixo, assim como algumas das escolhas de design mais curiosas que podem ter levantado algumas sobrancelhas para aqueles que leram até aqui.
Ao examinar a minha experiência operacional, encontrei múltiplas instâncias e ferramentas onde precisava de executar a ferramenta repetidamente; com o Pezor, um Operador deve enviar repetidamente o PE pela rede, criar um conhost.exe, alocar nova memória no Beacon, etc., o que me pareceu potencialmente indesejável considerando AV/EDR. Esta linha de pensamento levou à ideia de 'carregar' um PE no Beacon, de forma semelhante a como se pode carregar um .PS1 no Beacon para uso repetido. O conhost.exe é criado quando o PE é carregado pela primeira vez e persiste enquanto o PE estiver carregado na memória; da mesma forma, nova memória é alocada para o PE uma vez quando é carregado pela primeira vez e, claro, evita-se ter de enviar o PE pela rede cada vez que se queira usá-lo. O modelo que o Inline-Execute-PE adotou não está isento de falhas, que tentei abordar com vários graus de sucesso.
Uma escolha de design que deve saltar à vista é o facto de o Inline-Execute-PE mapear o PE no Beacon DUAS VEZES. Isto certamente não é desejável nem uma escolha que fiz de boa vontade, mas nasceu da necessidade. Como mencionado anteriormente, o Inline-Execute-PE tem de fazer hook em várias funções relacionadas com argumentos de linha de comando no PE. Porque o PE mapeado executa dentro do processo Beacon, o PE tentará usar os argumentos de linha de comando especificados na secção PROCESS_PARAMETERS do PEB; para contornar isso, quando o PE chama uma das várias funções que recuperam os argumentos de linha de comando, devemos direcionar o PE para as nossas próprias funções personalizadas, onde podemos fornecer os argumentos pretendidos conforme passados pelo CobaltStrike usando perun.
Isto funciona bem, mas durante o desenvolvimento notei algo estranho com vários PEs diferentes. Na primeira vez que o PE foi executado, a função personalizada que fornecemos à IAT do PE foi chamada corretamente; no entanto, em todas as vezes subsequentes em que o PE foi executado e recebeu argumentos diferentes, o PE não chamou a função personalizada e, como tal, não recebeu os argumentos passados pelo CobaltStrike. Não tenho a certeza do que está realmente a acontecer nos bastidores, mas sou levado a acreditar que, depois de o PE ter executado uma vez, ele copia os argumentos de linha de comando para algum lugar na memória e, nas execuções seguintes, procura esse local na memória primeiro antes de chamar as funções hookadas para recuperar os argumentos de linha de comando como fez na primeira vez. Corroborei esta teoria ao recuperar o local na memória onde residia um ponteiro para outro ponteiro para o array de ponteiros contendo os argumentos, e modifiquei manualmente este local na memória para conter o ponteiro correto em cada execução. Isto funcionou para as funções __getmainargs e __wgetmainargs, mas outros PEs chamam funções alternativas como __p___argv e __p___argc, para as quais este método não funcionou.
Para poder "repor" o PE a um estado em que realmente chamasse as funções hookadas para obter argumentos, recorri a fazer uma segunda cópia do PE na memória durante o peload. Esta cópia também é criptografada XOR e fica com proteções RX durante toda a vida útil do Inline-Execute-PE, sendo simplesmente usada para sobrescrever a cópia do PE que é realmente executada usando perun. Como mencionado, não é uma solução perfeita, mas é uma solução geral que cobre todos os PEs sem precisar de se perder em tentativas de encontrar uma solução para todos os diferentes PEs e as diferentes APIs que usam.
Sendo um dos principais pontos de venda do Inline-Execute-PE a capacidade de executar ferramentas sem criar novos processos, é um grande golpe ter de... criar um novo processo (conhost.exe) para o fazer. Este requisito vem do facto de que os streams padrão (stdin/stdout/stderr) não são inicializados em programas Windows a menos que exista uma consola. No nosso caso, não precisamos da consola; os streams padrão são redirecionados para um pipe anónimo e capturados dessa forma, mas sem o conhost os streams não são inicializados e não podem ser redirecionados.
O Inline-Execute-PE aborda o problema do conhost da mesma forma que o Pezor: chama AllocConsole e imediatamente depois esconde-o de vista usando ShowWindow. Numa VM Windows 11 com 8 GB de RAM, nunca vejo a janela da consola piscar e desaparecer, mas a experiência pode variar dependendo do sistema alvo.
Falei com um programador que trabalha num C2 Comercial muito avançado que recentemente lançou um equivalente nativo (ok, versão muito mais avançada) do Inline-Execute-PE, que me disse que conseguiram evitar gerar um conhost.exe ao "enganar o Windows fazendo-o pensar que tinha uma consola". Com esta informação, passei cerca de uma semana a vasculhar a internet por documentação sobre como os programas Windows interagem com o conhost, a tentar rastrear as chamadas de API associadas a funções de escrita e à consola no WinDBG, e até a examinar o código fonte do Windows Terminal que, surpreendentemente, está disponível no Github. Embora tenha aprendido muito sobre o PEB e coisas relacionadas a streams padrão, saí do outro lado de mãos vazias. Suspeito que o caminho a seguir possa envolver patching de certas funções relacionadas à consola no kernel32, mas não sei. Honestamente, estou bastante desapontado por não ter conseguido encontrar uma solução aqui, mas sendo autodidata e com apenas alguns anos de carreira, provavelmente é de esperar.### Tempo Limite e Resgate do PE Todos aqueles que já tentaram escrever um BOF sabem que, apesar de todas as vantagens, um enorme perigo reside no fato de que um erro ou travamento no seu BOF pode e irá matar seu Beacon. O perigo é amplificado neste projeto pela natureza de quanto controle os usuários têm sobre os dados passados ao Inline-Execute-PE e quão poucas medidas de segurança podem ser facilmente ou confiavelmente implementadas por mim, o desenvolvedor. Os usuários poderiam, por exemplo, travar seu Beacon ao carregar um PE x86 em um Beacon x64, ou, muito mais comumente, ao passar argumentos inadequados ao PE mapeado, como mencionei anteriormente. Embora eu não possa impedir os usuários de travarem seus Beacons com argumentos ruins para seus PEs, posso tentar resgatar seu Beacon no caso de um PE que executa infinitamente, como no caso do Mimikatz quando 'exit' não é especificado.
Idealmente, eu seria capaz de interromper a execução do PE, permitindo que o Beacon retomasse a função normal, e então imediatamente deixar o usuário tentar novamente com os argumentos (esperançosamente) corretos desta vez. Na prática, descobri que encerrar o PE parece quebrar os FILE* associados ao stdout/stderr, e mesmo descarregar o PE completamente e depois carregá-lo novamente do zero não resolve isso; eles estão quebrados em todo o processo.
Para encerrar um PE que continua a executar além da opção 'timeout', é chamado TerminateThread no handle retornado por CreateThread. Isso não permite que a thread saia graciosamente, então faz sentido que algumas coisas possam quebrar. Tentei mitigar isso implementando thread hijacking, com o objetivo de suspender a thread do PE e redirecionar sua execução para a API ExitThread(). A esperança aqui era que, se fosse a thread que iniciasse os procedimentos de saída (em oposição a ser encerrada à força externamente), isso poderia resultar na continuação do funcionamento do stdout/stderr, mas acabei tendo o mesmo problema (além de experimentar uma incapacidade de suspender a thread do PE no caso do Mimikatz).
Incapaz de mitigar esse problema, acabei simplesmente impedindo que os usuários pudessem continuar a executar o PE ou carregar PEs adicionais no Beacon afetado (o que RESULTARIA em um travamento). Esta é outra instância em que o Inline-Execute-PE fica aquém do que eu gostaria, mas me contentei com o fato de que o Operador ainda teria seu Beacon e poderia usá-lo para funcionalidades normais.
Uma parte desafiadora deste projeto foi garantir a disponibilidade dos PEs carregados nos Beacons para todos os Clientes CobaltStrike conectados ao Team Server. Os dados do Inline-Execute-PE são armazenados em estruturas criadas pelo Inline-Execute-PE.cna, que devem ser carregadas em cada Cliente que deseja usar a ferramenta; como resultado, essas estruturas de dados residem dentro de cada Cliente, não no Team Server. Se esses dados residissem em um único local central (TS), seria trivial recuperá-los de cada Cliente e tudo isso não seria um problema; se a Equipe CobaltStrike integrasse formalmente uma capacidade como o Inline-Execute-PE no CobaltStrike, tenho certeza de que essa seria a direção que eles tomariam. Mas como este é um complemento da comunidade, nos viramos com o que temos.
Existem alguns cenários diferentes com os quais temos que nos preocupar quando se trata de garantir que cada Cliente CobaltStrike tenha os dados mais recentes e precisos sobre os PEs carregados nos Beacons:
Uma abordagem multifacetada foi adotada para lidar com esses cenários. Para lidar com o caso em que apenas um único Cliente CobaltStrike está conectado ao TS (e, portanto, é a única entidade que possui os dados da petable), cada vez que o Cliente altera a petable (peload, peconfig, peunload, etc.), ele também grava o conteúdo de sua petable em um arquivo de texto local localizado no diretório do CobaltStrike. Caso o Cliente saia/reinicie, ou quando o Inline-Execute-PE.cna for recarregado, ele primeiro tentará ler do arquivo local petable.txt para preencher sua petable na memória.
Quando vários Clientes estão conectados a um TS e um novo Cliente se junta (conforme o Event Log), cada Cliente obtém uma lista de todos os usuários conectados ao TS e a ordena alfabeticamente. O Cliente que está primeiro nessa lista é selecionado como o Cliente "Broadcast", e após esperar 5 segundos (para permitir que o novo Cliente inicialize e leia seu petable.txt local), ele enviará mensagens (Actions) no Event Log para cada entrada em sua petable. Todos os clientes (exceto o que está transmitindo) lerão essas mensagens e atualizarão suas petables com as informações transmitidas; isso inclui atualizar entradas existentes, bem como adicionar quaisquer outras que suas respectivas petables não contenham.
Operações normais envolvendo o Inline-Execute-PE também dependem do envio de mensagens no Event Log. Quando o Cliente A executa peload, uma mensagem é transmitida contendo todas as informações pertinentes da petable; TODOS os clientes atualizam suas respectivas petables analisando essas mensagens transmitidas do Event Log usando o hook "on Event_Action". Alterações também são feitas nos dados do Inline-Execute-PE quando peload e peunload terminam de executar seus BOFs; essas alterações são comunicadas de volta pelo Beacon (por exemplo, após executar peload, o Beacon retorna com a localização da memória da struct pMemAddrs) e, como tal, são visíveis para todos os Clientes conectados, que atualizam suas respectivas petables usando o hook "on Beacon_Output".
Esses esforços separados combinados resultam em que o Inline-Execute-PE seja capaz de sincronizar de forma eficiente e confiável dados críticos entre vários Clientes.
Este projeto não teria sido possível sem os seguintes projetos e recursos que foram amplamente referenciados e dos quais partes centrais deste projeto se originaram. Muito obrigado aos autores por seu código e sua visão.