HackTheBox Permuted Challenge
Explore the basics of cybersecurity in the Permuted Challenge on Hack The Box. This medium-level Challenge introduces encryption reversal and file handling concepts in a clear and accessible way, perfect for beginners.
https://app.hackthebox.com/challenges/661
Description#
You drop to the ground as a voltaic mist of energy surrounds you; within it are the Aranaya, reflections of your emotions that break into the physical world from the spiritual realm. Love, hate, pain and more writhe and dance before your eyes in an endless storm. As one tears into your soul, a lightning bolt strikes your inner being and the emotion remoulds into another. Startled and wide-eyed, you recognise an undeniable truth: they are all reflections of one another, an ecosystem of your being that you could lose forever. Consciousness leaves you as the psychedelic show whirls on. To retain your self, you must brave the storm: a cyclone of patterns, an infinitude of permutations.
Exploitation#
#!/usr/bin/env python3
from Crypto.Cipher import AES
from Crypto.Util.Padding import pad, unpad
from Crypto.Util.number import long_to_bytes
from hashlib import sha256
from math import gcd
from functools import reduce
class Permutation:
def __init__(self, mapping):
if isinstance(mapping, Permutation):
self.mapping = tuple(mapping.mapping)
else:
self.mapping = tuple(mapping)
self.length = len(self.mapping)
def __call__(self, x):
return self.mapping[x]
def __mul__(self, other):
return Permutation([self(other(i)) for i in range(self.length)])
def __pow__(self, n):
if n == 0:
return Permutation(range(self.length))
if n == 1:
return self
if n < 0:
raise ValueError("Negative powers not implemented")
half = self ** (n // 2)
if n % 2 == 0:
return half * half
return half * half * self
def cycles(self):
cycles = []
used = set()
for i in range(self.length):
if i in used:
continue
current_cycle = [i]
used.add(i)
next_idx = self(i)
while next_idx not in used:
current_cycle.append(next_idx)
used.add(next_idx)
next_idx = self(next_idx)
if len(current_cycle) > 1:
cycles.append(current_cycle)
return sorted(cycles)
def extended_gcd(a, b):
if a == 0:
return b, 0, 1
gcd, x1, y1 = extended_gcd(b % a, a)
x = y1 - (b // a) * x1
y = x1
return gcd, x, y
def lcm(a, b):
return abs(a * b) // gcd(a, b)
def crt(moduli, remainders):
if not moduli or not remainders:
raise ValueError("Empty inputs to CRT")
if len(moduli) != len(remainders):
raise ValueError("Number of moduli must equal number of remainders")
if len(moduli) == 1:
return remainders[0] % moduli[0]
result = remainders[0]
modulus = moduli[0]
for m2, r2 in zip(moduli[1:], remainders[1:]):
g, p, q = extended_gcd(modulus, m2)
if r2 % g != result % g:
continue
new_mod = (modulus * m2) // g
result = (result + ((((r2 - result) * p * modulus) // g) % new_mod)) % new_mod
modulus = new_mod
return result
def solve_dlp(g, h):
g_cycles = g.cycles()
h_cycles = h.cycles()
print(f"Number of g cycles: {len(g_cycles)}")
print(f"Number of h cycles: {len(h_cycles)}")
G = []
H = []
for i in range(g.length):
g_pos = None
h_pos = None
for j, c in enumerate(g_cycles):
if i in c:
g_pos = (j, c.index(i))
for j, c in enumerate(h_cycles):
if i in c:
h_pos = (j, c.index(i))
if g_pos is not None:
G.append(g_pos)
if h_pos is not None:
H.append(h_pos)
First = []
Second = []
for c in h_cycles:
if len(c) > 1:
First.append(c[0])
Second.append(c[1])
D = []
L = []
for i in range(len(Second)):
dist = G[Second[i]][1] - G[First[i]][1]
cycle_len = len(h_cycles[i])
if cycle_len > 1:
D.append(dist % cycle_len)
L.append(cycle_len)
print("D:", D)
print("L:", L)
try:
return crt(L, D)
except Exception as e:
print(f"CRT error: {e}")
raise
def decrypt_shared_secret(B, a, ciphertext):
try:
C = B**a
sec = tuple(C.mapping)
sec = abs(hash(sec))
sec = long_to_bytes(sec)
hash_obj = sha256()
hash_obj.update(sec)
key = hash_obj.digest()[16:32]
iv = b'\xfb\x1d]\xbc\r\xa3\x91\x1cvV#\x13\xd8z\xb4\x16'
cipher = AES.new(key, AES.MODE_CBC, iv)
decrypted = cipher.decrypt(ciphertext)
try:
return unpad(decrypted, 16)
except ValueError:
return decrypted
except Exception as e:
print(f"Decryption error: {e}")
return None
def main():
print("Loading challenge data...")
namespace = {}
with open('output.txt', 'r') as f:
exec(f.read(), namespace)
g = Permutation(namespace['g'])
A = Permutation(namespace['A'])
B = Permutation(namespace['B'])
c = namespace.get('c', None)
if not c:
print("Error: No ciphertext found in output.txt")
return
print("Solving DLP...")
try:
a = solve_dlp(g, A)
print(f"Found private key a = {a}")
if g ** a == A:
print("Verified: g^a = A")
else:
print("Warning: g^a != A")
print("Decrypting flag...")
flag = decrypt_shared_secret(B, a, c)
if flag:
print(flag.decode('ascii'))
except Exception as e:
print(f"Error: {e}")
import traceback
traceback.print_exc()
if __name__ == "__main__":
main()
Summary#
The Permuted Challenge on Hack The Box is a medium-level task focused on permutation groups and advanced cryptographic concepts. The challenge involves solving a discrete logarithm problem (DLP) in a permutation group and using the solution to decrypt an AES-encrypted flag. The provided Python script implements a full solution by creating a Permutation class to handle group operations, solving the DLP using cycle decomposition and the Chinese Remainder Theorem (CRT), and finally using the recovered private key to perform AES decryption. This challenge demonstrates advanced mathematical concepts in cryptography, combining group theory with practical cryptographic implementations.