1 of 27

0x00_learning_the_ROPes

glem

__ _ _ _ _ ______ � / /| || | | || |____ |� ___ ___ / /_| || |_| || |_ / / � / __/ __| '_ \__ _|__ _|/ / � | (__\__ \ (_) | | | | | / / � \___|___/\___/ |_| |_|/_/ � � ��

2 of 27

# summary

  • quick computers 101
  • why ROP
  • how2rop
  • mitigations against ROP
  • tools and more
  • let’s hak

3 of 27

# before we start

4 of 27

# talk x86_64 to me..

  • RAX/RBX/RCX/RDX - general purpose
  • RIP - where current execution is
  • RSP - where the top of the stack is
  • RBP - current frame pointer
  • RSI/RDI - source / destination for data transfer
  • EFLAGS - processors state
  • R10/R9/R8 etc.. - just more registers okay

RAX

EAX

AH

AL

AX

64 bits

32 bits

16 bits

16 bits

8 bits

8 bits

5 of 27

# x86_32 cdecl

  • you might remember how things work in x86_32

> SYSCALLS

  • EAX => syscall number to perform / return value of syscall
  • EBX,ECX,EDX,ESI,EDI => arguments to the syscall
  • int 0x80 instruction executed to perform syscall

> FUNCTION CALLS

  • arguments pushed to stack in reverse order
  • EAX => return value of function
  • call <ADDR> instruction executed to call function

6 of 27

# x86_32 function calls?

> FUNCTION STACK FRAME HAS

  • local variables
  • arguments
  • return address & stack frame reference point

> FUNCTION CALL STEPS

  • push args to stack (reverse order)
  • push next RET address to stack
  • move function address into EIP
  • push current EBP to stack
  • move ESP into EBP
  • adjust ESP for local vars
  • execute function
  • adjust ESP to below local vars
  • pop EBP from stack
  • pop RET addr from stack into EIP

EBP ->

ESP ->

SAVED EBP

RET ADDR

ARG 1

ARG 2

LOCAL VAR 1

LOCAL VAR 2

LOCAL VAR N

0xFFFFFFFF

0x00000000

EBP-4

EBP-8

EBP+4

EBP+8

EBP+12

JUNK

JUNK

JUNK

7 of 27

# x86_64 fastcall

> SYSCALLS

  • RAX => syscall number to perform / return value of syscall
  • RDI,RSI,RDX,R10,R8,R9 => arguments to the syscall
  • syscall instruction executed to perform syscall

> FUNCTION CALLS

  • arguments passed to function in RDI,RSI,RDX,RCX,R8,R9
    • any additional are passed on the stack
  • RAX => return value of function
  • call <ADDR> instruction executed to call function

8 of 27

# x86_64 function calls?

> FUNCTION STACK FRAME HAS

  • local variables
  • arguments
  • return address & stack frame reference point

> FUNCTION CALL STEPS

  • move args into RDI,RSI,RDX,RCX,R8,R9
  • push remaining args to stack (rev.)
  • push next RET address to stack
  • move function address into RIP
  • push current RBP to stack
  • move RSP into RBP
  • adjust RSP for local vars
  • execute function
  • adjust RSP to below local vars
  • pop RBP from stack
  • pop RET addr from stack into RIP

RBP ->

RSP ->

SAVED RBP

RET ADDR

ARG N-1

ARG N

LOCAL VAR 1

LOCAL VAR 2

LOCAL VAR N

0xFFFFFFFFFFFFFFFF

0x0000000000000000

RBP-8

RBP-16

RBP+8

RBP+16

RBP+24

JUNK

JUNK

JUNK

9 of 27

# that virtual memory thing

  • operating system gives a ~view~ of entire memory to a process
  • memory in VM address space segmented into pages
  • pages are generally of size 4096 bytes
  • each having READ/WRITE/EXECUTE permissions
  • each mapped to a physical 4096 byte frame of memory by the MMU/TLB/Kernel
  • each page is not mapped in AS until process needs it
  • when we run out of frames, page-out some to disk

10 of 27

# address space

$ cat /proc/`pidof sh`/maps

08048000-080ef000 r-xp /bin/sh

080ef000-080f1000 rwxp /bin/sh

080f1000-080f3000 rwxp

089a2000-089c4000 rwxp [heap]

f778a000-f778b000 rwxp /lib/x86_64-linux-gnu/libc-2.23.so

f887e000-f889c000 r-xp [vdso]

ffe09000-ffe2a000 rwxp [stack]

^^ addresses ^^ ^^ perms ^^ what’s inside

.text

.rodata

.data

.bss

~~ heap ~~

~~ stack ~~

mmap’d area (libs)

11 of 27

# quick history of sploit’n bins

> 1995 - Mudge, “How to write buffer overflows” > 1996 - Aleph One, “Smashing the stack for fun and profit”� < We’ll stop executing things you can write to!� > 1997 - Solar Designer, “Getting around non-executable stack”> 2001 - Nergal, “Advanced return-into-lib(c) exploits”� < We’ll make it so you don’t know where things are!� > 2002 - Tyler Durden, “Bypassing PaX ASLR protection”> 2005 - Sebastian Krahmer, “Borrowed code chunks technique”� < We’ll... umm, alright I don’t really know.

(@ceyxiest)

12 of 27

# good old exploitation

  • traditional approach
    • leverage memory corruption to control process execution
    • direct execution control into shellcode
  • this isn’t the 2000’s anymore
  • hardware enforced mitigations�

> reason things suck:

  • ASLR - randomises memory locations of various mappings
  • NX - prevents execution of mappings that are writable
  • PIE - randomises the mapping of the TEXT section

13 of 27

# so what now?

  • so we have a limited number of regions where we can actually execute code
  • because of ASLR, we don’t generally know where our shellcode is in the HEAP/STACK
    • even if we did - we can’t execute there!
  • so where can we redirect execution? the TEXT section or mapped-in libraries!
    • generally LIBC

.text

.rodata

.data

.bss

~~ heap ~~

~~ stack ~~

mapped libraries

14 of 27

# but why?

  • so we can direct execution into the TEXT section or LIBC..
  • by why would we want to execute code we can run normally..?�

~~~ THE SOLUTION ~~~

  • borrow chunks of instructions
  • chain them together
  • shell

15 of 27

# chain what? chain where? chain who?

  • sounds great - but how do we chain chunks of existing code together?
  • let’s step back a bit…�

void func1(char * s) {char buf[80];strcpy(buf, s);}�

  • where should we return to?

16 of 27

# go-go-gadget RET!

  • the basis of ROP involves returning into chunks of code called gadgets�

Gadget: 0x00000000004008f5�0x00000000004008f5: sub esp, 8�0x00000000004008f8: add rsp, 8�0x00000000004008fc: ret

Gadget: 0x00000000004008e1�0x00000000004008e1: pop rsi�0x00000000004008e2: pop r15�0x00000000004008e4: ret

  • gadget - a short sequence of instructions, followed by a ret/jmp/call

17 of 27

# go-go-gadget RET!

0x400235:

syscall

0x4009e5:

pop rdi

ret

0x40073f:

mov al, 0x3b

xor rsi, rsi

ret

0x4003f9:

xor rax, rax

xor rdx, rdx

ret

pop rdi

xor rax, rax

xor rdx, rdx

mov al, 0x3b

xor rsi, rsi

syscall

RET addr ->

0x400235

WHO CARES

WHO CARES

...

AAAAAAAAAAAAAAAA

buf ->

saved RBP ->

AAAAAAAAAAAAAAAA

0x4009e5

0xb0010f

0x4003f9

0x40073f

0xb0010f:

“/bin/sh”

18 of 27

# ideal chain

  • say you leak the libc addr for system():
    • 0x7fcc67a1d5e9a
  • and you have the address for the string “/bin/sh”
    • 0x7fcc93f1e7d1f
  • and a pop rdi gadget
    • 0x4009e5
  • provide system() RET address
    • exit() for clean exit

RET addr ->

JUNK / exit()

...

AAAAAAAAAAAAAAAA

buf ->

saved RBP ->

AAAAAAAAAAAAAAAA

0x4009e5

0x7fcc67a1d5e9a

WHO CARES

...

/bin/sh ptr ->

system() addr ->

0x7fcc93f1e7d1f

system() RET addr ->

19 of 27

# auto-win

void MLG_360_noscope() in libc (64 bit):

20 of 27

# got a leak?

  • got a libc leak?
  • want to know the addr of other symbols? (system?)
  • want to know what libc your binary is using?
  • easy to obtain offsets of important libc funcs!

21 of 27

# okay so ROP is neat

  • designed to bypass DEP/NX
  • chaining instructions from code/libraries for arb. execution
  • limited overflow on the stack? but write access elsewhere? stack pivot! (sub rsp, 0xf8)
  • because x86 uses multi-byte instructions (CISC <3) we can pull gadgets out of the middle of instructions
  • need a NOP? then ROPNOP (jmp rsp / ret)
  • to take advantage of libs (libc?) need an info leak because of ASLR. same to defeat PIE
  • libc is turing complete!

22 of 27

# tooling

  • find gadgets
  • automatically generate chains
  • bad bytes

> notable:

  • ropper
  • ROPgadget
  • pwntools

23 of 27

# click clack auto ROP goodness

  • so angr can do SAT solving, but what else?
  • (pwntools can do a similar job)

In [1]: import angrIn [2]: import angropIn [3]: p = angr.Project("./bin")In [4]: rop = p.analyses.ROP()�In [5]: rop.find_gadgets()�In [6]: chain = rop.set_regs(rdi=0x1337)�In [7]: chain.print_payload_code()�chain = ""�chain += p64(0x3973ca3) # pop rdi; ret �chain += p64(0x1337)

rop.set_regs(rax=0x1337, rbx=0x56565656)

rop.write_to_mem(0x61b100, "/bin/sh\0")

rop.func_call("read", [0, 0x804f000, 0x100])

rop.add_to_mem(0x804f124, 0x41414141)

24 of 27

# other types of rop?

25 of 27

# more practice

  • checkout rop emporium
  • a lot of good challenges
  • tests stack pivot, bad bytes, 32bit vs 64bit
  • great challenge explanations
  • https://ropemporium.com

  • aeg from http://pwnable.kr
  • dynamically create ROP chains for new binaries every 10 seconds
  • ~little~ bit of SAT solving but that’s fun to learn

26 of 27

# references

27 of 27

# thanks & hack responsibly