format string 0 — PicoCTF Writeup

Eric H
4 min readAug 26, 2024

--

Day 29

Hey everyone! Today, we’re diving into another Binary Exploitation challenge. These challenges can be tough, especially since I don’t work with code as often as I should. But let’s tackle it together! The challenge brief was simple:

Can you use your knowledge of format strings to make the customers happy?

We’re given two files: a source code file and a binary. I started by analyzing the source code, which is shown below:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <signal.h>
#include <unistd.h>
#include <sys/types.h>

#define BUFSIZE 32
#define FLAGSIZE 64

char flag[FLAGSIZE];

void sigsegv_handler(int sig) {
printf("\n%s\n", flag);
fflush(stdout);
exit(1);
}

int on_menu(char *burger, char *menu[], int count) {
for (int i = 0; i < count; i++) {
if (strcmp(burger, menu[i]) == 0)
return 1;
}
return 0;
}

void serve_patrick();

void serve_bob();


int main(int argc, char **argv){
FILE *f = fopen("flag.txt", "r");
if (f == NULL) {
printf("%s %s", "Please create 'flag.txt' in this directory with your",
"own debugging flag.\n");
exit(0);
}

fgets(flag, FLAGSIZE, f);
signal(SIGSEGV, sigsegv_handler);

gid_t gid = getegid();
setresgid(gid, gid, gid);

serve_patrick();

return 0;
}

void serve_patrick() {
printf("%s %s\n%s\n%s %s\n%s",
"Welcome to our newly-opened burger place Pico 'n Patty!",
"Can you help the picky customers find their favorite burger?",
"Here comes the first customer Patrick who wants a giant bite.",
"Please choose from the following burgers:",
"Breakf@st_Burger, Gr%114d_Cheese, Bac0n_D3luxe",
"Enter your recommendation: ");
fflush(stdout);

char choice1[BUFSIZE];
scanf("%s", choice1);
char *menu1[3] = {"Breakf@st_Burger", "Gr%114d_Cheese", "Bac0n_D3luxe"};
if (!on_menu(choice1, menu1, 3)) {
printf("%s", "There is no such burger yet!\n");
fflush(stdout);
} else {
int count = printf(choice1);
if (count > 2 * BUFSIZE) {
serve_bob();
} else {
printf("%s\n%s\n",
"Patrick is still hungry!",
"Try to serve him something of larger size!");
fflush(stdout);
}
}
}

void serve_bob() {
printf("\n%s %s\n%s %s\n%s %s\n%s",
"Good job! Patrick is happy!",
"Now can you serve the second customer?",
"Sponge Bob wants something outrageous that would break the shop",
"(better be served quick before the shop owner kicks you out!)",
"Please choose from the following burgers:",
"Pe%to_Portobello, $outhwest_Burger, Cla%sic_Che%s%steak",
"Enter your recommendation: ");
fflush(stdout);

char choice2[BUFSIZE];
scanf("%s", choice2);
char *menu2[3] = {"Pe%to_Portobello", "$outhwest_Burger", "Cla%sic_Che%s%steak"};
if (!on_menu(choice2, menu2, 3)) {
printf("%s", "There is no such burger yet!\n");
fflush(stdout);
} else {
printf(choice2);
fflush(stdout);
}
}

At first glance, this code might seem overwhelming, so I won’t dive too deep into it. Instead, let’s jump straight into the practical part.

The program begins by asking for a menu selection:

After doing some research on format strings and revisiting the source code, I discovered that the option "Gr%114d_Cheese" is the only one that fulfills the specific conditions in the code. Let's explore why:

After doing some research on format strings and revisiting the source code, I discovered that the option "Gr%114d_Cheese" is the only one that fulfills the specific conditions in the code.

Why? well, let’s take a look into the code again:

int count = printf(choice1);
if (count > 2 * BUFSIZE) {
serve_bob();
} else {
printf("%s\n%s\n",
"Patrick is still hungry!",
"Try to serve him something of larger size!");
fflush(stdout);
}
  • The printf(choice1); line prints the user's choice.
  • int count = printf(choice1); captures the number of characters printed.
if (count > 2 * BUFSIZE) {
serve_bob();
}

The key condition is if (count > 2 * BUFSIZE), which checks if the printed characters exceed 64 bytes (since BUFSIZE is defined as 32 bytes).

Why Only “Gr%114d_Cheese” Works:

  • String Lengths: "Breakf@st_Burger" has 16 characters, "Gr%114d_Cheese" has 13 characters, and "Bac0n_D3luxe" also has 13 characters.
  • The Trick with %114d: %114d is a format specifier in "Gr%114d_Cheese". When printf encounters %114d, it expects to print a number with a width of 114 characters. Since no number is provided, it can print some garbage data, inflating the count to 114 characters.

This format specifier increases the output length significantly, making the count large enough to satisfy the condition if (count > 2 * BUFSIZE). This is why selecting "Gr%114d_Cheese" moves the program to the next stage where you serve Bob.

Moving to Bob’s Order:

Next, we encounter another menu, and this time, the third option "Cla%sic_Che%s%steak" reveals the flag. But why?

Why Only “Cla%sic_Che%s%steak” Works:

  • Format Specifiers: The string "Cla%sic_Che%s%steak" contains several % symbols, typically used by printf to insert specific data types.
  • %s expects a string argument.
  • %steak is not valid, but %s still tries to process it.
  • Undefined Behavior: When printf processes %s without a corresponding string argument, it can access arbitrary memory or cause the program to crash. This might be what fulfills the condition for SpongeBob's "outrageous" order.

The other choices, "Pe%to_Portobello" and "$outhwest_Burger", don't trigger the same vulnerability, making "Cla%sic_Che%s%steak" the only one that reveals the flag.

Thank you for reading today’s write-up! Binary exploitation can be confusing, especially for beginners, but it’s an excellent learning experience. Even though this was marked as “easy,” it’s clear how complex these challenges can get.

See you tomorrow!

Day 29/31

--

--

Eric H

Hello! I'm currently pursuing my bachelor's degree at Asia Pacific University which is based in Malaysia, I'm very enthusiastic about the cybersecurity field