Mass Surveillance Software

lien pour télécharger l'archive : http://repo.zenk-security.com/ctfs/ndh2k15/mass.tar.gz

Unpack

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

Analyse

Desobfuscation

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.

Reverse des routines de validation

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.

Code de résolution

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/