README.md
Rendering markdown...
#!/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
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
<title>404 Not Found</title>
<h1>Not Found</h1>
<p>The requested URL was not found on the server.</p>
""")
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)