Binary

See my note on Software Security.

Intro book to to binary exploitation / reverse engineering.

Pwntools Cheatsheet

Syscalls for x64

Syscalls for x86

Syscalls for arm64

Compare tools on the forefront of static analysis in your web browser

Binary Ninja

Keybindings

macOS

~/Library/Application Support/Binary Ninja

Linux

~/.binaryninja

Windows

%APPDATA%\\Binary Ninja

Create a keybindings.json file inside the directory and set my keybindings:

keybindings.json
{
	"" : "",
	"" : ""
}
Remote Dubugging
  • Move on VM lldbdebugger-linux/plugins/lldb

Debug Server

On VM

lldb-server p --server --listen 0.0.0.0:31337

On Binary Ninja

"Debugger" —> "Connect to Debug Server”

Platform: remote-linux
Host: <IP>
Port: 31337

Reopen "Debugger" —> "Connect to Debug Server”

Working Directory: <PATH_WHERE_UPLOAD_AND_RUN_ELF_ON_VM> # edit only this

Remote Process

On VM

./lldb-server g 0.0.0.0:31337 -- <PATH_ELF> <ARGs>
./lldb-server g 0.0.0.0:31337 --attach <PID>

On Binary Ninja

"Debugger" —> "Remote Process Settings"

Plugin: gdb-remote
Host: <IP>
Port: 31337

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>

j *<EXPRESSION>

Jump and force execution to a specific address (*addr, *$reg, function).

pwndbg

pwndbg [<TOPIC>]

Print info about pwndbg commands

vmmap

Display memory mappings information

context [<SECTION>]

Display context or a given context section (regs, disasm, args, code, stack, backtrace, expressions, ghidra, threads)

search <WHAT>

Search memory for a given value

telescope <WHERE> [<COUNT>]

Examine memory dereferencing valid pointers

hexdump <WHERE> [<COUNT>]

Print hexdump of given address

xinfo <WHERE>

Show offsets of the specified address from various useful locations

retaddr

Print return addresses on the stack

canary

Print the global stack canary/cookie value and finds canaries on the stack

xuntil

Continue until an address or function

nextcall

Continue to next call instruction

nextjmp

Continue to next jump instruction

nextret

Continue to next return-like instruction

stepret

Step until a ret instruction is found

checksec

Print binary mitigations status

piebase

Print the relocated binary base address

got

Print symbols in the .got section

gotplt

Print symbols in the .got.plt section

plt

Print symbols in the .plt section

tls

Print thread local storage address

distance <WHERE1> <WHERE2>

Compute difference between two addresses

procinfo

Display process information

heap_config

Show glibc allocator hacking configuration

heap

Iteratively print chunks on heap (glibc only)

vis_heap_chunks

Visualize chunks on a heap

try_free <ADDRESS>

Check what would happen if free was called with given address

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>

Code
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

Was this helpful?