Disclaimer :
Cette solution a été ajoutée par l’équipe de modération Hackropole alors que le fichier source (
guess-me.py
) n’était pas disponible. Cette oubli lors de la première publication de l’épreuve sur Hackropole a été corrigée : le fichier est désormais disponible.
Très peu d’information, pour ne pas dire aucune hormis le fait qu’on n’a pas beaucoup de temps. On se connecte au Docker une fois instancié :
$ nc localhost 4000
>>> _
Même pas un message de bienvenue ! Quelques chaines de caractères fournies au programme se soldent vite par aucune réponse et se finit en déconnexion.
Et puis au petit bonheur la chance on entre un chiffre et une réponse arrive (enfin) :
>>> 1
+1
>>>
On tatonne en répondant la valeur plus 1, rien de bien probant sinon que cela finit par nous déconnecter. On entre un nombre de taille conséquente à tout hasard encore une fois:
>>> 100000000000000000000000000
-1
>>>
Mais bien sûr, l’épreuve “Guess Me” (devinez-moi) évoque le fait de deviner un nombre en ayant comme réponses “plus grand”, “plus petit” ou “trouvé”. Nous y sommes, on va proposer une approche dichotomique. On se fixe un maximum tel le nombre proposé ci-dessus et on élabore un script python pour jouer les devins.
Mais tout ne va pas se dérouler comme prévu, les réponses du programme deviennent incohérentes alors que notre pivot converge. On finit par comprendre que nous avons un nombre maximum de propositions avant que le programme reparte silencieusement sur un autre nombre à deviner.
Mais, les tentatives arrivent au bout de la devinette de temps à autres et nous livrent une information précieuse:
...
0
1 found, 15 more to go
>>>
Quand nous trouvons le nombre, la réponse est 0
et il nous reste encore un certain nombre de devinettes à résoudre (16 au total). En comptant le nombre de tentatives on s’aperçoit que lorsque nous trouvons, ce nombre est toujours inférieur ou égal à 64. Par déduction et parce que nous utilisons une dichotomie il se trouve que le nombre maximum est forcément 2<sup>64</sup>. Nous en déduisons que l’intervalle de recherche est entre 0
et 18446744073709551616
. On modifie notre intervalle de recherche initialement incorrect et aboutissons au script Python suivant:
from pwn import *
#context.log_level = "DEBUG" # or INFO, WARNING, ERROR
r = remote("localhost", 4000)
MAX=2**64
found = 0
def reset():
global high, low, pivot, attempts
high = MAX
low = 0
pivot = int(MAX/2)
attempts = 0
reset()
while found != 16:
r.recvuntil(b'>>> ')
attempts += 1
r.sendline(str(pivot).encode())
resp = r.readline()
if resp == b'+1\n':
low = pivot
pivot = int((high-low)/2+0.5)+low
elif resp == b'-1\n':
high = pivot
pivot = int((high-low)/2)+low
elif resp == b'0\n':
print(f"GUESSED in {attempts:2d} attempts: {pivot:20d}")
resp = r.readline() # XX found, YY more to go
#print(resp)
found += 1
reset()
resp = r.readline() # Flag print out
print(resp.decode())
Nous l’exécutons:
$ python3 flag.py
[+] Opening connection to localhost on port 4000: Done
GUESSED in 64 attempts: 14059376565456887785
GUESSED in 62 attempts: 7256452694383020500
GUESSED in 64 attempts: 2216318486037663445
GUESSED in 64 attempts: 15209481763932981093
GUESSED in 64 attempts: 17851258354905307973
GUESSED in 64 attempts: 8980214639731081755
GUESSED in 64 attempts: 13064650034230644695
GUESSED in 64 attempts: 6554281060954183169
GUESSED in 64 attempts: 16832598034445009503
GUESSED in 64 attempts: 611549602364064215
GUESSED in 62 attempts: 5834000467620771324
GUESSED in 63 attempts: 17525556039374496222
GUESSED in 64 attempts: 13786508951627555673
GUESSED in 63 attempts: 7730071269392626630
GUESSED in 64 attempts: 10437364889857448369
GUESSED in 64 attempts: 17652118682399944415
FCSC{c9e2d81308b989c22cac85890228fb9bcf1d53ce68e51f52a12d98d0fc868319}
[*] Closed connection to localhost port 4000
Au bout des 16 bonnes réponses (les nombres à deviner sont aléatoires, enfin plus ou moins) nous obtenons notre flag.
Tada.