For the complete documentation index, see llms.txt. This page is also available as Markdown.

Challenge example

Code example

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

void secret() {
  char *filename = "./.passwd";
  FILE *file = fopen(filename, "r");
  if (file == NULL) {
    printf("Error opening file.\n");
    return;
  }

  char line[256];
  while (fgets(line, sizeof(line), file)) {
    printf("%s", line);
  }

  fclose(file);
}

char *authenticate() {
  char password[16];
  printf("Enter password: ");
  scanf("%15s", password);

  if (strcmp(password, "secret") == 0) {
    return "admin";
  } else {
    return "guest";
  }
}

void menu(char *user_type) {
  int choice;
  char header[32] = "Hello World !";
  while (1) {
    printf("\n--- Menu ---\n");
    printf(header);
    printf("\n");
    printf("1. Change header\n");
    if (strcmp(user_type, "admin") == 0) {
      printf("2. Read secret\n");
    }
    printf("0. Exit\n");
    printf("Enter your choice: ");
    scanf("%1d", &choice);

    if (choice == 1) {
      // Change the header string
      printf("Enter new header: ");
      scanf("%30s", header);
    } else if (choice == 0) {
      // Exit
      break;
    } else if (choice == 2 && strcmp(user_type, "admin") == 0) {
      // Call the secret() function
      secret();
    } else {
      printf("Invalid choice. Try again.\n");
    }
  }
}


int main() {
  char *user_type = malloc(16);
  strcpy(user_type, authenticate());
  printf("Welcome, %s!\n", user_type);
  menu(user_type);
}

The objective is to rewrite the user_type value to "admin" in order to read the secret file

Exploitation

The first step is to identify the offset between the user input used to exploit the format string and the "user_type" variable into the stack.

Retrieving offset

Retrieving user input

Using the stack command, it's possible to retrieve the user input location on the stack :

Retrieving "user_type"

Using the find command it's possible to search a specific value into the memory :

Due to the malloc the value is stored into the heap. Here the location address is 0x804d1a0

Still Using the find command, it's possible to locate this pointer into the stack :

Offset calculation

Then ([pointer stack address] - [user_input stack address]) / 4 = offset

the Parameter field of the placeholder is used specify the number of the parameter to display. if this value is over than the amount of parameter, thus it's possible to read arbitrary value on the stack such as using multiple placeholder as explained here.

The possible offset values could be bruteforced by sending a format string with successively increasing offset values, such as "%1$s", "%2$s", "%3$s", and so on. If the output of the program is "guest" when the format string "%x$s" is sent, then the offset value x might be considered valid.

As for retrieving user_type this fuzzing function allow to retrieve the user_input into the stack :

Editing value

In order to validate the objective, it's possible to directly edit the "user_type" variable directly ans see the repercussion on the program execution !

When the process is continued, the "3. read secret" option apear into the menu :

In order to write into the heap, it's necessary to know the address. To do that it's possible to read the pointer into the heap at the offsets previously obtained.

As explained into the Placeholder page, the %n format string will be used to write data.

This format string writes the number of characters written so far into an integer pointer parameter.

So, 1768776801 characters must be sent to write 0x696d6461 ("admi" in little endian) at the target address and 230 characters must be sent to write 0x0000006e ("n" in little endian) to the target address + 4. This process is necessary to achieve the goal of writing the word 'admin' at the desired location.

⚠️ However, sending an input of 1768776801 characters is far too lengthy

It is necessary to divide this input into two and send 25697 to write 0x6461 ("ad" in little endian) to the target address and 26989 to write 0x696d ("mi") to the target address +2. This process helps to achieve the goal of writing the desired information at the specified location in a more efficient manner.

In this case, only 2 bytes are sent at a time, however an integer is coded on 4 bytes. Therefore, the use of a short (coded on 2 bytes) is necessary to accurately write 2 bytes. To do this, the %hn format string must be used.

There is the entire payload composition :

  • The first 4 bytes are the targeted address. The user input will be used as a pointer for the $n format string

  • The following bytes will be a placeholder with sufficient padding to write the correct number of bytes for the format string %n. To do this, the placeholder %Xd should be used where X is the amount of necessary bytes to write what is needed.

The number of padding bytes needed is equal to the value we want to write, minus 4. This is because the first 4 bytes of the input are used to specify the target address, so they are not included in the padding.

ex for "ad" :

Here is the function to generate all needed payloads to write the arbitrary value :

And here is the function that use the payloads :

✅ All packaged, the secret appear :

It's also possible to overwrite the value of the saved instruction pointer into the stack to jump directly into the "secret" function when closing the menu instead of return to the main function.

Exercice

If you want to try this exploit by yourself, you can pull this docker image :

Deploy the image using the followed command :

Access to the web shell with your browser at the address : http://localhost:3000/

Last updated