lundi 26 septembre 2011

CTF Quals CSAW 2011 - Reversing .Net

Salut à tous !

Nous revoilà aprés le CSAW CTF pour quelques writeups.
Ici ça sera celui du reverse .Net à 200 pt.

  1. What ?

    On nous donne un fichier zip "students-easy.zip" que vous pourrez retrouver sur Shell-Storm. On dézippe et on obtient ces 3 fichiers :
    • 9-9-2011 11-24-28 PM.dmp
    • DumpPrepper.exe
    • README.txt

    En lisant le README, on apprends qu'un dump a été fait, puis chiffré par DumpPrepper.exe. Le but étant bien évidemment de déchiffrer ce dump pour en extraire la clé. C'est une épreuve .Net, il y a des chances pour que le .exe soit du .Net ...

    Un coup de .Net Reflector ou de votre decompiler .Net préféré plus tard, on obtient le code de DumpPepper :

    1. using System;
    2. using System.IO;
    3.  
    4. namespace DumpPrepper
    5. {
    6.   internal class Program
    7.   {
    8.     private static uint[] key = new uint[4]
    9.     {
    10.       1929540644U,
    11.       2488374377U,
    12.       339237175U,
    13.       54625381U
    14.     };
    15.  
    16.     static Program()
    17.     {
    18.     }
    19.  
    20.     private static void Main(string[] args)
    21.     {
    22.       if (args.Length < 1 || args.Length > 1)
    23.         Console.WriteLine("Usage: DumpPrepper.exe file");
    24.       else if (!File.Exists(args[0]))
    25.         Console.WriteLine("Could not find " + args[0]);
    26.       else
    27.         File.WriteAllBytes(DateTime.UtcNow.ToString().Replace('/', '-').Replace(':', '-') + ".dmp", Program.Encrypt(File.ReadAllBytes(args[0]), Program.key));
    28.     }
    29.  
    30.     private static byte[] Encrypt(byte[] plaintext, uint[] key)
    31.     {
    32.       byte num1 = (byte) (8 - plaintext.Length % 8);
    33.       byte[] numArray1 = new byte[plaintext.Length + (int) num1];
    34.       Array.Copy((Array) plaintext, (Array) numArray1, plaintext.Length);
    35.       for (int length = plaintext.Length; length < numArray1.Length; ++length)
    36.         numArray1[length] = num1;
    37.       byte[] numArray2 = new byte[numArray1.Length];
    38.       int num2 = 0;
    39.       while (num2 < numArray1.Length)
    40.       {
    41.         uint[] numArray3 = Program.ProcessBlock(64U, new uint[2]
    42.         {
    43.           BitConverter.ToUInt32(numArray1, num2),
    44.           BitConverter.ToUInt32(numArray1, num2 + 4)
    45.         }, key);
    46.         Array.Copy((Array) BitConverter.GetBytes(numArray3[0]), 0, (Array) numArray2, num2, 4);
    47.         Array.Copy((Array) BitConverter.GetBytes(numArray3[1]), 0, (Array) numArray2, num2 + 4, 4);
    48.         num2 += 8;
    49.       }
    50.       return numArray2;
    51.     }
    52.  
    53.     private static uint[] ProcessBlock(uint num_rounds, uint[] v, uint[] key)
    54.     {
    55.       if (key.Length != 4)
    56.         throw new ArgumentException();
    57.       if (v.Length != 2)
    58.         throw new ArgumentException();
    59.       uint num1 = v[0];
    60.       uint num2 = v[1];
    61.       uint num3 = 0U;
    62.       uint num4 = 2654435769U;
    63.       for (uint index = 0U; index < num_rounds; ++index)
    64.       {
    65.         uint num5 = (num2 << 4 ^ num2 >> 5) + num2;
    66.         uint num6 = num3 + key[(IntPtr) (num3 & 3U)];
    67.         num1 += num5 ^ num6;
    68.         num3 += num4;
    69.         uint num7 = (num1 << 4 ^ num1 >> 5) + num1;
    70.         uint num8 = num3 + key[(IntPtr) (num3 >> 11 & 3U)];
    71.         num2 += num7 ^ num8;
    72.       }
    73.       v[0] = num1;
    74.       v[1] = num2;
    75.       return v;
    76.     }
    77.   }
    78. }
    79.  

  2. Le Chiffrement

  3. On reconnait assez vite un chiffrement par bloc de 8 octets. Ainsi que 16 octets de clé. Pas de fonction de déchiffrement à l'horizon :(.

    Encrypt

    Le principe assez clair, on regarde la taille du plaintext, on l'a pad pour qu'elle fasse un multiple de 8 octets. La valeur du padding est le nombre d'octet à rajouter. (Look PKCS7) Ensuite pour chaque bloc, on le passe dans processBlock. Puis on écrit le résultat dans le fichier de sortie.

    ProcessBlock

    Voilà la fonction intéressante !
    On initialise quelques variables, puis on passe les num_round+1 tours de l'algo pour obtenir les octets chiffrés.

  4. Déchiffrement

    Il faut essayer d'inverser l'algo. La boucle complique un peu la chose mais pas trop. En premier essayons de trouver ce que l'on sait à la fin de la fonction :
    • On a num1 et num2 parce que ce sont les octets chiffrés
    • On a num4 parce qu'il ne change jamais, il vaut toujours 2654435769U
    • On a aussi num3, parce qu'il contient i*num4 pour le tour n°i !

    A partir de num3, on peut avoir num8 et num6 parce que leur valeur dépendent uniquement de num3 et de quelques constantes
    Avec num8 et num6, on peut avoir num7 et num5 en xorant avec les chiffrés.
    Et voilà, c'est suffisant, on a retrouvé toutes nos variables pour chaque tour. On peut inversé l'algo.

  5. Résultats

    il suffit d'effectuer l'algo en sens "inverse". En initialisant num3 avec num_round*num4, puis en calculant d'abord num8, avec le chiffré, num7. On décrémente ensuite num3. Puis pareil pour num5 et num6 pour retrouver num1. Voilà le code en C final :

    1. #include <stdio.h>
    2.  
    3. int ROUND = 64;
    4. unsigned int KEY[] = {1929540644U, 2488374377U, 339237175U, 54625381U};
    5.  
    6. void usage(){
    7.     printf("Usage : decrypt file");
    8.     exit(0);
    9. }
    10.  
    11. void decrypt(int* v, int round, int* key){
    12.     int i;
    13.     unsigned int num3 = 0;
    14.     unsigned int num4 = 2654435769U;
    15.     for(i=0; i<ROUND; i++) num3 += num4;
    16.     unsigned int num1 = v[0];
    17.     unsigned int num2 = v[1];
    18.     unsigned int num5, num6, num7, num8;
    19.     for(i=0; i<ROUND; i++){
    20.         num8 = num3 + key[(unsigned int)(num3 >> 11 & 3U)];
    21.         num7 = (num1 << 4 ^ num1 >> 5) + num1;     
    22.         num2 -= num7 ^ num8;
    23.         num3 -= num4;
    24.         num6 = num3 + key[(unsigned int)(num3 & 3U)];
    25.         num5 = (num2 << 4 ^ num2 >> 5) + num2;     
    26.         num1 -= num5 ^ num6;   
    27.     }
    28.     v[0] = num1;
    29.     v[1] = num2;
    30. }
    31.  
    32. void processFile(char* filename){
    33.     FILE* f = fopen(filename, "rb");
    34.     FILE* fo = fopen("plaintext.txt", "wb");
    35.     if (f && fo){
    36.         unsigned int v[2];
    37.         fseek (f, 0, SEEK_END);
    38.         long lSize = ftell(f);
    39.         rewind(f);
    40.         long count=0;
    41.         while (count<lSize){
    42.             fread(v, 8, 1, f);
    43.             decrypt(v, ROUND, KEY);
    44.             fwrite(v, 8, 1, fo);
    45.             count += 8;
    46.         }
    47.         fclose(f);
    48.         fclose(fo);
    49.     }
    50. }
    51.  
    52. int main(int argc, char* argv[]){
    53.     if (argc < 2) {
    54.         usage();
    55.     }else{
    56.         processFile(argv[1]);
    57.     }
    58.     return 0;
    59. }
    60.  
    61.  

    Un fois le log déchiffré, on retrouvé le clé en plein milieu :
    key{ f79b5967afade81c142eab7e4b4c9a3b }

Aucun commentaire:

Enregistrer un commentaire