LunaCrypt
4 minutes to read
The challenge provides an output file with encrypted data and the source code used to it. However, the code is a little difficult to read because there are a lot of functions:
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()
Source code deobfuscation
Basically, the _Encrypt
function takes the characters of the plaintext and encrypts each character in EncryptCharacter
. There are a lot of lambda
functions that perform simple operations on characters and numbers.
In the end, we can simplify the encryption as:
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)))
The file output.txt
contains the following numbers:
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
Decryption
These numbers belong to the CHARS
(odd positions) and FLAGS
(even positions) lists. The encryption is based in XOR, NOT and bit-shifts in a certain order. And therefore, the decryption is mostly the same (in reverse order). A Python script is used to decrypt the output.txt
:
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
And then, the flag is decrypted:
$ python3 solve.py
HTB{Lun4_Lu4_L4t1n_M00n}
The full script can be found in here: solve.py
.