Write-Up du Challenge CyberSEC de la DGSE
 >  2019 , Cyber-sécurité , Sécuriser par l'attaque






Un collègue m’a parlé jeudi d’un challenge où il s’agissait de trouver un document caché dans un PDF, un challenge proposé par la DGSE pour trouver leurs meilleurs recrues. On a alors, commencé à fouiller... et le challenge n’était pas que le PDF en question...





0x01. SI>PRESENTATION



Le challenge se déroulait sur le site www.challengecybersec.fr. Les pièces jointes sont accessible à la fin de l’article.

Les images de l’articles sont des miniatures (car trop grandes), leurs versions se retrouvent à la fin avec les autres pièces jointes.





0x02. ANALYSE DU SITE



Il s’agissait d’une page simple avec un javascript de leurre. Il s’agit de télécharger le fichier PDF.





0x02. ANALYSE DU PDF


Après moultes analyses du PDF avec l’outil de parsing PDF peepdf, et des outils de forensic (foremost, scalpel), on voit en petit du texte écrit en blanc ... qui ressemble à du base 64, du coup on le copie/colle dans notre éditeur de texte, puis on décode :


base64 -d Richelieu.b64 > Richelieu.raw 

On vérifie son type :


file Richelieu.raw 

On trouve JPG dans la réponse. L’image ici est une miniature, l’orginale est disponible en pièce jointe à la fin de cet article.


mv Richelieu.raw Richelieu.jpg 

./files/dgse/chall/02/Richelieu_thumb.jpg




0x03. ANALYSE DU JPG



On retente, avec l’outil de forensic foremost ...


foremost 'Richelieu.jpg' 

Un fichier zip est extrait.





0x04. ANALYSE DU ZIP



Le fichier .bash_history nous donne une information intéressante, qui nous permet de retrouver la bonne version du fichier prime.txt


mkdir found
 
cp Richelieu/prime.txt found/prime.txt
 
sed -i 's/cd/b5/g' found/prime.txt
sed -i 's/57/a4/g' found/prime.txt
sed -i 's/54/16/g' found/prime.txt
sed -i 's/12/f4/g' found/prime.txt
sed -i 's/66/e1/g' found/prime.txt
sed -i 's/fb/7f/g' found/prime.txt 

On sait qu’on va devoir retrouver d’abord le contenu de motDePasseGPG.txt qui nous permettra ensuite de déchiffrer via openGPG l’image PNG lsb_RGB.png

Il semblerait qu’on ait à attaquer le chiffrement fait via RSA. Pour cela, on va d’abord chercher toutes les informations disponibles afin de reconstituer la clé privée.

Pour trouver le modulo de chiffrement :


openssl rsa -pubin -in Richelieu/public.key -text -modulus 

Des infos, de *openssl*, on a alors comme informations de la clé publique :

- l’exposant :


Exponent: 65537 (0x10001) 

- le modulus :


Modulus=CD5F8A24C7605008897A3C922C0E812E769DE0A46442C350CB78C7868539F3D38AAC80B
3E6A506605910E8599806B4D1D148F2F6B81DA04796A8A5AEE18F29E83E16775A2A0A00870541F6
574ED1438636AE0A0C116E07104F48F72094863A3869E1C8FC220627278962FB22873E3156F18E5
5DEC94E970064EC7F4E0E88454012E2FD5DFE5F8D19BF170F9CCB3F46E0FD1019BCB02D9083A070
3C617F996379E6478354A73AE6E6ACBCE1F4333ECFAF24366A3E977D3CD3CBFE8D8A387BD876BFD
AB8488F6F47BF1FBE33010FD2D7E22B4DB2E567783CE0B606DB86B93759714C4F6396A7FB9F74C4
021043B0F3D46D2633EBD43A877863DF7D680F506587C119DD64100CA831CE2AF33D951B524C5F0
6B49F5BF2CB381E74181930D06A80505C06ABD5BF4870F0C9FB581BD80DBA889660639F936EDEA8
FE5D0C9EAE58062ED693252583C71CC782BA613E01438E69B43F9E64ECA84F9EA04E811AD7B39EF
D7876D1B6B501C4F48ACCE6F24239F6C04028788135CD88C3D15BE0F2EBB7DE9E9C19A7A9303700
5EE0A9A640BADA332EC0D05EE9F08A832354A0487A927D5E88066E2569E6C5D4688E422BFA0B27C
6171C6D7BF029BFD9165752AF19AA71B33A1EA70B6C371FB21E47F527D80B7D04F582AD9F9935AF
723682DC01CA9880621870DECB7AD15648CDF4EF153016F3E6D87933B8EC54CFA1FDF87C467020A
3E753 

Nous aurons besoin de mettre le modulus en nombre décimal, qu’on enregistrera dans found/modulus.txt. La fonction python *int* nous permet de trouver cette information :


print( int(modulus) ) 

Ainsi que le nombre premier issu de prime.txt, qu’on enregistrera dans found/prime.txt

Du aux modifications par *sed*, nous avons besoin de faire un script pour corriger prime.txt en prenant en compte de la présence précédente des caractères modifiés, au moment du :


sed -i 's/cd/b5/g' found/prime.txt 

Il pouvait y avoir déjà *b5* présent. On va devoir alors testé les permutations de chaque *sed* pour retrouver la clé prime.txt correcte.

Pour la vérifier on utilisera la fonction *is_prime_miller_rabin* trouvée sur Internet puis on vérifiera la concordance avec le modulo :


n = p * q
q = n//p 

À noter que la division de grands entier se fait par l’opérateur // et non pas /.

L’algo est alors le suivant :

1: p doit être premier
2: si q = n // p
3: alors n doit être égal à p * q
4: sinon, c’est que le résultat est un décimal


#!/usr/bin/env python3
#coding: utf8
 
from time import sleep
from sys import exit
import hashlib
import random
 
### Imports ###################################################################
 
n=83784956386244326846714518697411969526471369973686909064535495474922790157234
7301978135797019317859500555501198030540582269024532041297110543579716921121054
6084946800639924358087085937964762517960640600741704581939974245351495355710098
6266110698681684499174832599175224151673601984040184015028056378056521007187656
8736454876944081872530701199426927496904961840225828224638335830986649773182889
2919534295815502696883924601265005002419692002454898157786993337337629612815508
7303169293356600282271912903433626497500213065177112731398075856290972623311133
5221426610990708111420561543408517386750898610535272480495075060087676747037430
9939462357924058510070909878574003365667987600954010969976965586115882643030877
8867365032104950398065586693627925140674264188833266505450530569784189968516581
0087938256696223326430000379461379116517951965921710056451210314300437093481577
5782734954921846430025393935736517970544971885463817234789520179723469250205983
7500090865596498254101671935658660278120994394331764454799623251663047602532179
5055805235006790200867328602560320883328523659710885314500874028671969578391146
7017395155003702686793010805774683161591021419539413149190394044703481126902140
65442074200255579004452618002777227561755664967507
e=65537
 
### Functions #################################################################
 
def is_prime_miller_rabin(n, k=40):
 # Source: https://gist.github.com/Ayrx/5884790
 # Wiki: https://fr.wikipedia.org/wiki/Test_de_primalit%C3%A9_de_Miller-Rabin
 if n == 2:     return True
 if n % 2 == 0: return False
 r, s = 0, n - 1
 while s % 2 == 0:
  r += 1
  s //= 2
 for _ in range(k):
  a = random.randrange(2, n - 1)
  x = pow(a, s, n)
  if x == 1 or x == n - 1:
   continue
  for _ in range(r - 1):
   x = pow(x, 2, n)
   if x == n - 1:
    break
   else:
    return False
 return True
 
### Functions #################################################################
 
count=0
max_count=2048
 
print(”
Recovering 'prime.txt' ...”)
for a0 in [”fb”,7f]:
 for a1 in [57,”a4”]:
  for a2 in [cd,”b5”]:
   for a3 in [12,”f4”]:
    for a4 in [”fb”,7f]:
     for a5 in [”fb”,7f]:
      for a6 in [57,”a4”]:
       for a7 in [57,”a4”]:
        for a8 in [cd,”b5”]:
         for a9 in [54,16]:
          for a10 in [”fb”,7f]:
           count +=1
           str_prime=”””
00:”””+a0+”””:40:dc:44:ba:03:d1:53:42:f7:59:08:e0:f9:
30:05:96:64:4a:de:94:68:5e:08:e2:8c:9a:b1:64:
0c:2f:62:c2:9a:b9:a2:39:82:4b:9e:be:eb:76:ae:
6d:87:21:a3:5e:9e:d9:8d:7e:”””+a1+”””:38:3e:59:09:34:
a5:78:”””+a2+”””:f7:2e:89:5d:5c:37:52:ea:fd:f6:31:cc:
ba:d2:d9:60:e4:45:1d:67:76:d2:1f:”””+a3+”””:9c:9d:c9:
b1:90:45:51:ed:d2:”””+a4+”””:dd:b6:74:b4:99:”””+a5+”””:b1:0a:
d9:b7:c2:be:8b:”””+a6+”””:07:22:0a:8e:3a:36:ff:6d:c1:
1d:63:93:af:cb:4e:c0:47:9f:65:bf:df:e3:f0:5f:
1e:98:61:45:74:ec:36:a7:a5:b1:f1:8d:3d:97:6b:
5a:82:49:09:00:08:0d:9d:c2:74:”””+a7+”””:4e:30:a1:39:
68:2f:22:34:71:13:aa:3b:f2:20:4f:8e:10:eb:d4:
d0:9b:”””+a8+”””:8c:c2:53:5f:9d:71:13:0c:0f:21:b6:6e:
13:39:40:d3:a6:b1:eb:74:ad:dd:0a:29:14:81:b1:
90:ad:e0:53:f0:89:c8:00:fe:dc:ad:56:59:fc:28:
1d:c0:cf:5e:08:c0:”””+a9+”””:33:24:a3:52:bb:f3:25:10:
43:c3:73:b8:40:4f:fc:6b:6b:77:bd:5f:22:24:eb:
”””+a10+”””:15
”””
           print(# Try: ”+str(count)+”/”+str(max_count)+”... ,end=''),
           p = int( str_prime.replace(”
”,””).replace(”:”,””) , 16 )
           if( is_prime_miller_rabin(p) ):
            h = hashlib.md5(str(p).encode(”utf-8)).hexdigest()
            print(”
 
Potential candidate, md5(prime): ”+h+” ...”)
            q = n//p
            if p*q == n:
             print(”
Peer factors found:”)
             print(”
p=
”+str(p))
             print(”
q=
”+str(q))
             print(”
State: success
”)
             exit()
             break
 
print(”
State: failed
”) 

Ainsi nous trouvons p et q. Nous avons toutes les informations pour retrouver la clé privée. L’outil rsatool.py nous permettra de la réécrire :


# p
p=31717798413454838971739311391870214101486054474438584033384974797696836002786
8897419223280382492839351481675893964757645159847922483252765626356754830855933
0763238494548008432435419938671125906959070172837765461005984915246156538531566
3116675949927841648944186909571007960100266136674008112969369512988507367569334
8380934031447319511135280400137636153542834919552267043343948562530055513935452
9349154758743906426973558312170609879818216829262176790209564135244387116778910
4818694502605762517497996162527138366803593402358312011933528177646501311411008
602369245268966975849244259437732574655956250792264392115994984213
 
# q
q=26415754111957012456882978698568998595543408604540679602012008905307229528398
4195086966992588615416732083340317201180657220151917350562509911241598297576150
3533110481462681542807146138974098835862157517528899513725013147970211866693581
8278843603742157096854979085966659730153703721295913600711482578441198179188796
8497801019268788672722227478487753115011077378386876246477491877645631560548584
2989701826107761606092910288086078944999524086965296334991306237540202133417323
7153511868665597034141167420444312865576543827395764456254276820652449103574192
677537410783040227047254038577626347718324544296319787177388746439
 
rsatool -p $p -q $q -o found/private.key 

Pour déchiffrer le texte, on utilisera la commande *openssl* :


openssl rsautl -in motDePasseGPG.txt.enc -decrypt -inkey found/private.key 

DGSE{Ti,%yei3=stlh_,5@pIrrMU.^mJC:luYbt1Qe_-Y} 

Ce qui nous donne, du coup, le mot de passe pour déchiffrer l’image :


gpg -d lsb_RGB.png.enc > lsb_RGB.png 




0x05. ANALYSE DE L’IMAGE PNG



Le nom du fcihier parle de lui même, le LSB (Less Significant Bit) des couleurs RGB (Red Green Blue) permettra d’extraire les données pour l’étape suivante.

./files/dgse/chall/05/lsb_RGB_thumb.jpg



L’algorithme se trouve relativement facilement via les tags stega, lsb, rgb, pil, python

Original, la sortie que donne le contenu déchiffré est un hexdump d’un binaire Linux ... suivi d’un paquet de données binaires inutiles décorélées, il faudra les ignorer à partir de la 18594 ème ligne.

De plus, on ne reprendra que la parties hexadécimale :


00000000: 7f45 4c46 0201 0103 0000 0000 0000 0000  .ELF............
00000010: 0200 3e00 0100 0000 107f 4400 0000 0000  ..>.......D.....
00000020: 4000 0000 0000 0000 0000 0000 0000 0000  @...............

// doit devenir ;

7f454c46020101030000000000000000
02003e0001000000107f440000000000
40000000000000000000000000000000 

Pour cela :


head -n 18594 output.bin|cut -d' ' -f2,3,4,5,6,7,8,9,10|sed 's/ //g'|xxd -r -p > output.elf 




0x06. ANALYSE DU FICHIER ELF



On jette un coup d’oeil dans un éditeur hexadécimal, on voit :


d.....$Info: Thi
s file is packed
 with the ALD ex
ecutable packer
http://upx.sf.ne
t $..$Id: ALD 3.
91 Copyright (C)
 1996-2013 the A
LD Team. All Rig
hts Reserved. $. 

On comprends alors que le programme a été packé avec UPX et que la chaîne UPX a été remplacée par ALD, on remplace cette chaine dans notre éditeur hexadécimal, qu’on enregistre sous *output-patched.elf* puis on tente de le décompresser :


upx -d output-patched.elf -o output-patched_unpacked.elf 

                       Ultimate Packer for eXecutables
                          Copyright (C) 1996 - 2018
UPX 3.95        Markus Oberhumer, Laszlo Molnar & John Reiser   Aug 26th 2018

        File size         Ratio      Format      Name
   --------------------   ------   -----------   -----------
    738384 <-    297492   40.29%   linux/amd64   output-patched_unpacked.elf

Unpacked 1 file. 

Lors de son exécution :


usage : ./output-patched_unpacked.elf <mot de passe> 

C’est un crackme. Autant le renommer en conséquence. Pas de *strings* cette fois, on tente de tracer son exécution :


mv ./output-patched_unpacked.elf crackme_dgse.elf
strace ./crackme_dgse.elf 'p@ssw0rd' 

Toujours rien. En arrière plan, j’ai tenté un simple bruteforce (dès fois que) :


m=1 ; for i in $(seq 1000 9999) ; do
  printf ”
Trying length(pass)=$i ...”
  m=1$m
  ./crackme_dgse.elf $m 2>&1 |grep Bravo && break
done 

Ce qui ne donnera évidemment rien. Je vais devoir analyser le code assembleur. Hopper Disassembler, permet de tenter une décompilation en pseudo-langage.

D’après le schéma qu’a généré Hopper, nous pouvons voir que la fonction fait le traitement sur le mot de passe sub_0400aee. Ce qui va nous orienter pour le débogage.


./files/dgse/chall/06/crackme_0.png

./files/dgse/chall/06/crackme_1.png

function sub_400af0 {
    do {
            rax = r8 & 0xff;
            rsi = *(int8_t *)rdx & 0xff; 
            rcx = *(int8_t *)rdi & 0xff;
            rdx = rdx + 0x1;  // avance dans le pointeur
            rdi = rdi + 0x1;
            if (rcx == 0x0) { // fin de la chaine de caractère de l‘argument 
                break;
            }
            r8 = r9;
            if (rax == 0x0) { // fin de la chaine de caractère du flag
                continue;
            }
            r8 = (rcx ^ rsi) == *(int8_t *)rdx ? 0x1 : 0x0;
            // r8 contient une chiffrée ”xorée” entre le caractère
            // courant (rcx) et le caractère précedent (rsi)
    } while (true);
    return rax;
}
 
 
function sub_400aae {
    rsi = rdi;
    rcx = 0xffffffffffffffff;
    asm { repne scasb al, byte [rdi] };
    rax = 0x0;
    if (rcx == 0xffffffffffffffe0) {
            if ((*(int8_t *)rsi & 0xff) != 0x0) {
                    rax = loc_400b06(rsi + 0x1, 0x33, 0x4898c0);
                    // soit loc_400b06() doit retourner 0x1
            }
            else {
                    rax = 0x1;
                    // soit on doit arriver là ... 
            }
    }
    return rax;
}
 
 
function sub_400b20 {
    rsp = rsp - 0x8;
    if (rdi > 0x1) {
            rax = sub_400aae(*(rsi + 0x8));
            rbx = rax;
            if (rax == 0x0) {
                    sub_408010(”Mauvais mot de passe”, rsi, rdx, rcx, r8, r9);
            }
            else {
                    sub_408010(”Bravo ! Vous pouvez utiliser ce mot passe pour la suite ;-)”, rsi, rdx, rcx, r8, r9);
                    rbx = 0x0;
            }
    }
    else {
            sub_407840(”usage : %s <mot de passe>
”, *rsi, rdx, rcx, r8, r9, stack[0]);
            rbx = 0x2;
    }
    rax = rbx;
    return rax;
} 

Je n’ai pas réussi à faire du débogage avec Hopper et gdbserver. Je n’ai pas non plus réussi à déboguer via gdb, ni via lldb, ni via Ghidra, ni via Cutter ... Heureusement, il restait une dernière alternative : edb présent dans les dépots Kali.

Dans edb, on peut voir que le mot de passe saisi est comparé caractère par caractère à ce qui est en mémoire. D’après ce qu’on comprends :

Le caractère courant du mot de passe saisi est comparé au caractère du mot passe XOR caractère précédent du mot de passe


Note : Dans la vidéo, manque la dernière étape où RSI passe à 11


RDX w0c&]:               ;
M+                -O{Q7z                        !M!l
RSI 33 77 30 63 26 5d 3a 0e 0d 4d 2a 1f 2e 1f 2d 4f 28 51 37 7a 14 76 20 78 0f 21 4d 21 6c 11 

Le script python, permet alors de retrouver le flag :


x = [0x33,0x77,0x30,0x63,0x26,0x5d,0x3a,0x0e,0x0d,0x4d,0x2a,0x1f,0x2e,0x1f,0x2d,0x4f,0x28,0x51,0x37,0x7a,0x1
4,0x76,0x20,0x78,0x0f,0x21,0x4d,0x21,0x6c,0x11]
 
s = ””
 
for i in range(len(x)):
    s += chr(int(x[i-1]^x[i]))
 
print(s)
 
# DGSE{g4@g5112bgyfMnbVXw.llM} 

Ce qui nous permet de dézipper correctement le fichier suite.zip et d’accéder à la suite.





0x07. ANALYSE DU WARGAME ”ATTRAPELEDRAPEAU”



Le programme défi n°1 du Wargame semble appeler des fonctions systèmes *date*, *sl* et *cal*, ce qui nous oriente vers une vulnérabilité dans le chemin d’exécution.

L’idée est de surcharger le chemin via la variable PATH afin que le programme exécute un programme créé pour l’occasion :


defi1@AttrapeLeDrapeau:~$ echo '/bin/cat drapeau.txt' > cal
defi1@AttrapeLeDrapeau:~$ chmod +x cal
defi1@AttrapeLeDrapeau:~$ export PATH=.:$PATH
defi1@AttrapeLeDrapeau:~$ ./prog.bin
#################################################
##    Bienvenue dans ce lanceur (_wrapper_)    ##
#################################################
Ce logiciel vous permet de lancer ces programmes utiles simplement...
Menu :
   -> 1 : Affichage de la date et de l‘heure actuelle
   -> 2 : Affichage du nombre de secondes écoulées depuis le 01/01/1970 (Epoch)
   -> 3 : Affichage du train
   -> 4 : Affichage du calendrier du mois en cours
4
 

Ce qui nous donne :


Suite du challenge Richelieu :

ssh defi2.challengecybersec.fr -l defi2 -p 2222

mot de passe : DGSE{H#M?W)el{0YZ-)77/C#ogrp}k4&EbP} 



0x08. WARGAME (ATTRAPELEDRAPEAU) : DÉFI 2 : ROP



Je m’arrête là, de ce que j’ai cru comprendre, l’épreuve suivante est un programme vulnérable au ROP (Return Oriented Programming).

De ce que j’en ai compris, le but est d’exploiter un bufferoverflow, en utilisant non pas un shellcode mais des adresses de retours sur des *gadgets*, des adresses internes au programme pour appeler le jeu d’instruction qui permettra de le contrôler.





0x09. CONCLUSION



Le spectre de connaissance pour ce challenge est large. Cela met en avant plusieurs points que la DGSE recherche chez sa future recrue :
- le travail en équipe est primordial
- il faut être polyvalent
- ne pas se contenter d’utiliser des outils de hacking (*)
- maitriser un langage de programmation

(*) : on appelera ”hacking” dans ce contexte, l’ensemble de la stéganographie, du chiffrement, de la rétro-ingénieurie (reverse), de la criminalistique (forensic) et des applications systèmes.

Si la façon de faire n’est pas nouvelle, l’enchaînement des épreuves est bien fait.

Je tiens à remercier le forum ATNL qui m’a bien aidé.





0x010. PIECES JOINTES



Etape du PDF
-> Richelieu.pdf.zip

Etape du JPG
-> Richelieu.jpg

Etape du ZIP
-> Richelieu.zip

Etape RSA
-> lsb_RGB.png.enc
-> prime.txt
-> _bash_history
-> motDePasseGPG.txt.enc
-> public.key

Les scripts :
- script_1_is_prime.py
- script_2_prime_corrector.py
- script_3_rsatool.py

Etape du crackme ELF
-> output.elf.zip
-> suite.zip

Sa version patchée et dépackée:
-> crackme_dgse.elf.zip




   =>   Écrit par : Nicolas, le 15 juin 2019


 
Mots clés :  
  challenges 
  
  security 
    >   Articles connexes :

NDH2K18 - 16ème NDH



CVE-2018-4407 challenge!



/tmp et /var/log en noexec sur macOS



Durcissement de Windows



3833707