#!/usr/bin/env python3 """ Generate packets with a GUARANTEED collision for Sweet32 demo. This creates a "rigged" scenario where we craft two packets such that a known-plaintext block collides with a cookie block. This demonstrates the attack formula working correctly without needing billions of packets. The collision is achieved by manipulating the plaintext in the "known" block so that its CBC input matches the cookie block's CBC input. """ import argparse from string import Template import os from datetime import datetime, timedelta from lib import packetfile from lib.tridescbc import TripleDESCBC, BLOCK_SIZE, DEMO_BLOCK_SIZE, DEFAULT_KEY TIME_FORMAT = "%a, %d %b %Y %H:%M:%S GMT" START_TIME = datetime.now() request_template = Template("""GET /nonexistent/$suffix HTTP/1.1 Host: localhost:5000 Connection: keep-alive User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/59.0.3071.115 Safari/537.36 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8 Accept-Encoding: gzip, deflate, br Accept-Language: en-US,en;q=0.8 Cookie: session=$cookie """) response_template = Template("""HTTP/1.0 404 NOT FOUND Content-Type: text/html Content-Length: 233 Server: Werkzeug/0.12.2 Python/3.5.2 Date: $date 404 Not Found

Not Found

The requested URL was not found on the server.

""") def make_unique_request(index: int, cookie: str): return request_template.substitute( suffix="{:010d}".format(index), cookie=cookie) def make_unique_response(index: int): cur_time = START_TIME + timedelta(seconds=index) formatted_time = cur_time.strftime(TIME_FORMAT) return response_template.substitute(date=formatted_time) def encrypt(plain, iv, key=None): """Encrypt plaintext with 3DES CBC. Accepts str or bytes.""" cipher = TripleDESCBC(BLOCK_SIZE, iv, key) result = cipher.encrypt(plain, demo_mode=False) # Full blocks only return result def format_cookie(cookie: str): return "{:32s}".format(cookie[0:32]) def create_rigged_collision_packets(original_cookie): """ Create a packet with a GUARANTEED internal collision. We craft the cookie such that cookie block 47's ciphertext equals block 10's ciphertext (which contains known plaintext "\nUser-Ag"). For CBC: c_i = E_k(p_i XOR c_{i-1}) For collision: c_10 = c_47 This means: p_10 XOR c_9 = p_47 XOR c_46 So: p_47 (cookie) = p_10 XOR c_9 XOR c_46 Since c_9 and c_46 only depend on blocks 0-46 (not on the cookie), we can compute them first, then derive the required cookie value. """ key = DEFAULT_KEY print() print("=" * 60) print(" GENERATING RIGGED PACKET WITH GUARANTEED COLLISION") print("=" * 60) print() print("[*] Creating packet with internal birthday collision...") print("[*] Target: Make cookie block 47 collide with known block 10") print() # Create initial request with placeholder cookie placeholder_cookie = "XXXXXXXX" + original_cookie[8:] # Keep rest of cookie req_initial = make_unique_request(0, placeholder_cookie) iv = os.urandom(8) # Encrypt to get c_9 and c_46 cipher_initial = encrypt(req_initial, iv, key) c_9 = cipher_initial[9] c_46 = cipher_initial[46] # Known plaintext at block 10 (bytes 80-88) p_10 = b'\nUser-Ag' # This is fixed and known (HTTP header) print("[*] CBC Chain Analysis:") print(f" c_9 (prev of block 10): {c_9.hex()}") print(f" c_46 (prev of block 47): {c_46.hex()}") print(f" Known plaintext p_10: {repr(p_10)}") print() # Calculate required cookie bytes: p_47 = p_10 XOR c_9 XOR c_46 rigged_cookie_bytes = bytes([p_10[i] ^ c_9[i] ^ c_46[i] for i in range(8)]) print("[*] Calculating cookie to force collision:") print(f" Formula: cookie = p_10 ⊕ c_9 ⊕ c_46") print(f" Result: {rigged_cookie_bytes.hex()}") print() # Build the full 32-byte cookie with rigged first 8 bytes full_cookie_bytes = rigged_cookie_bytes + b'-RIGGED-COLLISION-DEM' full_cookie = full_cookie_bytes[:32].ljust(32, b'X') # Create the request with the rigged cookie req_template = make_unique_request(0, "X" * 32) req_bytes = bytearray(req_template.encode()) req_bytes[376:408] = full_cookie # Encrypt the rigged request cipher_rigged = encrypt(bytes(req_bytes), iv, key) print("[*] Verifying collision:") print(f" Block 10 ciphertext: {cipher_rigged[10].hex()}") print(f" Block 47 ciphertext: {cipher_rigged[47].hex()}") if cipher_rigged[10] == cipher_rigged[47]: print() print(" ✓ COLLISION ACHIEVED!") else: print() print(" ✗ ERROR: Collision not achieved!") return [] # Create response res = make_unique_response(0) res_iv = os.urandom(8) cipher_res = encrypt(res, res_iv, key) packets = [ { "request": { "cipher": cipher_rigged, "cipher_full": cipher_rigged, "plain_length": len(req_bytes), "iv": iv, }, "response": { "cipher": cipher_res, "cipher_full": cipher_res, "plain_length": len(res), "iv": res_iv, } } ] print() print("=" * 60) print(" SECRET COOKIE") print("=" * 60) print(f" First 8 bytes (hex): {rigged_cookie_bytes.hex()}") print(f" Full cookie (hex): {full_cookie.hex()}") print("=" * 60) print() print("[*] The attacker does NOT know this cookie value!") print("[*] The attack will recover it using only ciphertext.") print() return packets def main(file: str, cookie: str): packets = create_rigged_collision_packets(cookie) if not packets: print("[!] Failed to generate packets!") return # Write packets packetfile.write_packets(packets, file) print(f"[+] Wrote {len(packets)} encrypted packet(s) to: {file}") print() print("Now run the attack:") print(f" python sweet32.py --block-size 8 {file}") print() if __name__ == "__main__": parser = argparse.ArgumentParser(description='Generate rigged Sweet32 packets with guaranteed collision') parser.add_argument('file', type=str, help="Output file") parser.add_argument('--cookie', type=str, default="DEADBEEF-CAFE-FADE-FEED-DEADBEEF", help="The value of the cookie") args = parser.parse_args() cookie = format_cookie(args.cookie) main(args.file, cookie)