Blog Zenika

#CodeTheWorld

Sécurité

Challenge Brigitte Friang : Write-up (solutions) 2/3 – catégorie Algo


Dans un article précédent, je vous avais présenté le challenge Brigitte Friang et la solution des premières épreuves qu’il comportait.

Cet article est donc la suite de cette série présentant mes solutions aux épreuves de ce challenge, et cette fois-ci nous allons nous intéresser à la catégorie Algo.


Analyse de fichiers – Algorithme

Le texte du message ci-dessous nous indique clairement la démarche à suivre.

Commençons par consulter les deux fichiers et identifier leurs différences.

capture diff fichier

Le script extract.py, ci-dessous, permet de réaliser un diff entre les deux fichiers intercepte.txt et original.txt. On constate rapidement par le préambule qu’il s’agit d’informations encodées en base64

Fichier extract.py

#!/usr/bin/env python3
import base64 

extracted = ''
with open('intercepte.txt', 'r') as fi, open('original.txt', 'r') as fo:
    ci = fi.readline()
    co = fo.readline()
    while ci:
        i = j = 0
        while i < len(ci) and j < len(co):
            if ci[i] == co[j]:
                i += 1
                j += 1
            else:
                extracted += ci[i]
                i += 1
        ci = fi.readline()
        co = fo.readline()

output = base64.b64decode(extracted.split(':')[1])

out = open('output.jpg', 'wb')
out.write(output)
out.close()

Après avoir modifié le script on extrait directement le contenu dans un fichier output. La commande file nous permet d’en savoir plus :

$ file output
output: JPEG image data, JFIF standard 1.01, resolution (DPI), density 72x72, segment length 16, progressive, precision 8, 596x842, components 3

Il s’agit donc de l’image suivante :

Affiche Evil Gouv recrute

Il reste à se rendre à l’url indiquée : https://challengecybersec.fr/22caeee05cb8b2a49133be134a5e9432

Petite subtilité, il faut penser à transformer les majuscules en minuscules comme pour les autres URLs.


Mission Evil Gouv – Algorithme

Capture Mission Evil Gouv Algorithme

En consultant les instructions, on voit que l’épreuve consiste à résoudre le problème du sac à dos pour 4 fichiers données. Testons avec une librairie issue de chez Google qui implémente une solution rapide (mais pas forcément la meilleure) à ce problème bien connu.

Pour voir plus d’informations sur la résolution, vous pouvez consulter le projet https://github.com/pyaillet/ctf-knapsack/ et en particulier le script knapsack.py

Ce script génère les fichiers de solution à envoyer sur la plateforme ce qui nous donne le résultat suivant :


Blog Evil Newspaper – Python scripting

L’épreuve, consiste cette fois-ci à récupérer des digest MD5

Effectivement, sur chaque article on voit bien un digest MD5 à la fin :

En cherchant rapidement, on voit qu’il y a 1000 articles à scrapper. En utilisant les modules requests et BeautifulSoup dans le script suivant on trouve rapidement la solution : 

Fichier list.py

#!/usr/bin/env python3

url = 'https://challengecybersec.fr/9bcb53d26eab7e9e08cc9ffae4396b48/blog/post/{}'

import requests
from bs4 import BeautifulSoup
import hashlib

def get_md5(page):
    resp = requests.get(url.format(page))
    soup = BeautifulSoup(resp.text, 'html.parser')
    return soup.select('#partial-proof')[0].text

content = ''
for i in range(1, 1001):
    content += get_md5(i)

print(hashlib.md5(content.encode('ascii')).hexdigest())

Ce qui nous permet d’arriver sur la page suivante :


EvilGouv Classified Archives – Reverse JS

En regardant la source de la page, on identifie un script login.js dont une fonction est appelée lors du clic sur le login :

En passant le code dans un formateur, on a quelque chose de plus exploitable (mais tout de même assez obscur…)  :

var _0x5f46 = ['\x37\x3c\x30\x6c\x3c\x6e\x69\x30\x33\x3c\x6c\x3c\x6c\x3c\x33\x3e\x35\x3c\x62\x60\x3e\x64\x6b\x3e\x6a\x3b\x33\x6e\x30\x3e\x3e\x6f\x39\x6e\x30\x60\x6e\x6b\x33\x39', '\x39\x6f\x23\x6a\x7a\x51\x24\x3d\x57\x38\x73\x4e\x3e\x6e\x3f\x6b\x49\x58\x75\x49\x4d\x37\x73\x68\x36\x20\x57\x69\x6c\x62\x44\x50\x78\x60\x31\x26\x59\x46\x35\x7a', '\x6c\x65\x6e\x67\x74\x68', '\x73\x70\x6c\x69\x74', '\x63\x68\x61\x72\x41\x74', '\x6a\x6f\x69\x6e', '\x64\x5b\x63\x6e\x3f\x6b\x2b\x71\x6a\x65\x29\x2f\x4e\x7c\x74\x2e\x77\x6b\x47\x72\x5d\x72\x4f\x2b\x6b\x39\x62\x3d\x32\x79\x2c\x7d\x40\x5a\x79\x62\x3a\x38\x70\x6c\x61\x32\x27\x36\x25\x64\x6e\x29', '\x63\x68\x61\x72\x43\x6f\x64\x65\x41\x74', '\x66\x72\x6f\x6d\x43\x68\x61\x72\x43\x6f\x64\x65'];
var _0x19fd = function(_0x5f4656, _0x19fd1f) {
    _0x5f4656 = _0x5f4656 - 0x0;
    var _0x79ddde = _0x5f46[_0x5f4656];
    return _0x79ddde;
};
function _0x10dbec(_0x1975bf) {
    var _0x1b7fa0 = _0x19fd('\x30\x78\x30');
    var _0x4e7c63 = 0x0;
    var _0x6ae845 = _0x4bf1ad(_0x53e54e(_0x1975bf));
    if (_0x6ae845 == _0x1b7fa0) {
        _0x4e7c63 = 0x1;
    } else {
        _0x4e7c63 = 0x0;
    }
    return _0x4e7c63;
}
function _0x44d925() {
    var _0x44809b = [0x2, 0x15, 0x0, 0x22, 0xb, 0x9, 0x17, 0x1e, 0xe, 0x5, 0x1d, 0x4, 0x18, 0x16, 0x8, 0x14, 0x1f, 0x11, 0x26, 0x23, 0xf, 0x1, 0xd, 0x6, 0xc, 0x1a, 0x19, 0x1b, 0x21, 0xa, 0x7, 0x10, 0x20, 0x1c, 0x3, 0x13, 0x25, 0x24, 0x12, 0x27];
    return _0x44809b;
}
function _0x22f9d2() {
    var _0xb974a1 = [0x0, 0x15, 0x0, 0x22, 0x4, 0x9, 0x17, 0x1e, 0xe, 0x5, 0x1d, 0x4, 0x18, 0x16, 0x8, 0x14, 0x1f, 0x11, 0x26, 0x23, 0xf, 0x1, 0xd, 0x6, 0xc, 0x1a, 0x19, 0x1b, 0x21, 0xa, 0x7, 0x10, 0x20, 0x1c, 0x3, 0x13, 0x25, 0x24, 0x12, 0x27];
    return _0xb974a1;
}
function _0xdbb8b3() {
    var _0x22dcfa = [0x0, 0x15, 0x0, 0x22, 0x4, 0x9, 0x17, 0x7, 0xe, 0x5, 0x1d, 0x4, 0x18, 0xd, 0x8, 0x14, 0x1f, 0x11, 0x26, 0x23, 0xf, 0x1, 0xd, 0x6, 0xc, 0x1a, 0x19, 0x1b, 0x21, 0xa, 0x7, 0x10, 0x20, 0x1c, 0x3, 0x13, 0x25, 0x24, 0x12, 0x27];
    return _0x22dcfa;
}
function _0x33903e(_0x3c3da1) {
    var _0x334fb8 = _0x44d925();
    var _0x1ef0fc = _0x19fd('\x30\x78\x31');
    var _0x242379 = 0x0;
    var _0x53fbc5 = _0x1ef0fc[_0x19fd('\x30\x78\x32')];
    while (_0x3c3da1[_0x19fd('\x30\x78\x32')] = _0x53fbc5) {
            _0x242379 = 0x0;
        }
    }
    var _0x2e4ee5 = _0x3c3da1[_0x19fd('\x30\x78\x33')]('');
    for (_0x242379 = 0x0; _0x242379 < _0x2e4ee5[_0x19fd('\x30\x78\x32')]; _0x242379++) {
        _0x2e4ee5[_0x334fb8[_0x242379]] = _0x3c3da1[_0x19fd('\x30\x78\x34')](_0x242379);
    }
    return _0x2e4ee5[_0x19fd('\x30\x78\x35')]('');
}
function _0x53e54e(_0x44511f) {
    var _0x4d3936 = _0x22f9d2();
    var _0x5414b5 = _0x19fd('\x30\x78\x31');
    var _0x1f5864 = 0x0;
    var _0x564a67 = _0x5414b5[_0x19fd('\x30\x78\x32')];
    while (_0x44511f[_0x19fd('\x30\x78\x32')] = _0x564a67) {
            _0x1f5864 = 0x0;
        }
    }
    var _0x505f9e = _0x44511f['\x73\x70\x6c\x69\x74']('');
    for (_0x1f5864 = 0x0; _0x1f5864 < _0x505f9e['\x6c\x65\x6e\x67\x74\x68']; _0x1f5864++) {
        _0x505f9e[_0x4d3936[_0x1f5864]] = _0x44511f[_0x19fd('\x30\x78\x34')](_0x1f5864);
    }
    return _0x505f9e['\x6a\x6f\x69\x6e']('');
}
function _0x4bf1ad(_0x350d56) {
    var _0xb038d9 = _0x19fd('\x30\x78\x36');
    var _0x4b4483 = _0x350d56[_0x19fd('\x30\x78\x33')]('');
    var _0x7d38d1 = 0x0;
    for (var _0x1c9024 = 0x0; _0x1c9024 < _0x4b4483['\x6c\x65\x6e\x67\x74\x68']; _0x1c9024++) {
        _0x7d38d1 = _0x350d56[_0x19fd('\x30\x78\x37')](_0x1c9024) ^ _0xb038d9[_0x19fd('\x30\x78\x37')](_0x1c9024) & 0xf;
        _0x4b4483[_0x1c9024] = String[_0x19fd('\x30\x78\x38')](_0x7d38d1);
        if (_0x7d38d1  0x7e) {}
    }
    return _0x4b4483[_0x19fd('\x30\x78\x35')]('');
}

Après avoir fait un peu de nettoyage (remplacement des appels de fonctions masqués par l’appel réel, suppression du code inutile, …), on y voit plus clair :

function _0x10dbec(searchDefinition) {
  var river = "7<0l<ni03<l<l<3>5<b`>dk>j;3n0>>o9n0`nk39";
  var _0x4e7c63 = 0;
  var temp = _0x53e54e(searchDefinition);
  var stripTerrain = _0x4bf1ad(temp);
  if (stripTerrain == river) {
    _0x4e7c63 = 1;
  } else {
    _0x4e7c63 = 0;
  }
  return _0x4e7c63;
}
function _0x53e54e(d) {
  var e = [0, 21, 0, 34, 4, 9, 23, 30, 14, 5, 29, 4, 24, 22, 8, 20, 31, 17, 38, 35, 15, 1, 13, 6, 12, 26, 25, 27, 33, 10, 7, 16, 32, 28, 3, 19, 37, 36, 18, 39];
  var tiledImageBRs = "9o#jzQ$=W8sN>n?kIXuIM7sh6 WilbDPx`1&YF5z";
  var b = 0;
  var tiledImageBR = tiledImageBRs.length;
  for (; d.length < 40;) {
    d = d + tiledImageBRs[b++];
    if (b >= tiledImageBR) {
      b = 0;
    }
  }
  var a = d.split("");
  b = 0;
  for (; b < a.length; b++) {
    a[e[b]] = d.charAt(b);
  }
  return a.join("");
}
function _0x4bf1ad($this) {
  var PL$6 = "d[cn?k+qje)/N|t.wkGr]rO+k9b=2y,}@Zyb:8pla2'6%dn)";
  var PL$13 = $this.split("");
  var artistTrack = 0;
  var PL$17 = 0;
  for (; PL$17 < PL$13.length; PL$17++) {
    artistTrack = $this.charCodeAt(PL$17) ^ PL$6.charCodeAt(PL$17) & 15;
    PL$13[PL$17] = String.fromCharCode(artistTrack);
  }
  return PL$13.join("");
}

On constate alors que l’algorithme est le suivant :

1. La fonction _0x10dbec vérifie l’identifiant entré (le mot de passe est ignoré), si l’entrée est correcte on redirige vers l’url trouvée.

2. La fonction _0x53e54e complète l’identifiant avec une chaîne précise pour qu’il fasse 40 caractères et réordonne les caractères suivant le tableau retourné par la fonction _0x44d925.

3. Ensuite la fonction _0x4bf1ad effectue un XOR entre chaque caractère de la chaîne entrée, et les 4 premiers bits de chacun des caractères de la chaîne retournée par la fonction _0x19fd('\x30\x78\x36'), soit

d[cn?k+qje)/N|t.wkGr]rO+k9b=2y,}@Zyb:8pla2'6%dn)

4. Le résultat est comparé avec la chaîne

"7<0l<ni03<l<l<3>5<b`>dk>j;3n0>>o9n0`nk39

Il faut donc réaliser le XOR inverse entre les deux chaînes, ce qui nous donne 3f3939527e73ad93b73b070bb12cde1292bbcde5.

Lorsqu’on teste avec cette valeur, on voit que les identifiants sont valides, malheureusement la page suivante est une 404…

Lorsqu’on revient sur les remplacements effectués à l’étape 2, on se rend compte que les index 0 et 4 de la chaîne sont remplacés 2 fois et qu’ils sont donc perdus.

Étant donné la classe de caractère rencontré (a-f0-9) et le fait que seuls 2 caractères sont perdus, ça nous fait 16^2 = 256 possibilités à tester.

On va le bruteforce avec un script python, jusqu’à obtenir une réponse http en 200, ce qui nous permet de récupérer l’identifiant correct :

5f3949527e73ad93b73b070bb12cde1292bbcde5

Nous voilà sur l’url https://challengecybersec.fr/1410e53b7550c466c76fc7268a8160ae/5f3949527e73ad93b73b070bb12cde1292bbcde5.

Seule l’opération Diablerie est intéressante, elle nous permet d’accéder à l’Arène du challenge Brigitte Friang et à ses challenges qui seront abordés dans le dernier article de cette série.

Je vous donne donc rendez-vous la semaine prochaine pour le dernier article dans lequel nous retrouverons, de la cryptographie, du forensic Android…


Découvrez toutes nos formations Sécurité

Auteur/Autrice

Laisser un commentaire

Ce site utilise Akismet pour réduire les indésirables. En savoir plus sur comment les données de vos commentaires sont utilisées.