Dernier crackme de cette série de writeup de la NDH 2k12,
une application windows phone !
1.
What
On a donc une application Windows phone dans son fichier « xap »,
en regardant de plus près on voit qu’il s’agit d’un fichier compressé (PK).
On decompresse puis on se retrouve avec les fichiers de l’application :
Si on passe les images, le xaml pour l’interface et le manifest, il reste
la dll qui contient le code de l’application, probablement une appli qui
demande un mot de passe …
On peut essayer d’ouvrir la dll avec IDA mais on voit rapidement qu’il s’agit
de .Net. On switch donc dans notre décompileur de .Net préféré (pour moi
Reflector).
private void
ndhPasswordLogin_Click(object sender,
RoutedEventArgs e)
{
string password = this.ndhPassword.get_Password();
if (password.Length > 0)
{
if (string.Equals(this.cryptPassword(password), this.trustedHash()))
{
base.get_NavigationService().Navigate(new Uri("/LoginError.xaml", UriKind.Relative));
}
else
{
MessageBox.Show("Login failed, try again.", "Error", 0);
}
}
}
{
string password = this.ndhPassword.get_Password();
if (password.Length > 0)
{
if (string.Equals(this.cryptPassword(password), this.trustedHash()))
{
base.get_NavigationService().Navigate(new Uri("/LoginError.xaml", UriKind.Relative));
}
else
{
MessageBox.Show("Login failed, try again.", "Error", 0);
}
}
}
On a une fonction qui sera appelé pour vérifier un password. On test si
le mot de passe chiffré avec « cryptPassword » correspond au « trustedHash() ».
Rien de spécial, continuons …
2.
TrustedHash
Un simple writeLn ou copier/coller et on obtient :
+R2kbpJn0tqCMmUNZzFwQFMU+EW0/Nkoup5Zl4hxoPFRfmk1fhKJhfBkdhNYiDqt23jbRoxDbO3
QHpy6M2kY/8hd3Z1ds8a1StSQkXxNyjZd2mTQPpTR4zmyf9FzK4Y4XUfzw1hUP3qj+dGyKgNjQm
XdtPTOqRVh2T41NSvmK/YV6XzIk6SFdhF9XyXUJOlcII5mXQ/SuHiHmkEoTJiUF3XGN/LkjnNy6pRzZH6s2YC27g7sqqFj71xPY+S3KHLy1y/PwJtz1E7EBzqaXxTxDOE9wiPArIoY9Rl//8RalprwBKeEzZajgbqFz5sEkV6hplGCkiua1FfPXht9Ef8br
3.
CryptPassword
private
string cryptPassword(string
password)
{
string key = "AYOOYOYOAYOOYOYO";
string salt = "WOLOLOWOLOLOOOO";
string str3 = "ndh2k12!";
string str4 = null;
string data = null;
for (int i = 0; i < password.Length; i++)
{
string str6 = password[i].ToString();
string str7 = null;
if ((i % 2) == 0)
{
str7 = this.base64Encode(str3 + str6);
}
else
{
str7 = this.aesEncode(str6, str4, str3);
}
data = data + str7;
str4 = str7;
}
return this.aesEncode(data, key, salt);
}
{
string key = "AYOOYOYOAYOOYOYO";
string salt = "WOLOLOWOLOLOOOO";
string str3 = "ndh2k12!";
string str4 = null;
string data = null;
for (int i = 0; i < password.Length; i++)
{
string str6 = password[i].ToString();
string str7 = null;
if ((i % 2) == 0)
{
str7 = this.base64Encode(str3 + str6);
}
else
{
str7 = this.aesEncode(str6, str4, str3);
}
data = data + str7;
str4 = str7;
}
return this.aesEncode(data, key, salt);
}
On parcours donc notre password, si on est sur un caractère pair on ajoute
à « data » base64(ndh2k12 ! + char) sinon on ajoute à « data »
le caractère chiffré avec la clé « key » et le salt « salt ».
A la fin, “data” subit une dernière transformation, aesEncode, toujours
avec la même key et salt.
Pourquoi ne pas faire l’inverse !
On part du « trustedHash », on sait qu’en dernier il a été
chiffré alors on le déchiffre. On obtient ensuite un certain nombre de blocs
suivant la taille du mot de passe hashé.
Pour les blocs pairs, il suffit de le décodé en base64 puis de garder le
dernier caractère.
Pour les blocs impairs, il faut le déchiffrer avec le bloc précédent comme
clé et toujours le même salt.
Ainsi de suite pour chaque bloc pair et impair …
4.
Résultat
Pour déchiffrer, la fonction s’écrit plus ou moins en copiant la fonction
de chiffrement :
private
string aesDecode(string
data, string
key, string
salt)
{
byte[] bytes = new byte[salt.Length];
bytes = Encoding.UTF8.GetBytes(salt);
Aes aes = (Aes) new AesManaged();
aes.Key = new Rfc2898DeriveBytes(key, bytes).GetBytes(0x10);
aes.IV = aes.Key;
MemoryStream stream = new MemoryStream();
CryptoStream stream2 = new CryptoStream(stream, aes.CreateDecryptor(), CryptoStreamMode.Write);
byte[] buffer = Convert.FromBase64String(data);
stream2.Write(buffer, 0, buffer.Length);
stream2.FlushFinalBlock();
return Encoding.UTF8.GetString(stream.ToArray());
}
{
byte[] bytes = new byte[salt.Length];
bytes = Encoding.UTF8.GetBytes(salt);
Aes aes = (Aes) new AesManaged();
aes.Key = new Rfc2898DeriveBytes(key, bytes).GetBytes(0x10);
aes.IV = aes.Key;
MemoryStream stream = new MemoryStream();
CryptoStream stream2 = new CryptoStream(stream, aes.CreateDecryptor(), CryptoStreamMode.Write);
byte[] buffer = Convert.FromBase64String(data);
stream2.Write(buffer, 0, buffer.Length);
stream2.FlushFinalBlock();
return Encoding.UTF8.GetString(stream.ToArray());
}
Notre “trustedHash” déchiffré donne :
bmRoMmsxMiFomEpoK3JjVufPG4PHIZOr8A==bmRoMmsxMiF2yXWqntvVZX50EN1IlnTo3w==bmRoMmsxMiF3bZY/Kf1nEbIZJkxufD/aAQ==bmRoMmsxMiFs28dlLHl69r6pmBbjIPgYBw==bmRoMmsxMiEtpGqGLudtlrHht7kVeEfMLA==bmRoMmsxMiFlCW4AQUIJeTVAbcdBXbRDQg==bmRoMmsxMiFlMZm7jEzQutN6OwSAVddgAw==bmRoMmsxMiEtVbvy0FmdISllXQlSmMRR0w==bmRoMmsxMiFpWGsPB3wrW20H79tUp8T47w==
On peut ensuite découper ça en
bloc :
bmRoMmsxMiFo
mEpoK3JjVufPG4PHIZOr8A==
bmRoMmsxMiF2
yXWqntvVZX50EN1IlnTo3w==
bmRoMmsxMiF3
bZY/Kf1nEbIZJkxufD/aAQ==
bmRoMmsxMiFs
28dlLHl69r6pmBbjIPgYBw==
bmRoMmsxMiEt
pGqGLudtlrHht7kVeEfMLA==
bmRoMmsxMiFl
CW4AQUIJeTVAbcdBXbRDQg==
bmRoMmsxMiFl
MZm7jEzQutN6OwSAVddgAw==
bmRoMmsxMiEt
Vbvy0FmdISllXQlSmMRR0w==
bmRoMmsxMiFp
WGsPB3wrW20H79tUp8T47w==
Le premier bloc décodé donne :
ndh2k12!h donc le caractère du passe
est : h
Le deuxième bloc déchiffré donne :
z
On continue comme ça jusqu’au
dernier bloc pour obtenir : hzv-will-never-die