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.