How do I even pwn anything? Part 7— Return-Oriented Programming with 64-bit
Explore how to perform Linux Binary Exploitation from Capture-the-Flag (CTF) competitions.
Course Link
You can find all the files required for the exercises on GitHub.
ROP With 64-bit Binaries
If you recall, 64-bit binaries store their parameters in registers instead of the stack.
To convert your script from 32-bit to 64-bit, all you have to do is find the gadget that pops the specific register. For example, if you wish to pass one argument, you would need to find the POP RDI; RET;
gadget.
Once again, you can do it with ropper in Pwndbg with ropper -- --search "pop rdi; ret;"
Notice that the layout between 32-bit and 64-bits differs. The gadget is placed first, followed by the argument and the function address.
Exercise
Return To Function — Chaining a bunch of gadgets to chain multiple functions together
You can find the binary and source code in at ret2func64. If you deployed the services locally, this exercise can be accessed using nc localhost 30005
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdbool.h>
bool win1 = false;
bool win2 = false;
void func1(int arg1) {
if (arg1 == 0xdeadbeef)
win1 = true;
}
void func2(int arg2) {
if (arg2 == 0xcafebabe)
win2 = true;
}
void win(char* secret) {
if (!(win1 && win2)) {
return;
}
if (!strncmp(secret, "magicman", 8))
system("/bin/sh");
}
void vuln() {
char buffer[64];
gets(buffer);
}
int main() {
setvbuf(stdout, NULL, _IONBF, 0);
setvbuf(stdin, NULL, _IONBF, 0);
puts("Guess my name");
vuln();
puts("Wrong!");
return 0;
}
Solution
If you didn’t manage to pwn it, do not worry, look at the solution and see if you can replicate it for yourself.
Similar to the previous part, all we have to do is just get the addresses of each function and the string of “magicman”. Remember to use the p64
function instead of the p32
(because it’s 64-bit instead of 32-bit).
However, Pwntools provides a shortcut to doing all these. We can use ROP
to generate our ROP chains automatically.
We can use rop.raw
to add in raw bytes (for example, our padding). Then use rop.call
to call the function name, and an array of arguments. The rop.dump()
function dumps the ROP chain in an easy to read manner. The rop.chain()
builds the actual ROP chain in bytes to send to the program.
As you can see, Pwntools does a lot of the magic for us.
Solution can be found here.
As you might have noticed, this doesn’t seem very useful. Could we call a function that does not exist in the binary, such as system()
and pass /bin/sh
as our argument?