[io64][smashthestack] level7: ROP me baby, ROP me all night long

In this post, I’ll be covering the resolution of smash the stack’s io64 level7.

First you need to login to the server:

As usual, the binary has the suid bit set, and the source code is provided:

The code is quite straightforward, there’s a buffer orverflow opportunity,Β  let’s look at the protections:

NX and ASLR are enabled on almost everything, this is going to be interesting πŸ™‚

The idea, is to use a ROP chain in order to be able to end-up calling execve and spawn a shell.

The .text section is not relocatable, addresses are predictable, so we might get a few gadgets from it, we’ll see that later.

First, we need to make sure we’ll be able to execute a sycall instruction, let’s check on the .text section first:

Nothing, but not really surprising.

Let’s look at the vsyscall area which isn’t radomised as well:

As you can see this is good news for us.

As you may already know, the calling convention for X86_64 syscalls is as follows:

rax is used for the syscall number, then, rdi, rsi, rdx, r10, r8 and r9 are used for the parameters.

We have two options in order to have access to the password:

  • launch a shell, with execve
  • open the file, read the file, write its content to stdout

All of them require rax, rdi, rsi and rdx.

So as execve may seem the best solution, let’s see if we can find some gadgets to set all those registers to the proper value.

Modifying RAX:

first, let’s try to find a way to properly set rax:

Unfortunately radare can’t find any gadget which could set rax to something we control, or set it to the values we are looking for (read: 0, write: 1, open 2, execve: 59)

Let’s try configuring radare with bigger rop length:

One new gadget, and it seems to be some code from main (0x00400514), but it ends up calling read which in turn will set rax to 0.

BUT ! read returns the number of bytes read πŸ™‚

As you know, return values are stored in rax, so if we control the amount of bytes provided to read, we’ll be able to set rax to whatever value we’d need πŸ™‚

let’s look more closely to that code:

The call to read is immediately followed by a leave and a ret instruction, this is a good thing for us, rax won’t be modified between the read and out next gadget.

only three registers to go !

Modifying RDI:

I won’t bother you with long radare outputs this time πŸ™‚

The only gadget I found was a gadget modifying edi:

This should be ok as long as the last value stored by RDI is a 32 bits value, otherwise this gadget could be useless.

let’s look at the registers values after calling read:

Great ! rdi is null, so this gadget seems perfect


Modifying RSI:

Same thing here, the only gadget I found was a gadget modifying esi:

If you look at the previous register dump you’ll see rsi is storing a 64bits value (the input buffer).

Fortunately, if you look at the assembly code, you’ll notice that this value is takenΒ  as an offset in rbp, so if we manage to change rbp we’ll be good to go.

rbp being quite often used in epilogues, there are very good chances we can find a pop rbp followed by a ret, let’s confirm this:

Nice, but expected πŸ™‚

Modifying RDX:

Unfortunately all the gadgets I found would have required some convoluted long ropchains, we don’t want that.

Looking back at the registers dump it seems RDX will always have the 0x200 value, so execve is simply not possible, but read/write will be ok, this just mean we’ll only be able to read/write up to 0x200 bytes per call.

Regarding the open syscall, RDX is the mode, and this will be ignored if O_CREAT is not set; as we don’t plan to create any file, this will be fine.

now we have all the gadgets we need to perform whatever syscall we want to (among open, read and write)

Getting our hands dirty:

Pivoting the stack:

Now if you look back at the disassembly and the registers, you’ll notice our data will be stored in the stack. If we load the filename onto the stack, we won’t be able to access it as the stack address is not predictable.

So we need to figure out a way to move the stack to a predictable address.

In this particular usecase, this is something seasily manageabl.

As we are able to modify rbp, we should be able to move the stack to an address we chose thanks to the leave instruction at the end of the main function. As a reminder leave does exactly the same as:

So what we could do is load a first stage ropchain which will pop a chosen value of rbp and then just call read back in order to load a second stage ropchain in a controlable address.

But which address will you ask ?

Easy ! In the mapping where all global variables, got, etc are store !

we just need to make sure we won’t override the got as we need it to call read:

starting at 0x600900 should be fine.

Let’s do a first test with python:

the raw_input calls are just here to allow us attach to the binary with radare:

We’ve successfully pivoted our stack πŸ™‚

Now let’s start the real work, let’s open that passowrd file.

Opening the password file:

In order to open that file, we’ll first need to load the path of that file in a known address.

We can replace the B’s in the previous code with the path.

the path being longer than 16 bytes we’ll be overwriting rbp, but that’s not a problem as we have a gadget in order to update it:

At this point, don’t take attention on the last read gadget, we’ll come to that very soon.

All you need to understand is that after the execution of leave (at the end of the ret_read gadget) rsp will point to the gadget after it. (see my inlined comments)

Let’s debug that:

great ! we can now prepare our open syscall πŸ™‚

A little reminder, we need to set the following registers:

  • rax to 2 (open)
  • rdi to the buffer address (new_stack)
  • rsi to 0 (O_RDONLY)
  • rdx can be kept unchanged (0x200) as it will be ignored
let’s begin with rax:

in order to incread rax, we just need to read 2 bytes of some random data, as you can see in the previous python code, I’ve already prepared a call to read.

All I need to do then, print 2 chars to the process stdin:

You’ll notice I only provided 1 char, this is because print will send a \x0a as well.

Let’s debug that:


Now RDI (the path):

First a little reminder of the gadget used:

This gadget is taking edi’s value from an offset of 0x30 in the stack, and taking the stack right after this value, so we’ll need to add some padding before storing our value (remember the path is at the begining of our new_stack)

let’s check everything works fine:

Now rdi (the flags):

Gadget reminder:

same thing, we’ll need some padding:

let’s check everything works fine:

isn’t that wonderful ? πŸ˜€

syscall time !

In case, like me you are running the tests on you own machine, on a recent kernel, calling syscall instructions from vsyscall will provably fail.

in order to overcome this, I wrote a small script in order to patch rip when you reach the vsyscall syscall instruction:

to perform the syscall we just need to append our syscall gadget:

let’s check everything works fine:

as you can see, here we caught a segfault, but the patch worked as rip is at a randomised libc address.

printing rax shows that we correctly opened the file πŸ™‚

I will spare you the description of all steps as it is just repeating what we just done for read and for write.

The only diverging point will be when calling read.

As read is already available, there is no need to perform a first read to set rax. The read function does that already.

Without further delay, here’s the end result:


OMG, did I just bypass NX + ASLR ? πŸ˜€

Leave a Reply

Your email address will not be published. Required fields are marked *