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.
- The objective of this challenge is to implement an algorithm to find the min,max values of an array using python bytecode.
- HTB{r1ddle_m3_th1s_r1ddle_m3_th4t}
- Easy
release/misc_cubicle_riddle.zip (f5bfddb7230c2b2cdbc96cac6348533533bd2a52172610929f7a21fb90fe46a1
)
We're greeted with a socket server waiting for our answer on a riddle. If we check the riddler.riddler.py
code we can see that we construct a Python function using the FunctionType
which is called _answer_func
. As an argument FunctionType
gets a Code
object which basically represents byte-compiled executable Python code. After we construct _answer_func
we check if it's results are the same with (min(num_list),max(num_list))
. So we basically want to construct a function that returns a tuple with the min and max of an array.
We can see that this object has already most attributes already there. The most importants are the:
codeobject.co_consts
codeobject.co_varnames
codeobject.co_code
co_consts
is a tuple containing the literals used by the bytecode in the function. In this case the tuple is: (None, self.max_int, self.min_int)
co_varnames
is a tuple containing the names of the local variables in the function (starting with the parameter names). In this case the tuple is: ("num_list", "min", "max", "num")
The one that we need to complete is the co_code
which is a bytestring representing the sequence of bytecode instructions in the function. We can see that we already have the start and the end of this function. Let's see what it does...
(Pdb) dis.dis(_answer_func)
0 LOAD_CONST 1 (1000)
2 STORE_FAST 1 (min)
4 LOAD_CONST 2 (-1000)
6 STORE_FAST 2 (max)
8 LOAD_FAST 1 (min)
10 LOAD_FAST 2 (max)
12 BUILD_TUPLE 2
14 RETURN_VALUE
We can see that the function stores 1000 in the min
var and -1000 in the max
var. After that it returns a tuple of the form (min, max)
. We can also see that the parameter name num_list
is loaded in the function. So we can recontruct the function as:
def _answer_func(num_list: list[int]):
min: int = 1000
max: int = -1000
# missing code
return (min,max)
In between our "answer" is loaded which is the remaining missing bytecode sequence. We can either construct from the start using Python's opcodes, or we can write the function, dissassemble it and get the part that we need.
>>> def _answer_func(num_list: int):
... min: int = 1000
... max: int = -1000
... for num in num_list:
... if num < min:
... min = num
... if num > max:
... max = num
... return (min, max)
...
>>> _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'
Last thing would be to return a list of the bytes of the sub-bytestring:
>>> [b for b in b'|\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']
[124, 0, 68, 0, 93, 18, 125, 3, 124, 3, 124, 1, 107, 0, 0, 0, 0, 0, 114, 2, 124, 3, 125, 1, 124, 3, 124, 2, 107, 4, 0, 0, 0, 0, 114, 2, 124, 3, 125, 2, 140, 19]
When we send the bytes (without the []) we'll get the flag:
➜ htb nc 127.0.0.1 1337
___________________________________________________________
While journeying through a dense thick forest, you find
yourself reaching a clearing. There an imposing obsidian
cube, marked with a radiant green question mark,halts your
progress,inviting curiosity and sparking a sense of wonder.
___________________________________________________________
> 1. Approach the cube...
> 2. Run away!
(Choose wisely) > 1
___________________________________________________________
As you approach the cube, its inert form suddenly comes to
life. It's obsidian parts start spinning with an otherwordly
hum, and a distorted voice emanates from withing, posing a
cryptic question that reverbates through the clearing,
, shrouded in mystery and anticipation.
___________________________________________________________
> Riddler: 'In arrays deep, where numbers sprawl,
I lurk unseen, both short and tall.
Seek me out, in ranks I stand,
The lowest low, the highest grand.
What am i?'
(Answer wisely) > 124, 0, 68, 0, 93, 18, 125, 3, 124, 3, 124, 1, 107, 0, 0, 0, 0, 0, 114, 2, 124, 3, 125, 1, 124, 3, 124, 2, 107, 4, 0, 0, 0, 0, 114, 2, 124, 3, 125, 2, 140, 19
___________________________________________________________
Upon answering the cube's riddle, its parts spin in a
dazzling display of lights. A resonant voice echoes through
the woods that says... HTB{r1ddle_m3_th1s_r1ddle_m3_th4t}
___________________________________________________________
import telnetlib
import re
_payload = b'|\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'
_payload_string = ','.join(str(b) for b in _payload)
HOST = "127.0.0.1"
PORT = 1337
tn = telnetlib.Telnet(HOST, PORT)
print(" > Connected succesfully to server...")
tn.read_until(b"\n(Choose wisely) > ")
print(" > Read garbage...")
tn.write(b'1')
tn.read_until(b"\n(Answer wisely) > ")
print(" > Read some more garbage...")
tn.write(_payload_string.encode())
last_message = str(tn.read_all())
pattern = re.compile("HTB\{.*?\}")
match = re.search(pattern, last_message)
print(f" > Found the flag: {match.group()}")
htb python solver.py
> Connected succesfully to server...
> Read garbage...
> Read some more garbage...
> Found the flag: HTB{r1ddle_m3_th1s_r1ddle_m3_th4t}