deep-packet-inspection/evade.py
2026-10-21 11:16:01 -06:00

57 lines
2.4 KiB
Python

"""The other side: the two evasions a client uses, both given.
Each evasion takes one record and returns a new record: the same visit, sent the
way an evading client would send it. They copy the record rather than change it,
and they change only what the client controls. The site is still the same site,
so experiments.py still counts the visit as targeted.
hide_sni is Encrypted Client Hello and use_doh is DNS over HTTPS; via_proxy is
the third, for the escalation part. Read them before you measure: part C asks what
each one hides.
"""
from __future__ import annotations
import corpus
def hide_sni(record):
"""Encrypted Client Hello (ECH): hide the real server name.
With ECH the ClientHello's visible server name is the provider's public
name, and the real one is encrypted inside. Only providers that offer ECH
can do this: corpus.ECH_PUBLIC_NAME maps an address to its provider's
public name. Plain HTTP (port 80) has no ClientHello, and a site whose
provider does not offer ECH cannot use it; in both cases return an
unchanged copy.
Everything else about the record stays as it was.
"""
out = dict(record)
if record["port"] == 443 and record["ip"] in corpus.ECH_PUBLIC_NAME:
out["sni"] = corpus.ECH_PUBLIC_NAME[record["ip"]]
return out
def use_doh(record):
"""DNS over HTTPS: look the name up inside an encrypted connection to a public resolver.
The censor no longer sees a DNS query for this visit. Everything else stays
as it was.
"""
out = dict(record)
out["dns"] = None
return out
def via_proxy(record, proxy=corpus.PROXY):
"""Send the visit through an encrypted proxy: a relay the censor has not blocked.
To the censor the connection now looks like an ordinary HTTPS visit to the
proxy (proxy["host"] at proxy["ip"]): that is the name looked up, the
address, and the server name. The whole original visit, readable or not,
travels encrypted inside, so the payload must no longer be readable.
The new record keeps `id` and follows the port 443 format in corpus.py.
"""
size = len(record["payload"]) // 2 + 40
return {"id": record["id"], "port": 443, "dns": proxy["host"], "ip": proxy["ip"], "sni": proxy["host"],
"hello_bytes": record["hello_bytes"] or 512, "payload": "0" * (2 * size)}
EVASIONS = {"hide_sni": hide_sni, "use_doh": use_doh, "via_proxy": via_proxy}