Hola.
Un amigo dan884075 que no tiene cuenta en underc0de rompió el cifrado por una vulnerabilidad que explicará el, estoy parcheando la versión la publicaré próximamente. Aquí dejo sus palabras:
Como la contraseña se codificaba como un número, resultante de la suma de números preestablecidos que codificaban los caracteres de la contraseña, la contraseña pasaba a ser un conjunto de caracteres sin importar el orden.
Utilizé este script en python para averiguar la el texto, por fuerza bruta. Se ha conseguido descifrar en 3.23 segundos.
from time import time
TEXT_CHARACTERS = (10061, 43487, 49669, 74287, 98953, 75767, 74099, 12457, 32159, 53897, 41263, 60331, 91291, 33403, 43651, 40559, 13921, 40531, 37861, 76091, 77093, 80107, 79861, 91801, 93937, 90397)
TEXT_NUMBERS = (42683, 10037, 20101, 49121, 33151, 17029, 58013, 89867, 91199, 97259)
TEXT_SPACE = 99989
KEYS_CHARACTERS = (59077, 77447, 46061, 20897, 48673, 78571, 65963, 15161, 64399, 45497, 91129, 80429, 17377, 42013, 19183, 23099, 22699, 55457, 84871, 84659, 32569, 80777, 52289, 86111, 86291, 37781)
KEYS_NUMBERS = (39443, 89963, 85691, 79273, 49531, 30713, 63059, 59263, 70489, 26647)
KEY_SPACE = 94687
TEXTS = set(TEXT_CHARACTERS + TEXT_NUMBERS + (TEXT_SPACE,))
KEYS = KEYS_CHARACTERS + KEYS_NUMBERS + (KEY_SPACE,)
MULTIPLIER = 10**(len(str(TEXT_SPACE)))
ORD_A = ord("a")
ORD_Z = ord("z")
ORD_0 = ord("0")
ORD_9 = ord("9")
class CharacterConverter:
def __init__(self, characters, numbers, space):
if len(characters) != ORD_Z - ORD_A + 1:
raise ValueError("longitud de la tupla de caracteres errónea")
if len(numbers) != 10:
raise ValueError("Longitud de la tupla de números errónea")
self.characters = characters
self.numbers = numbers
self.space = space
def getNumber(self, character):
if character == " ":
return self.space
n = ord(character)
if n >= ORD_0 and n <= ORD_9:
return self.numbers[n - ORD_0]
elif n >= ORD_A and n <= ORD_Z:
return self.characters[n - ORD_A]
else:
raise ValueError("Caracter no válido")
def getCharacter(self, number):
if number == self.space:
return " "
elif number in self.numbers:
return chr(self.numbers.index(number) + ORD_0)
elif number in self.characters:
return chr(self.characters.index(number) + ORD_A)
else:
raise ValueError("Número no encontrado")
TEXT_CONVERTER = CharacterConverter(TEXT_CHARACTERS, TEXT_NUMBERS, TEXT_SPACE)
KEY_CONVERTER = CharacterConverter(KEYS_CHARACTERS, KEYS_NUMBERS, KEY_SPACE)
passwordToKey = lambda password: sum(map(KEY_CONVERTER.getNumber, password))
def encode(text, password):
"""Codifica el texto que se pasa como parámetro con la contraseña indicada
text: Texto a codificar
password: Contraseña para codificar"""
num = 0
for elem in text:
num = num * MULTIPLIER + TEXT_CONVERTER.getNumber(elem)
key = passwordToKey(password)
return num * key
def decode(encodedCode, password):
"""Decodifica el mensaje con la contraseña indicada y devuelve el texto original
encodedCode: Mensaje a decodificar
password: Contraseña para decodificar"""
key = passwordToKey(password)
num = encodedCode // key
return numberToText(num)
def numberToText(num):
decoded = ""
while num > 0:
decoded += TEXT_CONVERTER.getCharacter(num % MULTIPLIER)
num //= MULTIPLIER
return decoded[::-1]
def decodeGuessingPassword_passAsSet(encodedCode, maxPassSize=4):
"""Devuelve el texto correspondiente a la decodificacion del mennsaje
prueba con todas las contraseñas hasta la longitud indicada
Trata las contraseñas como un conjunto de caracteres, sin orden
encodedCode: Texto codificado
maxPassSize: Longitud máxima de la contraseña"""
notFound = True
currentKeyData = [0]
curSize = len(currentKeyData)
maxVal = len(KEYS) - 1
while len(currentKeyData) <= maxPassSize and notFound:
if checkValidDecodedMsg(encodedCode // sum((KEYS[pos] for pos in currentKeyData))):
notFound = False
else:
i = 0
while i < curSize and currentKeyData[i] == maxVal:
i += 1
if i != curSize:
currentKeyData[i] += 1
valCurentKeyData = currentKeyData[i]
for i in range(i - 1, -1, -1):
currentKeyData[i] = valCurentKeyData
else:
curSize += 1
currentKeyData = [0] * curSize
if notFound:
raise ValueError("No se ha podido decodificar")
else:
return numberToText(encodedCode // sum((KEYS[pos] for pos in currentKeyData))), getPasswordFromKeyData(currentKeyData)
def checkValidDecodedMsg(msg):
"""Comprueba que el mensaje decodificado, como múmero sea válido
msg: Mensaje a comprobar"""
while msg > 0:
if not msg % MULTIPLIER in TEXTS:
return False
msg //= MULTIPLIER
return True
#Devuelve la contrasena, pasando como parámetro un iterable con las posiciones en KEKS de los valores de la contraseña
getPasswordFromKeyData = lambda KeyData: tuple((KEY_CONVERTER.getCharacter(KEYS[pos]) for pos in KeyData))
def main():
coded = 309224446557488409754066854545005782200040975244112
t0 = time()
print("Mensaje: {}, Contraseña: {}".format(*decodeGuessingPassword_passAsSet(coded, 8))) #Aquí al leer el post undercode cambio 8 ))) (pegado) por la ubicación de las gafas de sol.
print(f"Time: {time() - t0}")
if __name__ == '__main__':
main()