Project #2, Return-Oriented Programming

Sploit A Due: Wednesday, February 7th, 11:59 PM

Sploits B and C Due: Tuesday, February 13th, 11:59 PM


The goal of this assignment is to gain hands-on experience with exploitation of memory corruption bugs without code injection, in “code-reuse” or “return-oriented” style. You will be exploiting a vulnerable target program that is a simplified version of target1 from project 1, but this time there is no RWX page on which to place shellcode. Your goal is to write three progressively more sophisticated return-oriented payloads.

As in project 1, we will use a virtual 32-bit SPARC machine running NetBSD. You can reuse the VMs from project 1. You will want to refer to the SPARC references from project 1. In addition, you will want to read John McDonald’s 1999 post to the Bugtraq mailing list that introduced return-into-libc for SPARC, as well as the 2008 UCSD paper that showed how to perform (Turing-complete) ROP on SPARC.

You are given the source code for one exploitable program, target.c. Your goal is to write three exploit programs (sploita, sploitb.c, and sploitc), all exploiting the same target, but to different ends.

The Target

The download tarball includes a Makefile that specifies how to build the target.

The Makefile also provides a “make pipes” command that will create the named pipe /tmp/targetpipe. (Note that we have changed the name since project 1.) As before, you may need to rerun make pipes each time you restart the SPARC virtual machine.

You should not run the target directly. Instead, run it through the run-target wrapper. The run-target wrapper will ask for two arguments. The first is the number of file descriptors to allocate before executing the target. You should leave this at 0 for sploits A and B, and increase it only when you are ready to test sploit C. The second argument is the same four-digit ID you used for project 1. Make sure always to use the same, correct ID; this will ensure that addresses for stack variables remain consistent each time you examine your target, making your exploits reliable and repeatable. If you work with a partner, you may use either of your two four-digit IDs. (You don't have to work with the same partner for project 2 as you did for project 1, and even if you do, you don’t have to pick the same out of your two IDs. But you must work with the same partner and use the same ID for all three sploits in this project.)

Because the target in project 2 calls readcmd inside overflow, the project 1 strategy of attaching GDB to the target and setting a breakpoint at the beginning of overflow will not work. You will instead want to set the breakpoint in overflow after readcmd returns.

The Exploits

In addition to the sploita.c, sploitb.c, and sploitc.c starter code, you are given a sploit-util.c file that contains functions that will help you put together frames for a return-oriented payload. You should read and understand sploit-util.c, but you should not change it.

Call the configure_egg function first. Pass it a buffer you have allocated that is large enough for all the stack frames you will place. The buf_base argument is the address in the target’s memory where the egg will be placed. You can find the location of the buf local variable in overflow using GDB. The bytes_skip argument specifies how far into the egg the first frame should be placed.

The most important function is place_frame. This function will allocate 96 bytes for a stack frame in the egg. (There is also a place_frame_and_pad variant that will allocate a larger frame, though my own exploits never needed it.) In the 64 bytes of the new frame devoted to spilled registers, the place_frame will always set two: %i6, the frame pointer, which place_frame will set to the location where it would place the next frame; and %i7, the return address, which you must supply as the retaddr parameter. If you want other registers set, you can tell place_frame by supplying the register's name followed by the value you want it to have. When you are done, you supply the special name DONE without a value.

Here is a code skeleton showing how you might use the functions in sploit-util:

  char *egg;

  if (NULL == (egg = calloc(32768, 1)))
    error("Could not allocate egg buffer.\n");
  configure_egg(egg, BUF_BASE, BYTES_SKIP);

  place_frame(SEQ_RET_RESTORE, I5, 0xdecafbad,
                               L7, 0xdeadbeef,
  place_frame(SEQ_RET_RESTORE, DONE);

  writecmdbytes(PIPEPATH, egg, egg_bytes_used());

Exploit Behavior

Sploit A should cause the target to execve a shell on its own terminal, without making a network connection first. That is, you will want to induce behavior like:

  1. Write to memory location x the NUL-terminated string “/bin/sh”.
  2. Write to the 8 bytes at memory starting at location y first the four-byte address x and then the four-byte value zero.
  3. Put in register %o0 the value x, in register %o1 the value y, and in register %o2 either the value y+4 or the value 0.
  4. Put in register %g1 the value 59, for execve.
  5. Execute ta 0 to trap into the operating system.

Sploit B will make a network connection to localhost (IP address, port 12345), arrange for process standard input, standard output, and standard error to be redirected to the resulting network connection, and then spawn a shell. It can assume that the return value from the socket system call, which returns a file descriptor for the new socket, is always 3 (the first free descriptor after stdin, stdout, stderr aka 0, 1, 2), and hardcode this value later on in the payload. You will want to induce behavior like:

  1. Put in register %o0 the value 2 (AF_INET). Put in register %o1 the value 1 (SOCK_STREAM). Put in register %o2 the value 0.
  2. Put in register %g1 the value 394, for __socket30.
  3. Execute ta 0 to trap into the operating system.
  4. After the system call, the file descriptor of the newly allocated socket will be in the %o0 register, but you may assume for the purposes of sploit B that this register holds the value 3.
  5. Let x be the address of 16 contiguous bytes in memory, to store a struct sockaddr_in. Byte 0 and bytes 8–15 of this structure do not matter, but the others do. Byte 1 should be 2 (AF_INET); bytes 2 and 3 should be the port number 12345, stored big endian (0x3039); bytes 4 through 7 should be the IP address, stored big endian (0x7f000001).
  6. Put in register %o0 the value 3, the hardcoded file descriptor we assume the socket system call returned. Put in register %o1 the value x. Put in register %o2 the value 16.
  7. Put in register %g1 the value 98, for connect.
  8. Execute ta 0 to trap into the operating system.
  9. Next we call dup2 three times, to duplicate the socket descriptor onto each of descriptors 0, 1, and 2. Put in register %g1 the value 90, for dup2.
  10. Put in register %o0 the value 3, the hardcoded file descriptor we assume the socket system call returned. Put in register %o1 the value 0. Execute ta 0 to trap into the operating system.
  11. Put in register %o0 the value 3, the hardcoded file descriptor we assume the socket system call returned. Put in register %o1 the value 1. Execute ta 0 to trap into the operating system.
  12. Put in register %o0 the value 3, the hardcoded file descriptor we assume the socket system call returned. Put in register %o1 the value 2. Execute ta 0 to trap into the operating system.
  13. Finally spawn a shell using the same behavior as in sploit A above.

Sploit C will behave the same as sploit B, but it will not make the assumption that socket() returns descriptor 3. Instead, having made the socket() system call, before returning, it stores the value in %o0 somewhere convenient in memory. Then it loads that value back from memory into the %o0 register before the connect() system call and before each of the dup2() system calls.

Useful Instruction Sequences

For your return-oriented payloads, you will want to use instruction sequences in libc, the standard C library. You can get a dump of all the instructions in libc using the command

  objdump --disassemble --prefix-addresses --show-raw-insn /lib/

Warning: this will produce 250,000 lines and 16 MBs of output. Addresses in the leftmost column are offsets into libc. You will want to add the base address of libc to get an in-memory address. For the target, libc should be loaded at 0xede00000.

This dump reveals plenty of useful sequences ending in ret and restore. For example, here is a sequence that moves 4 bytes in memory (and incidentally allows you to set %g1):

00127044 <__bt_ret+0x164> c2 07 40 00 	ld  [ %i5 ], %g1
00127048 <__bt_ret+0x168> c2 27 00 00 	st  %g1, [ %i4 ]
0012704c <__bt_ret+0x16c> 81 c7 e0 08 	ret 
00127050 <__bt_ret+0x170> 81 e8 00 00 	restore 

Here is another that lets you move 8 bytes (and set both %g1 and %g2):

0007ad0c <_adjtime+0x50> c4 07 60 04 	ld  [ %i5 + 4 ], %g2
0007ad10 <_adjtime+0x54> c2 07 60 08 	ld  [ %i5 + 8 ], %g1
0007ad14 <_adjtime+0x58> c4 26 40 00 	st  %g2, [ %i1 ]
0007ad18 <_adjtime+0x5c> c2 26 60 04 	st  %g1, [ %i1 + 4 ]
0007ad1c <_adjtime+0x60> 81 c7 e0 08 	ret 
0007ad20 <_adjtime+0x64> 81 e8 00 00 	restore 

We can read from memory into a register like %i0:

0007724c <_difftime+0x9c8> f0 02 00 00 	ld  [ %o0 ], %i0
00077250 <_difftime+0x9cc> 81 c7 e0 08 	ret 
00077254 <_difftime+0x9d0> 81 e8 00 00 	restore 

We can store a register like %o0 into memory:

00070568 <__udivmodsi4+0x24> d0 26 80 00 	st  %o0, [ %i2 ]
0007056c <__udivmodsi4+0x28> 81 c7 e0 08 	ret 
00070570 <__udivmodsi4+0x2c> 81 e8 00 00 	restore 

And there is lots more, as a little grepping will show. (For this project, we ask that you use objdump and grep if you need to, but not ROP-specific tools like ROPgadget.)

System Calls

The convention that NetBSD uses for system calls on SPARC is as follows. The caller places the call index in %g1 and the arguments in %o0, %o1, %o2, and so on. The caller then executes a ta 0 instruction to trap into the kernel. The kernel places the return value in %o0 and zeroes out the other o-registers.

The UCSD SPARC ROP paper would lead us to expect to find sequences in libc that look like “ta 8; st %o0, [ location ]; ret; restore”, but there are no such sequences in the NetBSD libc. Instead, we see system call wrappers that look like

00112be8  82 10 2c 14 	mov  0xc14, %g1
00112bec  8a 03 e0 08 	add  %o7, 8, %g5
00112bf0  91 d0 20 00 	ta  0

What’s going on? NetBSD is taking advantage of an optimization called SYSCALL_G5RFLAG to make system calls more efficient. If system call index in %g1 (20 aka 0x14 in the case of getpid) is xored with the flag 0xc00, then on successful completion the system call transfers control not to the instruction immediately following the ta 0 instruction but to the instruction whose address is in %g5.

If we could set %g5 to point to something useful before returning directly to the ta 0 instruction, then we could make system calls, provided we also set the 0xc00 flag in %g1. Unfortunately, %g5 is an ABI-reserved register and there aren’s any convenient instruction sequences that set it to arbitrary values. (We can’s use the add %o7, 8, %g5 instruction in return-oriented style, because %o7 will contain the return address from the previous frame, i.e., the address of the currently executing instruction sequence.)

Luckily for us, the same NetBSD code that handles SYSCALL_G5RFLAG also supports two other legacy conventions that use the %g7 and %g2 registers, respectively. Specifically, if we xor a system call index in %g1 with 0x400, then on successful completion the kernel will transfer control to the instruction whose address is in %g2, a register that we can easily control, and this allows us to chain a system call with other frames in a return-oriented payload.


This project is based on Project 2 from Stephen Checkoway’s CS 487, Secure Computer Systems.

Navigation: CSE // CSE 127 // Project 2