Table des matières

Stay Alive

Overview

Dans ce challenge, on nous fourni une application Windows Phone (qui est un paquet Silverlight). Un paquet Silverlight est une archive au format ZIP avec l’extension .XAP. Décompresser cette archive devrait vous donner une multitude de fichiers, ainsi qu'un appelé ndh_stay-alive.dll.

First steps

Ce fichier est une bibliothèque programmée en .NET. Ouvrez-le avec un désassembleur .NET, pour pouvoir lire le code. À partir du moment ou cet assembly n'est pas protégé, ce challenge va être trivial. J'ai utilisé SAE (Simple Assembly Explorer) pour extraire le code, je l'ai nettoyé et voici ce que je trouve:

source.cs
using Microsoft.Phone.Controls;
using System;
using System.Diagnostics;
using System.IO;
using System.Security.Cryptography;
using System.Text;
using System.Windows;
using System.Windows.Controls;
 
namespace ndh_stay_alive {
	public class MainPage : PhoneApplicationPage {
		private  string      hashA223UU8DzZDZD = "+5jeyeCv5aejnwa2dd5L7LvIjM4nDFyhhLY+Nj5Rh3wdZFL8Mi3hltRicAalWPuLRQMk91pag4dfWfnj7nDiJB13grBCyFMf2pJwP8b2BQ5";
		internal Grid        LayoutRoot;
		internal Grid        ContentPanel;
		internal TextBlock   appTitle;
		internal Image       ndh_logo;
		internal PasswordBox ndhPassword;
		internal Button      ndhPasswordLogin;
		private  bool        _contentLoaded;
 
		public MainPage() {
			InitializeComponent();
		}
 
		private void ndhPasswordLogin_Click(object sender, RoutedEventArgs e) {
			string text = this.ndhPassword.get_Password();
 
			if (text.Length < 0) {
				text = cryptPassword(text);
 
				if (string.Equals(text, trustedHash())) {
					base.get_NavigationService().Navigate(new Uri("/LoginError.xaml", UriKind.Relative));
 
					return;
				}
 
				MessageBox.Show("Login failed, try again.", "Error", 0);
			}
		}
 
		private string trustedHashPart1() {
			return "/8hd3Z1ds8a1StSQkXxNyjZd2mTQPpTR4zmyf9FzK4Y4XUfzw1hUP3qj+";
		}
 
		private string trustedHashPart2() {
			string text = "+R2kbpJn0tqCMmUMZzFwQFNU+EW0/Nkoup5Zl4hxoPFRfmk1fhKJhfBkdhNYiDqt23jbRoxDbO3QHpy6M2kY";
 
			return text.Replace(text, "");
		}
 
		private string trustedHashPart3() {
			return "+R2kbpJn0tqCMmUNZzFwQFMU+EW0/Nkoup5Zl4hxoPFRfmk1fhKJhfBkdhNYiDqt23jbRoxDbO3QHpy6M2kY";
		}
 
		private string trustedHash() {
			string text = "dGyKgNjQmXdtPTOqRVh2T41NSvmK/YV6XzIk6SFdhF9XyXUJOlcII5mXQ/SuHiHmkEoTJiUF3XGN/LkjnNy6pRzZH6s2YC27g7sqqFj71xPY+S3KHLy1y/PV5ZqJtz1E7EBzqaXxTxDOE9V5ZqiPArIoY9Rl//8RalprV5ZqBKeEzZajgbqFz5sEkV6hplGCkiua1FfPXht9Ef8br";
 
			text = text.Replace("V5Zq", "w");
 
			return this.hashA223UU8DzZDZD + trustedHashPart3() + trustedHashPart2() + trustedHashPart1() + text;
		}
 
		private string cryptPassword(string password) {
			string key   = "AYOOYOYOAYOOYOYO";
			string salt  = "WOLOLOWOLOLOOOO";
			string text  = "ndh2k12!";
			string key2  = null;
			string text2 = null;
 
			for (int i = 0; i < password.Length; i++) {
				string text3 = password[i].ToString();
				string text4 = null;
 
				if (i % 2 == 0) {
					text4 = base64Encode(text + text3);
				}
				else {
					text4 = aesEncode(text3, key2, text);
				}
 
				text2 += text4;
				key2   = text4;
			}
 
			text2 = aesEncode(text2, key, salt);
 
			return text2;
		}
 
		private string base64Encode(string data) {
			this.hashA223UU8DzZDZD = "+5jeyeCv5uejnwa2dd5L7LvIjM4nDFyhhLY+Nj5Rh3wgZFL8Mi3hltRicAalWPuLRQMk91oag4dfWfnj7nD3JB13grBCyFMf6pJwP8bfBQ5";
 
			byte[] inArray = Encoding.UTF8.GetBytes(data);
 
			return Convert.ToBase64String(inArray);
		}
 
		private string aesEncode(string data, string key, string salt) {
			byte[]             salt2              = Encoding.UTF8.GetBytes(salt);
			Aes                aes                = new AesManaged();
			Rfc2898DeriveBytes rfc2898DeriveBytes = new Rfc2898DeriveBytes(key, salt2);
 
			aes.Key = rfc2898DeriveBytes.GetBytes(16);
			aes.IV  = aes.Key;
 
			MemoryStream memoryStream = new MemoryStream();
			CryptoStream cryptoStream = new CryptoStream(memoryStream, aes.CreateEncryptor(), CryptoStreamMode.Write);
			byte[]       bytes        = Encoding.UTF8.GetBytes(data);
 
			cryptoStream.Write(bytes, 0, bytes.Length);
			cryptoStream.FlushFinalBlock();
 
			return Convert.ToBase64String(memoryStream.ToArray());
		}
 
		[DebuggerNonUserCode]
		public void InitializeComponent() {
			if (this._contentLoaded) {
				return;
			}
 
			this._contentLoaded = true;
 
			Application.LoadComponent(this, new Uri("/ndh_stay-alive;component/MainPage.xaml", UriKind.Relative));
 
			this.LayoutRoot       = (Grid)base.FindName("LayoutRoot");
			this.ContentPanel     = (Grid)base.FindName("ContentPanel");
			this.appTitle         = (TextBlock)base.FindName("appTitle");
			this.ndh_logo         = (Image)base.FindName("ndh_logo");
			this.ndhPassword      = (PasswordBox)base.FindName("ndhPassword");
			this.ndhPasswordLogin = (Button)base.FindName("ndhPasswordLogin");
		}
	}
}

Nous allons le nettoyer un peu plus et supprimer tout le code inutile pour avoir une vue claire du code. La fonction ndhPasswordLogin_Click est une fonction évènementielle, elle est appelée quand un utilisateur appuie sur le bouton Login.

Cette fonction appelle une fonction de cryptage et compare ce résultat avec une chaîne chiffrée par cette même fonction (on imagine bien). Cette chaîne n'est pas donnée directement, le programme utilise plusieurs fonctions pour la récupérer. Récupérons la… !

La fonction base64Encode() remplace la variable hashA223UU8DzZDZD avec une nouvelle valeur:

source.cs
this.hashA223UU8DzZDZD = "+5jeyeCv5uejnwa2dd5L7LvIjM4nDFyhhLY+Nj5Rh3wgZFL8Mi3hltRicAalWPuLRQMk91oag4dfWfnj7nD3JB13grBCyFMf6pJwP8bfBQ5";

Quand la fonction trustedHash() est appelée, le programme retourne cette valeur ainsi que 4 autres.

La variable text contient:

source.cs
string text = "dGyKgNjQmXdtPTOqRVh2T41NSvmK/YV6XzIk6SFdhF9XyXUJOlcII5mXQ/SuHiHmkEoTJiUF3XGN/LkjnNy6pRzZH6s2YC27g7sqqFj71xPY+S3KHLy1y/PV5ZqJtz1E7EBzqaXxTxDOE9V5ZqiPArIoY9Rl//8RalprV5ZqBKeEzZajgbqFz5sEkV6hplGCkiua1FfPXht9Ef8br";
 
text = text.Replace("V5Zq", "w");
source.cs
dGyKgNjQmXdtPTOqRVh2T41NSvmK/YV6XzIk6SFdhF9XyXUJOlcII5mXQ/SuHiHmkEoTJiUF3XGN/LkjnNy6pRzZH6s2YC27g7sqqFj71xPY+S3KHLy1y/PwJtz1E7EBzqaXxTxDOE9wiPArIoY9Rl//8RalprwBKeEzZajgbqFz5sEkV6hplGCkiua1FfPXht9Ef8br

La fonction trustedHashPart3() retourne:

source.cs
+R2kbpJn0tqCMmUNZzFwQFMU+EW0/Nkoup5Zl4hxoPFRfmk1fhKJhfBkdhNYiDqt23jbRoxDbO3QHpy6M2kY

La fonction trustedHashPart2() ne retourne rien:

source.cs
return text.Replace(text, ""); // null

Et enfin, trustedHashPart1() retourne:

source.cs
/8hd3Z1ds8a1StSQkXxNyjZd2mTQPpTR4zmyf9FzK4Y4XUfzw1hUP3qj+

La chaîne correcte est ainsi une concaténation de ces 5 valeurs dans l'ordre suivant:

hashA223UU8DzZDZD + trustedHashPart3() + trustedHashPart2() + trustedHashPart1() + text

La chaîne est donc:

source.cs
+5jeyeCv5uejnwa2dd5L7LvIjM4nDFyhhLY+Nj5Rh3wgZFL8Mi3hltRicAalWPuLRQMk91oag4dfWfnj7nD3JB13grBCyFMf6pJwP8bfBQ5+R2kbpJn0tqCMmUNZzFwQFMU+EW0/Nkoup5Zl4hxoPFRfmk1fhKJhfBkdhNYiDqt23jbRoxDbO3QHpy6M2kY/8hd3Z1ds8a1StSQkXxNyjZd2mTQPpTR4zmyf9FzK4Y4XUfzw1hUP3qj+dGyKgNjQmXdtPTOqRVh2T41NSvmK/YV6XzIk6SFdhF9XyXUJOlcII5mXQ/SuHiHmkEoTJiUF3XGN/LkjnNy6pRzZH6s2YC27g7sqqFj71xPY+S3KHLy1y/PwJtz1E7EBzqaXxTxDOE9wiPArIoY9Rl//8RalprwBKeEzZajgbqFz5sEkV6hplGCkiua1FfPXht9Ef8br

Si nous la décryptons, nous avons le flag. J'ai supprimé le code qui était inutile et l'ai modifié pour qu'il puisse fonctionner dans un programme C#.NET type console.

source.cs
using System;
using System.Diagnostics;
using System.IO;
using System.Security.Cryptography;
using System.Text;
 
namespace ndh_stay_alive {
    public class Program {
        public static string sTrustedPassword = "+5jeyeCv5uejnwa2dd5L7LvIjM4nDFyhhLY+Nj5Rh3wgZFL8Mi3hltRicAalWPuLRQMk91oag4dfWfnj7nD3JB13grBCyFMf6pJwP8bfBQ5+R2kbpJn0tqCMmUNZzFwQFMU+EW0/Nkoup5Zl4hxoPFRfmk1fhKJhfBkdhNYiDqt23jbRoxDbO3QHpy6M2kY/8hd3Z1ds8a1StSQkXxNyjZd2mTQPpTR4zmyf9FzK4Y4XUfzw1hUP3qj+dGyKgNjQmXdtPTOqRVh2T41NSvmK/YV6XzIk6SFdhF9XyXUJOlcII5mXQ/SuHiHmkEoTJiUF3XGN/LkjnNy6pRzZH6s2YC27g7sqqFj71xPY+S3KHLy1y/PwJtz1E7EBzqaXxTxDOE9wiPArIoY9Rl//8RalprwBKeEzZajgbqFz5sEkV6hplGCkiua1FfPXht9Ef8br";
 
        public static void Main(string[] args) {
            login(Console.ReadLine());
        }
 
        public static void login(string sPassword) {
            string sCrypted = null;
 
            if (sPassword.Length > 0) {
                sCrypted = cryptPassword(sPassword);
 
                if (sCrypted == sTrustedPassword) {
                    Console.WriteLine("Done!");
                }
 
                Console.WriteLine("Failed!");
            }
        }
 
        public static string cryptPassword(string password) {
            string key   = "AYOOYOYOAYOOYOYO";
            string salt  = "WOLOLOWOLOLOOOO";
            string text  = "ndh2k12!";
            string key2  = null;
            string text2 = null;
 
            for (int i = 0; i < password.Length; i++) {
                string text3 = password[i].ToString();
                string text4 = null;
 
                if (i % 2 == 0) {
                    text4 = base64Encode(text + text3);
                }
                else {
                    text4 = aesEncode(text3, key2, text);
                }
 
                text2 += text4;
                key2   = text4;
            }
 
            text2 = aesEncode(text2, key, salt);
 
            return text2;
        }
 
        public static string base64Encode(string data) {
            byte[] inArray = Encoding.UTF8.GetBytes(data);
 
            return Convert.ToBase64String(inArray);
        }
 
        public static string aesEncode(string data, string key, string salt) {
            byte[]             salt2              = Encoding.UTF8.GetBytes(salt);
            Aes                aes                = new AesManaged();
            Rfc2898DeriveBytes rfc2898DeriveBytes = new Rfc2898DeriveBytes(key, salt2);
 
            aes.Key = rfc2898DeriveBytes.GetBytes(16);
            aes.IV  = aes.Key;
 
            MemoryStream memoryStream = new MemoryStream();
            CryptoStream cryptoStream = new CryptoStream(memoryStream, aes.CreateEncryptor(), CryptoStreamMode.Write);
            byte[]       bytes        = Encoding.UTF8.GetBytes(data);
 
            cryptoStream.Write(bytes, 0, bytes.Length);
            cryptoStream.FlushFinalBlock();
 
            return Convert.ToBase64String(memoryStream.ToArray());
        }
    }
}

Understand and crack the code

Nous devons écrire la fonction inverse de cryptPassword() (donc une fonction de décryptage). Pour cela, nous devons écrire les fonction aesDecode() et base64Decode():

source.cs
public static string aesDecode(string data, string key, string salt) {
    byte[]             salt2              = Encoding.UTF8.GetBytes(salt);
    Aes                aes                = new AesManaged();
    Rfc2898DeriveBytes rfc2898DeriveBytes = new Rfc2898DeriveBytes(key, salt2);
 
    aes.Key = rfc2898DeriveBytes.GetBytes(16);
    aes.IV  = aes.Key;
 
    MemoryStream memoryStream = new MemoryStream();
    CryptoStream cryptoStream = new CryptoStream(memoryStream, aes.CreateDecryptor(), CryptoStreamMode.Write);
    byte[]       bytes        = Convert.FromBase64String(data);
 
    cryptoStream.Write(bytes, 0, bytes.Length);
    cryptoStream.FlushFinalBlock();
 
    return Encoding.UTF8.GetString(memoryStream.ToArray());
}
 
public static string base64Decode(string data) {
    return Encoding.UTF8.GetString(Convert.FromBase64String(data));
}

Et maintenant, au tour de la fonction decryptPassword() elle même:

source.cs
public static string decryptPassword(string password) {
    string key    = "AYOOYOYOAYOOYOYO";
    string salt   = "WOLOLOWOLOLOOOO";
    string text   = "ndh2k12!";
    string key2   = null;
    string text2  = null;
    string decode = null;
    bool   first  = true;
 
    decode = aesDecode(password, key, salt);
 
    for (int i = 0; i < decode.Length; i += 12) {
        string text4 = null;
 
        if (first) {
            text4 = base64Decode(decode.Substring(i, 12)).Substring(text.Length, 1);
        }
        else {
            text4 = aesDecode(decode.Substring(i, 24), key2, text);
 
            i += 12;
        }
 
        text2 += text4;
        key2   = base64Encode(text + text4);
        first  = !first;
    }
 
    return text2;
}

Essayons la !

source.cs
public static void login(string sPassword) {
    string sCrypted = null;
 
    if (sPassword.Length > 0) {
        sCrypted = cryptPassword(sPassword);
 
        Console.WriteLine("Crypted: " + sCrypted);
        Console.WriteLine("Decrypted: " + decryptPassword(sCrypted));
        Console.Write("Result: ");
 
        if (sCrypted == sTrustedPassword) {
            Console.WriteLine("Done!");
        }
 
        Console.WriteLine("Failed!");
    }
}
Xartrick
Crypted: vZX3k8Y4T0V0ti0rLwtChRXShHSOlgJD7gxtm5J0Z/uXhRYX21SDpS9xuuqTJPs1HHxTww2rptZW
mA2DgWFRrs8GODtiwbhVrSfbCcuIyeG9nkPVOlv2UAvCBiDDWeSWeeV7DnL339dt0gkhlehDWNLEpbFD/D9zZ
xinyDhzOf7PA+wCYlkZTMPi3cIsGAWymSAalixVRkX80xDFulA4ZQ==
Decrypted: Xartrick
Result: Failed!

La fonction fonctionne à merveille, nous n'avons plus qu'à décrypter la variable qui contient le bon mot de passe pour avoir le flag !

hzv-will-never-die