25/05/2006 à 15h17 : Challenge Securitech : mes solutions...

Bon je vais présenter dans ce billet les éléments qui m'ont permis de marquer quelques points...
En espérant que ça pourra être utile pour certains.
Pour comprendre quelque chose, je vous invite à consulter les sujets sur le site officiel.
Le code que je donne ici est "brut", c'est à dire tel quel au moment où j'ai validé l'épreuve.
C'est donc du "quick & dirty" et je préfère ne pas remanier le code... vous allez donc voir que je code comme un porc !


Challenge 9

J'ai commencé par ce challenge car j'ai vu que c'était le premier où d'autres membres ont réussi à marquer des points.
On a donc 50 images de 25x1731 pixels, qu'il faut remettre dans l'ordre.
J'imagine que la solution "manuelle" est possible, mais c'est quand même plus sympa avec un programme.
Le sujet suggère d'ailleurs une solution information en utilisant QTimage.
Personnellement, je connais déjà quelques fonctions pratiques avec SDL (grâce à zeRace notamment).
Donc l'idée est de charger toutes les bandes de papier en mémoire, puis de les trier.
On prend une bande de papier, et on la compare à toute les autres.
On parcoure la bande de pixel à droite de la première bande et la bande de pixel à gauche de la seconde.
L'image est en niveau de gris, donc j'ai fixé un seuil arbitraire : 150. Au dessous de cette intensité, le pixel est considéré comme "noir".
A hauteur égale sur la bande de papier, si le point est noir à gauche et à droite, alors on a un point de concordance, sinon on diminue le score.
Une fois qu'on a parcouru toutes les bandes, on garde celle qui a le meilleur score, c'est celle qui va à droite de la bande qu'on a choisi.
Une première boucle me permet de vérifier que la bande "43" est la première, donc je met cette valeur comme bande de début, et lorsque je trouve la suivante, je la prend comme nouvelle bande.
A chaque itération, j'affiche la bande sur la sortie standard, et je continue pour toutes les autres bandes.
A la fin j'ai donc toutes les numéros de bande dans un ordre, que je met en dur dans le code et qui me permet d'afficher toutes* les bandes dans l'ordre à l'écran, et de lire le texte.
* en fait presque toutes les bandes, ma technique ne marche pas ou mal pour les bandes "blanches" qui sont sur le bord de l'image...

Voici le code :
#include <SDL_image.h>

Uint8 getpixel(SDL_Surface *surface, int x, int y)
{
	int bpp = surface->format->BytesPerPixel;
	Uint8 *p = (Uint8 *)surface->pixels + y * surface->pitch + x * bpp;
	return p[0];
}

int main(void)
{
	SDL_Surface *f[50];
	SDL_Surface *screen;
	SDL_Rect pos;
	char temp[20];
	int i,j,x,y,g,d,match;
	int best,ii,jj;
	int n=0;
	for (i=0;i<50;i++)
	{
		sprintf(temp,"f.%.2d.bmp",i);
		f[i]=IMG_Load(temp);
	}
	
	i=43;
	while (n++<50)
	{
		best=-999999;
		for (j=0;j<50;j++) if (i!=j) // 25 x 1731 | 0..24 x 0..1730
		{
			match=0;
			for (y=0;y<1730;y++)
			{
				d=getpixel(f[i],24,y)<150;
				g=getpixel(f[j],0 ,y)<150;
				if (d || g)
				{
					if (d == g) match++;
					else match--;
				}
			}
			if (match>best)
			{
				ii=i;
				jj=j;
				best=match;
			}
		}
		printf("%d,",ii,jj);
		i=jj;
	}
	printf("\n");

	int good[50]={43,15,45,30,48,2,29,12,16,10,26,21,11,8,27,44,13,46,23,49,22,28,
	              19,24,32,39,42,9,4,20,40,41,36,37,25,47,0,5,6,3,31,1,35,14,7,17};
	SDL_Init(SDL_INIT_VIDEO);
	int flags=SDL_HWSURFACE|SDL_ANYFORMAT;
	screen=SDL_SetVideoMode(25*50,1731,32,flags);
	for (i=0;i<50;i++)
	{
		pos.x=i*25;
		pos.y=0;
		pos.w=25;
		pos.h=1730;
		SDL_BlitSurface(f[good[i]],NULL,screen,&pos);
	}
      	for (;;)
	{
		SDL_Flip(screen);
		SDL_Delay(500);
	}
}
Pour compiler : gcc niv9.c -o niv9 `sdl-config --cflags --libs` -lSDL_image
Il ne reste plus qu'à lire et à extraire le mot souhaité.
Certains on trouvé une 2ème solution pour ce niveau, je pense à une truc de stégano mais je n'ai pas trouvé...


Challenge 10

J'ai continué sur le challenge 10 car il était disponible assez tôt dans le tournoi.
Autant le dire tout de suite je n'ai pas terminé ce niveau, et j'y ai passé pas mal de temps donc je ne me souviens plus exactement dans quel ordre j'ai trouvé quoi...
Au début j'ai lancé plein de fois la commande avec des paramètres différents pour "voir" ce qui se passait.
royale@royale:~/sec/10$ ./poeut XXXXXXXXXXXXXXXXXXXX
1CB17F857D5B4BAB8268D22F376CE61C-352-59969536-0C20C20C20C20C20C2937A584F13BA3BA3BA3BA
Les tirets en les différents paquets font penser qu'il y a concaténation de plusieurs choses. Le premier paquet fait 32 caractères, et donc je teste des fonctions "connues".
royale@royale:~/sec/10$ echo -n XXXXXXXXXXXXXXXXXXXX | md5sum
1cb17f857d5b4bab8268d22f376ce61c  -
Bingo. Mais bon ce n'est pas très utile vu que MD5 n'est pas réversible.
En testant d'autres chaines ciblées, on arrive à trouver la formule derrière les 2ème et 3ème portions.
Le 2ème paquet est la somme des 4 premiers caractères, et les 4 suivants le "produit" des 4 premiers caractères.
Ca nous donne une petite équation, dont on sait que les solutions sont entières.
Ensuite on s'attaque à la dernière portion, la plus complexe, mais aussi la plus "réversible" qui va nous permettre de trouver quelques caractères.
Vu que la formule ne saute pas aux yeux, j'ai essayé de regarder dans le code binaire... Avec un éditeur hexadecimal on trouve les paroles de "Be quick or be dead" de Iron Maiden... Je pense que ça sert juste à "obscurcir" le code et que ça n'a pas d'utilité (quoique je n'ai pas fini l'épreuve donc certains caractères me manquent encore).
J'ai passé le binaire dans 2 désassembleurs : W32DASM que je connaissais un peu ne comprend pas grand chose au code, et la démo de IDA que je découvre est déjà plus doué mais le code est très difficile à lire. Je pense qu'un débogueur genre SoftICE aurait eu plus de succès mais je n'ai pas eu le courage de me pencher la dessus ;)
Je continue donc sur la technique "au petit bonheur la chance".
En "brute forçant" un peu on arrive à isoler le début de la formule : les caractères 5 à 10 codes la première portion du dernier paquet, le 10ème caractère de la chaine d'entrée détermine les 3 premiers caractères, le 9ème les 3 suivants, etc.
Je "pense" donc que la chaine solution est de la forme : ----iopkdm----------.
On trouve d'autres portions "probables" par brute force, peut-être un "mi" à la fin ?
Les caractères 11 à 16 participent à une formule commune sur laquelle j'ai buté... Ils donnent une portion de la chaine de sortie, par exemple :
----iopkdm---a----mi: FCDB2D33
----iopkdm---b----mi: FE9D936A
----iopkdm---c----mi: FF5FF95D
----iopkdm---d----mi: FA10EFD8
----iopkdm---e----mi: FBD285EF
----iopkdm---f----mi: F9943BB6
----iopkdm---g----mi: F8565181
----iopkdm---h----mi: F30A16BC
----iopkdm---i----mi: F2C87C8B
----iopkdm---j----mi: F08EC2D2
----iopkdm---k----mi: F14CA8E5
----iopkdm---l----mi: F403BE60
----iopkdm---m----mi: F5C1D457
----iopkdm---n----mi: F7876A0E
----iopkdm---o----mi: F6450039
----iopkdm---p----mi: E13FE474
----iopkdm---q----mi: E0FD8E43
----iopkdm---r----mi: E2BB301A
----iopkdm---s----mi: E3795A2D
----iopkdm---t----mi: E6364CA8
----iopkdm---u----mi: E7F4269F
----iopkdm---v----mi: E5B298C6
----iopkdm---w----mi: E470F2F1
----iopkdm---x----mi: EF2CB5CC
----iopkdm---y----mi: EEEEDFFB
----iopkdm---z----mi: ECA861A2
J'ai isolé les caractères qui m'intéressent en sortie, et on devine une logique (mais laquelle ?).
Bref j'ai arrêté là vu que ça coince, mais j'espèrais qu'avec ces 6 caractères et les contraintes qu'on a sur les 4 premiers, le problème doit pouvoir ce finir par "brute force", par exemple en vérifiant le MD5.
Et j'ai squizzé mes expériences les plus obscures vu qu'il n'y a pas grand chose qui en ressort...


Challenge 14

On attaque la série des challenges "injections SQL". Ces exos ont mieux marché pour moi vu que je suis plus au courant de ce genre de problèmes lors de la création/maintenance de sites webs.
Dans la trace fournie pour cet exo, on trouve l'adresse d'un site web et un login / password valide.
Dans la page de "recherche" d'amis, la variable "hobbies" est vulnérable à une injection SQL.
Par exemple, on recopie le formulaire chez soi et on le bidouille avant de le soumettre. J'ai mis les champs "hobbies[]" en texte libre au lieu de cases à cocher, comme ça je peux y injecter ce que je veux. Si ça ne marche pas je reviens en arrière et je tente autre chose...
Si on y met "toto" on obtient un message d'erreur du genre "column `toto` invalid".
On devine que la table a une colonne "id" vu que c'est un des paramètres du formulaire dans d'autres pages.
Si on met "id" dans la variable hobbies[], on obtient "Arnaud" comme résultat, qui est le membre d'identifiant 1.
Si on essaye d'injecter une UNION, par exemple en mettant dans la variable hobbies[] :
id`=1 UNION SELECT *** FROM ***
On obtient une erreur car la fin de la requête SQL qu'on ne maitrise pas plante. Heureusement on peut la commenter avec le caractère #.
On veut savoir quel sont les noms des tables (on ne sait pas encore), donc on essaye différentes méthodes, et celle de MySQL 5 fonctionne, on injecte :
id`=1 UNION SELECT table_name,2,3 FROM INFORMATION_SCHEMA.TABLES #
Le 2 et le 3 en dur servent à avoir le bon nombre de colonnes pour l'union, sinon on a un message d'erreur. Du coup on "voit" le nom des tables s'afficher dans le formulaire de sortie, à la place des identifiants des membres qui correspondent à la recherche.
Les tables "SECRET_DATA" et "_USER_E_MEET_TB_" en particulier attirent l'oeil.
Pour trouver les colonnes de ces tables, on injecte :
id`=1 UNION SELECT column_name,2,3 FROM INFORMATION_SCHEMA.columns #
Petite astuce supplémentaire, la table "SECRET_DATA" n'est pas dans le même "schema" que les autres tables, il faut donc la préfixer.
On arrive à récupérer une première solution avec l'injection suivante :
id`=1 UNION SELECT 1,text,3 FROM PRIVATE_DATA.SECRET_DATA #
Qui affiche directement la colonne "text" de l'unique ligne de la table SECRET_DATA qui contient la solution.
On arrive aussi à trouver des infos sympas dans la table "_USER_E_MEET_TB_" :
id`=1 UNION SELECT 1,username,password FROM _USER_E_MEET_TB_ #
Mais les mots de passe semblent difficiles à casser, donc je n'ai pas poussé plus loin...


Challenge 4

Un challenge de cryptographie, dont on devine que la solution est "abordable" comme le texte n'est pas entièrement crypté.
La ponctuation est inchangée, et les lettres restent des lettres...
Au début j'essaye sans succès une approche statistique. On trouve plusieurs fois un mot qui semble être le même et qui se fini pareil :
MUABJ-KHCWFC
QZBKJ-QELMFC
ZFRIS-PUQLFC
YOZNW-GUQLFC
FWFMM-PUQLFC
UACEI-OVVLFC
EHIKE-KUQLFC
MICEY-UUQLFC
XCZUV-GUQLFC
YOQTQ-GUQLFC
UFRIS-PUQLFC
ESHEX-TRQLFC
FQBGX-KECSFC
OHVOI-UUQLFC
...
Le problème c'est qu'il y a beaucoup de mots qui peuvent correspondre à ce motif :
royale@royale:~/sec/4$ egrep '^.....-......$' /usr/share/dict/french 
aigue-marine
amour-propre
amuse-gueule
anglo-saxons
après-demain
après-guerre
après-rasage
avant-centre
avant-guerre
avant-postes
avant-projet
avant-propos
avant-trains
avant-veille
...
Donc je ne vais pas plus loin sur cette piste.
On essaye donc des algorithmes "classiques" comme Cesar, Vigenere (je découvre les noms officiels en même temps que le challenge).
Pour Vigenere, une technique d'attaque classique est celle du "mot probable" : http://www.apprendre-en-ligne.net/crypto/vigenere/motprobvig.html.
Ici, on ne sait pas de quoi le texte parle, mais on a la ponctuation qui aide à deviner des mots possibles.
Je vous passe tous mes essais infructueux... Puis j'ai fini par tomber sur :
YX'ENJBXZV'XOI
Il n'y a pas beaucoup de cas possibles où on a 2 apostrophes, et surtout en fin de mot. Le bloc est en fait : "QU'AUJOURD'HUI".
J'ai le petit programme suivant qui me donne "l'écart" entre le mot codé et le mot en clair, et j'espère y trouver la clef :
int main(int argc, char *argv[])
{
	int i;
	int t;
	for (i=0;i<strlen(argv[1]);i++) if (argv[1][i]>='A' && argv[1][i]<='Z')
	{
		t=argv[1][i]-argv[2][i];
		printf("%d ",t);
	}
	printf("\n");
	for (i=0;i<strlen(argv[1]);i++) if (argv[1][i]>='A' && argv[1][i]<='Z')
	{
		t=argv[1][i]-argv[2][i];
		if (t<0) t+=26;
		printf("%c",t+'A');
	}
	printf("\n");
}
Mais ça ne me donne rien d'exploitable :
royale@royale:~/sec/4$ ./vig YXENJBXZVXOI QUAUJOURDHUI
8 3 4 -7 0 -13 3 8 18 16 -6 0 
IDETANDISQUA
En continuant la même approche, je tombe sur une autre portion du texte :
- PH ! WNX, EC'L T-T-PZ YVSIPE ?
Les multiples tirets font penser à "A-T-IL", le "..'." est sûrement "QU'Y" vu qu'on a une forme intérrogative. Par extension je suppose que la phrase complète est peut-être :
- AH ! NON, QU'Y A-T-IL ENCORE ?
Et effectivement le résultat est meilleur :
royale@royale:~/sec/4$ ./vig PHWNXECLTTPZYVSIPE AHNONQUYATILENCORE
15 0 9 -1 10 -12 -18 -13 19 0 7 14 20 8 16 -6 -2 0 
PAJZKOINTAHOUIQUYA
La fin de la chaine de code supposée est bizarrement "AH OUI QU'Y A", en fait je me suis trompé, ce n'est pas "AH NON" mais "AH OUI" :
royale@royale:~/sec/4$ ./vig PHWNXECLTTPZYVSIPE AHOUIQUYATILENCORE
15 0 8 -7 15 -12 -18 -13 19 0 7 14 20 8 16 -6 -2 0 
PAITPOINTAHOUIQUYA
En fait c'est une variante de Vigénère, où le code est le texte lui même, décalé de 9 caractères.
Cette clef me permet de déchiffrer le texte à partir de la phrase "AH OUI QU'Y A-T-IL ENCORE...", mais forcément le mot que je cherche (la solution de l'énigme) est avant cette phrase, et mon approche ne me permet pas de remonter.
Mais avec la fin du texte et une recherche ciblée sur Google, on retrouve le texte complet original.
Avec ce texte on retrouve le début de la clef, et on peut déchiffrer tout le texte (oui il faut encore le déchiffrer car le mot est volontairement mal orthographié dans la solution de l'énigme).
Voici mon décodeur :
#include <stdio.h>

int main(int argc, char *argv[])
{
	char last[10]="RECLUSION";
	char c;
	int i;
	while (!feof(stdin))
	{
		c=getchar();
		if (c>='A' && c<='Z')
		{
			c-=last[0]-'A';
			if (c<'A') c+=26;
			for (i=0;i<8;i++) last[i]=last[i+1];
			last[8]=c;
		}
		putchar(c);
	}
}
Avec le programme fourni en annexe, on extrait facilement le mot solution.
Un effet amusant de la méthode de crypto utilisée est ce que j'ai remarqué au début, le fait que certaines statistiques de mots apparaissent.


Challenge 2

Bon là je n'ai pas gardé tous les détails... Mais l'application est encore une fois vulnérable à une injection SQL.
Une simple apostrophe suffit à faire planter le programme, qui en plus est "bavard" et donne des explications.
Plus précisément, c'est de l'XPath injection : http://www.webappsec.org/projects/threat/classes/xpath_injection.shtml.
Bref, on arrive à l'injection suivante :
' or position()=2 or ''=
Qui nous logue en admin.
Il y a également une 2ème injection possible sur l'autre champ :
' union select 1,'a',* from __confidential__ offset '0
Qui donne une autre solution.


Challenge 1

Là encore une injection SQL, l'apostrophe fait planter le formulaire qui donne beaucoup (trop) de détails.
On devine une requête de vérification du login/password de la forme :
SELECT ... FROM membres WHERE login='$login' AND password='$password';
En mettant ' OR 'a'<>' dans les 2 variables, on passe en force, puisque le serveur execute une requête bidon :
SELECT ... FROM membres WHERE login='' OR 'a'<>'' AND password='' OR 'a'<>'';
Mais il y en a un deuxième formulaire d'authentification derrière que je n'ai pas réussi à passer.


Bon je passe rapidement sur les échecs : Je n'ai pas eu le temps de regarder les autres sujets, ou bien j'ai juste "zieuté" pour deviner qu'ils étaient difficiles :p

En tout cas l'expérience était intéressante, et le classement général fait peur puisqu'il y a des acharnés qui ont tout trouvé (ou presque) !

Commentaires

Pseudo :
Site : (facultatif)
Email : (facultatif, pour votre avatar)
Message :
(pas de HTML)
: (recopiez le texte)
Se souvenir de mes informations

Skorn [ 26/05 - 17:11 ] : Sympa toutes ses explications royale ! Merci bien !

Moi ca me botte grave pour l'an prochain. Bien joué en tout cas ! Le vignere est bien mignon ! :p

Jo [ 26/05 - 15:11 ] : Sympa tout ça. Dommage qu'il faille encore s'inscrire pour visualiser les sujets, même si le challenge est terminé.

Je sais que je n'aurais jamais eu le temps de faire ça alors je ne me suis pas inscrit.

Royale [ 26/05 - 01:50 ] : Ok merci pour ces précisions :)

devloop [ 26/05 - 01:13 ] : Sympa tout ça :)
De mon côté j'ai laissé tomber le 10 très vite... le code était vraiment trop difficile à lire.
Pour le 9 j'ai retrouvé l'odre "à la main" sous Gimp puis j'ai recollé proprement avec la commande convert sous Linux et l'option +append.
Effectivement pour le second validateur c'était de la stéganographie, il fallait utiliser steghide avec le pass du premier validateur...
J'ai failli passer à côté car j'étais persuadé que steghide ne gérait pas le bmp :-/

Pour le 15 l'exploitation était très limitée, principalement parce que l'on ne pouvait pas remonter beaucoup dans l'arborescence...
Heureusement le disp.cgi qu'il fallait récupérer n'était pas loin...
Le système était juste une concaténation de ce qu'il y avait avant le point d'exclamation et après
Je me souviens plus exactement mais il fallait demander quelque chose comme
http://site/..?../cgi-bin/displ.cgi

J'ai pu faire le 8 mais je suis moins chaud pour écrire une correction... il me faudrait beaucoup de temps en de courage

Royale [ 25/05 - 15:57 ] : La solution pour le Challenge 3 : http://devloop.lyua.org/blog/index.php?2006/05/20/255-solution-de-lapreuve-forensics-du-challenge-securitech-2006
Pfiou, je ne regrette pas d'avoir évité cet exo :p (bon j'avoue j'avais quand même installé vmware, lancé l'image et tapé quelques commandes, mais c'est tout !).