CSAW-CTF – Reverse 300 WriteUp

Ce week end s’est tenu l’édition 2013 du CSAW-CTF. Bien ! J’y ai pas fait grand chose (as usual), mon week end dans la vie réelle étant chargé. J’ai eu cependant ce petit reverse de 300Pts pour BigDaddy ;) au final GG les gars, 6800 Pts, 18eme ex aequo sur 1387.

Voici la petite histoire;

Ce même vendredi Yodzeb n’était plus tenable, il a commencé a désosser tout seul le chall reverse 300 au GDB (oui, c’est un genre de geek reverveur sado maso). Il a passé tout l’après midi à tenter de trouver le password de façons détournée. Celui-ci était caché dans un crackme accessible à distance qui quand on lui donne le bon password, va lire et affiche un fichier. Dès lors il est apparu qu’a part brute forcer le mot de passe on n’arriverai pas a grand choses car le mot de passe passe dans une moulinette du diable qui shift et add les octets.

Capture d’écran 2013-09-21 à 17.00.58

En clair on commence avec un magic number 0x539h (1337), on le multiplie par 32 et on additionne le caractère de notre password. On rajoute au résultat le résultat de l’opération du dernier tour. et on recommence pour le caractère suivant. Le tout étant potentiellement écrété naturellement par les registre 32bits.

Au final il faut tomber sur 0xEF2E3558h sinon on perd. Il était possible de trouver rapidement cette fonction en reverse statique, le programme ne cachant pas grand choses. Sous IDA il suffit d’afficher les strings, de double cliquer sur le message “Invalide registration code” puis X et on y est. Apres le code de hashing vu plus haut, si EDX est égal au chiffre attendu on gagne.

Capture d’écran 2013-09-21 à 17.20.17

Capture d’écran 2013-09-21 à 17.19.20

Voici la version ‘perl’ du dit algorithme (c) YodZeb.

    $start=0x539;
    foreach $c (split //) {
        if ($c!~/\n/) {
            $old=$start;
            $start=( ($old << 5) + $old + ord($c) ) ;
            $start &= 0xFFFFFFFF;
        }
    }

Avec ce genre de moulinette tôt ou tard on tombe sur une collision, la version ‘Perl’ de cet algo n’ayant pas fait mouche avec une juteuse wordlist, et n’ayant pas les compétences mathématique pour le faire élégamment (Si tenté que l’on puisse); Allons y pour un brute forçage plus académique. Avec un peu d’optimisation je suis arrivé à 19 millions de hashs/sec sur un seul CPU de mon petit AMD Phenom II et je monte a 28 avec un peut de nettoyage de code C pour le write-up.

C’est l’occasion de monter comment on appelle une routine ‘pure assembleur’ en C. Ca pourra toujours re-servir. Première étape ré-encoder la fonction en ASM. Pour que cela dépote plus,. L’idée est de virer le plus possible les accès mémoire dans la boucle interne et de garder toute la mamaille dans des registres. Voici ma version du hash, plus ‘concise’ :

;File hash.asm

section .text
GLOBAL mybrute 

mybrute:
  ; Normalement il faut un appel  
  ;       qui sauve ebp et esp 
  ; Mais ici on touchera pas a la pile 
  ; on s'en passe et on libere un registre

  ;[esp] = retour addr
  ;[esp+4]  = mystring

    mov   esi, dword [esp+4]  ; mystring
    xor   eax,eax    ; pour nettoyer la partie haute d'eax
    lodsb            ; On increment ESI, on met le 1er char dans AL
    mov   ebx, 0x539 ; Ebx sera le checksum, on y met le magicnumber

.myloop
    xor   ecx,ecx    ; xor 32 suivis de mov 8...
    mov   cl,al      ; equivalent a movzx mais en moins de cycles
    mov   edx,ebx    ; [currentsum] est ebx
    shl   edx,5      ; Multiplication
    add   ecx,edx    ; Addition avec char   
    add   ebx,ecx    ; Addition avec checksum 
    lodsb            ; pointer+1, next char dans al
    test  al,al      ; est t'on a la fin de la stringZ ?
    jnz   .myloop

  ; Ici EAX est a O, en C, 1 c'est true  
    cmp    ebx, 0xEF2E3558 ; TEST pour la string mystère
    je    .good
    ret
.good:
    inc    eax
    ret

Tout ce qu’il faut dans le fichier ASM c’est déclarer la fonction pour l’exportation avec le mot clef GLOBAL (voir ligne 4), la convention d’appel en C pour 32bits passera les paramètres de la fonction sur la pile. ici je vais appeller ma fonction avec comme paramètre une string. Pour les fans qui veulent se renseigner sur le grand bordel que sont les convention d’appel, il y a une page Wikipedia pour cela. bref il nous suffira de faire.

mybrute(‘toto’);

Sur la pile j’aurai donc dans l’ordre l’adresse de retour puis l’offset de cette string. au retour le C vérifie eax qui est a 0 false ou 1 pour true. EBP et ESP doivent être préservés.

voici le code C autour;

/*
brute.c
(c) Thanat0s.trollprod.org / 2013 

// jusque 6 char printable 742.912.017.120  742 Milliards de combinaisons...
*/

// ************ LIB ***************
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
extern mybrute () asm ("mybrute");

// ********* Variables *************
int g_bf_len, g_char_max,pass_len, g_char_min;

// ************ CODE ***************

// Hexadecimal print
void hexprint(unsigned char buf[255],int x) {
  int i;
  for (i = 0; i < x; i++) {
      if (i > 0) printf(":");
          printf("%02X", buf[i]);
  }
}

// BruteForce loop from AAA to ZZZ
void brute_force(int max_len,int char_max,int min_char) {
  unsigned char tmp_buff[255];
  int last_char, count, idx = 0;

  // remplis le buffer de AAAA
  for (idx = 0; idx <= max_len; idx++) {
    tmp_buff[idx] = min_char;
  }
  tmp_buff[0]--; // Fixup 1er Char

  // Big Loop.. tant que le dernier char est <...
  while (tmp_buff[max_len] <=  min_char    ) {
    tmp_buff[0]++;  // inc char

  // Scan et inc/dec char autour
  for (idx = 0; idx < max_len; idx++ )  {
    if (tmp_buff[idx] > char_max ) {  // inc char suivant
      tmp_buff[idx] = min_char;
      tmp_buff[idx+1]++;
    }
  }
  last_char = tmp_buff[max_len]; // Create Stringz
  tmp_buff[max_len]=0;

  // TEST print si ok
  if (mybrute(tmp_buff)) {
    printf("\nGot a Winner ---->");
    hexprint(tmp_buff,idx);
    printf ("<->%s<---- WIN\n",tmp_buff);
    exit(0);  // Break on WIN
  }
  tmp_buff[max_len] = last_char;
 }
}

// Main programm
int main() {
  for (pass_len = 1;  pass_len <= 25   ; ++pass_len) { // Bruteforcons de 1 a 25 Char (mec patient)
    g_char_max = 126; // Maximum tilda
    g_char_min =32; // min char commence au espace
    brute_force(pass_len,g_char_max,g_char_min);
  }

  printf("Welcome in 2019, i didn't find it\n");
  return 0;
}

Et le makefile qui va bien;

all: brute

force: clean brute

clean:
        rm brute *.o

brute: brute.c hash.o
        gcc -c brute.c
        gcc hash.o brute.o -o brute
        chmod +x brute

hash.o: hash.asm
        yasm -f elf -m x86 hash.asm -o hash.o

Avec ça, et la combinaison juteuse de tous les caractères printables de 0x20h ‘espace’ à 0x7Eh ’tilda’ je m’en suis sortis avant que je n’eu a me lancer dans une laborieuse version multi-threadé. Le mot de passe est apparu en pleine nuit en moins de 4h. La version ‘propre’ le fait en un peu plus de 2h.

$ time ./brute

Got a Winner ---->62:33:20:36:3E:36<->b3 6>6<---- WIN

real 124m36.048s
user 124m1.713s
sys 0m0.000s

Ce genre de truc donne envie de mettre le doigt dans Opencl la prochaine fois !

Au flag, on sent que quelque part un soft utilise cela… mais lequel !

Enter registration code: b3 6>6
Thank you, valued customer!
Your key is: day 145: they still do not realize this software sucks

 

 

 

 

 

 

This entry was posted in Asm, Challenge, Coding, Reverse and tagged , , , , , , . Bookmark the permalink.

Leave a Reply

Your email address will not be published. Required fields are marked *

AlphaOmega Captcha Classica  –  Enter Security Code