Binary

Intro book to to binary exploitation / reverse engineering.

Pwntools Cheatsheet

Syscalls for x64

Syscalls for x86

Syscalls for arm64

To be expanded with PEDA, GEF or PWNDBG.

Start

gdb <ELF>

Run gdb on that program. Be careful with addresses (it would be better to use PID).

gdb --pid <PID>

Run gdb on a process. Run the program in the background, get PID and provide it to gdb.

gdb --args <ELF> <ARGs>

Run gdb on that program passing it arguments.

Execution

file <ELF>

Load the program to analyze

set args <ARGs>

Set the arguments to use on each bugger run.

r

Run

r <ARGs>

Run with arguments

r ${python3 -c ‘print "\xBYTES"’}

Run with byte arguments using Python

r ${echo -e “\xBYTES”}

run with byte arguments using Echo

Breakpoints

b <function> b *<address>

Set a breakpoint. Also possible to do: b *main+10

i b

Info breakpoint

enable <ID>

Enable breakpoints. If no ID is specified, enable all breakpoints.

disable <ID>

Disable breakpoints. If no ID is specified, disable all breakpoints.

d <ID>

Delete breakpoint If no ID is specifyied, deletes all breakpoints.

c

Continue. Normal execution.

s

Step. IN function.

n

Next. NO IN function.

finish

Continue until the current function returns.

k

Kill. Stop the current execution.

Visualization

disassemble <FUNCTION>

Get assembly and their memory addresses.

i fu

Get the list of program functions with their addresses.

bt [full]

Show backtrace (function calls list). With full print even the local variables of each frame, if you have access to them.

i s

Info Stack Frame.

i f

Info Current Stack Frame. (ex. $RIP in BOF)

i r i all-r

Info Registers.

i dll

list of used DLLs

p/<F> <expr>

Print what you want in the specified format <F>. ex. p/x $esp p/x $esp+8 p <FUN> p <VAR>

x/<N><F><U> <addr>

Print and display memory only once. Specifies how many elements (<N>), in what format (<F>), and in what units (<U>) to display starting at address <addr>. ex. x/40x $esp x/5i $pc (next 5 instruction)

display/<N><F><U> <addr>

As x/ but are saved and printed/displayed at each step. i display undisplay <ID> enable display <ID> disable display <ID> If you don't put ID it applies to all. ex. display/5i $pc (next 5 instruction)

Format

  • a Pointer.

  • c Read as integer, print as character.

  • d Integer, signed decimal.

  • f Floating point number.

  • o Integer, print as octal.

  • s String.

  • t Integer, print as binary (t = "two").

  • u Integer, unsigned decimal.

  • x Integer, print as hexadecimal.

  • i Disassembly.

Edit memory

set {type}address = value

Change the value of the addresses. ex. set{int}0x650000 = 0x42 set{char[1]}0x650000 = 0x42

set variable = value

Change the value of variables

set $reg = value

Change the value of the registers

set *address = value

Change the value of an address

Utility

call <FUNCTION>()

Call function. ex. call (void)win()

echo $((16#<NUM>))

From bash convert hex to decimal.

r << <CommandFile>

It is possible to specify a file with the inputs that should be given to the program in read, scanf, etc. Like cat <CommandFile> | ./<ELF>

checksec <ELF>
pwn checksec <ELF>

Context

Architecture

context.arch = '<ARCH>' 'aarch64', 'arm', 'i386', 'amd64'

Log Output

context.log_level = '<VALUE>'

'debug', 'info', 'warn', 'error'

Endianness

context.endian = '<ENDIANNESS>'

'big', 'little'

Start

ELF

elf = ELF('<PATH_BINARY>') libc = ELF("./libc.so.6")

Process

p = process('<PATH_BINARY>')

Remote

p = remote('<IP/DOMAIN>', PORT)

Hybrid

elf = ELF('<PATH_BINARY>')

p = elf.process()

Debugger

p = gdb.debug('<PATH_BINARY>', aslr=False, gdbscript='b *main+123')

Encoding e Packing

Hex

enhex(b'/flag') ('2f666c6167') unhex('2f666c6167') (b'/flag')

Base64

b64e(b'/flag') ('L2ZsYWc=')

b64d('L2ZsYWc=') (b'/flag')

Packing

p32(0x41424344) (b'\x44\x43\x42\x41')

p8, p16, p64

Unpacking

u32(b'\x44\x43\x42\x41') (0x41424344)

u8, u16, u64

Address

Function address

address = elf.sym['<Name_Function>'] address = elf.symbols.<Name_Function> address = elf.plt[’<Name_Function>’] (in PLT) address = elf.got[’<Name_Function>’] (in GOT)

String address

address = next(elf.search(b'<STRING>')) Or with ghidra in .rodata, .text

Cyclic

Create Search String

cyclic(16) (b'aaaabaaacaaadaaa') cyclic(16, n=8) (b'aaaaaaaabaaaaaaa')

Find Offset

cyclic_find(0x61616164) (12) cyclic_find(0x6161616161616162, n=8) (8)

cyclic_find(io.corefile.fault_addr, n=8) (in BOF) In BOF use setuid=False in p = process()

Shellcode

Shellcraft, must be NULL-free.

Assembly

code = shellcraft.cat('/flag') code = shellcraft.amd64.linux.sh()

Bytecode

shellcode = asm(code) (use the context architecture) shellcode = asm(code, arch='x86_64')

Start

rop = ROP(elf)

rop = ROP(elf, badchars=b'\n') (char not to be used in the chain)

rop = ROP([elf, libc]) (specify double elf)

Extract Gadget

pop_ = rop.find_gadget(['pop <REG>', 'ret']).address

pop_ = rop.<REG>.address or ROPgadget.

ROP chain

rop.call(<ADDR_FUNCTION>, [<ADDR_ARG1>, ... ])

rop.puts(); rop.main() (adds puts and main to the chain) rop.chain() (builds the chain with the elements called before) rop.dump() (text representation, to be put in log.info(X)) rop.clear() (clears the rop chain) rop.execve(next(libc.search(b'/bin/sh\0')), 0, 0) (calls execv with parameters)

Set Base Address (ex. libc)

libc = ELF(<PATH_LIBC>) (ex ./glibc/libc.so.6) libc.address = <INT_ADDRESS_BASE> rop_libc = ROP(libc) (after set base libc)

Format String

%p

Pointer

%s

String

%d

Int

%x

Hex

%c

Char

%n

Writes the number of characters printed in the specified pointer. %n = int (8 byte) %hn = short (4 byte) %hhn = byte (1 byte)

%<P>x

Specifies the precision <P> (filled with 0)

%<N>$x

Take the <N>-th argument

fmtstr_payload(offset, writes, numbwritten=0, write_size='byte')

  • offset: Offset to the buffer

  • writes: Dictionary with address:value {addr: value, addr2: value2, etc.}

  • numbwritten: Number of bytes already written by printf() ex. printf(hello <VAR>) = 6

  • write_size: The write size: byte, short or int (hhn, hn or n)

Note: Set the right <ARCH>

import angr
import claripy
import sys

FILE        = './<ELF>'
FLAG_LENGTH = 30
FLAG_KNOW   = 'FLAG{'
FIND_ADDR   = 0x1def 
AVOID_ADDR  = 0x1df8

# Define program
project = angr.Project(FILE, main_opts={"base_addr": 0x0}) # auto_load_libs=False

# Define the initial state simply
OPTION_NO_ERROR = {angr.options.ZERO_FILL_UNCONSTRAINED_MEMORY, angr.options.ZERO_FILL_UNCONSTRAINED_REGISTERS}
initial_state = project.factory.entry_state(add_options = OPTION_NO_ERROR)

# ---------------- OR ----------------
# OPTIONAL: Define variable format 
# known_chars = [claripy.BVV(FLAG_KNOW[i]) for i in range(len(FLAG_KNOW))]
# flag_chars  = [claripy.BVS(f"flag_{i}",8) for i in range(FLAG_LENGTH-len(FLAG_KNOW))]
# flag_input  = claripy.Concat(*known_chars+flag_chars)

# OPTIONAL: Define how to make the input
# Define initial state with interactive input
# initial_state = project.factory.entry_state(stdin=flag_input, add_options = OPTION_NO_ERROR)
# Define initial state with input from argument
# initial_state = project.factory.entry_state(args=[FILE, flag_input], add_options = OPTION_NO_ERROR)
# ------------------------------------

# Create a simulator with the initial state
simgr = project.factory.simulation_manager(initial_state)

# Start the exploration of the program path
simgr.explore(find=FIND_ADDR, avoid=AVOID_ADDR)

# If Angr finds a path that meets the success criterion.
if simgr.found:
    # Extract the input that led to the path to success
    flag_solution = simgr.found[0].posix.dumps(0)
    print("Flag found:", flag_solution.decode())
else:
    print("No flag found.")

# ---------------- OR ----------------
# If there are multiple inputs
# if simgr.found:
#     print("[+] Input:")
#     for state in simgr.found:
#         solution = state.posix.dumps(0)
#         print(solution.decode())
# else:
#     print("No solution found.")
# ------------------------------------

Last updated