Cubicle Riddle
Catégorie: Misc Difficulté: easy Flag: HTB{r1ddle_m3_th1s_r1ddle_m3_th4t}
Challenge
Description
Navigate the haunting riddles that echo through the forest, for the Cubicle Riddle is no ordinary obstacle. The answers you seek lie within the whispers of the ancient trees and the unseen forces that govern this mystical forest. Will your faction decipher the enigma and claim the knowledge concealed within this challenge, or will the forest consume those who dare to unravel its secrets? The fate of your faction rests in you.
Ce challenge tourne sur un docker, disponible sur Github
Analyse du code
On a accès à la partie du code source qui vérifie ce que l’on envoie. Dedans on voit la fonction check_answer
qui construit une fonction avec notre réponse et cette fonction doit retourner les mêmes valeurs que (min(self.num_list), max(self.num_list))
def check_answer(self, answer: bytes) -> bool:
_answer_func: types.FunctionType = types.FunctionType(
self._construct_answer(answer), {}
)
return _answer_func(self.num_list) == (min(self.num_list), max(self.num_list))
Concernant la fonction construct_answer
on remarque plusieurs choses :
Notre réponse est encadrée par
co_code_start
etco_code_end
Une fonction est construite via CodeType où 18 arguments sont passés
def _construct_answer(self, answer: bytes) -> types.CodeType:
co_code: bytearray = bytearray(self.co_code_start)
co_code.extend(answer)
co_code.extend(self.co_code_end)
code_obj: types.CodeType = types.CodeType(
1,
0,
0,
4,
3,
3,
bytes(co_code),
(None, self.max_int, self.min_int),
(),
("num_list", "min", "max", "num"),
__file__,
"_answer_func",
"_answer_func",
1,
b"",
b"",
(),
(),
)
return code_obj
En prenant la documentation de CodeType on voit qu’à chaque version de Python, le nombre de paramètre change. Et cela a son importance puisque chaque version compilera notre réponse différement. La plus grande difficulté ici était donc d’identifier que c’était du Python 3.11 grâce aux 18 arguments
#!/usr/bin/python3.11
print(len(inspect.signature(types.CodeType).parameters))
# 18
Toujours avec la documentation, on comprend que :
Le premier argument (
1
) correspond au nombre de paramètres de la fonction("num_list", "min", "max", "num")
correspond aux variables locales(None, self.max_int, self.min_int)
sont les constantes utilisées
Donc on a 1 paramètre, c’est num_list
puisque c’est le premier définit dans les variables locales. Sa valeur est None
Ensuite on a 3 variables déclarés dans le corps de la fonction : min
max
puis num
(définis dans cet ordre)
Si on transforme le début et la fin en instructions avec dis :
print(dis.dis(b"d\x01}\x01d\x02}\x02")) # co_code_start
# 0 LOAD_CONST 1
# 2 STORE_FAST 1
# 4 LOAD_CONST 2
# 6 STORE_FAST 2
print(dis.dis(b"|\x01|\x02f\x02S\x00")) # co_code_end
# 0 LOAD_FAST 1
# 2 LOAD_FAST 2
# 4 BUILD_TUPLE 2
# 6 RETURN_VALUE
On interprête ce code comme
def _answer_func(num_list=None):
min = 1000
max = -1000
# a nous de construire ici avec notre réponse
return (min, max)
Il suffit donc de compléter cette fonction puis d’envoyer son co_code
sans les 8 premiers et 8 derniers octets sachant que l’on ne peut utiliser que la variable num
def _answer_func(num_list=None):
min = 1000
max = -1000
for num in num_list:
if min > num:
min = num
if max < num:
max = num
return (min, max)
print(_answer_func.__code__.co_code)
# b'\x97\x00d\x01}\x01d\x02}\x02|\x00D\x00]\x12}\x03|\x03|\x01k\x00\x00\x00\x00\x00r\x02|\x03}\x01|\x03|\x02k\x04\x00\x00\x00\x00r\x02|\x03}\x02\x8c\x13|\x01|\x02f\x02S\x00'
On voit qu’il y a juste un décalage, notre co_code
commence par \x97\x00
, il faut donc penser à les retirer en plus des 8 premiers
Script de résolution
from pwnlib.tubes.remote import remote
def _answer_func(num_list=None):
min = 1000
max = -1000
for num in num_list:
if min > num:
min = num
if max < num:
max = num
return (min, max)
answer = ', '.join([str(n) for n in _answer_func.__code__.co_code[10:-8]])
client = remote('x.x.x.x', port)
client.sendlineafter(b'(Choose wisely) > ', b'1')
client.sendlineafter(b'(Answer wisely) >', answer.encode())
flag = re.search(r"(HTB{.*})", client.recvall(timeout=1).decode()).group(1)
print(f"Flag: {flag}")
Dernière mise à jour
Cet article vous a-t-il été utile ?