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.
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 :
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
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…