Implementing Hell’s Gate Indirect Syscalls in Go for EDR Evasion
Implementing Hell’s Gate Indirect Syscalls in Go for EDR Evasion
Implementing Hell’s Gate Indirect Syscalls in Go for EDR Evasion
Implementing Hell’s Gate Indirect Syscalls in Go for EDR Evasion
Or: How I built a C2 agent that bypasses Windows Defender
The Problem
You’re on a red team engagement. You generate a Havoc payload. You execute it on Windows 11. Windows Defender catches it immediately.
This happened to me. Repeatedly.
Stock open-source C2 frameworks (Havoc, Sliver, Covenant) are heavily signatured. EDR vendors have had years to reverse them. The signatures aren’t just for the binaries anymore — they’re for the behaviors.
I decided to build a new C2 framework called ByteCode from scratch, with one priority:evasion.
After months of work, here are the results:
- Undetected on Windows Defender (24H2)
- Undetected on Triage, Any.Run, and Joe Sandbox
This post explains the core evasion technique: Hell’s Gate indirect syscalls implemented in Go
## Why Syscalls Matter
Most Windows APIs (CreateFile, WriteProcessMemory, etc.) eventually call into `ntdll.dll`, which transitions to the kernel via a `syscall` instruction.
EDRs hook these APIs **in user-mode** by patching `ntdll.dll`. When you call `CreateFile`, you're actually calling into the EDR's monitoring code first.
**The bypass:** Call the syscall directly, skipping `ntdll.dll` entirely.
But there's a catch. Syscall numbers (SSNs) change with every Windows version.
| API | Windows 10 20H2 | Windows 11 22H2 | Windows 11 24H2 |
|-----|-----------------|-----------------|-----------------|
| NtCreateFile | 0x55 | 0x55 | 0x56 |
| NtAllocateVirtualMemory | 0x18 | 0x18 | 0x19 |
| NtProtectVirtualMemory | 0x50 | 0x50 | 0x51 |
Hardcoding SSNs means your agent breaks on the next Windows update.
**The solution:** Hell's Gate — dynamically extract SSNs from `ntdll.dll` at runtime.
---
## Hell's Gate in Theory
Hell's Gate works in three steps:
1. **Parse `ntdll.dll` in memory** from `PEB->Ldr`
2. **Find target function** (e.g., `NtCreateFile`) by walking exports
3. **Extract SSN** from the function's assembly prologue
The prologue pattern for most syscall functions looks like:
```asm
mov r10, rcx
mov eax, SSN ; <-- This byte is the syscall number
test byte ptr [r7], 0
syscall
ret
메타데이터
- post_id
- e528e1610df2
- slug
- implementing-hells-gate-indirect-syscalls-in-go-for-edr-evasion-e528e1610df2
- url
- https://medium.com/@wadecalvin9/implementing-hells-gate-indirect-syscalls-in-go-for-edr-evasion-e528e1610df2
- canonical_url
- https://medium.com/@wadecalvin9/implementing-hells-gate-indirect-syscalls-in-go-for-edr-evasion-e528e1610df2
- author_url
- https://medium.com/@wadecalvin9
- status
- ok
- fetched_at
- 2026-06-09 21:21:26