lien pour télécharger l'archive : http://repo.zenk-security.com/ctfs/ndh2k15/mass.tar.gz
Le binaire était packé avec un upx modifié, il suffisait donc de voir le pushad à 0x004460A0, de chercher le popad qui est à 0x00446235 et le tail jump pas très loin (à 0x00446243) qui pointe vers l'OEP : 0x004015A7
On utilise les outils classique LordPE / ImpRec pour avoir une version du binaire unpackée
Une fois unpacké, le binaire a été chargé dans IDA pour une analyse statique. Le code était très légèrement obfusqué avec des instructions inutiles qui permettaient de désaligner le code. IDA est alors perdu et affiche un code qui n'a rien à voir avec celui qui s’exécute réellement. Le premier travail de l'analyste est donc de “proprifier” un peu tout ça :
.text:00401642 mov dword ptr [esp-4], (offset 40164A+1) .text:0040164A mov esi, 0E6FF465Eh .text:0040164F call dword ptr [esp-4]
Une adresse qui se situe au milieu de l'instruction “mov esi, 0E6FF465Eh” est placée sur la pile, puis un call est fait sur cette adresse. Le code est donc désaligné, et le vrai code exécuté correspond alors à :
pop esi inc esi jmp esi
En bref, le call place l'adresse de retour sur la pile, qui est “popé” dans esi. La suite du code est donc à <adresse retour> + 1 !!! C'était la seule obfuscation du code, le reste du travail est alors de trouver la routine qui s’exécute lorsque les 14 caractères du flag sont entrés par l'utilisateur.
Nous avons affaire à une fenêtre windows, donc il faut trouver la callback qui lui est fourni et qui s'occupe de gérer tous les évènements qui s'applique à ladite fenêtre. Classiquement, la routine fait un switch sur les types d’évènements (ou “message” dans le langage Windows) qui lui arrive.
On remarque que quand le message WM_INIT_DIALOG (autrement dit quand la fenêtre s'ouvre) arrive, une nouvelle routine est affectée à la place de l'ancienne. Qu'à cela ne tienne, on va voir ce que fait la deuxième. Cette dernière s'occupe des messages WM_CHAR (une touche a été pressée). Ca tombe vachement bien, c'est ce qu'on fait quand on entre le flag à trouver…
La routine attend que 14 caractères aient été entrés, puis appelle une première routine de hashage sur les 7 premiers caractères et une second sur les 7 derniers.
Routine 1 : .text:004010C5 xor al, PasswordEntered[ecx] .text:004010CB rol eax, 7 .text:004010CE imul eax, edx .text:004010D1 not eax .text:004010D3 imul eax, edx .text:004010D6 ror eax, 2 .text:004010D9 loopne loc_4010C5
Routine 2 : .text:00401245 xor al, (PasswordEntered+7)[ecx] .text:0040124B ror eax, 2 .text:0040124E imul eax, edx .text:00401251 not eax .text:00401253 rol eax, 7 .text:00401256 loopne loc_401245
Rien de bien sorcier en l’occurrence. Une fois les hash calculés, il sont comparés avec deux valeurs hardcodées. Une autre indication nous est donné un peu plus loin; le flag devait commencer par 'M' et avoir un 'd' en 8ème position.
L'analyse statique terminée, il ne nous est plus resté qu'à écrire le code pour bruteforcer les combinaisons possibles et trouver le flag.
J'ai repris le script python de FlyingWolf que j'ai recodé en C et j'y ai rajouté le bruteforce (de manière dégueulasse)
#include <stdio.h> #include <stdlib.h> #include <string.h> #define rol(x,n) ((x << n) | (x >> (32-n))) #define ror(x,n) ((x >> n) | (x << (32-n))) unsigned int hash1(char *str) { int i=0; unsigned int eax = 0xC1AC0FF3; unsigned int edx = 0xBADB0155; for(i=6; i>0; i--) { eax ^= str[i]; eax = rol(eax,7); eax = (eax * edx) & 0xFFFFFFFF; eax = ~eax; eax = (eax * edx) & 0xFFFFFFFF; eax = ror(eax,2); } return eax; } unsigned int hash2(char *str) { int i=0; unsigned int eax = 0x17A5F4CC; unsigned int edx = 0x0B194553; for(i=6; i>0; i--) { eax ^= str[i]; eax = ror(eax, 2); eax = (eax * edx) & 0xFFFFFFFF; eax = ~eax; eax = rol(eax, 7); } return eax; } int main(int argc, char **argv) { char *alphabet = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"; char str[7]; unsigned int out; int i, j, k, l, m, n, o; str[0] = 'M'; for(j=0; j<strlen(alphabet); j++) { str[1] = alphabet[j]; printf("%c\n", alphabet[j]); for(k=0; k<strlen(alphabet); k++) { str[2] = alphabet[k]; for(l=0; l<strlen(alphabet); l++) { str[3] = alphabet[l]; for(m=0; m<strlen(alphabet); m++) { str[4] = alphabet[m]; for(n=0; n<strlen(alphabet); n++) { str[5] = alphabet[n]; for(o=0; o<strlen(alphabet); o++) { str[6] = alphabet[o]; out = hash1(str); if(out == 0x8AD3629B) printf("FOUND : %s\n", str); }}}}}}; return 0; }
Pour avoir la deuxieme partie du flag, on réutilise le même code mais on remplace out == 0x8AD3629B par 0x25B978C2, str[0] = 'M' par str[0] = 'd' et hash1(str) par hash2(str)
Le problème c'est qu'il y a des collisions, on trouve plusieurs hashes valides. Lorsque l'on valide l'épreuve, un message de félicitation est affiché en allemand et un hint a été donné plus tard par l'équipe de hzv avec aussi une phrase en allemand.
En regardant les différents hashes valides, on s'aperçoit qu'on a deux hashes qui ressemblent à des mots allemands :
en les concaténant, on valide enfin l'épreuve \o/