Logo
PicoCTF: Writeups

PicoCTF: Writeups

February 11, 2025
3 min read
Table of Contents

This is a series of selected challenges from the picoCTF 2022 competition which are more catered towards beginner players. Through these writeups, I hope you can find takeaways to help you in your future CTFs!


Binary Exploitation

Category
Binary Exploitation (pwn)
Points
100
Files
program-redacted.c

The program provided allows you to write to a file and read what you wrote from it. Try playing around with it and see if you can break it! Connect to the program with netcat:
$ nc saturn.picoctf.net [PORT]

Warning: This is an instance-based challenge. Port info will be redacted alongside the last eight characters of the flag, as they are dynamic.

Let’s connect to the server using netcat to see what’s going on:

$ nc saturn.picoctf.net [PORT]
Hi, welcome to my echo chamber!
Type '1' to enter a phrase into our database
Type '2' to echo a phrase in our database
Type '3' to exit the program

Since this is the binary exploitation category, we’ll be looking for a vulnerability in the source code that allows us to either break or control the program at a lower level. Let’s view the attachment program-redacted.c:

program-redacted.c
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <string.h>
#include <stdint.h>
#include <ctype.h>
#include <unistd.h>
#include <sys/time.h>
#include <sys/types.h>
 
 
#define WAIT 60
 
 
static const char* flag = "[REDACTED]";
 
static char data[10][100];
static int input_lengths[10];
static int inputs = 0;
 
 
 
int tgetinput(char *input, unsigned int l)
{
    fd_set          input_set;
    struct timeval  timeout;
    int             ready_for_reading = 0;
    int             read_bytes = 0;
    
    if( l <= 0 )
    {
      printf("'l' for tgetinput must be greater than 0\n");
      return -2;
    }
    
    
    /* Empty the FD Set */
    FD_ZERO(&input_set );
    /* Listen to the input descriptor */
    FD_SET(STDIN_FILENO, &input_set);
 
    /* Waiting for some seconds */
    timeout.tv_sec = WAIT;    // WAIT seconds
    timeout.tv_usec = 0;    // 0 milliseconds
 
    /* Listening for input stream for any activity */
    ready_for_reading = select(1, &input_set, NULL, NULL, &timeout);
    /* Here, first parameter is number of FDs in the set, 
     * second is our FD set for reading,
     * third is the FD set in which any write activity needs to updated,
     * which is not required in this case. 
     * Fourth is timeout
     */
 
    if (ready_for_reading == -1) {
        /* Some error has occured in input */
        printf("Unable to read your input\n");
        return -1;
    } 
 
    if (ready_for_reading) {
        read_bytes = read(0, input, l-1);
        if(input[read_bytes-1]=='\n'){
        --read_bytes;
        input[read_bytes]='\0';
        }
        if(read_bytes==0){
            printf("No data given.\n");
            return -4;
        } else {
            return 0;
        }
    } else {
        printf("Timed out waiting for user input. Press Ctrl-C to disconnect\n");
        return -3;
    }
 
    return 0;
}
 
 
static void data_write() {
  char input[100];
  char len[4];
  long length;
  int r;
  
  printf("Please enter your data:\n");
  r = tgetinput(input, 100);
  // Timeout on user input
  if(r == -3)
  {
    printf("Goodbye!\n");
    exit(0);
  }
  
  while (true) {
    printf("Please enter the length of your data:\n");
    r = tgetinput(len, 4);
    // Timeout on user input
    if(r == -3)
    {
      printf("Goodbye!\n");
      exit(0);
    }
  
    if ((length = strtol(len, NULL, 10)) == 0) {
      puts("Please put in a valid length");
    } else {
      break;
    }
  }
 
  if (inputs > 10) {
    inputs = 0;
  }
 
  strcpy(data[inputs], input);
  input_lengths[inputs] = length;
 
  printf("Your entry number is: %d\n", inputs + 1);
  inputs++;
}
 
 
static void data_read() {
  char entry[4];
  long entry_number;
  char output[100];
  int r;
 
  memset(output, '\0', 100);
  
  printf("Please enter the entry number of your data:\n");
  r = tgetinput(entry, 4);
  // Timeout on user input
  if(r == -3)
  {
    printf("Goodbye!\n");
    exit(0);
  }
  
  if ((entry_number = strtol(entry, NULL, 10)) == 0) {
    puts(flag);
    fseek(stdin, 0, SEEK_END);
    exit(0);
  }
 
  entry_number--;
  strncpy(output, data[entry_number], input_lengths[entry_number]);
  puts(output);
}
 
 
int main(int argc, char** argv) {
  char input[3] = {'\0'};
  long command;
  int r;
 
  puts("Hi, welcome to my echo chamber!");
  puts("Type '1' to enter a phrase into our database");
  puts("Type '2' to echo a phrase in our database");
  puts("Type '3' to exit the program");
 
  while (true) {   
    r = tgetinput(input, 3);
    // Timeout on user input
    if(r == -3)
    {
      printf("Goodbye!\n");
      exit(0);
    }
    
    if ((command = strtol(input, NULL, 10)) == 0) {
      puts("Please put in a valid number");
    } else if (command == 1) {
      data_write();
      puts("Write successful, would you like to do anything else?");
    } else if (command == 2) {
      if (inputs == 0) {
        puts("No data yet");
        continue;
      }
      data_read();
      puts("Read successful, would you like to do anything else?");
    } else if (command == 3) {
      return 0;
    } else {
      puts("Please type either 1, 2 or 3");
      puts("Maybe breaking boundaries elsewhere will be helpful");
    }
  }
 
  return 0;
}
Inspecting provided source code

In the midst of this complex program, we need to figure out where the flag is, and how to trigger it to print:

program-redacted.c
static const char* flag = "[REDACTED]";
// [!code skip:16:142]
  if ((entry_number = strtol(entry, NULL, 10)) == 0) {
    puts(flag);
    fseek(stdin, 0, SEEK_END);
    exit(0);
  }
Inspecting provided source code

The flag is defined in line 13 as "[REDACTED]", which will be the actual location on the remote server. From lines 139-143 it looks like a condition needs to be met in order to puts() the flag, which writes a string to the output stream stdout.

Google defines strtol() as a function that “converts the initial part of the string in str to a long int value according to the given base”. To break it, we need to input something that is unconvertible into a long integer. In this case, it would be a string, as they can’t be properly coalesced into long integers!

This if statement is located within a function called data_read(). Let’s see where it’s called in the program:

program-redacted.c
    } else if (command == 2) {
      if (inputs == 0) {
        puts("No data yet");
        continue;
      }
      data_read();
      puts("Read successful, would you like to do anything else?");
Inspecting provided source code

After we write some data with the command 1, We should be pressing the command 2 to read from the stored data. Once it prompts us to “enter the entry number of your data”, we’ll send a string instead to break it. Let’s head back to the netcat and test it out:

$ nc saturn.picoctf.net 50366
Hi, welcome to my echo chamber!
Type '1' to enter a phrase into our database
Type '2' to echo a phrase in our database
Type '3' to exit the program
> 1
1
Please enter your data:
> hello
hello
Please enter the length of your data:
> 5
5
Your entry number is: 1
Write successful, would you like to do anything else?
> 2
2
Please enter the entry number of your data:
> "NO!"
"NO!"
picoCTF{M4K3_5UR3_70_CH3CK_Y0UR_1NPU75_[REDACTED]}