RustyTheClown | leHack 2022 | Catégorie reverse
RustyTheClown 2 | leHack CTF 2022
Fichier(s)
Nécessaires
Flag
lh_68eca3c515dbefd71ec8fec3849ba0083af806447d9f9f7cdca2a5cc
Solution détaillée
Analyse Statique
Le challenge est un crackme . On nous demande un mot de passe et celui est modifié puis comparé avant de nous donner ou non le flag .
Désassemblons le binaire dans IDA :
Dans void rustyTheClown::main::hecc4c87ee1b9aeab()
voici ce qu’on trouve d’important :
std::io::stdio::Stdin::read_line::hd0723957e63cf850();
if ( &unk_52408 )
core::ptr::drop_in_place$LT$std..io..error..Error$GT$::h815d6777c4f5f9e1(*((_QWORD *)&dest + 1));
v4 = 0LL;
v3 = 1uLL;
_$LT$core..iter..adapters..map..Map$LT$I$C$F$GT$$u20$as$u20$core..iter..traits..iterator..Iterator$GT$::fold::h77524fc9083f58e3(
1LL,
1LL,
&v3);
if ( v4 == 17
&& !(*(_QWORD *)v3 ^ 0x6F686F6F686F6F48LL | *(_QWORD *)(v3 + 8) ^ 0x216168616861686FLL | *(unsigned __int8 *)(v3 + 16) ^ 0xALL) )
{
dest = v3;
v10 = v4;
src[20] = 0u;
src[21] = 0u;
src[18] = 0u;
src[19] = 0u;
src[16] = 0u;
src[17] = 0u;
src[14] = 0u;
src[15] = 0u;
src[12] = 0u;
src[13] = 0u;
src[10] = 0u;
src[11] = 0u;
src[8] = 0u;
src[9] = 0u;
src[6] = 0u;
src[7] = 0u;
src[4] = 0u;
src[5] = 0u;
src[2] = 0u;
src[3] = 0u;
src[0] = 0u;
src[1] = 0u;
sha3::Keccak224::absorb::h29f13918aa92cc08(src, v3);
if ( *((_QWORD *)&dest + 1) )
_rust_dealloc();
v7[0] = 0LL;
v7[1] = 0LL;
v8 = 0;
v7[2] = 0LL;
memcpy(&dest, src, 0x160uLL);
_$LT$sha3..Sha3_224$u20$as$u20$digest..fixed..FixedOutputDirty$GT$::finalize_into_dirty::h6affdc907d7bd5a0(
&dest,
v7);
v10 = 0LL;
v11 = 0LL;
v6[0] = (__int64)v7;
v6[1] = (__int64)generic_array::hex::_$LT$impl$u20$core..fmt..LowerHex$u20$for$u20$generic_array..GenericArray$LT$u8$C$T$GT$$GT$::fmt::h66b0e2e11014508f;
*(_QWORD *)&dest = &unk_52428;
*((_QWORD *)&dest + 1) = 2LL;
v12 = v6;
v13 = 1LL;
std::io::stdio::_print::h99789c75449e1f7a();
}
else
{
}
On peut simplifier le code en pseudo-python :
v3 = input('password : ')
v3 = weirdfunction(v3)
if(len(v3)==17 and xor(v3[:8],0x6F686F6F686F6F48) == 0*8 and xor(v3[8:16],0x216168616861686F) == 0*8):
print('Flag is : %s'%sha3(v3))
La première chose à faire est d’expliciter les valeurs :
0x6F686F6F686F6F48
0x216168616861686F
Avec IDA ou binascii.unhexlify , on obtient respéctivement :
ohoohooH
!ahahaho
Pour conclure l’analyse statique ; on sait que d’après les propriétés du Xor
: A ^ A = 0
On veut donc que :
- v3[:8] == ohoohooH
- v3[8:16] == !ahahaho
Analyse Dynamique:
Juste après notre entrée dans stdin
, notre buffer stocké dans v3
et appelé dans une fonction
_$LT$core..iter..adapters..map..Map$LT$I$C$F$GT$$u20$as$u20$core..iter..traits..iterator..Iterator$GT$::fold::h77524fc9083f58e3(
1LL,
1LL,
&v3);
Quand on décompile cette fonction. On voit tout de suite qu’elle sort des enfers ..
v6 = (unsigned __int8)*v5;
if ( (*v5 & 0x80000000) != 0 )
{
v9 = v5[1] & 0x3F;
if ( v6 <= 0xDF )
{
v5 += 2;
v7 = v9 & 0xFFFFF83F | ((v6 & 0x1F) << 6);
v8 = v7 & 0xFFFFFFDF;
if ( (v7 & 0xFFFFFFDF) - 65 > 0xC )
goto LABEL_15;
}
else
{
v10 = v5[2] & 0x3F | ((v5[1] & 0x3F) << 6);
if ( v6 < 0xF0 )
{
v5 += 3;
v7 = v10 & 0xFFFE0FFF | ((v6 & 0x1F) << 12);
v8 = v7 & 0xFFFFFFDF;
if ( (v7 & 0xFFFFFFDF) - 65 > 0xC )
goto LABEL_15;
}
else
{
v7 = (v5[3] & 0x3F | ((v10 & 0xFFF) << 6)) & 0xFFE3FFFF | ((v6 & 7) << 18);
if ( v7 == 1114112 )
return result;
v5 += 4;
v8 = v7 & 0xFFFFFFDF;
if ( (v7 & 0xFFFFFFDF) - 65 > 0xC )
{
LABEL_15:
if ( v8 - 78 <= 0xC )
v7 = (unsigned __int8)(v7 - 13);
goto LABEL_17;
}
}
}
}
else
{
v7 = (unsigned __int8)*v5++;
v8 = v6 & 0xFFFFFFDF;
if ( (v6 & 0xFFFFFFDF) - 65 > 0xC )
goto LABEL_15;
}
....
On va donc devoir utiliser un debugger pour comprendre le fonctionnement de cette fonction et ainsi passer le bon mot de passe qui vérifie les 3 conditions précédentes.
Mise en place d’un environnement de debug .
root@DESKTOP-HNQJECB: /c
➜ file rustyTheClown
rustyTheClown: ELF 64-bit LSB pie executable, ARM aarch64, version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux-aarch64.so.1, for GNU/Linux 3.7.0, BuildID[sha1]=59299c846572d2809876d380f7710f0d1735429e, with debug_info, not stripped
Le binaire est un binaire codé en Rust
et compilé en ARM aarch64
. Il n’est donc pas exécutable facilement :
root@DESKTOP-HNQJECB: /c
➜ ./rustyTheClown
zsh: exec format error: ./rustyTheClown
Pour résoudre ce soucis , on peut soit :
- Utiliser une machine avec un processeur adapté (Raspberry)
- Utiliser une machine Qemu.
Nous allons ici utiliser la 2nd option même si elle reste la plus compliquée.
Installation :
sudo apt install qemu-user qemu-user-static gcc-aarch64-linux-gnu binutils-aarch64-linux-gnu binutils-aarch64-linux-gnu-dbg build-essential
Lancement :
qemu-aarch64 -L /usr/aarch64-linux-gnu ./rustyTheClown
Résultat:
`.:`
.:ooy.
./+///.. `..-/+osso
.-/ssooo:. `-`````..-:- `
-ssssso:.`:``````````.: `://+++:.
/ssssssso.......---`./ ``:+osssss+/`
.sssssss:-` .-` `.-.oo/ooossssy/.
ossssso:-/:+:` :`-osssssssssy-
`ssss/---/+o+/:::`++ssssssss/
.osy+:-.......::-/yssssssy`
`syy/ys/+::+///+yssssy:`
`o-NMMMNNs:/hysys/:.
+:yshhd+/.:``
`/+soo+++s//+`
.--` `....+/oossssoy/:+:
--`-..`...----:/:::/+:///::/+++o++/++:.
....-` -.+--------+:::/+::::::::::+/::o/::/:---..` `-.-
--.- `./`+--------+:::+::::::::::/o::++:---------::::.`.:
-:.` ../::::::::/+///::::::::::/o+/o/::-------:-:.` ::``
:.--- ``:-:.``` .//:::::::::::::/+ooso+/::::--::` .. `..-.`
`` :`.-`` ::::::::::::::::::://+o: `..-/:: .-.`-:
..` :::::::::::::::::::::///+: `--. .``--..`
./:::::::::::::::::::::///+- :. /--`-.
/-:::::::::::::::::::::////+ `:`:``..
:o+///////////+///+//://///o `.`
`/--------------:::://+++o+
-/::-----------------::/+-`
Hey hey, kids!
What's your answer? :
Vozec
Hoohoohoohahaha!
On va utiliser gdb-mutliarch
:
- Dans un premier shell :
gdb-multiarch rustyTheClown
set architecture aarch64
- Dans un deuxième shell :
qemu-aarch64 -g 1337 -L /usr/aarch64-linux-gnu ./rustyTheClown
- Dans le premier shell :
target remote :1337
Maintenant que notre GDB est lié à notre binaire , nous pouvons placer un breakpoint à la sortie de la fonction mystère . On va essayer de trouver cette instruction :
ldr x8, [sp, #40]
Elle se situe juste après le call de la fonction _ZN13rustyTheClown4main17hecc4c87ee1b9aeabE
- Dans le deuxième shell :
disas _ZN13rustyTheClown4main17hecc4c87ee1b9aeabE
# Ici on va chercher l'instruction
b* 0x0000005500006ea8
continue
On peut ainsi entrer un mot de passe de 16 charactères aléatoire, ici HelloFromVozec!!
Une fois ceci fait, notre premier shell avec GDB à atteint le breakpoint: Affichons les registres !
X(gdb) x/s $x0
0x5500058b10: "UryybSebzIbmrp!!\n"
Et oui, tout de suite cela devient évident … C’est un Rot13 Banal ! Toutes les lettres sont décalés de 13 dans l’alphabet .
On peut donc former le mot de passe :
Ubbubbubbununun!
qui sortant de la fonction aura pour valeur : Hoohoohoohahaha!
et validera le challenge :