1 Punkte von GN⁺ 2025-03-19 | 1 Kommentare | Auf WhatsApp teilen
  • Es handelt sich um einen PoC, bei dem zwischen Apple TV und dem Internet ein pfSense-basierter MITM-Proxy platziert wird, HTTPS entschlüsselt und anschließend die von YouTube ausgelieferten Protobuf-Antworten verändert werden, damit auf Apple-Geräten keine Werbe-Slots registriert werden
  • Bestehende DNS-Blocker und VPN-Routing stießen an Grenzen wie DNS-TTL, IP-Abweichungen, 403-Fehler und ASN-Leaks, weil YouTube-Werbung und das eigentliche Video über dieselbe Domain- und Infrastrukturstruktur laufen
  • Das Squid-Experiment wurde wegen Performance- und Konfigurationsproblemen abgebrochen; danach wurde auf einen Ansatz mit mitmproxy/mitmdump in einer FreeBSD-Jail umgestellt, der TLS-Traffic entschlüsselt und JSON- sowie Protobuf-Antworten direkt verändert
  • Bei Web-YouTube konnten adPlacements, playerAdParams, pagead-URLs usw. aus JSON entfernt werden, aber die iOS-YouTube-App speicherte Werbe-Slots und Tracking-Informationen in application/x-protobuf-Antworten, sodass die Protobuf-Struktur behandelt werden musste
  • Der finale Ansatz ist ein linearer Scan mit 1-Byte-Modifikation, der in der Nähe des Strings /pagead/ rückwärts nach dem Field-Tag sucht und Tags wie 50195462 in target_field_tag - 1 ändert; das liefert ohne vollständiges Decoding eine für Echtzeitverarbeitung geeignete Performance

Ziel und anfängliches Netzwerkdesign

  • Ziel war es, einen Router auf Basis von FreeBSD und pfSense zu bauen, um pre-roll-, mid-roll- und end-roll-YouTube-Werbung auf Apple TV und iPhone netzwerkweit zu blockieren
  • Wenn man zwischen Apple TV und dem externen Internet einen Man-in-the-Middle-Proxy platziert, kann man HTTPS-Traffic entschlüsseln und die Protocol-Buffer-Daten lesen, die Google zum Befüllen von YouTube-Werbung verwendet
  • Nach einigen Monaten, in denen YouTube-Werbeblocking umgesetzt wurde, wurde schließlich YouTube Premium bezahlt; dabei wird klargestellt, dass „es ist möglich“ und „es sollte getan werden“ zwei verschiedene Dinge sind
  • Als Gründe für das Blockieren von Werbung und Tracking werden Datenschutz-Tracking, Bandbreitenverschwendung, Clickbait und Cryptojacking genannt
    • Es wird davon ausgegangen, dass 25 bis 40 % des Netzwerk-Traffics aus Werbung, Tracking-Skripten, fingerprint.js, googletagmanager.js und Echtzeit-Analyse-Loadern wie Hotjar bestehen können
    • Crypto-Mining-JavaScript wie CoinHive.js könne Computer überhitzen oder missbrauchen, um kleine Geldbeträge zu erwirtschaften

pfSense-Hardware und Grundeinstellungen

  • Um ein gesamtes SMB-Netzwerk zu schützen, seien VM, Docker-Images und Raspberry Pi leistungsmäßig unzureichend; stattdessen werde dedizierte Hardware benötigt, die ausschließlich Paket-Routing, Entschlüsselung und Monitoring übernimmt
  • Die verwendete Router-Hardware bestand aus einem Mini-PC mit AES-NI-Befehlssatz, DDR4-RAM, mSATA-SSD und einem USB-Laufwerk zum Flashen von pfSense
    • Eine Beispielkonfiguration ist ein J4125-Mini-PC mit 32 GiB DDR4-RAM und 128 GiB mSATA-SSD
    • 128 GB Speicherplatz werden als ausreichend für Logs, geringeren SSD-Verschleiß, Paketmitschnitte und NPM-/Docker-Edge-Cache angesehen
  • Das pfSense-Installations-Image ist etwa 360 MB groß und kann mit der Etcher-AppImage auf ein USB-Laufwerk geflasht werden
  • Nach der ersten Einrichtung wurde AES-NI als „Yes (inactive)“ angezeigt und dann unter System › Advanced › Miscellaneous manuell aktiviert
  • Um die 32 GiB RAM zu nutzen, wurden /var und /tmp großzügig als RAM-Disk zugewiesen; die 128-GiB-SSD wurde mit erwartetem Wear-Leveling konfiguriert, während stündliche RAM-Disk-Backups eingerichtet wurden
  • Zum Dashboard wurde ein S.M.A.R.T.-Widget hinzugefügt, damit sich Auffälligkeiten der SSD erkennen lassen

DNS-Blockierung, Netzwerksegmentierung und pfBlockerNG

  • Zuvor wurde Pi-hole auf einem Raspberry Pi als Werbeblocker auf DNS-Ebene verwendet; auf pfSense wurde pfBlockerNG-devel installiert, um Werbe- und Schadinhalt-Blockierung sowie Geo-Blocking zu testen
  • Wenn der Dienst pfb_dnsbl nicht startet oder im Status-Tab [ Missing CRON task ] angezeigt wird, wird empfohlen, das leere File /var/run/booting zu löschen
  • Mithilfe der drei Gigabit-Ports des Mini-PCs wurde statt VLAN ein physisch getrenntes Netzwerk aufgebaut, um „phoning-home“-Geräte wie Alexa und Apple TV vom Hauptnetz zu isolieren
    • Nicht vertrauenswürdige Geräte wurden im privaten Netzwerk 172.31.1.0/24 platziert
    • Das vertrauenswürdige LAN blieb bei 192.168/16
    • Das Hardware-LAN für IoT soll durch den Adblocker laufen, und fest kodierte DNS-Abfragen an 1.1.1.1 oder 9.9.9.9 sollen abgefangen werden, damit YouTube den DNS-Blocker nicht umgehen kann
  • Es wurden NAT-Regeln eingerichtet, damit alle Clients hinter pfSense den lokalen Unbound-DNS-Server verwenden
    • Dafür müsse zunächst DNS over TLS blockiert werden, damit sich DNS-Abfragen umleiten lassen
    • Das iPhone könne zwar eine Privacy Warning wegen blockiertem verschlüsseltem DNS-Traffic anzeigen, aber Upstream-DNS-Anfragen würden weiterhin verschlüsselt an Cloudflare gesendet
    • NAT-Reflection sollte deaktiviert werden, damit das externe Internet keinen Zugriff auf den DNS-Server erhält
  • Es wurde ein Firewall-Alias Non_WAN erstellt, um Port 53 lokaler DNS-Abfragen auf Interfaces außer WAN auf localhost umzuleiten
  • Da YouTube Werbung und Hauptvideo über dieselbe Domain ausliefert, war es schwierig, Werbung allein mit Domainnamen-Blockern wie pfBlockerNG oder Pi-hole herauszufiltern

Experimente mit VPN-Umgehung und deren Scheitern

  • Zusätzlich zum Werbeblocking wurde auch mit einem Ansatz experimentiert, den YouTube-Werbealgorithmus zu täuschen, sodass der Nutzer für Werbekunden weniger attraktiv erscheint
    • Dazu sollte der pfSense-Router YouTube-Standort-Tracking-Traffic über ein VPN in Regionen mit geringerer Zuschauerzahl routen
    • Ziel war, vom YouTube-Konto als „70-jähriger Mann, wohnhaft in Italien“ erkannt zu werden
  • Auf pfSense wurde statt OpenVPN WireGuard genutzt, um in einem Basisversuch den gesamten Traffic des Apple TV über ein VPN zu leiten
    • Das FreeBSD-WireGuard-Paket wurde installiert und ein Tunnel hinzugefügt sowie aktiviert
    • Für die NordLynx-Konfiguration wurde auf einer Linux-VM mit sudo wg showconf nordlynx der private Schlüssel ausgelesen und nach pfSense übertragen
  • Die Tests ergaben, dass Google auf dem Laptop auf Italienisch angezeigt wurde und auch YouTube auf dem Apple TV auf Italienisch umschaltete
    • Werbung wurde weiterhin teilweise ausgeliefert, aber weniger als zuvor
    • Bei Netflix und Amazon Prime traten Probleme auf; offenbar wurden CSS- oder Font-Dateien blockiert oder Thumbnails nicht geladen
    • Es wird davor gewarnt, den gesamten Apple-TV-Traffic über ein VPN zu schicken, da Netflix und Prime VPN-Anbieter und Geofencing gut erkennen
  • Danach wurden Firewall-Policy-Regeln erstellt, um nur den YouTube-Traffic des Apple TV über das VPN zu leiten, darunter www.youtube.com, youtube.com, googlevideo.com, accounts.google.com, googleapis.com und gstatic.com
    • Das Ergebnis war, dass YouTube den Nutzer in Mailand verortete, während Netflix und Prime Video ihn in Kanada sahen
    • Die Werbung sei auf ein „few and far between“-Niveau zurückgegangen
  • Einen Tag später trat eine DNS-Race-Condition auf
    • pfSense-Hostname-Aliase werden standardmäßig alle 300 Sekunden aufgelöst
    • Die YouTube-DNS-TTL kann 1.440 Sekunden, also 24 Minuten, betragen
    • Wenn sich die vom Alias-Daemon aufgelöste IP von der tatsächlich vom Client erhaltenen IP unterscheidet, kann die Policy den YouTube-Traffic möglicherweise nicht durch den Tunnel leiten
  • Einige YouTube-Videos ließen sich wegen 403 Forbidden nicht abspielen
    • YouTube bette laut Beschreibung die IP des Nutzers in jede googlevideo.com-Anfrage ein
    • Wenn abgewandelte Domains wie r5---sn-hpa7kn76.googlevideo.com nicht durch den Tunnel geleitet werden, wird die Anfrage von der falschen IP gesendet und verursacht Probleme
    • Erforderlich wäre ein Wildcard-Tunneling für *.googlevideo.com, aber NAT und Firewall-Regeln arbeiten nicht mit Wildcard-Hostnamen, sondern mit IPs

PoC zur IP-Verfolgung auf Basis von DNS-Queries

  • Es wurde ein Ansatz für Google Video DNS Query Hijacking entworfen, um *.googlevideo.com über ein VPN zu routen
    • Dabei werden DNS-Query-Logs regelmäßig verfolgt und *.googlevideo.com-Queries einer Alias-Liste hinzugefügt
    • Wenn jedes Video eine eigene und veränderte Domain verwendet, funktioniert diese Methode nach Einschätzung des Autors nicht, es sei denn, man aktualisiert sie für jedes Video neu
  • Das neue Ziel war, mit Python 3 und der pfSense-REST-API DNS-Queries zu überwachen, IPs zu erfassen, die Antwort kurz zurückzuhalten, dann die IP zur VPN-Tunneling-Regel hinzuzufügen und anschließend die DNS-Antwort freizugeben
  • Die pfSense-REST-API wurde installiert und per GET-Request an https://pfsense/api/v1/firewall/alias der Alias VPN_domains abgefragt
  • Das Python-Modul des Unbound DNS Resolver wurde untersucht, und das Logging von DNS-Query-Messages gelang
    • Die aktuelle Python-Version war 3.8
    • Da die Beispielcodes für das Unbound-Python-Modul auf Python 2.4 basierten, wurde angenommen, dass 2to3 oder zusätzliche Formatierung nötig sein könnten
  • Das PoC-Skript extrahiert A/AAAA-Record-IPs aus der DNS-Antwort und fügt sie dem pfSense-Alias hinzu
    • A-Records werden mit ipaddress.IPv4Address(d.rr_data[j][2:]).exploded verarbeitet
    • AAAA-Records werden mit ipaddress.IPv6Address(d.rr_data[j][2:]).exploded verarbeitet
    • Für den Alias wurden TTL auf 1 Stunde und die Kapazität auf 500 gesetzt
  • Am nächsten Tag trat im Unbound DNS Resolver ein Segfault auf, und da bei jedem Hinzufügen einer IP die pfSense-Regel neu geladen werden musste, wurde pfSense sehr langsam

Wechsel von Squid zu mitmproxy

  • Das neue Ziel änderte sich dahin, einen Proxy aus der Squid-Familie zu prüfen und zu installieren, ein vertrauenswürdiges gefälschtes CA-Zertifikat zu erstellen und dann den TLS-Traffic zu entschlüsseln
  • In den Squid-Experimenten wurde getestet, ob der als pfSense-Paket bereitgestellte squid3-Proxy die Anforderungen erfüllt
    • Es wurde ein eigener Ordner /squid_cache angelegt und die Cache-Größe auf 8 GiB gesetzt
    • Transparent HTTPS Support wurde erwartet
  • Nach einem Tag Konfiguration von Squid und SquidGuard wurde das Vorhaben aufgegeben
    • Die Geschwindigkeit war sehr langsam
    • Die ACL-Konfiguration war umständlich
    • Es gab ein Problem im Zusammenhang mit https://http/*
    • Das Aktualisieren der SquidGuard-URL-Filterliste dauerte sehr lange
    • Die Squid-Oberfläche war unzureichend
  • Danach fiel die Entscheidung auf mitmproxy, das in Python geschrieben ist
    • Wegen der Erweiterbarkeit über Python-Hooks und der UI wurde mitmproxy statt SSLSplit gewählt
    • Die FreeBSD-Version von pfSense war 12.2-Stable, 64-bit build
  • In der Standardumgebung von pfSense waren Jails deaktiviert, daher wurde ezjail manuell installiert und ein Jail für mitmproxy erstellt
    • Das Jail wurde mit ezjail-admin create mitmproxy 'lo0|127.0.1.1' angelegt
    • Für den transparenten Proxy-Modus wurde allow.raw_sockets=1 gesetzt
    • Wenn Raw Sockets blockiert sind, können Fehler wie Transparent mode failure oder Cannot open connection, no hostname given. auftreten
  • Das Ausführen des Linux-Tarball-Binaries schlug unter FreeBSD fehl
    • Es trat ELF interpreter /lib64/ld-linux-x86-64.so.2 not found auf
    • Auch libdl.so.2, libz.so.1, libpthread.so.0, libc.so.6 wurden nicht gefunden
  • Innerhalb des Jails wurde pkg install mitmproxy ausgeführt; die Installation verlangte 50 Pakete, 206 MiB zusätzlichen Speicherplatz und 33 MiB Download
  • Damit MITMProxy im LAN erreichbar ist, wurde die virtuelle IP 127.0.1.1 an localhost gebunden und per NAT-Regel [Private IPs]:8080 temporär auf 127.0.1.1:8080 weitergeleitet
  • Die von MITMProxy automatisch erzeugte CA-PEM-Datei ist ~/.mitmproxy/mitmproxy-ca-cert.pem, und dieses CA-Zertifikat wurde im Trusted Root Store des Testgeräts installiert
  • mitmproxy nutzte selbst im Idle-Zustand viel CPU; als Ursache wurden die Echtzeit-Erzeugung von TLS-Zertifikaten pro Request und übermäßiges Logging vermutet, was die Geschwindigkeit deutlich verringerte
    • mitmdump lässt die UI und übermäßiges Logging weg und verursacht daher voraussichtlich weniger CPU-Last

Entfernung von YouTube-JSON-Werbung im Web

  • Certificate Pinning ist ein Verfahren, bei dem Server oder Client den erwarteten Zertifikats-Fingerprint bereits kennen, sodass eine Zertifikatsfälschung durch MITMProxy nicht funktioniert
  • Problematische Hosts können mit der Option --ignore-hosts am Proxy vorbeigeleitet werden
    • Als Beispiele werden apple.com:443 und icloud.com:443 ignoriert
  • Beim Zugriff auf YouTube waren Seitenanzeigen in MITMProxy zusammen mit unverschlüsselten Headern sichtbar, und es wurde geprüft, ob eine einfache Sperrung per Regex möglich wäre
  • Um das YouTube-Adblock-Skript anzuwenden, wurde mitmdump um --scripts "youtube.py" ergänzt
  • Der Smoke-Test-Filter blockiert Werbeanfragen anhand von URL-Substrings
    • youtube.com: /pagead/, /log_event?, /stats/ads, /stats/qoe?, /ptracking?, /generate_204, el=adunit, adformat=, /activeview?
    • google.com, google.ca: /pagead/
    • ggpht.com: .
  • Die zu blockierenden Requests schienen in MITMProxy und im DevTools-Network-Panel tatsächlich blockiert zu werden, dennoch wurden weiterhin Ads angezeigt; manchmal wurden sie auch von selbst übersprungen oder die Wiedergabe schlug fehl
  • Später wurden in der JSON-Payload zahlreiche werbebezogene URLs gefunden
  • Nach der Analyse der YouTube-UI und des HTTP-Workflows bis hin zu Cookies und Service Workern sei es gelungen, Pre-Roll-, Post-Roll- und Mid-Video-Werbung vollständig zu entfernen
  • In diesem Stadium ließ sich auf dem Router Werbung aus der JSON-Payload von YouTube-Web-Ads entfernen

iOS-YouTube und das Protobuf-Problem

  • Die YouTube-iOS-App zeigte in der Protobuf-Version desselben API-Calls sehr ähnliche Daten wie die Webversion
  • Bei Protobuf sind die Keys numerisch und können sich ändern, daher ließ sich die Advertisement-Sektion nicht mit einem Ansatz wie JSONPath finden
  • YouTube sendet eine große Liste kommender Ads als Payload; wenn diese vollständig verbraucht ist, trifft kurz darauf die nächste große Liste ein
  • Im Protobuf-Payload waren Zeichenketten wie „Telus“, „Samsung TV“, „Boxing Week“ und „Buy now“ zu sehen
  • Das iOS-YouTube-Protokoll unterschied sich vom Web-Traffic
    • In der Webversion ließen sich Ad-Video und gewünschtes Video bis zu einem gewissen Grad anhand der URL und des Query-Parameters range unterscheiden
    • Das iOS-Protokoll verwendet weder den Query-Parameter range noch den Header Range, sondern Zähler wie &nr=2 und &nr=3 für Video-Chunks
    • Um Ads auf iOS zu blockieren, musste die Protobuf-Antwort reverse-engineert werden
  • In der decodierten Protobuf-Nachricht wurden die Einträge has_unlimited_entitlement: False und has_premium_lite_entitlement: False gefunden, statt diese umzuschalten kehrte man aber zu Heuristiken zurück
  • Das Decodieren von etwa 500 KiB rohem Protobuf mit einer reinen Python-Implementierung war sehr langsam
    • Auf einem i7-6700-Desktop lag das Python-Ergebnis bei etwa 2,06 bis 2,11 Sekunden
    • Auf einem pfSense-Router lag das Python-Ergebnis bei etwa 22,8 bis 24,2 Sekunden
    • C++ protoc --decode_raw brauchte auf dem Desktop etwa 0,017 bis 0,022 Sekunden und auf dem pfSense-Router etwa 0,12 bis 0,14 Sekunden

Versuche zur Protobuf-Decodierung und Schema-Extraktion

  • Da rohes Protobuf-Decoding in Python nicht unterstützt wurde, entschied man sich statt direkter Nutzung von C++ libprotobuf.so für einen Ansatz, der per subprocess.Popen mit der C++-Binary protoc kommuniziert
  • Beim Fuzzing von Ad-Video-Antworten wurden leere 200, 404, 503, abgeschnittene Response-Bodys und teilweise genullte Ad-Videos ausprobiert, doch die iOS-App wurde nur langsamer, stürzte dann ab oder blieb auf dem Ad-Bildschirm hängen
  • Das Blockieren von URLs löste Gegenreaktionen der App aus, und die Video-Response-Chunks enthielten auch Session-Metadaten
  • blackboxprotobuf für Burp Suite kann rohe Protobuf-Wire-Messages decodieren, Inhalte injizieren und sie anschließend wieder encodieren, um das Verhalten von Protobuf-Endpoints zu prüfen
    • Empfohlen wird die originale Burp-Suite-Version, nicht ein Fork auf PyPI
    • Einige Forks haben wegen tiefer Rekursion Probleme mit Stack Overflow oder unendlicher Rekursion
    • Mit C++-Bindings lassen sich etwa 500 KiB rohes Protobuf in wenigen Sekunden transkodieren
  • Das erzeugte Schema war nicht perfekt, groß und tief verschachtelt, und Pretty-Print war langsam, aber zum Finden von Ad-Details ausreichend
  • Um echte .proto- oder Schema-Dateien aus der Android-YouTube-APK zu extrahieren, wurden PBTK, Apktool, dex2jar und Java Decompiler ausprobiert
    • PBTK extrahierte nur eine 59-Byte große Proto-Datei
    • In Java gab es Protobuf-Klassen sowie Getter/Setter, aber da keine echten Schema-Dateien gefunden wurden, wurde dieser Ansatz abgebrochen

Der endgültige Durchbruch: 1-Byte-Änderung eines Protobuf-Field-Tags

  • Aus entschlüsseltem Netzwerk-Traffic und Protobuf-Fuzzing ergab sich, dass Ads offenbar in Slots eines bestimmten Videos eingetragen werden
    • Zu den Slot-Typen gehören Pre-Roll, Mid-Roll, End-Roll, Full-Page und Ad Pods
    • Wenn Ad-URLs blockiert werden, tritt ein Fehler auf nach dem Muster „eine nicht existierende Ad hat einen Slot reserviert“, was eine UI-Panik auslöst
  • Ohne das ursprüngliche Schema führt Decode-, Edit- und Re-Encode zu veränderter Encodierung; da unklar ist, ob ZigZag verwendet wird, welche Zahlentypen wie int32, int64, sint32/64 oder varint vorliegen und da die Reihenfolge von Objektfeldern meist nicht deterministisch ist, wurde das als problematisch angesehen
  • In der Backward-Kompatibilität von Protobuf und im Verhalten von UnknownFieldSet wurde ein möglicher Umweg gefunden
    • Wenn alte Software eine Message mit neu hinzugefügten Feldern liest, können Unknown Fields entstehen
    • Wird ein bestimmter Field-Key auf einen anderen Wert geändert, kann die gesamte Substruktur mit Ad- und Tracking-Informationen in einen nicht verfügbaren Zustand geraten
  • Als Beispiel wurde die Idee genannt, den Field-Key 49399797 in 49399796 zu ändern, damit diese Ad-/Tracking-Substruktur wie ein Unknown Field behandelt wird
  • Der Field-Key 49399797 lässt sich nicht per einfacher Hex-Suche finden; dafür muss Varint-/Tag-Encoding berücksichtigt werden
    • Der Wire-Typ ist 2 und bedeutet eine length-delimited verschachtelte Zeichenkette bzw. Message
    • Die Tag-Byte-Sequenz des Ziel-Field-Keys 49399797 lautet AA FF B8 BC 01
    • Mit 395198378 >> 3 werden die 3 Bits des Wire-Typs entfernt, wodurch wieder der ursprüngliche Field-Key 49399797 entsteht
  • In den Protobuf-Bytes wurde nach klassischen Ad-URL-Signaturen wie /pagead/ gesucht, um den Suchbereich für das Field einzugrenzen; von dort wurde rückwärts gegangen, um das zu ändernde Field-Tag und den Field-Key zu finden
  • In einem Beispiel-Intercept-Log wurden in einer 1,87-MiB-application/x-protobuf-Antwort auf einen POST-Request an youtubei.googleapis.com:443/youtubei/v1/browse?key=... der Key 49399797 an Position 4465 und der Key 50195462 an Position 4477 gefunden
  • Im O(n)-Smoke-Test wurden 1,8 MiB Protobuf-Daten ohne zusätzlichen Speicher in einem einzigen Scan verarbeitet
    • Das Ziel wurde im 30.593. Byte von 1,8 MiB gefunden
    • Mit etwa 600 Bytes Backtracking wurde der zu denaturierende Field-Key gefunden
  • Nachdem diese Methode funktionierte, mussten URLs mit *.googleadservices.com oder /pagead/ nicht mehr blockiert werden, und die entsprechenden Requests wurden gar nicht erst ausgelöst

Aufbau des MITMProxy-Add-on-Skripts

  • Das MITMProxy-Add-on-Skript wird als Proof of Concept bereitgestellt, um YouTube-Werbung auf vernetzten Apple-Geräten zu blockieren
    • Der Dateiname ist youtube.py
    • Ein Ausführungsbeispiel ist mitmdump --listen-port 8080 --listen-host 127.0.0.1 -s "youtube.py"
    • Die Voraussetzungen unter FreeBSD sind pkg install protobuf, pkg install py38-pip, pip install jsonpath-ng
  • Das Skript enthält eine Fairness-Funktion, die zur Unterstützung von Content-Erstellern 5 % der Werbung zulässt
    • in_allowed_ads_window() überspringt das Blockieren von Werbung, wenn die aktuelle Zeit zwischen Minute 0 und 2 jeder Stunde liegt
  • YouTubeAdBlocker fängt YouTube-bezogene Domains ab und entfernt Werbeinformationen, indem JSON- oder Protobuf-Antworten verändert werden
    • Der Host-Regex für das Abfangen ist \.youtube\.com|google\.(com|ca)|googleapis\.com|googleadservices\.com|googlevideo\.com
    • Die Zeichenkette zur Protobuf-Werbeerkennung ist b"/pagead/"
    • Das Suchlimit beträgt 80_000 Bytes
    • Das Ziel-Feld-Tag ist 50195462
  • Zur Sperrliste in der Anforderungsphase gehören bei YouTube-Hosts unter anderem pagead/, log_event?, stats/ads, stats/qoe?, ptracking?, generate_204, error_204, adformat=, activeview?, _ad_, ai?, sw.js
  • Die JSON-Ersetzungen für Web-YouTube entfernen oder deaktivieren werbebezogene Felder
    • yt_ad wird zu "0"
    • adPlacements wird zu []
    • adPlacementRenderer, adPlacementConfig, playerAdParams, gutParams werden zu {}
    • adVideoId wird zu ""
    • showCompanion, showInstream, useGut werden zu False
  • Der Hook load() deaktiviert HTTP/2 und setzt anticomp=True, mode="transparent"
  • Der Hook running() aktualisiert allow_hosts, sodass das Abfangen nur auf YouTube-bezogene Domains angewendet wird
  • Der Hook response() sucht, wenn content-type protobuf enthält, in den ersten 80.000 Bytes des Antwortkörpers nach /pagead/
    • Wenn gefunden, erzeugt er mit TagBytes(self.target_field_tag, WIRETYPE_LENGTH_DELIMITED) die Ziel-Tag-Bytes
    • Er erzeugt neue Bytes für das Tag target_field_tag - 1
    • Er führt eine Rückwärtssuche vor der Position von /pagead/ aus, um das Ziel-Tag zu finden
    • Er ersetzt die Bytes an dieser Position durch die Bytes, die target_field_tag - 1 entsprechen
    • Er setzt den modifizierten Protobuf-Inhalt mit flow.response.set_content(bytes(body)) wieder ein
  • Die Code-Kommentare erklären, dass dieses PoC bereits 90 % der Werbung blockiert, und ergänzen, dass es in anderen Abschnitten weitere Field-Keys geben könnte und mehrere Werbeabschnitte neutralisiert werden müssten

Leistung, Grenzen und Zielgruppe

  • Die finale Technik nutzt zum einen die Eigenschaft von Protobuf, bei Schema-Änderungen aus Gründen der Abwärtskompatibilität unknown fields zuzulassen, und zum anderen die Empfindlichkeit kompakter Formate gegenüber Einzelbyte-Änderungen
  • Wenn man an einer kritischen Stelle 1 Byte verändert und dadurch ein tief verschachtelter Abschnitt so aussieht, als gehöre er zu einer zukünftigen Schema-Version, kann Protobuf ihn ignorieren und die Werbeinformationen entfernen
  • Google liefert große Protobuf-Antworten zurück, die sogar das Layout der iOS-App enthalten; die Beispiel-Payload ist 1,8 MiB groß
  • Um die gesamte Payload zu parsen, wäre nativer Code wie C++ oder Swift nötig, und Python-Decoding sei um mehrere Größenordnungen langsamer und verursache Connection-Timeouts
  • Das webbasierte JSON muss die gesamte Payload parsen, bearbeiten und erneut serialisieren, doch die Protobuf-Technik benötigt nur einen linearen Scan und schnelles Backtracking, verarbeitet also im Mikrosekundenbereich und eignet sich damit für Adblocking in Echtzeit, ohne dass eine Blocklist nötig ist
  • Alle *.googleadservices.com- und /pagead/*-URLs auf Apple-Geräten stammen aus der Protobuf-Payload; wenn die Ad-Daten aus der Payload verschwinden, verschwinden auch diese Requests automatisch
  • Die YouTube-App versucht dann nicht, Ad-URLs abzurufen, wirkt dadurch schneller, und der Content wird sofort abgespielt, weil keine Werbung im Video-Slot registriert wird
  • Diese Methode wird als hochspezialisierte Technik zum Blockieren von YouTube-Werbung auf Apple-Geräten oder von Tracker-Traffic bei Instagram, WhatsApp und Facebook vorgestellt
  • Der CPU-Bedarf zum Entschlüsseln und erneuten Verschlüsseln von HTTPS-Traffic übersteige die Leistung eines Raspberry Pi deutlich
  • Da sie sich an Apple-Gerätebesitzer richtet, die ihr OS nicht kompromittieren wollen, wird die Zielgruppe als noch kleiner eingeschätzt

YouTube Premium und ein Experiment zu Werbekosten

  • Ob der Preis von YouTube Premium mit CAD $9.99/mo oder CAD $11.99/mo und inklusive Steuern etwa CAD $13.43/mo angemessen ist, wird als unklar bewertet
  • Mit einem sauberen Laptop und privatem Browsing wurde einen Tag lang ein Experiment zur Werbeeinblendung beim gelegentlichen Ansehen von YouTube durchgeführt
    • Laut Wiedergabeverlauf wurden nur 10 Videos „angesehen“
    • Beim Ansehen von Teilen dieser 10 Videos wurden 8 Werbeanzeigen eingeblendet
    • Nur 2 Anzeigen waren überspringbar, und beide wurden übersprungen
  • Unter der Annahme eines groben CPV von USD $0.15 entsprechen 8 Anzeigen pro Tag Werbekosten von 8 x $0.15 = $1.20; auf einen Monat hochgerechnet etwa USD $36/mo
  • Auf Basis von Statista-Daten wird auch eine Berechnung vorgestellt, bei der die US-Werbeausgaben durch die Gesamtzahl der Views geteilt werden
    • 2019 gaben US-Werbetreibende $15.1 billion für YouTube aus
    • Einwohner der USA hätten 916 billion Videos angesehen
    • Der Durchschnitt beträgt $15.1B / 916B = USD $0.0165 per view
    • Im Fall des Autors entspreche das etwa USD $0.13 pro Tag und etwa USD $3.96 pro Monat an Werbekosten
  • Während des Werbeexperiments war die Hardware stummgeschaltet und der Blick häufig abgewendet, daher wird das für ihn ausgegebene Werbebudget als verschwendet angesehen
  • Trotzdem wolle er Creator unterstützen und eine 3-monatige Premium-Testphase ausprobieren, während er weiter beobachtet, was Google über ihn trackt
  • Es wird die Sorge geäußert, dass ab dem Moment eines eingereichten DMCA-Claims sämtliche Werbeeinnahmen nicht an den Creator, sondern an den Claimant gehen könnten; daher sei es nicht überraschend, dass viele Creator zu Patreon wechseln

Abschließende Zusammenfassung

  • Ein Hardware-Router wurde von Grund auf eingerichtet und das LAN in vertrauenswürdige und nicht vertrauenswürdige Zonen aufgeteilt
  • Traditionelles DNS-basiertes Adblocking wurde eingerichtet
  • Ein transparenter MITM-Proxy wurde zusätzlich eingeführt
  • Am Ende konnten YouTube-Werbeanzeigen auf vernetzten Apple-Geräten mit guter Performance blockiert werden
  • Da der schwierige Teil abgeschlossen sei, werde die Bezahlung von YouTube Premium in Betracht gezogen, während Tracker weiterhin konsequent blockiert würden

1 Kommentare

 
GN⁺ 2025-03-19
Meinungen auf Hacker News
  • Es scheint weniger ein Fehler im Protobuf-Format zu sein, sondern eher so, als habe der Autor die Feldnummer auf eine große, ungenutzte Nummer geändert.
    Die Methode besteht darin, in den Protobuf-Bytes nach Signaturen von Werbe-URLs wie /pagead/ zu suchen, daraus den Feldbereich zu bestimmen und sich von dort rückwärts zum Ziel-Feld-Tag und Feldschlüssel vorzuarbeiten, um sie unschädlich zu machen. Das ist aber kein Fehler, sondern eher beabsichtigtes Verhalten.
    Wenn man sich schon die Mühe macht, den Tag zu finden, ist es kein großer zusätzlicher Aufwand, direkt daneben die Varint-Länge zu lesen und die entsprechenden Bytes zu überspringen. Man müsste zwar den Buffer kopieren oder Bytes verschieben, aber auch das PoC-Skript muss ohnehin bereits kopieren, weil die von der mitmproxy-API zurückgegebenen bytes unveränderlich sind.

    • Auf Protokollebene funktioniert es wie erwartet, aber die Schwachstelle scheint zu sein, dass Google bei unbekannten Feldern in der Werbedatenstruktur keinen Fehler auslöst, sondern sie so behandelt, als gäbe es keine Werbung.
      Google wird vor einer Protokolländerung, die dazu führen würde, dass in allen alten App-Versionen gar keine Werbung mehr erscheint, erst eine neue App ausrollen. Daher ließe sich diese Blockade schon mit einfachem Certificate Pinning oder einem weniger toleranten Decoding bei fehlgeschlagener Extraktion von Werbeinformationen sofort verhindern. Das YouTube-Team würde so etwas vermutlich als Fehler ansehen.
    • bytes-Objekte sind unveränderlich, bytearray-Objekte dagegen nicht.
  • Dasselbe ließe sich auch mit einem kleinen C++/Go-Proxy mit deutlich weniger Overhead erledigen. Eine so klar definierte Aufgabe ist stabiler und weniger mühsam, als sich mit mitmproxy herumzuschlagen.
    Wenn man den gesamten Traffic über den Proxy leitet, sinkt die Performance, selbst wenn man SNI-Interception nutzt. Bei pfSense ist es ähnlich: Mit einem einfachen Linux-Server und simplen iptables-Regeln lässt sich das erledigen, ohne gegen die Abstraktionsschichten von pfSense anzukämpfen.
    Man schreibt nur die benötigten Teile der reverse-engineerten Proto-Felder in eine .proto-Datei, generiert den Code automatisch und ändert ein Flag. Das ist günstiger als eine Python-Implementierung und leichter zu aktualisieren, wenn sich das Proto ändert. Unbekannte Feld-Tags zu ignorieren ist eine wichtige Eigenschaft von Protobuf und ermöglicht kompatible Schemaänderungen, ohne bestehende Deployments zu beschädigen.

    • Vielleicht ist es sogar besser, die YouTube-Erfahrung absichtlich langsamer zu machen und auch den Wechsel zwischen Videos träger zu gestalten. Das dürfte insbesondere die Suchtwirkung von Shorts deutlich verringern.
    • Ich würde mich über einen Blogpost freuen, der im Detail erklärt, wie man das macht.
    • Es wäre gut, wenn du selbst einen Leitfaden schreiben würdest, wo die Ineffizienzen liegen und wie man sie mit einfacherer Software abmildern kann.
      Der Autor scheint einen großen Teil der Punkte aus den Kommentaren bereits berücksichtigt zu haben, und der Artikel ist ziemlich gründlich. Er hat Benchmarks mit Python und C++ durchgeführt, und die endgültige Implementierung decodiert Protobuf nicht einmal. Er hat auch mehrere mitm-Lösungen ausprobiert, und pfSense nutzt er nicht als simplen Sicherheitsrouter, sondern um über VLANs und VPNs gezielt nur den Apple-TV-Traffic zu erfassen.
      Dieser Kommentar wirkt sehr billig und herablassend. Der Originalbeitrag ist das nicht; wenn man so argumentiert, sollte man es der Community zuliebe selbst beweisen.
    • Mich würde interessieren, ob jemand einen leichtgewichtigen Proxy empfehlen kann, der unter macOS läuft und auch andere Geräte im Haus versorgen kann.
  • Unterstützt man Creator, wenn man für YouTube Premium bezahlt? Falls ja, würde mich interessieren, in welchem Umfang im Vergleich zu direkter Unterstützung wie Patreon.

    • Im Vergleich zu Patreon sicher nicht viel, aber man kann auch kaum erwarten, dass jemand bei mehreren YouTubern jeweils deren Patreon abonniert.
      Die Einnahmen, die ein einzelner Creator durch ein YouTube-Premium-Abo erhält, sind gering, aber es ist immerhin besser, als Videos mit Adblocker anzusehen.
    • Es heißt, dass Creator bei YouTube-Premium-Wiedergaben einen größeren Anteil bekommen als bei normalen werbefinanzierten Views. Wenn Werbung übersprungen wird, gibt es nämlich keine Einnahmen. Allerdings gibt es nur wenige Premium-Nutzer, daher bleibt der Effekt begrenzt.
    • Aktuelle Informationen sind rar, aber als es ursprünglich als YouTube Red startete, war es üblicherweise deutlich mehr als die Werbeeinnahmen pro View.
    • Mehr als Werbung, weniger als Patreon.
      Es richtet sich nicht nach Werbeeinblendungen, sondern nach Wiedergabezeit, daher sind Creator mit längeren Inhalten im Vorteil.
  • Der YouTube-Account meiner Freundin bekommt seltsamerweise auf keinem Gerät Werbung, wenn sie eingeloggt ist. Apple TV eingeschlossen; sie hat kein Premium und hatte auch nie Premium.
    Ich frage mich, ob intern irgendein Flag gesetzt ist, das Werbung deaktiviert.

    • Schick mir den Account-Nutzernamen und die E-Mail per DM, dann kann ich nachsehen und es beheben.
    • Deine Freundin scheint praktisch in der Kontrollgruppe für Werbung zu sein. So könnte man ihr Verhalten mit dem von Leuten vergleichen, die Werbung sehen, um herauszufinden, welchen Einfluss Werbung auf Nutzer hat.
    • Es könnte auch ein Holdback-Experiment sein. Um zu sehen, welchen Einfluss Funktionen wie die Ausspielung von Werbung auf Metriken haben, hält man häufig einen Teil der Nutzer in einer zurückgehaltenen Gruppe; solche Experimente habe ich auch während meiner Zeit bei Google durchgeführt.
    • Mit einem früheren Google-Music-Abo war YouTube-Werbung deaktiviert. Selbst nachdem der Dienst eingestellt oder das Abo gekündigt wurde, kam YouTube-Werbung über sechs Monate lang nicht zurück.
      Erst dann kam der Moment, in dem ich verstand, warum sich die Leute beschweren.
    • Ich habe dieselbe Erfahrung bei Twitch.
      Obwohl ich keinen Adblocker nutze, bekomme ich nirgends Werbung, sobald ich eingeloggt bin, weder auf der Website noch in der Mobile-App. Ich habe auch kein Twitch Turbo und kein Amazon Prime mehr. Andere Turbo-Vorteile habe ich ebenfalls nicht, also bin ich nicht vollständig als Turbo markiert.
      Vielleicht habe ich vor Jahren bei Bug-Bounty-Sachen herumprobiert und dabei mein Account-Profil versehentlich kaputtgemacht; ich weiß es nicht. Wenn man mir diesen Vorteil lässt, kann ich gern nähere Informationen liefern.
      Seltsam ist, dass ich mich daran erinnere, wie ich vor Jahren im Krankenhaus unter Medikamenten und Schmerzen einfach nur fernsehen wollte, die Twitch-Werbung aber so schlimm war, dass ich fast einen Nervenzusammenbruch bekam. Ein bis zwei Jahre später fiel mir dann plötzlich auf, dass ich seit Jahren keine Werbung mehr gesehen hatte.
      Wahrscheinlich gibt es irgendeinen längst vergessenen werbefreien A/B-Test, der nie aufgeräumt wurde, weil es den Aufwand nicht wert war. Ich habe davon jahrelang profitiert und Twitch mehr geschaut als jede andere Plattform. Twitch Turbo kostet im Vereinigten Königreich 12 £ pro Monat, also etwa 15,50 $, und ist damit auch weltweit eher teuer; verglichen mit 12 $/12 € in den USA und Europa ist das ein ziemlich schlechter Preis.
  • Es war ziemlich überraschend, dass man HTTPS-Traffic entschlüsseln kann, wenn man einen Man-in-the-Middle-Proxy zwischen Apple TV und das externe Internet setzt.
    Normalerweise hätte ich erwartet, dass das nicht funktionieren sollte; später war ich dann noch einmal überrascht, als ich erfuhr, dass man dem Zertifikatsspeicher von Apple TV eine CA hinzufügen kann. Das war ein gründlicher Artikel, der den gesamten Stack durchgeht.

    • Wenn man spekuliert, warum Apple das Hinzufügen von Zertifikaten unterstützt, liegt es wahrscheinlich daran, dass Apple TV in Unternehmen oder Bildungseinrichtungen als AirPlay-Box verwendet wird und daher zu IT- und Geräteverwaltungsanforderungen passen muss.
      An Universitäten musste man zum Beispiel die MAC-Adresse auf eine Allowlist setzen oder ein Zertifikat installieren, um Geräte ins WLAN zu bringen.
    • Google könnte diese Methode in der YouTube-App leicht blockieren, indem es einfach prüft, welche CA das SSL-Zertifikat signiert hat.
      Ob sie das tatsächlich tun würden, weiß ich nicht, denn dadurch könnte YouTube in vielen Unternehmensumgebungen kaputtgehen. Leider wäre es aber sehr einfach zu blockieren.
    • Dass man auf Apple TV eine CA hinzufügen kann, hatte ich nicht erwartet. Ich glaube, ich wusste es nicht, weil ich mit Apple TV noch nie auf Ressourcen ohne gültige Zertifikatskette zugegriffen habe.
    • Die meisten Geräte erlauben das Hinzufügen von CAs, aber heutzutage verwenden fast alle Apps Certificate Pinning und ignorieren damit den System-Zertifikatsspeicher. Dass YouTube das nicht tut, ist sehr überraschend.
    • Ironischerweise erlaubt Android TV das zumindest in Version 7.x nicht. Das habe ich auf die harte Tour herausgefunden, als ich versuchte, ein nicht vertrauenswürdiges Let’s-Encrypt-Zertifikat zu umgehen.
  • Ich habe ein paar Mal versucht, das auf Apple TV umzusetzen, hatte aber überhaupt keinen Erfolg. YouTube hat inzwischen wohl Certificate Pinning in die App eingebaut oder etwas in der Art. Ich frage mich, ob das in letzter Zeit jemand zum Laufen gebracht hat.

    • Wenn du bereit bist, Zeit zu investieren, kannst du dich in Frida [0] einarbeiten. Auch gepinnte Zertifikate sind dann kein Problem.
      [0] https://frida.re/docs/home/
  • Ich mag jeden Versuch, die miesen Online-Dienste, zu deren Nutzung man gedrängt wird, netzwerkweit zu blockieren.
    Adblocking ist gut, aber ich wünschte, es gäbe mehr und einfachere Möglichkeiten, aggressive Infinite-Scroll-Angebote wie YouTube Shorts oder Instagram Reels netzwerkweit zu blockieren.
    Auf Instagram möchte ich nur Posts und Stories der Leute sehen, denen ich folge, und keine dummen Videos empfohlen bekommen, die darauf ausgelegt sind, meine Aufmerksamkeit zu kapern. Vielleicht zeigt das nur mangelnde Willenskraft, aber oft schaue ich mir am Ende doch ein paar an und verliere 15 Minuten meines Lebens.

    • Man wird nicht dazu gezwungen. Man kann es auch nicht nutzen oder dafür bezahlen.
      Internetnutzer haben sich im Großen und Ganzen dafür entschieden, nicht zahlen zu wollen, also übernimmt irgendjemand die Kosten. Insgesamt belohnen Internetnutzer nicht diejenigen, die keine Werbung zeigen. Sie wollen Inhalte, aber meistens kostenlos.
    • Lösche die App, nutze die Webseite und verwende einen Browser, der User Scripts erlaubt.
      Ich habe ein Script gefunden, das die Instagram-Seite praktisch in etwas wie ein Image-Tag verwandelt, sodass man nur Fotos ansehen kann: https://greasyfork.org/en/scripts/5014-un-instagram
    • Ich denke, solche Taktiken nutzen unsere natürliche Neugier und die Ästhetik darum herum aus.
      Deshalb wirkt es weniger wie fehlende Willenskraft, sondern eher wie die Abstumpfung, die wir aufgebaut haben, und das ist ziemlich schlecht. Die Mühe und Kreativität, die Plattformen wieder so umzubauen, dass wir sie benutzen statt sie uns, verdient Respekt.
    • Als Elternteil kann ich das besonders gut nachvollziehen. Es ist schwer mit anzusehen, wie Kinder in den Algorithmus hineingezogen werden.
      Ich spreche regelmäßig mit meinen Kindern darüber, und sie stimmen auch zu, dass es schädlich ist, aber sie finden es zu schwer, dagegen anzukämpfen. Selbst ich werde manchmal ins Doomscrolling hineingezogen.
      Wo es möglich ist, habe ich Werbefilterung per Pi-hole eingerichtet, aber ich möchte YouTube nicht komplett blockieren. Um meine Familie zu schützen, werde ich das künftig aber wohl ernsthaft in Erwägung ziehen.
    • Diese App hat sich sehr gut dafür geeignet, Infinite Scroll auf Instagram zu blockieren: https://www.distractionfreeapps.com/index.html
  • Das Engineering ist gut, aber es ist schon etwas traurig, dass man so weit gehen muss, nur um die eigene Hardware oder Software zumindest ein Stück weit so zu nutzen, als würde man sie besitzen.

    • In diesem Fall besitzt man das Gerät. Ich sehe allerdings keine Grundlage dafür, zu behaupten, man besitze auch YouTube oder dessen Inhalte.
    • Mit einer NewPipe APK auf einer 30-Dollar-Android-Box ist das schon seit fast zehn Jahren möglich.
  • Gibt es auf YouTube Werbung? Mein Browser blockiert sie so gut, dass ich es gar nicht wusste.
    Das eigentliche Problem ist, dass die Apple-TV-Erfahrung so viel schlechter ist als die in einem normalen Webbrowser. Apple sperrt die Hardware so stark ab, dass sie eher YouTubes Werbeeinnahmen zugutekommt als dem zahlenden Endkunden.

    • Unter Linux, Windows und Android sehe ich überhaupt keine Werbung. Wenn ich gelegentlich versuche, YouTube auf dem iPad anzusehen, bin ich überrascht, wie häufig und nervig die Werbung ist.
      Dasselbe gilt, wenn ich außerhalb des Pi-hole-Netzwerks zu Hause mit dem iPad im Web surfe. Ich weiß nicht, wie Menschen das täglich aushalten.
      Das iPad ist ein dienstlich bereitgestelltes Gerät, daher nutze ich es privat nicht oft, aber jedes Mal erinnert es mich daran, wie nervig das ist.
      Seltsamerweise dachte ich, bevor ich das iPad bekam, es wäre nur für Medienkonsum nützlich. Tatsächlich ist es sehr praktisch, um schnell remote auf Arbeitsressourcen zuzugreifen, während es beim normalen Browsen und Streaming von Medien ein in Werbung versunkenes Ödland ist.
  • Wenn du YouTube ohne Werbung brauchst, kannst du https://yewtu.be oder eine andere Invidious-Instanz https://docs.invidious.io/instances/ verwenden.
    Zwischen YouTube und Invidious gibt es ein Wettrüsten, und manchmal funktioniert Invidious nicht, aber das Team hat immer wieder neue Wege gefunden, YouTube zu umgehen und Videos ohne Werbung auszuliefern.

    • Es gibt einen Grund, warum im Titel „on AppleTV“ steht. Alternative Clients oder Frontends laufen dort nicht.
    • Auf Geräten wie Roku TV funktioniert diese Methode nicht, weil es dort keinen Browser gibt.