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_startetco_code_endUne 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_objEn 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))
# 18Toujours 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_VALUEOn 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}")Mis à jour
Ce contenu vous a-t-il été utile ?