LunaCrypt
4 minutos de lectura
Se nos proporciona el resultado de un cifrado personalizado y su código fuente. Sin embargo, el código fuente es difícil de leer porque hay muchas funciones:
import math
from random import randint, seed
from time import time, process_time
strchr = lambda x: chr(x)
strbyt = lambda x, y=0: ord(x[y])
bitlst = lambda x, y: x << y
bitrst = lambda x, y: x >> y
bitext = lambda x, y, z=1: bitrst(x, y) & int(math.pow(2, z) - 1)
bitxor = lambda x, y: x ^ y
bitbor = lambda x, y: x | y
btest = lambda x, y: (x & y) != 0
mthrnd = lambda x, y: randint(x, y)
mthrsd = lambda x: seed(x)
osltim = lambda: int(time())
oslclk = lambda: process_time()
FL_NEGATE = bitlst(1, 1)
FL_UNUSED3 = bitlst(1, 2)
FL_XORBY6B = bitlst(1, 3)
FL_XORBY3E = bitlst(1, 4)
FL_UNUSED2 = bitlst(1, 5)
FL_SWAPBYTES = bitlst(1, 6)
FL_UNUSED1 = bitlst(1, 7)
currtime = osltim()
while True:
if osltim() - currtime != 0:
break
mthrsd(osltim() + oslclk() * 1000)
def ValidateChar(char):
if type(char) is str and len(char) == 1:
char = strbyt(char)
return char
def GenerateFlag():
finalflag = 0
if mthrnd(0, 1) == 1:
finalflag = bitbor(finalflag, FL_SWAPBYTES)
if mthrnd(0, 1) == 1:
finalflag = bitbor(finalflag, FL_NEGATE)
if mthrnd(0, 1) == 1:
finalflag = bitbor(finalflag, FL_XORBY6B)
if mthrnd(0, 1) == 1:
finalflag = bitbor(finalflag, FL_XORBY3E)
if mthrnd(0, 1) == 1:
finalflag = bitbor(finalflag, FL_UNUSED3)
if mthrnd(0, 1) == 1:
finalflag = bitbor(finalflag, FL_UNUSED2)
if mthrnd(0, 1) == 1:
finalflag = bitbor(finalflag, FL_UNUSED1)
return finalflag
def CheckFlag(f, flag):
return btest(f, flag)
def ESwapChar(char):
char = ValidateChar(char)
THIS_MSB = bitext(char, 4, 4)
THIS_LSB = bitext(char, 0, 4)
return strchr(bitbor(bitxor(THIS_MSB, 0x0D), bitxor(bitlst(THIS_LSB, 4), 0xB0)))
def XorBy6B(char):
char = ValidateChar(char)
return strchr(bitxor(char, 0x6B))
def XorBy3E(char):
char = ValidateChar(char)
return strchr(bitxor(char, 0x3E))
def NegateChar(char):
char = ValidateChar(char)
return strchr(255 - char)
FLAGS = []
CHARS = []
def AppendFlag(flag):
FLAGS.append(strchr(bitxor(flag, 0x4A)))
def EncryptCharacter(char):
char = ValidateChar(char)
flag = GenerateFlag()
if CheckFlag(flag, FL_SWAPBYTES):
char = ESwapChar(char)
if CheckFlag(flag, FL_NEGATE):
char = NegateChar(char)
if CheckFlag(flag, FL_XORBY6B):
char = XorBy6B(char)
if CheckFlag(flag, FL_XORBY3E):
char = XorBy3E(char)
return char, flag
def _Encrypt(string):
for i in range(0, len(string)):
char, flag = EncryptCharacter(strbyt(string, i))
if type(char) is int:
char = strchr(char)
CHARS.append(char)
AppendFlag(flag)
def Encrypt(string):
_Encrypt(string)
output = [f"{str(ord(v))} {str(ord(FLAGS[i]))}" for i, v in enumerate(CHARS)]
file = open("output.txt", "w")
file.write(' '.join(output))
file.close()
Desofuscación del código fuente
Básicamente, la función _Encrypt
coge los caracteres del texto claro y cifra cada carácter en EncryptCharacter
. Hay un montón de funciones lambda
que realizan operaciones sencillas sobre caracteres y números.
Al final, podemos simplificar el cifrado como sigue:
from random import randint
def generate_flag() -> int:
finalflag = 0
if randint(0, 1):
finalflag |= 0b0100_0000
if randint(0, 1):
finalflag |= 0b0000_0010
if randint(0, 1):
finalflag |= 0b0000_1000
if randint(0, 1):
finalflag |= 0b0001_0000
if randint(0, 1):
finalflag |= 0b0000_0100
if randint(0, 1):
finalflag |= 0b0010_0000
if randint(0, 1):
finalflag |= 0b1000_0000
return finalflag
def encrypt_character(char: int, flag: int) -> int:
if flag & 0b0100_0000: # ESwap
THIS_MSB = (char >> 4) & 0b1111
THIS_LSB = char & 0b1111
char = ((THIS_MSB ^ 0b1101) | ((THIS_LSB << 4) ^ 0b1011_0000))
if flag & 0b0000_0010: # Negate
char = 255 - char
if flag & 0b0000_1000: # XOR by 0x6B
char ^= 0b0110_1011
if flag & 0b0001_0000: # XOR by 0x3E
char ^= 0b0011_1110
return char
def encrypt(string: str):
FLAGS, CHARS = [], []
for s in string:
flag = generate_flag()
char = encrypt_character(ord(s), flag)
CHARS.append(char)
FLAGS.append(flag ^ 0x4a)
print(' '.join(f'{c} {f}' for c, f in zip(CHARS, FLAGS)))
El archivo output.txt
contiene los siguientes números:
108 182 82 176 167 158 69 222 39 102 234 14 241 16 10 218 160 108 76 234 225 224 1 12 97 122 114 90 10 90 250 14 155 80 101 186 97 218 115 218 207 76 190 174 196 84 192 144
Proceso de descifrado
Estos números pertenecen a las listas CHARS
(posiciones impares) y FLAGS
(posiciones pares). El cifrado está basado en XOR, NOT y desplazamientos de bits en cierto orden. Y entonces, el proceso de descifrado es prácticamente igual (en orden inverso). Para descifrar output.txt
se utiliza un script en Python:
def decrypt_character(char: int, flag: int) -> int:
if flag & 0b0001_0000:
char ^= 0b0011_1110
if flag & 0b0000_1000:
char ^= 0b0110_1011
if flag & 0b0000_0010:
char = 255 - char
if flag & 0b0100_0000:
THIS_MSB = (char >> 4) & 0b1111
THIS_LSB = char & 0b1111
char = ((THIS_LSB << 4) ^ 0b1101_0000) | (THIS_MSB ^ 0b1011)
return char
Flag
Y así conseguimos la flag:
$ python3 solve.py
HTB{Lun4_Lu4_L4t1n_M00n}
El script completo se puede encontrar aquí: solve.py
.