GreHack WriteUps

Tristan Leiter
Blog cybersécurité

Une équipe de Navixia s'est rendue le 15 novembre au symposium international de hacking éthique GreHack 2013. Au programme de la journée, plusieurs conférences, suivies d'un Capture The Flag qui a duré jusqu'au petit matin. Nous présentons ici deux challenges proposés durant ce CTF.

Le premier challenge (WTF) se compose de Javascript obfusqué, entre autres, dans les méandres de la librairie jQuery!

Le second challenge fait appel à un langage de programmation ésotérique du nom de Befunge93.

WTF (Web)

Ce challenge se présentait sous la forme d'une page web et de plusieurs javascripts.

Une analyse des sources montre qu'il est constitué d'une page web wtf.html qui charge deux javascripts:

  • jquery-1.10.2,min.js (librairie JQuery)
  • wtf.js (Un javascript maison)

La page web se compose d'un champ éditable et d'un bouton. Celui-ci appelle une fonction kek() définie dans wtf.js

Le contenu de wtf.js est le suivant:

var YOU_WANT_THIS_RIGHT = "Congratz! That's the flag!";
var kek=function(){eval(atob("ZXZhbChhdG9iKCJaWFpoYkNoaGRHOWlLQ0pVZWtFNVdtNVdkVmt6VW5CaU1qUnZXbE5zTjJSdFJubEpSemh6VkRJNGMxUXhPVkJRVm5Sa1R6SmFkbU5wYUhaUVYxcDJZMjVTTlZnelVqTmllV2R3VHpJNE9GcFROWE5hVnpWdVpFZG5jbHB0T1hsa1NHeG1aRWhrZGt0RGF6ZGllakJuWWpFNGIySjVhM0JsTURsbVZERjBka3hYV25aamJsSTFXRE5TTTJKNVozQllWREZtUzBkVmRWa3lhR2hqYTA1MldrZFdRbVJEYUhaTVYxcDJZMjVTTlZnelVqTmllV2R3UzFZMVptSjVOV3BoUjBaNVVUSTVhMXBWUmpCTFJ6aDBXbTA1ZVdSSWJHWmtTR1IyUzBOcmNFdFVkRGxVTVRsUVVGZFJiMVF4T1ZCTFZIUjVXbGhTTVdOdE5HZFVNVGxRVHpNd055SXBLVHRsZG1Gc0tHRjBiMklvSW1SdFJubEpSamxtVUZkYU1XSnRUakJoVnpsMVMwTnNOMXB1Vm5WWk0xSndUVWMwT1ZsWGVHeGpibEUzV0hveFZHUklTbkJpYldOMVdtNUtkbUpWVG05WldFcEVZakpTYkU4eE9IZGxSMUY0V2tSSk9WZDVTbU5sUkZwRVdFaG5NbEpzZURST2EwNWpaVVJKZDFoSVp6SlNWbmcwVG10YVkyVkVZM2RZU0djeVRsTktaRTh4T0hkbFJGWnBUakpWT1ZkNVNtTmxSRnBFV0Vobk1sSnNlRFJPYWs1alpVUlplRmhJWnpOT1JuZzBUbXBzWTJWRVdrZFlTR2N5VWxOSmMwbHNlRFJPYW1oalpVUmpNRmhJWnpOT1JuZzBUbnBDWTJWRVRrSllTR2Q1VW14NE5FMXJXbU5sUkdNeldFaG5NMDR4ZURST2VtUmpaVVJLUmxoSVp6TlBWbmcwVG10YVkyVkVZekZZU0djelRrWjRORTU2Vm1ObFJGbDVXRWhuTWs1V2VEUk5hMVpqWlVSWmVsaElaekpTYkhnMFRtdFNZMlZFU2tkWVNHY3pUakY0TkU1cVJtTmxSR013V0Vobk1rMHhlRFJPYW1oalpVUk9SMWhJWnpOT2JIZzBUVEJTWTJWRVkzZFlTR2N5VDFaNE5FNVVXbU5sUkZwR1dFaG5NRTFXZURST2VrcGpaVVJqZDFoSVozcFBWbmcwVGxWR1kyVkVVVEZZU0dkNlRVTktaRTh4YUdabFJERnJZakpPTVdKWFZuVmtSSFJtWW5veGNWVllWbXhqYm10MVpFYzVWR1JJU25CaWJXTnZTV3hTYjFwVFFuZFpXRTU2WkRJNWVWcERRbkJqZVVJd1lqTlNhR0pIZURWSlJHOW5XVEpGTlU0eVdUSlBSMDV0VDBSb2JFMUhVWHBaYW14cVRtcFZlVmw2UVRGWmVtTXlUVlJyTVU1NlZXbExWSFIyWW5vd2EwdERTbU5sUkVsNlNXbHphVmhJWnpOTlJuZzBUbXBHWTJWRVkzcFlTR2N6VFRGNE5FNTZaR05sUkZwSFdFaG5NMDFzZURST2FsRnBTMVp6YVZoSVp6Tk9iSGcwVG1wR1kyVkVXa1JKYkRCdlMxUnpka3RzVW05aFdFMW5XVEk1ZEdKWFZuVmtRMEl6WVZkNGMwbElRbmxpTWtwb1dXMTROVWxIYUd4aVNFRnhUREk1VUV0SE9YWkxWSFI1V2xoU01XTnROR2RhYlVaell6SlZOMlpUZUhaVWVqRnRaRmMxYW1SSGJIWmlhV2h3V0RKcmNHVXliR1poVkRGUVMwZHNabUZUYXpkaFYxbG5TMGRzWm1GVU1EbEpiRUkxWlVWb1RWRXpUbnBXYld3elRtdDRSMk16VGs5aFdHaHFWRVZqTkdNeVNqVmtNMHBOVWxWV2VsUXpiRFJrUlhoRFZGaE9TV0ZZWkhaVVJWSk9ZekJ3UkdRd2JFMVNSMDU2VlRKc00xRXdlRUpQU0U1UVZUTm9URlJGVFhkak1WbzFaSGs1VFZGc1JucFpibXcwVjFWNFJWa3pUa1psV0dSWlZFVktkbU13ZEVSbFJGSk5VV3BvZWxOR1RqTlBSWGhIVkZoT1RGRXpZelZKYVd0blpURm9abVZHZEdaTlNHY3hXV3BrYkZkNlFtUllWREZtVFVobk1WbHFaR3hYZWtaa1R6TXhiR0pJVG14SlIyeHRTMGRzWm1GVU1EbEphelZVWkRJeFRWRlhPWHBTYm13elQxVjRSRkpZVGxGVk0yUmFWRVZXU21NeFZsUmtNalZOVWxaV2VsSlZUalJsUlhoRlZsaE9VMlZZWkhwVVJWSXpZekJ3TldRd1ZrMVJNazU2VjI1c00wMUZlRU5XV0U1SFZUTm9UMVJGVG5Kak1VNVVaSGs1VFZKRVFucFphMDR6VTBWNFFtUXpUbGhsV0doUFZFVldUbU13ZUVSbFNFcE5VWHBTZWxkSWJETmphM2hKVGtoT1NXRllhSFZVUldSS1l6SktWR1ZGWkUxVFJrWjZWakpzTkZWcmVFVk5TRTVSVlZRd09VbHBiRGRhYmxaMVdUTlNjRTFITkc5WFZUbFdXREZrUWxSc1VtWldSV2hLVlRFNVUxTlZaRWxXUTJzM1psZFdjMk15VmpkYWJsWjFXVE5TY0UxSE5HOVlla0kwV2tSR2EwMXNjM2RZVTJzM1psZ3djMkl4T0RsYWJsWjFXVE5TY0dJeU5HOWllV3czWW5semNrOHpTbXhrU0ZaNVltbENkazh6TURkWU1UaHZTMVJ6UFNJcEtUcz0iKSk="));
return false;
}
var forty_two = function() {
    /* Always useful */
    return 42;
}

Nous nous attaquons à la désobfuscation de ce code et plus particulièrement à la longue chaîne de caractères constituée de plusieurs couches successives de "eval" et de atob (base64 encode).

Après un peu de travail nous nous trouvons avec quelque chose de plus clair, enfin presque:

O0 = function (e) {
    var o, Oo, O_O = [];
    for (o = forty_two(); o  e.length + forty_two(); o = o_(o)) {
        O_O[o - forty_two()] = _(echarCodeAt(o - forty_two()) ^ _o.charCodeAt(o - forty_two()));
    }
    O_O = d(O_O);
    return O_O;
};
var __ = function () {
    functi0n = alert;
    _ = String.fromCharCode;
    _0xd1d2 = ["x6Cx6Fx6Cx20x6Ex6Fx70x65"];
    _0x5b7e = ["x6Cx6Fx63x61x74x69x6Fx6E", "x68x74x74x70x3Ax2Fx2Fx77x77x77x2Ex79x6Fx75x74x75x62x65x2Ex63x6Fx6Dx2Fx77x61x74x63x68x3Fx76x3Dx70x69x56x6Ex41x72x70x39x5Ax45x30"];
    X_x = document;
    _o = jQuery.toString("The password is totally : ca97f68cf88e0d3b9c652c05c7619575");
    oo = $("x23" + "x70x61x73x73x77x6Fx72x64")["x76x61x6C"](); /*This comment will probably help*/
    oO(oo);
    return false;
}
oO = function (i_i) {
    i_i = O(i_i);
    if (i_i == "PyxHLCssViw6LFssNixcLG8sbywrLEEsOyxtLBMsHiwoLDMsJCwILDcsSiwCLA8sOSxKLC0sVyw/LBQsbyxYLDcsEywXLBosKCx4LB8sHSw8LFMsKCw9"){
        X_x[_0x5b7e[0]] = _0x5b7e[1];
    } else if (i_i == "NSwmLAosFyw9LCEsPSwYLEIsUSwnLEUsECxxLDUsRywsLDwsJywELCcsZyw0LBUsFSxNLCksSSw/LD0sbCwHLAwsWyxNLEMsLCxrLC4sXywrLH4sHixnLGIsbSxGLHQsWixRLD0sPQ==") {
        functi0n(YOU_WANT_THIS_RIGHT);
    } else {
        functi0n(_0xd1d2[0]);
    }
}

Nous repérons rapidement la chaîne: "The password is totally : ca97f68cf88e0d3b9c652c05c7619575"...trop facile!

En essayant de valider, nous nous faisons rediriger vers une page youtube... http://www.youtube.com/watch?v=piVnArp9ZE0. Le message est assez explicite!

Le script gère donc le cas particulier où on lui donne en entrée le password ca97f68c..., puisque pour tout autre password la page retourne une alerte avec le message "lol nope". Nous mettons cette info de côté pour la suite.

Nous continuons en nettoyant le code et en enlevant les fonctions inutiles. Un point à noter, les fonctions 0(), d() et e() ne sont définies nulle part dans wtf.js. On cherchant un peu, nous les retrouvons cachées dans le jquery-1.10.2,min.js.

var YOU_WANT_THIS_RIGHT = "Congratz! That's the flag!";
var kek=function(){
var func1 = function () {
    _0xd1d2 = ["x6Cx6Fx6Cx20x6Ex6Fx70x65"];
    _0x5b7e = ["x6Cx6Fx63x61x74x69x6Fx6E", "x68x74x74x70x3Ax2Fx2Fx77x77x77x2Ex79x6Fx75x74x75x62x65x2Ex63x6Fx6Dx2Fx77x61x74x63x68x3Fx76x3Dx70x69x56x6Ex41x72x70x39x5Ax45x30"];
    X_x = document;
    iv = jQuery.toString("The password is totally : ca97f68cf88e0d3b9c652c05c7619575");
    pass = "ca97f68cf88e0d3b9c652c05c7619575"; //$("x23" + "x70x61x73x73x77x6Fx72x64")["x76x61x6C"](); /*This comment will probably help*/
    func2(pass);
    return false;
}
func2 = function (pass) {
    //i_i = O(i_i);

    val22 = func3(btoa(pass));

    if (val22 == "PyxHLCssViw6LFssNixcLG8sbywrLEEsOyxtLBMsHiwoLDMsJCwILDcsSiwCLA8sOSxKLC0sVyw/LBQsbyxYLDcsEywXLBosKCx4LB8sHSw8LFMsKCw9") {
        document[location]] = "http://www.youtube.com/watch?v=piVnArp9ZE0";
    } else if (val22 == "NSwmLAosFyw9LCEsPSwYLEIsUSwnLEUsECxxLDUsRywsLDwsJywELCcsZyw0LBUsFSxNLCksSSw/LD0sbCwHLAwsWyxNLEMsLCxrLC4sXywrLH4sHixnLGIsbSxGLHQsWixRLD0sPQ==") {
        console.log(YOU_WANT_THIS_RIGHT);
    } else {
        console.log(_0xd1d2[0]);
    }
}
func3 = function (e) {
    var i;
    var result = [];

    for (i = 0; i < e.length; i++) {
        result[i] = String.fromCharCode(e.charCodeAt(i) ^ iv.charCodeAt(i));
    }
    result = btoa(result);
    return result;
};
func1();
;return false;
}

Le code devient plus clair à comprendre: La fonction func1 définit plusieurs constantes et appelle la fonction func2 avec, comme paramètre le password introduit par l'utilisateur.

La fonction func2 commence par encoder ce password en base64 puis passe le tout en paramètre de la fonction func3. Le résultat est ensuite évalué par rapport à deux chaînes de caractères. S'il est égal à la première chaîne on est redirigé vers la page YouTube, s'il est égal à la deuxième le message "Congratz! That's the flag!" s'affiche.

Le cas du password trouvé auparavant est donc traité; spécifiquement par le script. Cette info va nous être très utile par la suite car elle nous permettra de valider la justesse de notre fonction permettant de renverser le password.
En donnant comme paramètre "PyxHLCssViw6LFssNixcLG8sbywrLEEsOyxtLBMsHi..." (ligne 17) nous devrions retomber sur le password ca97f68cf88e0d3b9c652c05c7619575 Finalement, la fonction func3 encode le paramètre en entrée en faisant un xor avec un seed donné (iv dans le code) puis en retransformant le tout en une chaîne de caractères.

Il nous suffit donc d'écrire une fonction qui calcule l'inverse et de lui donner comme input la variable val22 qui est checkée dans la fonction func2. Petite subtilité, la fonction btoa encode en base64 le password sous la forme d'un tableau. Au lieu de prendre le tableau comme une chaîne de caractères, celle-ci est encodée sous sa forme imprimée, c'est à dire base64("a,G,u,V,i,m,6") au lieu de base64 ("aGuVim6"). Il faut faire un split du résultat pour reconstruire le tableau correspondant en utilisant la fonction split sur le caractère ",".

giveMeTheFlag = function () {
    var iv = jQuery.toString("The password is totally : ca97f68cf88e0d3b9c652c05c7619575);
    var hash = atob("NSwmLAosFyw9LCEsPSwYLEIsUSw...to be completed");

    var hashSplited= hash.split(",");
    var i;
    var resultEnc = "";
    for (i = 0; i < hashSplited.length; i++) {
        resultEnc = resultEnc + String.fromCharCode(hashSplited[i].charCodeAt(0) ^ iv.charCodeAt(i));
    }
    result = atob(resultEnc);
    console.log("sortie f4="+result);
};

Ce script est censé; nous sortir le password, cependant nous avons une erreur à l'exécution car la chaîne resultEnc n'est pas une chaîne valide base64.

En testant chaque étape, avec la string attendue "NSwmLAosFyw9LCEsPSwYLEIsUSwnLEUs...." nous constatons qu'une fois encodé avec btoa (au début de la fonction giveMeTheFlag), le tableau du hash contient "étrangement" des virgules pour certaines valeurs, notre split(",") n'est donc pas valable pour séparer chacune d'entre elles.

//var hashSplited= hash.split(",");
var hashSplited=[];
for(i=0;ihash.length divide by 2;i++){
    hashSplited[i] = hash.charAt(2*i);

Cette fois c'est réussi, le script nous sort le bon flag!

It's gonna be fun (Misc)}

L'un des challenges du groupe "Misc" nous livrait uniquement le code source suivant:

code.png

Impossible de reconnaître à première vue le langage utilisé, néanmoins les habitués des CTF savent bien que les langages un peu ésotériques sont souvent de la partie. On utilise la bonne référence Wikipedia: "Esoteric programming languages". Et heureusement pour nous, la première référence "Befunge" nous livre suffisamment d'explications pour nous permettre de déduire que nous sommes bien face à un code en langage Befunge93.

La particularité du langage est bien résumée par Wikipedia: "Befunge is a stack-based, reflective, esoteric programming language. It differs from conventional languages in that programs are arranged on a two-dimensional grid. ‘Arrow’ instructions direct the control flow to the left, right, up or down, and loops are constructed by sending the control flow in a cycle."

Avant de se plonger dans la lecture fastidieuse de ce code (et d'après les instructions Befunge), nous allons d'abord tenter d'exécuter le code source que nous avons à notre disposition. Il existe de nombreuses solutions pour exécuter ce code Befunge mais nous avons retenu deux outils:

  • Tim's Befunge Compiler (TBC): Prend en entrée un code Befunge (.bf) et produit un code ANSI C avec le .bf placé dans un tableau et une adaptation en C de l'interpréteur de référence b93. Ceci permet d'obtenir, après compilation, un exécutable ELF.
  • jsFunge IDE (http://befunge.flogisoft.com/): Un éditeur, interpréteur et debugger Befunge en JS. L'outil ne supporte malheureusement pas toutes les instructions Befunge, en particulier l'input utilisateur (la commande ~) dont nous aurons besoin pour ce challenge. Nous allons l'utiliser uniquement pour l'analyse et la compré;hension du code. Pour les tests avec input utilisateur, nous utiliserons l'exécutable compilé à partir du code C généré par TBC.

Commençons par lancer une première fois le programme :

./tbc igbf.bf > igbf.c
gcc igbf.c –o igbf
./igbf

Le programme semble attendre un input utilisateur. En jouant un peu avec les inputs, nous croyons comprendre que le programme attend un input précis de quelques caractères (un "password") pour nous délivrer le précieux flag qui nous permettra de valider le challenge.

igbf-1.png

Plongeons nous dans le code et utilisons le debugger jsFunge pour appréhender un peu ce langage obscur. "A picture is worth a thousand words", le gif animé suivant exprime bien la complexité de compréhension d';un code Befunge. Il est important de comprendre quelques concepts :

Les flèches (<>v^) orientent l’exécution du code ; Les instructions opérent sur les éléments de la pile (« stack ») ; L’instruction « ~ » attend un input utilisateur et pose la valeur ASCII sur le haut de la pile L’instruction « | » est un branchement logique (similaire à un if) qui oriente l’exécution vers le bas (v) si le haut de la pile est égal à 0, et vers le haut (^) sinon. La liste des instructions est plutôt claire sur Wikipedia pour le reste : https://en.wikipedia.org/wiki/Befunge

loop.gif

Comme on peut le voir sur le gif animé, nous avons une première boucle qui récupère plusieurs inputs utilisateurs (ligne 5) et s'achève avec un branchement « | ».

Après un peu de réflexion et d’observation de la stack, on peut voir que le 4 de la première ligne (juste avant le « ^ ») est en fait un compteur de taille d’input qui se décrémente en ligne 20 (le -1 à gauche du $$$$ « Deva ») ; en effet, la boucle d’attente d’input utilisateur va se faire 4 fois avant que le branchement « | » en fin de ligne 5 dirige le flot d’exécution vers le bas.

Nous savons donc maintenant que le « password » est une chaîne de 4 caractères ASCII.

Une fois la boucle d’input terminée, nous arrivons par la droite sur la ligne 6. Les instructions suivantes manipulent la stack (duplication de valeur par l’instruction « : » puis opérations arithmétiques).

Changeons le code source pour simuler le dernier input utilisateur (4ème caractère) avec un « A » (code ASCII 65).

line5-6.png

La ligne 6 va effectuer les opérations suivantes (que nous pouvons suivre avec le debugger Befunge en JS):

If ((« code ASCII du 4ème caractère du password » % 10) – 3) = 0 then:
    Go down
Else:
    Go up

Nous voyons bien que le reste du code n’est atteignable que si nous continuons vers le bas, donc il faut que le code ASCII du 4ème caractère soit égal à 3 modulo 10. Si nous supposons que nous restons dans l’alpha-numérique, nous avons donc les 5 candidats suivants :

53 : 5 73 : I 83 : S 103 : g 113 : q Si nous choisissons « S » (83), voyons la suite du code (ligne 7). Les opérations de duplications faites sur la ligne 6 impliquent que le caractère manipulé reste le 4ème caractère du password. Les opérations effectuées se traduisent par :

If ((« code ASCII du 4ème caractère du password » / 10) – 10) = 0 then
    Go down
Else
    Go up

Nous cherchons donc les caractères qui répondent aux contraintes :

  • La division entière impose que le caractère soit entre 100 et 109

La combinaison de la dernière contrainte et des 5 candidats précédents implique que le seul candidat est « g » (103).

Nous savons donc que le password est de la forme « _g ». En suivant le même raisonnement, nous pouvons trouver les contraintes pour les différents caractères et trouver le password complet.

Une fois le bon password fourni en input, nous obtenons le précieux flag permettant de valider le challenge.