Egg Hunter
Egg Hunter Method
Principle
The egg_hunter method is a two-phase technique:
- At compile time, the inline
syscallopcode is replaced by a unique 8-byte marker (the "egg"). The compiled binary contains nosyscallinstruction. - At runtime, before
main()runs, an automatic initialisation function scans the binary's.textsection for the egg bytes and patches them in-place with realsyscall; retbytes.
This means the binary that is written to disk never contains syscall opcodes. Static analysis of the file on disk does not reveal syscall stubs. The stubs become functional only in memory at process startup.
Generated stub (x64, from source)
The ASM is identical to direct, except the syscall opcode is replaced by the egg placeholder:
SPT_Syscall:
pop rax ; discard return address
pop rax ; load function hash
mov [rsp+ 8], rcx ; save arg1
mov [rsp+16], rdx ; save arg2
mov [rsp+24], r8 ; save arg3
mov [rsp+32], r9 ; save arg4
sub rsp, 0x28
mov rcx, rax
call SPT_GetSyscallNumber ; returns SSN in EAX
add rsp, 0x28
mov rcx, [rsp+ 8]
mov rdx, [rsp+16]
mov r8, [rsp+24]
mov r9, [rsp+32]
mov r10, rcx
##__EGG_MARKER__## ; 8-byte placeholder — no syscall opcode in the object file
ret
The ##__EGG_MARKER__## placeholder is expanded at code-generation time into an 8-byte sequence that serves as the egg.
The sanitizer (from stubs/sanitizer.c)
The sanitizer provides two elements.
Replacement bytes
BYTE SPT_EGG_REPLACE[] = {
0x0f, 0x05, // syscall
0x90, // nop
0x90, // nop
0xC3, // ret
0x90, // nop
0xCC, // int3 (debug break)
0xCC // int3
};
The egg is replaced with syscall; nop; nop; ret; nop; int3; int3. The extra bytes after ret pad the 8-byte egg slot.
Automatic initialisation
__attribute__((used))
static void __SPT_EggHunterInit(void) {
SPT_SanitizeSyscalls();
}
This function is registered in the .CRT$XCU section, which the C runtime processes before calling main(). No explicit initialisation call is needed in application code.
You can also call SPT_SanitizeSyscalls() manually if your runtime does not honour .CRT$XCU (e.g., some cross-compilation setups).
How SPT_SanitizeSyscalls works
- Locates the current module's PE header.
- Finds the
.textsection. - Calls
VirtualProtectto make the.textsection read-write-execute (RWX). - Scans the section byte-by-byte for the egg pattern.
- Replaces each egg occurrence with
SPT_EGG_REPLACE. - Calls
VirtualProtectagain to restore the original memory protection.
After this function returns, every egg in the .text section has become a functional syscall; nop; nop; ret; nop; int3; int3 sequence.
Characteristics
| Property | Value |
|---|---|
syscall opcode on disk | No |
syscall opcode in memory | Yes — after .CRT$XCU init |
| Requires manual init | No (automatic via .CRT$XCU) |
| Manual call available | Yes — SPT_SanitizeSyscalls() |
| Return address during syscall | Inside your module (same as direct) |
Limitations
- The
.CRT$XCUmechanism is a MinGW/GCC feature. Ensure your build toolchain supports CRT init table sections for the auto-init to trigger. VirtualProtecton the.textsection is a detectable memory operation. EDR products that monitor RWX page transitions may flag the sanitisation step itself.