Загрузка данных


from flask import Flask, request, jsonify
from Crypto.Cipher import AES
from Crypto.Random import get_random_bytes
import base64, struct, os

app = Flask(__name__)
SERVICE_KEY = os.urandom(16)
KEY_VICTIM = os.urandom(16)
KEY_ATTACKER = os.urandom(16)
SECRET_VICTIM = os.urandom(16)
AAD_PREFIX = b"user-aad-prefix:"
FLAG = os.environ.get("FLAG") or "DUCKERZ{default_flag}"
FLAG_IV = get_random_bytes(12)

def aes_ecb_encrypt_block(key: bytes, block16: bytes) -> bytes:
    cipher = AES.new(key, AES.MODE_ECB)
    return cipher.encrypt(block16)

def aes_ctr_keystream(key: bytes, j0: bytes, length: int) -> bytes:
    out = bytearray()
    ctr = int.from_bytes(j0, "big")
    ctr += 1
    cipher = AES.new(key, AES.MODE_ECB)
    while len(out) < length:
        block = (ctr & ((1 << 128) - 1)).to_bytes(16, "big")
        out_block = cipher.encrypt(block)
        take = min(16, length - len(out))
        out.extend(out_block[:take])
        ctr += 1
    return bytes(out)

R = 0xE1000000000000000000000000000000
MASK128 = (1 << 128) - 1

def gf128_mul(x: int, y: int) -> int:
    z = 0
    v = x & MASK128
    for i in range(128):
        if (y >> (127 - i)) & 1:
            z ^= v
        msb = (v >> 127) & 1
        v = ((v << 1) & MASK128)
        if msb:
            v ^= R
    return z & MASK128

def ghash(H_bytes: bytes, aad: bytes, ct: bytes) -> bytes:
    H = int.from_bytes(H_bytes, "big")
    len_a_bits = len(aad) * 8
    len_c_bits = len(ct) * 8
    len_block = struct.pack(">QQ", len_a_bits, len_c_bits)
    data = aad + ct + len_block
    Y = 0
    for i in range(0, len(data), 16):
        block = data[i:i+16]
        if len(block) < 16:
            block = block + b"\x00" * (16 - len(block))
        X = int.from_bytes(block, "big")
        Y = gf128_mul(Y ^ X, H)
    return Y.to_bytes(16, "big")

def compute_j0_from_iv(iv12: bytes) -> bytes:
    return iv12 + b"\x00\x00\x00\x01"

def buggy_encrypt(tenant_key: bytes, iv12: bytes, pt: bytes, aad: bytes, global_H: bytes):
    j0 = compute_j0_from_iv(iv12)
    ks = aes_ctr_keystream(tenant_key, j0, len(pt))
    ct = bytes(x ^ y for x, y in zip(pt, ks))
    e_j0 = aes_ecb_encrypt_block(tenant_key, j0)
    g = ghash(global_H, aad, ct)
    tag = bytes(x ^ y for x, y in zip(e_j0, g))
    return ct, tag

def buggy_decrypt_and_verify(tenant_key: bytes, iv12: bytes, ct: bytes, tag: bytes, aad: bytes, global_H: bytes):
    j0 = compute_j0_from_iv(iv12)
    e_j0 = aes_ecb_encrypt_block(tenant_key, j0)
    g = ghash(global_H, aad, ct)
    expected_tag = bytes(x ^ y for x, y in zip(e_j0, g))
    if expected_tag != tag:
        return None, False
    ks = aes_ctr_keystream(tenant_key, j0, len(ct))
    pt = bytes(x ^ y for x, y in zip(ct, ks))
    return pt, True

GLOBAL_H = aes_ecb_encrypt_block(SERVICE_KEY, b"\x00" * 16)
AAD_VICTIM = AAD_PREFIX + SECRET_VICTIM
FLAG_CT, FLAG_TAG = buggy_encrypt(KEY_VICTIM, FLAG_IV, FLAG.encode("latin1"), AAD_VICTIM, GLOBAL_H)
FLAG_BLOB = base64.b64encode(FLAG_IV + FLAG_CT + FLAG_TAG).decode()

@app.route("/", methods=["GET"])
def index():
    return jsonify({
        "note": "Use /flag, /aes_ecb_service, /aes_ecb_tenant, /ghash_probe, /verify.",
        "AAD_PREFIX_hex": AAD_PREFIX.hex(),
        "source_code": "Available at /source"
    })

@app.route("/flag", methods=["GET"])
def get_flag():
    return jsonify({"flag_blob": FLAG_BLOB})

@app.route("/source", methods=["GET"])
def get_source():
    import os
    current_file = os.path.abspath(__file__)
    try:
        with open(current_file, 'r', encoding='utf-8') as f:
            source_code = f.read()
        return source_code, 200, {'Content-Type': 'text/plain; charset=utf-8'}
    except Exception as e:
        return f"Error reading source code: {e}", 500

@app.route("/aes_ecb_service", methods=["POST"])
def aes_ecb_service():
    data = request.get_json(force=True)
    block_b64 = data.get("block_b64", "")
    try:
        blk = base64.b64decode(block_b64)
    except Exception:
        return jsonify({"error": "block_b64 must be base64"}), 400
    if len(blk) != 16:
        return jsonify({"error": "block must be 16 bytes"}), 400
    c = aes_ecb_encrypt_block(SERVICE_KEY, blk)
    return jsonify({"cipher_b64": base64.b64encode(c).decode()})

@app.route("/aes_ecb_tenant", methods=["POST"])
def aes_ecb_tenant():
    data = request.get_json(force=True)
    who = data.get("who", "")
    block_b64 = data.get("block_b64", "")
    if who not in ("victim", "attacker"):
        return jsonify({"error": "who must be 'victim' or 'attacker'"}), 400
    try:
        blk = base64.b64decode(block_b64)
    except Exception:
        return jsonify({"error": "block_b64 must be base64"}), 400
    if len(blk) != 16:
        return jsonify({"error": "block must be 16 bytes"}), 400
    key = KEY_VICTIM if who == "victim" else KEY_ATTACKER
    c = aes_ecb_encrypt_block(key, blk)
    return jsonify({"cipher_b64": base64.b64encode(c).decode()})

@app.route("/encrypt", methods=["POST"])
def encrypt_endpoint():
    data = request.get_json(force=True)
    who = data.get("who", "")
    if who not in ("victim", "attacker"):
        return jsonify({"error": "who must be 'victim' or 'attacker'"}), 400
    aad_hex = data.get("aad_hex", "")
    msg_b64 = data.get("msg_b64", "")
    try:
        aad_from_client = bytes.fromhex(aad_hex) if aad_hex != "" else b""
        pt = base64.b64decode(msg_b64) if msg_b64 != "" else b""
    except Exception:
        return jsonify({"error": "invalid inputs"}), 400
    iv = get_random_bytes(12)
    aad_actual = AAD_PREFIX + aad_from_client
    tenant_key = KEY_VICTIM if who == "victim" else KEY_ATTACKER
    ct, tag = buggy_encrypt(tenant_key, iv, pt, aad_actual, GLOBAL_H)
    blob = base64.b64encode(iv + ct + tag).decode()
    return jsonify({"blob": blob})

@app.route("/ghash_probe", methods=["POST"])
def ghash_probe():
    data = request.get_json(force=True)
    who = data.get("who", "")
    if who != "attacker":
        return jsonify({"error": "ghash_probe allowed only for attacker in this lab"}), 403
    aad_hex = data.get("aad_hex", "")
    ct_b64 = data.get("ct_b64", "")
    try:
        aad = bytes.fromhex(aad_hex) if aad_hex != "" else b""
        ct = base64.b64decode(ct_b64) if ct_b64 != "" else b""
    except Exception:
        return jsonify({"error": "invalid inputs"}), 400
    g = ghash(GLOBAL_H, aad, ct)
    return jsonify({"ghash_b64": base64.b64encode(g).decode()})

@app.route("/verify", methods=["POST"])
def verify():
    data = request.get_json(force=True)
    who = data.get("who", "")
    if who not in ("victim", "attacker"):
        return jsonify({"error": "who must be 'victim' or 'attacker'"}), 400
    blob_b64 = data.get("blob", "")
    aad_hex = data.get("aad_hex", "")
    try:
        blob = base64.b64decode(blob_b64)
    except Exception:
        return jsonify({"error": "blob must be base64"}), 400
    if len(blob) < 12 + 16:
        return jsonify({"error": "blob too short"}), 400
    try:
        aad_from_client = bytes.fromhex(aad_hex) if aad_hex != "" else b""
    except Exception:
        return jsonify({"error": "aad_hex invalid"}), 400
    iv = blob[:12]; ct = blob[12:-16]; tag = blob[-16:]
    aad_actual = AAD_PREFIX + aad_from_client
    tenant_key = KEY_VICTIM if who == "victim" else KEY_ATTACKER
    pt, ok = buggy_decrypt_and_verify(tenant_key, iv, ct, tag, aad_actual, GLOBAL_H)
    if ok:
        return jsonify({"status": "valid", "plaintext_b64": base64.b64encode(pt).decode()})
    else:
        return jsonify({"status": "invalid"}), 403



if __name__ == "__main__":
    app.run(host="0.0.0.0", port=5000, debug=True)