#!/usr/bin/env bash # ───────────────────────────────────────────────────────────── # capture_tls.sh - Capture TLS ClientHello fingerprints (JA3) # # mitmproxy terminates TLS so it can't see the real JA3 that # Claude CLI / Antigravity sends to Anthropic. This script # captures the REAL TLS fingerprint using tshark. # # Usage: # # Run BEFORE starting claude login / claude "hello" # # (don't use HTTPS_PROXY for this - direct connection) # # sudo ./capture_tls.sh # capture on default interface # sudo ./capture_tls.sh en0 # specify interface # sudo ./capture_tls.sh en0 30 # capture for 30 seconds # # Output: # ./captures/tls_capture_.txt # ./captures/tls_capture_.pcap # ───────────────────────────────────────────────────────────── set -euo pipefail IFACE="${1:-en0}" DURATION="${2:-60}" OUTDIR="./captures" TIMESTAMP=$(date +%Y%m%d_%H%M%S) PCAP_FILE="${OUTDIR}/tls_capture_${TIMESTAMP}.pcap" TXT_FILE="${OUTDIR}/tls_capture_${TIMESTAMP}.txt" mkdir -p "$OUTDIR" # Resolve target IPs echo "Resolving target domains..." DOMAINS=( "api.anthropic.com" "platform.claude.com" "claude.ai" "cloudaicompanion.googleapis.com" "generativelanguage.googleapis.com" "oauth2.googleapis.com" "accounts.google.com" ) HOST_FILTER="" for domain in "${DOMAINS[@]}"; do ips=$(dig +short "$domain" 2>/dev/null | grep -E '^[0-9]+\.' | head -5) for ip in $ips; do if [ -n "$HOST_FILTER" ]; then HOST_FILTER="$HOST_FILTER or host $ip" else HOST_FILTER="host $ip" fi done echo " $domain → $ips" done if [ -z "$HOST_FILTER" ]; then echo "ERROR: Could not resolve any target domains" exit 1 fi CAPTURE_FILTER="tcp port 443 and ($HOST_FILTER)" echo "" echo "═══════════════════════════════════════════════════════" echo " TLS Fingerprint Capture" echo " Interface: $IFACE" echo " Duration: ${DURATION}s" echo " Filter: $CAPTURE_FILTER" echo " PCAP: $PCAP_FILE" echo " Report: $TXT_FILE" echo "═══════════════════════════════════════════════════════" echo "" echo ">>> Now run 'claude login' or 'claude \"hello\"' in another terminal <<<" echo ">>> Press Ctrl+C to stop early <<<" echo "" # Capture pcap in background tshark -i "$IFACE" -f "$CAPTURE_FILTER" -w "$PCAP_FILE" -a "duration:$DURATION" 2>/dev/null & TSHARK_PID=$! # Wait for capture to complete or Ctrl+C trap "kill $TSHARK_PID 2>/dev/null; wait $TSHARK_PID 2>/dev/null" INT TERM wait $TSHARK_PID 2>/dev/null || true echo "" echo "Capture complete. Analyzing..." echo "" # ─── Analysis ─── { echo "═══════════════════════════════════════════════════════" echo " TLS ClientHello Fingerprint Report" echo " Captured: $(date)" echo " PCAP: $PCAP_FILE" echo "═══════════════════════════════════════════════════════" echo "" # Extract JA3 fingerprints echo "─── JA3 Fingerprints (ClientHello) ───" echo "" tshark -r "$PCAP_FILE" \ -Y "tls.handshake.type == 1" \ -T fields \ -e frame.time \ -e ip.dst \ -e tls.handshake.extensions_server_name \ -e tls.handshake.ja3 \ -e tls.handshake.ja3_full \ 2>/dev/null | while IFS=$'\t' read -r ts dst sni ja3 ja3_full; do echo " Time: $ts" echo " Dest IP: $dst" echo " SNI: $sni" echo " JA3 Hash: $ja3" if [ -n "$ja3_full" ]; then echo " JA3 Full: $ja3_full" fi echo "" done echo "" echo "─── TLS Versions ───" echo "" tshark -r "$PCAP_FILE" \ -Y "tls.handshake.type == 1" \ -T fields \ -e tls.handshake.extensions_server_name \ -e tls.handshake.version \ -e tls.handshake.extensions.supported_version \ 2>/dev/null | sort -u | while IFS=$'\t' read -r sni ver supported; do echo " SNI: $sni" echo " Record Version: $ver" echo " Supported Versions: $supported" echo "" done echo "" echo "─── ALPN Protocols ───" echo "" tshark -r "$PCAP_FILE" \ -Y "tls.handshake.type == 1" \ -T fields \ -e tls.handshake.extensions_server_name \ -e tls.handshake.extensions_alpn_str \ 2>/dev/null | sort -u | while IFS=$'\t' read -r sni alpn; do echo " SNI: $sni → ALPN: $alpn" done echo "" echo "" echo "─── Cipher Suites (per ClientHello) ───" echo "" tshark -r "$PCAP_FILE" \ -Y "tls.handshake.type == 1" \ -T fields \ -e tls.handshake.extensions_server_name \ -e tls.handshake.ciphersuite \ 2>/dev/null | head -5 | while IFS=$'\t' read -r sni ciphers; do echo " SNI: $sni" echo " Cipher Suites:" echo " $ciphers" | tr ',' '\n' | while read -r c; do echo " $c" done echo "" done echo "" echo "─── Extensions (per ClientHello) ───" echo "" tshark -r "$PCAP_FILE" \ -Y "tls.handshake.type == 1" \ -T fields \ -e tls.handshake.extensions_server_name \ -e tls.handshake.extension.type \ 2>/dev/null | head -5 | while IFS=$'\t' read -r sni exts; do echo " SNI: $sni" echo " Extensions: $exts" echo "" done echo "" echo "─── Unique JA3 Summary ───" echo "" tshark -r "$PCAP_FILE" \ -Y "tls.handshake.type == 1" \ -T fields \ -e tls.handshake.extensions_server_name \ -e tls.handshake.ja3 \ 2>/dev/null | sort | uniq -c | sort -rn | while read -r count sni ja3; do echo " ${count}x SNI: $sni JA3: $ja3" done echo "" echo "─── TCP Fingerprint (Initial Window Size, TTL) ───" echo "" tshark -r "$PCAP_FILE" \ -Y "tcp.flags.syn == 1 && tcp.flags.ack == 0" \ -T fields \ -e ip.dst \ -e ip.ttl \ -e tcp.window_size_value \ -e tcp.options.mss_val \ -e tcp.options.wscale.shift \ 2>/dev/null | sort -u | while IFS=$'\t' read -r dst ttl win mss wscale; do echo " Dest: $dst TTL: $ttl Window: $win MSS: $mss WScale: $wscale" done } 2>/dev/null | tee "$TXT_FILE" echo "" echo "═══════════════════════════════════════════════════════" echo " Report saved to: $TXT_FILE" echo " PCAP saved to: $PCAP_FILE" echo "" echo " To re-analyze: tshark -r $PCAP_FILE -Y 'tls.handshake.type==1' ..." echo "═══════════════════════════════════════════════════════"