wtorek, 7 stycznia 2025

PicoCTF - basic-file-exploit

W tym poście chciałbym opisać zadanie basic-file-exploit z picoCTF.


Do zadania dołączony jest również kod programu:

  1. #include <stdio.h>
  2. #include <stdlib.h>
  3. #include <stdbool.h>
  4. #include <string.h>
  5. #include <stdint.h>
  6. #include <ctype.h>
  7. #include <unistd.h>
  8. #include <sys/time.h>
  9. #include <sys/types.h>
  10.  
  11.  
  12. #define WAIT 60
  13.  
  14.  
  15. static const char* flag = "[REDACTED]";
  16.  
  17. static char data[10][100];
  18. static int input_lengths[10];
  19. static int inputs = 0;
  20.  
  21.  
  22.  
  23. int tgetinput(char *input, unsigned int l)
  24. {
  25.     fd_set          input_set;
  26.     struct timeval  timeout;
  27.     int             ready_for_reading = 0;
  28.     int             read_bytes = 0;
  29.    
  30.     if( l <= 0 )
  31.     {
  32.       printf("'l' for tgetinput must be greater than 0\n");
  33.       return -2;
  34.     }
  35.    
  36.    
  37.     /* Empty the FD Set */
  38.     FD_ZERO(&input_set );
  39.     /* Listen to the input descriptor */
  40.     FD_SET(STDIN_FILENO, &input_set);
  41.  
  42.     /* Waiting for some seconds */
  43.     timeout.tv_sec = WAIT;    // WAIT seconds
  44.     timeout.tv_usec = 0;    // 0 milliseconds
  45.  
  46.     /* Listening for input stream for any activity */
  47.     ready_for_reading = select(1, &input_set, NULL, NULL, &timeout);
  48.     /* Here, first parameter is number of FDs in the set,
  49.      * second is our FD set for reading,
  50.      * third is the FD set in which any write activity needs to updated,
  51.      * which is not required in this case.
  52.      * Fourth is timeout
  53.      */
  54.  
  55.     if (ready_for_reading == -1) {
  56.         /* Some error has occured in input */
  57.         printf("Unable to read your input\n");
  58.         return -1;
  59.     }
  60.  
  61.     if (ready_for_reading) {
  62.         read_bytes = read(0, input, l-1);
  63.         if(input[read_bytes-1]=='\n'){
  64.         --read_bytes;
  65.         input[read_bytes]='\0';
  66.         }
  67.         if(read_bytes==0){
  68.             printf("No data given.\n");
  69.             return -4;
  70.         } else {
  71.             return 0;
  72.         }
  73.     } else {
  74.         printf("Timed out waiting for user input. Press Ctrl-C to disconnect\n");
  75.         return -3;
  76.     }
  77.  
  78.     return 0;
  79. }
  80.  
  81.  
  82. static void data_write() {
  83.   char input[100];
  84.   char len[4];
  85.   long length;
  86.   int r;
  87.  
  88.   printf("Please enter your data:\n");
  89.   r = tgetinput(input, 100);
  90.   // Timeout on user input
  91.   if(r == -3)
  92.   {
  93.     printf("Goodbye!\n");
  94.     exit(0);
  95.   }
  96.  
  97.   while (true) {
  98.     printf("Please enter the length of your data:\n");
  99.     r = tgetinput(len, 4);
  100.     // Timeout on user input
  101.     if(r == -3)
  102.     {
  103.       printf("Goodbye!\n");
  104.       exit(0);
  105.     }
  106.  
  107.     if ((length = strtol(len, NULL, 10)) == 0) {
  108.       puts("Please put in a valid length");
  109.     } else {
  110.       break;
  111.     }
  112.   }
  113.  
  114.   if (inputs > 10) {
  115.     inputs = 0;
  116.   }
  117.  
  118.   strcpy(data[inputs], input);
  119.   input_lengths[inputs] = length;
  120.  
  121.   printf("Your entry number is: %d\n", inputs + 1);
  122.   inputs++;
  123. }
  124.  
  125.  
  126. static void data_read() {
  127.   char entry[4];
  128.   long entry_number;
  129.   char output[100];
  130.   int r;
  131.  
  132.   memset(output, '\0', 100);
  133.  
  134.   printf("Please enter the entry number of your data:\n");
  135.   r = tgetinput(entry, 4);
  136.   // Timeout on user input
  137.   if(r == -3)
  138.   {
  139.     printf("Goodbye!\n");
  140.     exit(0);
  141.   }
  142.  
  143.   if ((entry_number = strtol(entry, NULL, 10)) == 0) {
  144.     puts(flag);
  145.     fseek(stdin, 0, SEEK_END);
  146.     exit(0);
  147.   }
  148.  
  149.   entry_number--;
  150.   strncpy(output, data[entry_number], input_lengths[entry_number]);
  151.   puts(output);
  152. }
  153.  
  154.  
  155. int main(int argc, char** argv) {
  156.   char input[3] = {'\0'};
  157.   long command;
  158.   int r;
  159.  
  160.   puts("Hi, welcome to my echo chamber!");
  161.   puts("Type '1' to enter a phrase into our database");
  162.   puts("Type '2' to echo a phrase in our database");
  163.   puts("Type '3' to exit the program");
  164.  
  165.   while (true) {  
  166.     r = tgetinput(input, 3);
  167.     // Timeout on user input
  168.     if(r == -3)
  169.     {
  170.       printf("Goodbye!\n");
  171.       exit(0);
  172.     }
  173.    
  174.     if ((command = strtol(input, NULL, 10)) == 0) {
  175.       puts("Please put in a valid number");
  176.     } else if (command == 1) {
  177.       data_write();
  178.       puts("Write successful, would you like to do anything else?");
  179.     } else if (command == 2) {
  180.       if (inputs == 0) {
  181.         puts("No data yet");
  182.         continue;
  183.       }
  184.       data_read();
  185.       puts("Read successful, would you like to do anything else?");
  186.     } else if (command == 3) {
  187.       return 0;
  188.     } else {
  189.       puts("Please type either 1, 2 or 3");
  190.       puts("Maybe breaking boundaries elsewhere will be helpful");
  191.     }
  192.   }
  193.  
  194.   return 0;
  195. }

Po analizie programu, możemy sprawdzić w jaki sposób należy uzyskać flagę.

Flaga zapisana jest w zmiennej flag:

  1. static const char* flag = "[REDACTED]";

Jej wyświetlenie pojawia sie w funkcji data_read():

  1. if ((entry_number = strtol(entry, NULL, 10)) == 0) {
  2.     puts(flag);
  3.     fseek(stdin, 0, SEEK_END);
  4.     exit(0);
  5. }

Aby ją wyświetlić należy podać jako argument wartość 0. Wywołanie funkcji data_read następuje w głównej pętli programu. 

  1. else if (command == 2)
  2. {
  3.     if (inputs == 0) {
  4.         puts("No data yet");
  5.         continue;
  6.     }
  7.     data_read();
  8.     puts("Read successful, would you like to do anything else?");
  9. }

Przed jej wywołaniem należy keszce wprowadzić jakieś dane. Przebieg odczytu flagi jest następujący:


Na samym początku należy wprowadzić dane w opcji 1. Potem przy próbie odczytu, gdzie podawana jest ilość danych potrzebnych do odczytania, należy wprowadzić wartość 0. Spowoduje to wyświetlenie flagi, jak na screenie powyżej.