#!/usr/bin/env python3 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 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): cipher = TripleDESCBC(BLOCK_SIZE, iv) result = cipher.encrypt(plain) # In demo mode, returns (truncated_blocks, full_blocks) if isinstance(result, tuple): return result return (result, result) # Same blocks for both if not demo mode def generate_req_and_res(index: int, cookie: str): return (make_unique_request(index, cookie), make_unique_response(index)) def generate_n_rounds(count, cookie): roundTrips = [] for i in range(count): roundTrips.append(generate_req_and_res(i, cookie)) return roundTrips def encrypt_round(round_trip, block_size_bytes): req = round_trip[0] req_iv = os.urandom(8) # 3DES requires 8-byte IV req_truncated, req_full = encrypt(req, req_iv) res = round_trip[1] res_iv = os.urandom(8) # 3DES requires 8-byte IV res_truncated, res_full = encrypt(res, res_iv) return { "request": { "cipher": req_truncated, # Truncated for collision matching "cipher_full": req_full, # Full blocks for XOR recovery "plain_length": len(req), "iv": req_iv, }, "response": { "cipher": res_truncated, "cipher_full": res_full, "plain_length": len(res), "iv": res_iv } } def format_cookie(cookie: str): # truncate to 32 characters and pad to 32 characters if smaller return "{:32s}".format(cookie[0:32]) def main(count: int, file: str, cookie: str, block_size_bytes: int): rounds = generate_n_rounds(count, cookie) encrypted = [encrypt_round(r, block_size_bytes) for r in rounds] packetfile.write_packets(encrypted, file) if __name__ == "__main__": parser = argparse.ArgumentParser(description='Generate N 3DES encrypted packets for Sweet32 attack demo') parser.add_argument('file', type=str, help="File to write encrypted packets to (binary format)") parser.add_argument('--count', metavar='N', type=int, default=20, help="Number of requests to send (in 1000s). Defaults to 20 to create 20k requests") parser.add_argument('--cookie', type=str, default="DEADBEEF-CAFE-FADE-FEED-DEADBEEF", help="The value of the cookie written in each request. Will be truncated/padded to 32 chars") parser.add_argument('--block-size', type=int, default=8, help="Block size in bytes. Defaults to 8 bytes for 3DES (64-bit blocks).") args = parser.parse_args() cookie = format_cookie(args.cookie) main(args.count*1000, args.file, cookie, args.block_size)