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.

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.


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