This is the second post in a series (first post here) describing ROPC. Programs accepted by the compiler are written in ROPL (Return Oriented Programming Language). ROP programs are usually used as stage 0 payloads. They compute addresses, change memory protections, call few OS APIs. For this reason, language expressing them doesn’t have to be complex.

Grammar for QooL (language accepted by the Q compiler [0]) is shown below.

(exp) ::= LoadMem (exp) (type) | BinOp (op) (exp) (exp) | Const (value) (type) (stmt) ::= StoreMem (exp) (exp) (type) | Assign (var) (exp) | CallExternal (func) (exp list) | Syscall

QooL is simple but sufficient for stage 0 functionality. ROPC has to support conditional jumps and local function invocations, so it’s more complex.

Sample program in ROPL computing first few elements of the Fibonacci sequence:

fun fib(n, out){ x = 0 y = 0 cmp n, 0 je copy cmp n, 1 je copy fib(n-1, @x) fib(n-2, @y) [out] = x+y jmp exit copy: [out] = n exit: } fun main(){ fmt = "%d

" i = 0 x = 0 print: fib(i, @x) !printf(fmt, x) i = i+1 cmp i, 11 jne print }

Sample above makes use of all of the features of ROPL, like:

functions,

function calls,

native function calls (like invoking printf),

local variables,

labels and conditional jumps,

address operator @,

dereference operator [],

arithmetic and comparison ops.

Design considerations

On “high levelness” scale of languages, we can put ROPL below C but above ASM. Implementation was simplified by few assumptions:

all variables are unsigned 32 bit numbers,

reading and writing from/to memory is restricted to 32 bit values (you can’t “natively” write a byte),

all variables are stored in memory (registers are used as temporary variables, they never store local vars permanently),

every function is treated as recursive, so local vars are always stored on the emulated stack instead of a constant place in memory.

Syntax

You can read the full grammar here. Most important features below.

Arithmetic operators:

addition,

subtraction,

multiplication,

division,

negation.

Bitwise operators:

and,

or,

xor,

not.

Comparison operators:

equality,

greater than,

lower than,

negations of the three above.

All operators should be treated as their unsigned C equivalents. Function parameters are always passed by value. Since ROPL functions never return anything (in the same sense as void functions in C), it’s necessary to use pointers to get results from procedures. “@” is the “address of” operator. Below is its usage example:

fun foo(x){ [x] = 1 } fun main(){ x = 0 foo(@x) #here x=1 }

foo gets a pointer to x. Dereference operator [] interprets x as a memory address to save a constant. On exit from foo x=1.

Invoking native functions

For ROP programs to print their results or influence the OS in interesting ways, they have to be able to call native OS functions. In ROPL native function calls are preceded by an exclamation mark:

fun main(){ x = 0 fmt = "%d

" !printf(fmt, x) }

Above example will compile only if printf is found in the import table of the target executable.

Strings

Strings in ROPL are syntactic sugar. During parsing, instruction “fmt = ‘%d

'” is going to be replaced with “fmt = [37, 100, 10, 0]” (values in the table are ASCII codes for “%d

”).

Fin

Last post will discuss the actual implementation of ROPL constructs like tables, jumps, recursive calls, emulated stack, etc. Stay tuned 🙂

0 – http://users.ece.cmu.edu/~ejschwar/papers/usenix11.pdf