Skip to main content

Command Palette

Search for a command to run...

Hidden Handshake

(Very Easy Crypto Challenge)

Updated
Hidden Handshake

OVERVIEW


Start The Instance and Download The Given Files:

Let’s see the given server.py

import random, hashlib, string
from Crypto.Cipher import AES

def kdf(pass1, pass2):
    return hashlib.sha256(pass1 + pass2).digest()

def generate_password(length):
    alphabet = string.ascii_lowercase + string.digits + '!'
    return ''.join(random.choice(alphabet) for _ in range(length))

def encrypt(pass1, pass2, plaintext):
    key = kdf(pass1, pass2)
    cipher = AES.new(key, AES.MODE_CTR, nonce=pass2)
    ciphertext = cipher.encrypt(plaintext)
    return ciphertext

FLAG = open("flag.txt").read().strip()

print("=== Task Force Phoenix - Operation Blackout Interface ===")
print("Welcome, agent. Secure communications are now active.")
print("----------------------------------------------------------")

server_secret = generate_password(8)
while True:
    try:
        pass2 = input("Enter your secure access key: ")
        user = input("Enter your Agent Codename: ")
        assert len(pass2) == 8, "Secure access key must be 8 characters long."
        assert len(user) < 1337, "Codename too long."

        print("Establishing secure communication channel...")

        shared_secret = f"Agent {user}, your clearance for Operation Blackout is: {FLAG}. It is mandatory that you keep this information confidential."
        ciphertext = encrypt(server_secret.encode(), pass2.encode(), shared_secret.encode())

        print(f"Encrypted transmission: {ciphertext.hex()}")
        print("--- End of Transmission ---")
    except Exception as e:
        print(e)
        print("Transmission error.")
  • So I analyzed it with the help of chatgpt and came to know that:
    server_secret is generated once per server execution and used as the AES key derivation input. The server prints an encrypted message using encrypt(server_secret.encode(), pass2.encode(), shared_secret.encode()).

  • The encrypt function derives a key with kdf(pass1, pass2) and creates an AES-CTR cipher with nonce=pass2.

  • The important detail is that the same pass2 value is used for every encryption made during that connection (the program reads pass2 from input each loop, but the exploit keeps the TCP connection open and reuses the single pass2 for multiple requests).

  • So we exploit this by first obtaining the ciphertext that contains the flag, then requesting additional ciphertexts where we control the codename (sending carefully sized "A" strings so an 'A' aligns with each target flag byte), and finally XORing the target ciphertext with each controlled ciphertext to cancel the keystream — since C1 ⊕ C2 = P1 ⊕ P2 and P2 is 'A', the flag bytes are recovered by (C_target ⊕ C_known)[pos] ⊕ 0x41

So the below script is used to retrieve the flag (Be sure to change the required fields such as HOST , PORT)

#!/usr/bin/env python3
import socket
import time
import binascii
import sys

HOST = "Use Your Host Here"   #for eg "94.237.57.115"
PORT = <Use Your Port Here> #for eg 1337
PASS2 = "pass123!"
USER_TARGET = ""
MAX_FLAG_BYTES = 120
RECV_TIMEOUT = 3.0

PREFIX = "Agent "
MID = ", your clearance for Operation Blackout is: "
SUFFIX = ". It is mandatory that you keep this information confidential."

def recv_until(sock, marker, timeout=RECV_TIMEOUT):
    sock.settimeout(0.7)
    buf = ""
    tstart = time.time()
    while True:
        if marker in buf:
            return buf
        if time.time() - tstart > timeout:
            return buf
        try:
            data = sock.recv(4096)
            if not data:
                return buf
            buf += data.decode(errors="ignore")
        except socket.timeout:
            continue

def send_and_get_cipher(sock, pass2, user):
    recv_until(sock, "Enter your secure access key:")
    sock.sendall((pass2 + "\n").encode())
    recv_until(sock, "Enter your Agent Codename:")
    sock.sendall((user + "\n").encode())
    out = recv_until(sock, "--- End of Transmission ---", timeout=5.0)
    if "Transmission error." in out:
        return None
    for line in out.splitlines():
        if line.strip().startswith("Encrypted transmission:"):
            _, hx = line.split("Encrypted transmission:", 1)
            return hx.strip()
    return None

def hex_to_bytes(hx):
    return binascii.unhexlify(hx)

def xor_bytes(a, b):
    return bytes(x ^ y for x, y in zip(a, b))

def recover_flag_online(host, port, pass2, user_target, max_bytes=MAX_FLAG_BYTES):
    print(f"[+] connecting to {host}:{port} ...")
    sock = socket.create_connection((host, port))
    sock.settimeout(1.0)
    banner = recv_until(sock, "Enter your secure access key:", timeout=4.0)
    print(banner, end="")
    print(f"[+] requesting target ciphertext with user='{user_target}' ...")
    c_target_hex = send_and_get_cipher(sock, pass2, user_target)
    if not c_target_hex:
        print("[-] failed to get target ciphertext. Check pass2 and connectivity.")
        sock.close()
        return
    c_target = hex_to_bytes(c_target_hex)
    print(f"[+] got target ciphertext (len={len(c_target)} bytes)")
    start_target = len(PREFIX) + len(user_target) + len(MID)
    print(f"[+] computed FLAG start offset in plaintext = {start_target}")
    recovered = bytearray()
    for i in range(max_bytes):
        absolute_pos = start_target + i
        needed_user_len = absolute_pos - len(PREFIX) + 1
        if needed_user_len <= 0:
            needed_user_len = 1
        user_known = "A" * needed_user_len
        c_known_hex = send_and_get_cipher(sock, pass2, user_known)
        if not c_known_hex:
            print(f"[-] failed to get ciphertext for user_known length {needed_user_len}.")
            break
        c_known = hex_to_bytes(c_known_hex)
        if absolute_pos >= len(c_target) or absolute_pos >= len(c_known):
            print(f"[-] reached end of ciphertexts at pos {absolute_pos}. Stopping.")
            break
        pxor = xor_bytes(c_target, c_known)
        recovered_byte = pxor[absolute_pos] ^ ord('A')
        recovered.append(recovered_byte)
        ch = chr(recovered_byte) if 32 <= recovered_byte <= 126 else f"\\x{recovered_byte:02x}"
        print(f"[{i:03d}] pos={absolute_pos} -> {ch}")
        if recovered_byte == ord('}') or (len(recovered) > 5 and recovered[-1] == 10):
            print("[+] heuristic stop: found '}' or newline, stopping.")
            break
    sock.close()
    flag = recovered.decode(errors="ignore")
    print("\n[+] recovered bytes (best-effort):")
    print(flag)
    return flag

if __name__ == "__main__":
    print("CTR keystream-reuse exploit script")
    print("Make sure HOST/PORT and PASS2 are set at the top of this script.")
    try:
        recovered_flag = recover_flag_online(HOST, PORT, PASS2, USER_TARGET, MAX_FLAG_BYTES)
        print("\nDone.")
    except KeyboardInterrupt:
        print("\nInterrupted by user.")
    except Exception as e:
        print("Error:", e)
        raise

Use it as :

python3 filename.py  #for kali linux, you can use python also in windows/linux

And Here We Got The Flag !!

WE FINALLY DID IT !!!! CHALLENGE SOLVED !!

For Any Query Or Problem Either Leave A Comment Or Contact At reapsec.com

THANKS FOR READING !!!

HTB CHALLENGES

Part 10 of 13

In this series i will provide you HTB Retired challenges Full Walkthrough of various categories. Hope You Will Like It !!

Up next

Void Whispers

(Very Easy Web Challenge)