Embora os componentes de IA e aprendizado de máquina de última geração das soluções de segurança continuem aprimorando os recursos de detecção baseada em comportamento, em sua essência, muitos ainda dependem de detecções baseadas em assinatura. O Cobalt Strike sendo um framework popular de comando e controle (C2) de equipe vermelha, usado tanto por agentes de ameaça quanto por equipes vermelhas desde sua estreia, continua a ser fortemente assinado por soluções de segurança.
Para continuar o uso operacional do Cobalt Strike no passado, nós, da IBM X-Force Red Adversary Simulation, investimos esforços significativos de pesquisa e desenvolvimento para personalizar o Cobalt Strike com ferramentas internas. Algumas de nossas ferramentas internas específicas para o Cobalt Strike possuem versões públicas, como “InlineExecute-Assembly”, “CredBandit” e “BokuLoader”. Nos últimos dois anos, dada a assinatura excessiva do Cobalt Strike, restringimos seu uso à simulação de agentes da ameaça menos sofisticados e, em vez disso, aproveitamos outros C2 de terceiros e internos ao realizar exercícios mais avançados de red team.
Por meio de esforços de pesquisa e desenvolvimento, encontramos o melhor sucesso operacional em exercícios avançados de equipe vermelha com:
No entanto, ainda há uma grande quantidade de agentes de ameaças utilizando cópias piratas do Cobalt Attack, e continua sendo importante poder simular esses agentes de ameaças. Para as equipes vermelhas dispostas a se esforçar em pesquisa e desenvolvimento, elas ainda podem encontrar sucesso operacional com o Cobalt Attack ao simular esses adversários. Além disso, o Cobalt Strike é uma ótima ferramenta de aprendizado, que pode ser aproveitado por recém-chegados para obter experiência com um framework de C2 por meio de cursos de treinamento de equipe vermelha.
À medida que continuamos a expandir nossos recursos de C2, estamos compartilhando alguns insights sobre como, no passado, evoluímos a partir do framework Cobalt Strike, especificamente por meio do desenvolvimento de carregadores reflexivos personalizados. Este conteúdo também tem como objetivo ajudar defensores a compreender melhor como o Cobalt Strike funciona, de modo a possibilitar a criação de detecções mais robustas.
Mantenha-se atualizado sobre as tendências mais importantes (e intrigantes) do setor em IA, automação, dados e muito mais com o boletim informativo Think. Consulte a Declaração de privacidade da IBM.
Este post de blog é o primeiro de uma série que serve como um guia introdutório, abordando os fundamentos do desenvolvimento de um carregador refletivo para o Cobalt Strike. À medida que avançarmos nesta série, iremos construir sobre essa base e fazer referência a este post ao longo do caminho.
Ao final desta série, o nosso objetivo é criar um carregador refletivo que se integre aos mecanismos de evasão já existentes do Cobalt Strike e que, inclusive, os amplie com técnicas avançadas que atualmente não estão presentes na ferramenta. Os próximos artigos irão aprofundar-se no desenvolvimento de funcionalidades específicas de evasão e em como implementá-las no nosso carregador refletivo do Cobalt Strike.
Para dar início, este artigo abordará:
À medida que exploramos o reflective loading do Cobalt Strike sob a perspectiva de um desenvolvedor de ferramentas de segurança ofensiva, iremos destacar oportunidades tanto para detecção quanto para evasão. Alguns aspectos do desenvolvimento serão omitidos ou simplificados, e encorajamos o leitor a preencher essas lacunas depurando projetos existentes de carregador refletivo, reconstruindo-os do zero ou buscando formação especializada.
O implante de C2 do Cobalt Strike, conhecido como Beacon, é uma biblioteca de vínculo dinâmico do Windows (Dynamic-Link Library – DLL). O recurso modular de utilizar o nosso próprio carregador de DLL no Cobalt Strike é conhecida como User-Defined Reflective Loader (UDRL).
Normalmente, o carregador de DLL nativo do Windows é responsável por carregar DLLs no espaço de memória virtual de um processo. Esse carregador existe principalmente em user space, embora atravesse para o kernel quando mapeia DLLs a partir do disco.
O uso do carregador de DLL do Windows apresenta algumas desvantagens quando empregado em simulações de adversários:
Por esses motivos, utilizar o carregador de DLL do Windows para carregar a DLL do Beacon não é uma solução ideal. Para contornar essas limitações, carregamos a DLL do Beacon diretamente da memória por meio de um carregador reflexivo.
Os três principais pontos de detecção que o carregamento reflexivo evita são:
O carregamento reflexivo pode ser entendido, de forma simples, como o carregamento de uma DLL em formato bruto diretamente da memória, em vez de a partir do sistema de arquivos.
Tanto o carregamento reflexivo quanto o carregador de DLL do Windows têm o mesmo objetivo: carregar uma DLL do formato bruto de arquivo para o espaço de memória virtual de um processo. No entanto, o carregamento reflexivo apresenta uma vantagem fundamental em relação ao carregador de DLL do Windows: ele não exige que o arquivo da DLL exista no sistema de arquivos. Esse carregamento inteiramente em memória permite um número praticamente ilimitado de fases de carregamento em cadeia, já que a DLL do implante de C2 pode ficar oculta sob múltiplas camadas de criptografia e codificação dentro da memória do processo.
Um conceito essencial ao carregar uma DLL é compreender que ela possui formatações diferentes quando está em disco e quando está em memória. As principais diferenças entre a DLL em formato bruto de arquivo e em formato de endereço virtual são:
Formato de arquivo bruto:
Formato de endereço virtual:
Ao analisar uma DLL de beacon HTTP na ferramenta PE-Bear, de Aleksandra Doniec, é possível observar claramente as diferenças entre o endereçamento bruto e o endereçamento virtual de cada seção da DLL.
Tabela listando os endereços brutos e virtuais de cada seção da DLL do Beacon.
Essa DLL de beacon HTTP/S possui
O PE-Bear fornece uma representação visual da DLL do Beacon tanto no formato bruto de arquivo quanto no formato de espaço de endereços virtuais.
Representação visual da LLC do beacon em formato bruto (à esquerda) versus formato virtual (à direita)
Embora não seja a abordagem mais prudente durante uma simulação de adversário, gravar uma DLL de beacon em formato bruto no disco, sem qualquer ofuscação, e carregá-la usando o carregador de DLL do Windows é uma excelente forma de desmistificar tanto o funcionamento do beacon quanto o processo de carregamento de DLLs. Em essência, o beacon é apenas uma DLL. Tanto o carregador de DLL do Windows quanto um carregador reflexivo têm a mesma função básica: carregar uma DLL dentro de um processo.
Para carregar a DLL do beacon utilizando o carregador de DLL do Windows, seguimos os passos abaixo:
LoadLibrary
para carregar a DLL do beacon a partir do disco.Primeiro, desativamos todas as opções de Malleable PE que tornam a DLL do beacon incompatível com o carregador de DLL do Windows. Para isso, modificamos o nosso perfil Malleable C2 e desabilitamos as opções de evasão do Malleable PE localizadas no bloco de estágio.
Bloco de estágio do perfil Malleable C2 modificado para desabilitar os recursos de evasão do Cobalt Strike.
Após modificar o perfil, reiniciamos o Cobalt Strike Team Server, fornecendo o nosso perfil
Nós nos conectamos ao Team Server com o cliente Cobalt Strike. Em seguida, criamos um
Captura de tela da criação de uma DLL de beacon “raw stageless” a partir do cliente Cobalt Strike
Usando o código abaixo, criamos um programa em C chamado
Código C para Windows para carregar a DLL do beacon a partir do disco usando o carregador de DLL do Windows.
Usamos a API
Como parte do processo de carregamento, o carregador de DLL do Windows inicializará nossa DLL de beacon chamando seu ponto de entrada com
Depois que o carregador de DLL do Windows carregar e inicializar nossa DLL de beacon no espaço de memória virtual do nosso processo, precisaremos chamar novamente o ponto de entrada da DLL de beacon virtual com o argumento
O nosso programa precisa conhecer o ponto de entrada da DLL do beacon virtual para poder executá-la. Isso pode ser feito dinamicamente no próprio programa, analisando os cabeçalhos da DLL virtual do beacon para obter o Relative Virtual Address (RVA) do ponto de entrada, ou, de forma mais simples, podemos identificar esse valor previamente e codificá-lo diretamente no programa.
Para esta prova de conceito, iremos descobrir manualmente e codificar diretamente no programa o RVA do ponto de entrada da DLL do beacon. Usando o PE-Bear, descobrimos que o RVA do ponto de entrada do beacon é
Captura de tela da identificação do RVA do ponto de entrada da DLL do beacon usando o PE-Bear
O método
Com o código pronto, compilamos o nosso programa em C em um executável para Windows:
Comando usado para compilar o nosso programa.
Ao colocar a DLL do beacon e o nosso programa carregador do beacon no mesmo diretório, o carregador de DLL do Windows consegue localizar a DLL automaticamente durante o processo de carregamento.
Colocamos tanto
DLL do Beacon e programa carregador posicionados no mesmo diretório.
A partir do desktop do Windows, damos um duplo clique no programa loadBeaconDLL.exe e estabelecemos uma conexão ativa do beacon com o Team Server.
Conexão bem-sucedida com o Team Server de C2 a partir da DLL do beacon carregada usando o carregador de DLL do Windows.
O Cobalt Strike usa uma versão modificada do projeto do Carregador Refletivo, de Stephen Fewer. Esse lendário carregador de DLL em memória existe há mais de uma década e já foi utilizado no Metasploit e em outras ferramentas notáveis de segurança ofensiva.
Ao longo dos anos, o carregador reflexivo do Cobalt Strike foi aprimorado para lidar com todos os recursos de evasão do Malleable PE que a ferramenta oferece. A principal desvantagem de utilizar um User-Defined Reflective Loader (UDRL) personalizado é que os recursos de evasão do Malleable PE podem ou não ser suportados nativamente, exigindo implementação adicional.
Este lendário carregador de DNS na memória tem mais de uma década de idade e tem sido usado no Metasploit e em outras ferramentas de segurança ofensiva notáveis. No entanto, atualmente, funcionalidades como precisam ser tratadas pelo próprio UDRL, enquanto outras, como e podem ser gerenciadas pelo beacon quando há uma integração adequada com o UDRL.
O projeto original de Carregador Refletivo exige que o
Em seguida, outro projeto fica responsável por:
Diagrama do carregador refletivo original, carregando uma LLC na memória virtual.
Um método alternativo é anexar o carregador refletivo à LLC. Isso permite que qualquer vulnerabilidade não gerenciada seja carregada e não requer a compilação da vulnerabilidade a partir do código-fonte. Esse é um método robusto de carregamento refletivo capaz de carregar qualquer arquivo PE (EXE ou LLC).
Diagrama de um carregador refletivo anexado a uma LLC, carregando uma LLC na memória virtual.
A implementação de cargas refletivas da Cobalt Strike usa um híbrido dos dois métodos acima. Este método de carregamento refletivo pode ser familiar para aqueles que já sabem como o Meterpreter da Metasploit faz carregamento refletivo.
Assim como no método original de carregamento reflexivo, a função
Quando um UDRL é carregado no Cobalt Strike e um operador gera um payload de beacon a partir do cliente, o mecanismo Malleable PE do Cobalt Strike injeta o shellcode do carregador reflexivo no deslocamento do arquivo bruto correspondente à exportação
Após o mecanismo Malleable PE concluir a aplicação dos patches na DLL bruta do beacon, essa DLL é entregue ao operador em um formato executável semelhante a um shellcode.
Diagrama do carregador reflexivo do Cobalt Strike carregando a DLL do beacon para a memória virtual.
Ao observar os bytes iniciais no desmontador PE-Bear, podemos ver que a própria DLL do beacon é executável.
O stub do carregador refletivo de chamada mostrado como códigos de operação em assembly executáveis.
Os bytes iniciais
Após a execução de
Confirmamos que o deslocamento no arquivo bruto da exportação
Captura de tela do uso do PE-Bear para determinar o deslocamento do arquivo bruto da exportação do ReflectiveLoader.
Como aparece no diretório de exportações, o endereço da exportação
Para identificar o deslocamento correspondente no arquivo bruto da exportação
Os endereços virtual e bruto da seção
Endereços bruto e virtual da seção .text da DLL do beacon.
A diferença entre os dois é de
Podemos confirmar isso no PE-Bear clicando com o botão direito do mouse no RVA da função da exportação
Em resumo, o fluxo do processo de carregamento refletivo do Cobalt Attack é:
Diagrama mostrando as fases principais de como o Cobalt Attack realiza o carregamento refletivo da LLC do beacon.
Como nosso carregador refletivo é executado antes que a LLC do beacon seja carregada, o código do carregador refletivo precisa ser um shellcode puro.
A maneira mais fácil de criar um shellcode complexo é escrevê-lo em C sem dependências externas. Em seguida, o arquivo C é compilado em um arquivo-objeto. Tudo deve ser incluído na seção do arquivo-objeto. Por fim, extraímos a seção.text
O mecanismo de Malleable PE do Cobalt Strike irá lidar com o trabalho de obter o shellcode do nosso arquivo de objeto de carregador refletivo e corrigi-lo na SSD do beacon bruto no deslocamento do arquivo bruto da exportação
Script do Aggressor para escrever o shellcode de carregador refletivo na DLL do beacon bruto usando o Cobalt Strike.
O nosso script UDRL Aggressor faz com que o Cobalt Strike escreva o shellcode do nosso carregador reflexivo executando as seguintes etapas:
<a href="https://hstechdocs.helpsystems.com/manuals/cobaltstrike/current/userguide/content/topics_aggressor-scripts/as-resources_functions.htm#extract_reflective_loader">extract_reflective_loader</a>analisará o arquivo objeto do nosso UDRL a partir da matriz de bytes
<a href="https://hstechdocs.helpsystems.com/manuals/cobaltstrike/current/userguide/content/topics_aggressor-scripts/as-resources_functions.htm#setup_reflective_loader">setup_reflective_loader</a>A função Aggressor do Cobalt Strike usará o mecanismo Malleable PE para descobrir o deslocamento do arquivo bruto da nossa exportação
O Cobalt Strike já fez o trabalho pesado para nós no que diz respeito a extrair a seção .text do arquivo objeto do nosso carregador reflexivo, aplicar o patch com o shellcode do nosso carregador reflexivo e chamar o nosso carregador reflexivo por meio do stub de carregador refletivo de chamada, localizado no cabeçalho da DLL do beacon.
Estas são as fases que precisamos desenvolver para carregar o beacon por carregamento reflexivo:
Existem vários métodos diferentes que podemos usar para lidar com o endereço da DLL do beacon bruto na memória. Alguns métodos são:
Ao utilizar técnicas baseadas em varredura reversa, o primeiro passo é obter o endereço atual do ponteiro de instrução (
Código em assembly Intel x64 para obter o endereço base da DLL de beacon raw a partir do registrador RDI.
O projeto original do carregador refletivo faz uma varredura regressiva em busca dos cabeçalhos MZ e PE. Esses cabeçalhos se tornaram pontos de detecção. Para contornar isso, o Cobalt Strike adicionou os recursos de evasão de Malleable PE
A documentação do Cobalt Strike afirma que a opção
Quando configurado, os bytes
Esses bytes precisam ser relativamente exclusivos, caso contrário o carregador refletivo não conseguirá encontrá-los. Além disso, os bytes do cabeçalho MZ devem ser de não-operação e executáveis. Eles não podem ser valores como
Após identificar esse possível ponto de detecção, desenvolvi um método diferente, porém semelhante, para encontrar o endereço base da DLL de beacon raw. Esse método utiliza um caçado de egg capaz de pesquisar de trás para frente a partir de
O endereço
Como não temos acesso fácil ao mecanismo Java Malleable PE, o script Aggressor do UDRL
Script do Aggressor para escrever um egg na DLL do beacon raw e exibir as alterações no console de script do Cobalt Strike.
O código UDRL deve saber o valor do egg escrito na DLL do beacon raw pelo script UDRL. Com o egg conhecido, o caçador de egg procura por duas instâncias do egg, conforme visto no código abaixo:
Código de assembly intel x64 para um caçador de egg que procura de trás para frente duas instâncias de um egg de 64 bits.
Agora que os cabeçalhos MZ e PE não são mais usados, podemos removê-los do script do Aggressor UDRL:
Script do Aggressor para mascarar MZ, PE e bytes não utilizados do banner do DOS localizados nos cabeçalhos da PHP do beacon raw.
Existe também outra forma, específica do Cobalt Strike, de descobrir o endereço base da DLL de beacon raw. Como visto anteriormente, os bytes iniciais no stub do carregador refletivo de chamadas armazenam o endereço base da DLL de beacon raw no registrador
Para examinar isso mais a fundo no depurador, geramos um beacon, prefixamos um breakpoint (
Captura de tela do x64dbg mostrando o passo a passo pelo stub do carregador refletivo de chamadas, evidenciando que o endereço base da DLL de beacon raw é salvo no registrador RDI antes da chamada ao carregador refletivo.
Abaixo está um exemplo funcional de como obter o endereço base da DLL de beacon raw a partir do stub do carregador refletivo de chamadas:
Código C com assembly inline para obter o endereço base da DLL de beacon raw a partir do registrador RDI.
Com o endereço base da DLL de beacon raw, agora podemos obter os valores necessários para carregar o beacon no espaço de endereçamento virtual do processo.
A tabela abaixo lista os valores necessários a partir dos cabeçalhos da DLL de beacon raw, os locais onde podem ser encontrados e seus respectivos tipos:
Tabela listando valores do cabeçalho da DLL de beacon raw que são úteis para carregar a DLL de beacon.
Nem todo o conteúdo dos cabeçalhos é necessário para carregar a DLL de beacon. Os valores obrigatórios podem ser reempacotados ou ofuscados. Os valores que não são necessários podem ser removidos ou randomizados.
Assim que conhecemos o valorSizeOfImagef
Diferentes métodos podem ser usados para alocar memória para a DLL de beacon virtual. Cada método utiliza diferentes tipos de memória. Os diferentes métodos suportados pelo carregador refletivo padrão do Cobalt Strike são:
Tabela mostrando as opções de alocação de memória do Cobalt Strike para a DLL de beacon virtual.
Isso pode ser levado um passo adiante com o UDRL. A versão NTAPI dessas funções pode ser usada em vez disso. Além disso, as funções NTAPI poderiam ser chamadas por meio de chamadas de sistema diretas ou indiretas, que podem ou não ajudar a reforçar os recursos de evasão.
Quando o método do alocador é definido como
Amostra de código do projeto BokuLoader mostrando uma chamada de sistema direta é usada para alocar memória para a DLL do beacon virtual.
A imagem abaixo mostra um exemplo de código do uso dos métodos HelsGate e HalosGate para determinar os números de chamada do sistema:
Amostra de código do projeto BokuLoader mostrando como as chamadas do sistema são descobertas a partir do processo.
Agora que alocamos memória para nossa DLL de beacon virtual, precisamos copiar as seções do beacon de seus deslocamentos de arquivo bruto, conforme elas existem na DLL de beacon raw, para a memória alocada em seus deslocamentos virtuais relativos.
Se alocarmos nossa memória comREADWRITE .text e seu tamanho. Antes de chamar o ponto de entrada da DLL de beacon virtual, precisaremos alterar as proteções de memória da seção para executável.
Alocar nossa memória com torna o processo de carregamento refletivo mais fácil, mas aumenta as chances de detecção por soluções de segurança.
Abaixo está um exemplo de código simplificado, do projeto BokuLoader, que demonstra isso:.
Exemplo de código do projeto BokuLoader mostrando seções copiadas da DLL de beacon raw para a DLL de beacon virtual.
Algumas funcionalidades de evasão relacionadas ao carregamento de seções são:
No projeto público BokuLoader, os cabeçalhos da DLL de beacon não são copiados da DLL de beacon raw para a DLL de beacon virtual. Atualmente, os primeiros
Outra possível oportunidade de evasão é fazer com que o script Aggressor UDRL criptografe as seções. As seções poderiam ser descriptografadas em memória pelo UDRL, usando uma chave compartilhada entre o UDRL e o script Aggressor UDRL.
O beacon HTTP/S x64 depende de quatro DLLs para funcionar corretamente. Se essas DLLs não estiverem carregadas no processo no momento, nosso carregador refletivo precisará carregá-las.
As quatro DLLs estão listadas no diretório de importação da DLLC do beacon HTTP/S:
Captura de tela do PE-Bear listando DLLs do diretório de importação da vulnerabilidade do beacon.
O carregador refletivo Cobalt Strike integrado usa a API kernel32.LoadLibraryA para carregamento de DLL.
O carregamento de vulnerabilidades pode ser alcançado de várias maneiras diferentes, com diferentes considerações de segurança operacional. Alguns métodos são:
Se a DLL já existir no processo, as APIs do Windows acima ainda poderão ser usadas para lidar com os endereços de base da DLL, embora isso possa desencadear alertas de detecção indesejados.
Como alternativa, o PEB contém um ponteiro para a estrutura
<a title="https://learn.microsoft.com/br-pt/windows/win32/api/winternl/ns-winternl-peb_ldr_data" href="https://learn.microsoft.com/br-pt/windows/win32/api/winternl/ns-winternl-peb_ldr_data">_PEB_LDR_DATA</a>
. Dentro, há uma lista vinculada de todas as DLLs carregadas no processo e suas informações relativas (
). O BokuLoader aproveita isso para descobrir as informações da DLL, evitando chamadas de API desnecessárias.
Se a DLL não existir em
O carregamento refletivo aninhado não pode ser usado facilmente para carregar dependências de DLL porque os carregadores refletivos geralmente não registram a DLL no processo. O código externo à DLL não pode usar corretamente uma DLL carregada de forma refletiva. O projeto DarkLoadLibrary pode ser capaz de carregar corretamente uma DLL na memória sem acionar um evento de carregamento de imagem do kernel.
Amostra de código do projeto BokuLoader mostrando como os endereços básicos carregados da DLL podem ser resolvidos percorrendo o InMemoryOrderModuleList.
Com as DLLs necessárias carregadas no processo, as APIs listadas no diretório de importação devem ser resolvidas. Os endereços da API precisarão então ser escritos na Tabela de Endereços de Importação (IAT) da DLL do beacon virtual. Dessa forma, o beacon sabe para qual endereço lidar com quando precisar chamar APIs como
A entrada de importação precisará ser resolvida por meio da sequência de caracteres ordinal ou de nome.
Na imagem abaixo, vemos que a DLL do beacon do Cobalt Strike usa uma combinação de ordinais e cadeias de caracteres de nomes para entradas de importação:
Captura de tela do PE-Bear mostrando que algumas entradas de importação para a DLL de beacon devem ser resolvidas por número ordinal.
O carregador refletivo Cobalt Strike integrado usa a API
Alguns métodos de evasão para lidar com endereços de API são:
GetProcAddress
NTDLL.LdrGetProcedureAddress
usa uma implementação de código personalizada deGetProcAddress
A função é capaz de lidar com strings de nomes e ordinais também. Se o endereço retornado para a Entrada de Importação for um encaminhador para outro DLL, o BokuLoader assume como padrão o para resolver o problema com o encaminhador.
Ao escrever a IAT, o hooking pode ser implementado gravando os endereços virtuais das funções de hook que implementamos, em vez dos endereços virtuais das APIs pretendidas. Desde que a saída esperada seja retornada ao beacon quando o endereço na IAT for chamado, podemos executar código adicional antes de retornar ao beacon. Publicações futuras e versões públicas do BokuLoader demonstrarão como podemos aproveitar o hooking da IAT para recursos avançados de evasão.
Com uma versão recente, o projeto público BokuLoader passou a oferecer suporte ao recurso do Malleable PE a partir do perfil C2 do Cobalt Strike, por meio de uma implementação personalizada. Ao modificar a chave de mascaramento no script Aggressor UDRLBokuLoader.cna
Em termos de segurança operacional, é importante saber que mecanismos de correspondência de padrões são capazes de realizar força bruta em máscaras XOR de um único byte. Publicações futuras demonstrarão como podemos criar nosso próprio mecanismo de Malleable PE usando a funcionalidade de scripts Aggressor do Cobalt Strike para ofuscar o beacon e superar mecanismos de correspondência de padrões.
A DLL do beacon possui muitas realocações que precisam ser resolvidas e gravadas na Tabela de Realocação de Base da DLL de beacon virtual antes de ela ser executada.
No PE-Bear, podemos ver que a DLL do beacon, por padrão, tem o endereço base da imagem de
Captura de tela do PE-Bear mostrando o endereço base da imagem da DLL do beacon.
Antes de começarmos a gravar as realocações, precisamos calcular o delta entre o endereço base da nossa DLL de beacon virtual e o endereço base codificado (hardcoded).
Por exemplo, vamos supor que o endereço base da nossa DLL de beacon virtual seja
Em seguida, para determinar o endereço virtual de cada entrada de realocação na Tabela de Realocação de Base, somamos o delta do endereço base ao endereço codificado (hardcoded) da entrada de realocação para determinar a realocação dentro da nossa DLL de beacon virtual.
Na imagem abaixo, podemos ver que as entradas de realocação de beacons são escritas ao contrário no formato pouco endian:
Captura de tela do PE-Bear mostrando que existem entradas de realocação em formato little-endian.
O endereço codificado (hardcoded) para essa entrada de realocação é
Somamos esse endereço ao delta do endereço base para obter o endereço virtual da realocação, conforme ela existe dentro da DLL de beacon virtual:
Para cada entrada de realocação, precisaremos verificar se o tipo é
<a title="https://learn.microsoft.com/br-pt/windows/win32/debug/pe-format" href="https://learn.microsoft.com/br-pt/windows/win32/debug/pe-format">IMAGE_REL_BASED_DIR64 (0xA)</a>
. Se isso for falso, vamos ignorar a gravação da realocação.
Assim que determinarmos o endereço virtual da realocação, conforme ela existe dentro da DLL de beacon virtual, gravamos esse valor no espaço de memória que contém o endereço codificado (hardcoded) da entrada de realocação.
Se você tiver interesse em aprender mais sobre como realizar realocações de PE, confira o código da função doRelocations no projeto público BokuLoader. Antes de publicar este post do blog, alterei o código de realocações de assembly para um código em C, espero que mais legível, para ajudar outras pessoas que queiram entender os detalhes técnicos de como isso é feito.
A execução do beacon pode ser dividida em três etapas:
Se a memória que alocamos para a DLL de beacon virtual for
Se alocarmos a memória do beacon virtual como não executável (
No projeto público BokuLoader, as alterações de proteção de memória são realizadas por meio de chamadas diretas de sistema para
Captura de tela feita para a postagem do blogExemplo de código do projeto BokuLoader demonstrando a alteração da seção .text da DLL de beacon virtual para executável.
O
Para que a DLL de beacon virtual seja executada corretamente, ela deve primeiro ser inicializada chamando o ponto de entrada da DLL de beacon virtual. O primeiro argumento é o endereço base da DLL de beacon virtual. O segundo argumento é o
Amostra de código do projeto BokuLoader inicializando a DLL do beacon virtual.
Após inicializar a DLL de beacon virtual, podemos optar por retornar o ponto de entrada do beacon virtual ao stub do Carregador Refletivo de Chamada, ou chamar o ponto de entrada da DLL de beacon virtual diretamente no nosso UDRL, com o
Diferentemente de uma DLL típica, em que o primeiro argumento
<a href="https://learn.microsoft.com/br-pt/windows/win32/dlls/dllmain">DLLMAIN</a>
seria o endereço base da DLL virtual, o beacon espera receber o endereço base da DLL de beacon raw. Caso isso não seja fornecido, alguns recursos de evasão do Malleable PE podem falhar.
Exemplo de código do projeto BokuLoader mostrando duas formas diferentes de executar a DLL de beacon virtual.
Espera-se que este post de blog ajude tanto equipes red team quanto blue team a compreender melhor o Cobalt Strike e o processo de carregamento refletivo. Ainda existem inúmeras oportunidades de evasão que podem ser implementadas por meio do carregamento refletivo. Com uma compreensão mais profunda desses conceitos, as organizações podem se preparar melhor para uma defesa eficaz contra ameaças cibernéticas.
Os próximos posts desta série terão foco na integração do UDRL com os recursos atuais de evasão do Cobalt Strike, na exploração de recursos de evasão não documentados já presentes no BokuLoader público, bem como em funcionalidades avançadas que ainda não foram disponibilizadas publicamente. Fique atento para mais informações detalhadas e técnicas avançadas sobre como elevar o uso do Cobalt Strike a um novo patamar por meio do desenvolvimento de UDRL.