P'Hack - Reverse

Tendu comme un slip - 64pts

Vous vous souvenez de la clé USB d'il y a quelques semaines ?
On a oublié de vous dire qu'il y avait ce fichier aussi.
On doit sûrement pouvoir en tirer quelque chose..

https://blog.julienmialon.com/uploads/ctf/phack/tendu

Un simple appel à strings nous permet ici de récupérer le flag

$ strings tendu
PHACK{s1mplY_5tr1ng5_i7}

No strings - 128pts

Encore un drôle de fichier.
Votre expertise nous est très utile.

https://blog.julienmialon.com/uploads/ctf/phack/no-strings

Bon ici, on lance notre décompilateur favori et on va voir ce qu'il se passe, on récupère une fonction main qui malheureusement ne fait pas grand chose

int __cdecl main(int argc, const char **argv, const char **envp)
{
  char s[56]; // [rsp+0h] [rbp-40h] BYREF
  unsigned __int64 v5; // [rsp+38h] [rbp-8h]

  v5 = __readfsqword(0x28u);
  printf("> ");
  fgets(s, 50, _bss_start);
  puts(&::s);
  return 0;
}

Quoiqu'il arrive, elle affichera toujours la même chose. En parcourant la liste des fonctions disponible, on tombe sur display_flag, ça semble prometteur. Pour le coup le code paraît beaucoup plus intéressant :

unsigned __int64 display_flag()
{
  char src; // [rsp+2h] [rbp-6Eh] BYREF
  char v2; // [rsp+3h] [rbp-6Dh] BYREF
  // [...]
  unsigned __int64 v38; // [rsp+68h] [rbp-8h]

  v38 = __readfsqword(0x28u);
  v31 = "i_t33Am4_rdRfii";
  v32 = "Ce7d_K_hH0{}nP5";
  strcpy(dest, "Well done! The is ");
  v34 = 0LL;
  v35 = 0LL;
  v36 = 0LL;
  v37 = 0;
  src = aCe7dKHh0Np5[13];
  strncat(dest, &src, 1uLL);
  v2 = v32[8];
  strncat(dest, &v2, 1uLL);
  v3 = v31[5];
  strncat(dest, &v3, 1uLL);
  v4 = *v32;
  // [...]
  strncat(dest, &v29, 1uLL);
  v30 = v32[11];
  strncat(dest, &v30, 1uLL);
  puts(dest);
  return v38 - __readfsqword(0x28u);
}

Il semble que le flag soit créé caractère par caractère dans cette méthode. Je me doute qu'il est tout à fait possible de copier/coller le code et de l'exécuter et il y a des chances qu'on récupère le flag sans problème. Néanmoins, puisque la méthode se donne la peine de faire un puts une fois que le flag est construit, on va tenter une méthode plus simple en lançant l'exécutable avec gdb.

$ gdb no-strings
(gdb) br main # on set un breakpoint au démarrage de la fonction main
Breakpoint 1 at 0x1623
(gdb) run
Starting program: no-strings 
Breakpoint 1, 0x0000555555555623 in main ()
(gdb) call display_flag()
Well done! The is PHACK{r34d_it_fR0m_th3_in5ide}
$1 = 0

Et voilà on obtient le flag très simplement. (On notera d'ailleurs le mot manquant entre The et is ;-)

PHACK{r34d_it_fR0m_th3_in5ide}

Military Grade Password - 512pts

Fraichement arrivé dans l'armée, vous avez déjà perdu votre identifiant pour accéder à l'intranet.

Dépêchez-vous de le retrouver avant que quelqu'un ne s'en aperçoive ! Le flag est attendu au format PHACK{identifiant}

https://blog.julienmialon.com/uploads/ctf/phack/military_grade_password.zip

Idem que le précédent, on commence par charger le fichier dans le décompilateur, on récupère la fonction main sans trop de problème.

__int64 __fastcall main(int a1, char **a2, char **a3)
{
  char input[104]; // [rsp+0h] [rbp+0h] BYREF
  unsigned __int64 vars68; // [rsp+68h] [rbp+68h]

  vars68 = __readfsqword(0x28u);
  puts("55th Infantry Division identify checker");
  printf("Personal access ID: ");
  __isoc99_scanf("%s", input);
  if ( sub_1530(input) )
    puts("[+] ACCESS GRANTED!");
  else
    puts("[+] ACCES REJECTED!");
  return 0LL;
}

Contrairement au précédent, on voit cette fois ci que l'input sert à quelque chose puisqu'elle est passée à la méthode sub_1530 pour être vérifié, on va de ce pas voir cette méthode

_BOOL8 __fastcall sub_1530(char *a1)
{
  int v1; // esi
  int v2; // er9
  int v3; // er8
  int v4; // ecx
  int v5; // edx
  _BOOL4 v6; // er10
  int v8; // er12
  int v9; // er14
  int v10; // er15
  int v11; // ebp
  int v12; // [rsp+0h] [rbp-5Ch]
  int v13; // [rsp+8h] [rbp-54h]
  int v14; // [rsp+Ch] [rbp-50h]
  int v15; // [rsp+10h] [rbp-4Ch]
  int v16; // [rsp+14h] [rbp-48h]
  int v17; // [rsp+18h] [rbp-44h]
  char v18; // [rsp+1Ch] [rbp-40h]
  int v19; // [rsp+20h] [rbp-3Ch]
  int v20; // [rsp+24h] [rbp-38h]
  int v21; // [rsp+28h] [rbp-34h]

  v1 = a1[6];
  v2 = a1[14];
  v3 = a1[12];
  v4 = a1[10];
  v5 = a1[13];
  v6 = 0;
  if ( v2 * v1 * (v5 ^ (v3 - v4)) == 16335 )
  {
    v8 = a1[7];
    v9 = a1[18];
    v12 = a1[15];
    v18 = a1[1];
    if ( ((char)(a1[18] ^ v18) ^ (v12 - v8)) == 83 )
    {
      v14 = a1[17];
      v13 = a1[16];
      v15 = a1[5];
      v16 = a1[9];
      v17 = *a1;
      if ( (v14 - v13) * (v17 ^ (v16 + v15)) == -5902 )
      {
        v19 = a1[3];
        v10 = a1[11];
        if ( v19 - v10 == 11 )
        {
          v11 = a1[4];
          v21 = a1[2];
          v20 = a1[8];
          if ( (v20 ^ (v11 + v21)) == 3
            && v20 + v12 - v11 == 176
            && (v1 ^ ((char)(v4 ^ a1[9]) - v9 - v10)) == -199
            && v21 * v13 + v18 * (char)(*a1 ^ a1[17]) == 9985
            && v5 * v2 - v8 == 2083
            && v3 + v19 - v15 == 110
            && v5 + v16 + v4 * v20 == 5630
            && v15 - v13 - v17 - v21 == -182
            && v14 * (char)(v2 ^ a1[7]) == 7200
            && v18 * v19 + v10 * v1 == 17872
            && v3 - v12 - v11 * v9 == -5408
            && v19 * v12 + v21 * v10 == 18888
            && v13 * (v15 + v5) == 15049
            && v14 * (v4 + v17) == 12150
            && (char)(v2 ^ v1) * v9 == 10080
            && v8 + v3 - v11 == 132 )
          {
            v6 = v16 * v18 + v20 == 2453;
          }
        }
      }
    }
  }
  return v6;
}

On est dans le cas typique d'un système d'équation où la string qu'on fourni en entrée doit valider les différentes conditions pour être valide. On renomme toutes les variables pour qu'elles aient un nom qui correspond à l'indice dans le tableau en entrée et on extrait les différentes conditions pour obtenir

id3 - id11 == 11
id12 + id3 - id5 == 110
id7 + id12 - id4 == 132
id8 + id15 - id4 == 176
(id8 ^ (id4 + id2)) == 3
id3 * id15 + id2 * id11 == 18888
id5 - id16 - id0 - id2 == -182
id16 * (id5 + id13) == 15049
id12 - id15 - id4 * id18 == -5408

((id18 ^ id1) ^ (id15 - id7)) == 83
id1 * id3 + id11 * id6 == 17872
id9 * id1 + id8 == 2453
id13 * id14 - id7 == 2083
id13 + id9 + id10 * id8 == 5630
id17 * (id10 + id0) == 12150

id14 * id6 * (id13 ^ (id12 - id10)) == 16335
(id17 - id16) * (id0 ^ (id9 + id5)) == -5902
(id6 ^ ((char)(id10 ^ id9) - id18 - id11)) == -199
id17 * (char)(id14 ^ id7) == 7200
id2 * id16 + id1 * (char)(id0 ^ id17) == 9985
(id14 ^ id6) * id18 == 10080

Une fois ces equations disponible, ça n'a pas l'air si compliqué, un petit bout de programme C# plus tard on a un code fonctionnel pour récupérer le flag

char[] id = new char[19];
for (var i = 0; i < id.Length; i++)
{
    id[i] = '¤';
}
for (int c11 = MIN ; c11 < MAX; c11++) // id3 - id11 == 11                  => id3 = id11 + 11
{
    int c3 = c11 + 11;
    int expected12_5 = 110 - c3;
    for (int c5 = MIN; c5 < MAX; c5++) // id12 + id3 - id5 == 110           => id12 - id5 = 110 - id3
    {
        int c12 = c5 + expected12_5;
        int expected7_4 = 132 - c12;
        for (int c4 = MIN; c4 < MAX; c4++) // id7 + id12 - id4 == 132           => id7 - id4 = 132 - id12
        {
            int c7 = c4 + expected7_4;
            int expected8_15 = 176 + c4;
            for (int c8 = MIN; c8 < MAX; c8++) // id8 + id15 - id4 == 176           => id8 + id15 = 176 + id4
            {
                int c15 = expected8_15 - c8;
                int c2 = (c8 ^ 3) - c4; // (id8 ^ (id4 + id2)) == 3          => id4 + id2 = 3 ^ id8
                if (c2 < MIN || c2 > MAX)
                {
                    continue;
                }

                if (c3 * c15 + c2 * c11 != 18888)
                {
                    continue;
                }

                int expected16_0 = 182 + c5 - c2;
                for (int c16 = MIN; c16 < MAX; ++c16)
                {
                    int c0 = expected16_0 - c16;
                    int c13 = 15049 / c16 - c5;
                    int c18 = (5408 + c12 - c15) / c4;
                    int c1 = (83 ^ (c15 - c7)) ^ c18;
                    if (c0 < MIN || c0 > MAX) continue;
                    if (c1 < MIN || c1 > MAX) continue;
                    if (c13 < MIN || c13 > MAX) continue;
                    if (c18 < MIN || c18 > MAX) continue;

                    int c6 = (17872 - c1 * c3) / c11;
                    if (c6 < MIN || c6 > MAX) continue;
                    int c9 = (2453 - c8) / c1;
                    if (c9 < MIN || c9 > MAX) continue;
                    int c14 = (2083 + c7) / c13;
                    if (c14 < MIN || c14 > MAX) continue;
                    int c10 = (5630 - c13 - c9) / c8;
                    if (c10 < MIN || c10 > MAX) continue;
                    int c17 = 12150 / (c10 + c0);
                    if (c17 < MIN || c17 > MAX) continue;

                    if ((c14 * c6 * (c13 ^ (c12 - c10)) == 16335 &&
                        (c17 - c16) * (c0 ^ (c9 + c5)) == -5902 &&
                        (c6 ^ ((c10 ^ c9) - c18 - c11)) == -199 &&
                        c17 * (c14 ^ c7) == 7200 &&
                        c2 * c16 + c1 * (c0 ^ c17) == 9985 &&
                        (c14 ^ c6) * c18 == 10080
                        ))
                    {
                        id[0] = (char) c0; id[1] = (char) c1; // [...]
                        Console.WriteLine(new string(id));
                    }
                }
            }
        }
    }
}

En quelques secondes on récupère la réponse à savoir q4Eo-eyMq-1dd0-leKx il ne nous reste plus qu'à soumettre notre flag

PHACK{q4Eo-eyMq-1dd0-leKx}

Un autre write-up fait par SoEasY pour ce challenge, qui notamment aborde l'utilisation de Z3 (sur lequel il faut vraiment que je me mette à regarder à quoi ça ressemble) et une seconde méthode plus originale à base de angr.

P’Hack CTF – Reverse Engineering – SoEasY
Afficher les commentaires