1 points par GN⁺ 2025-03-19 | 1 commentaires | Partager sur WhatsApp
  • Il s’agit d’un PoC qui place un proxy MITM basé sur pfSense entre l’Apple TV et Internet pour déchiffrer le HTTPS, puis modifie les réponses Protobuf envoyées par YouTube afin que les emplacements publicitaires ne soient pas enregistrés sur les appareils Apple.
  • Les bloqueurs DNS existants et le routage VPN se heurtent à des limites comme le TTL DNS, les incohérences d’IP, les erreurs 403 ou les fuites d’ASN, car les publicités YouTube et la vidéo principale utilisent les mêmes domaines et la même infrastructure.
  • Les essais avec Squid ont été abandonnés à cause de problèmes de performances et de configuration, puis l’approche a basculé vers le déchiffrement du trafic TLS avec mitmproxy/mitmdump dans une jail FreeBSD, afin de modifier directement les réponses JSON et Protobuf.
  • Sur YouTube Web, il était possible de supprimer adPlacements, playerAdParams, les URL pagead, etc. dans le JSON, mais l’app YouTube iOS plaçait les emplacements publicitaires et les informations de suivi dans des réponses application/x-protobuf, ce qui imposait de manipuler la structure Protobuf.
  • La méthode finale consiste en un scan linéaire et une modification d’un octet : chercher à rebours le field tag près de la chaîne /pagead/, puis remplacer un tag comme 50195462 par target_field_tag - 1. Elle offre des performances adaptées au traitement en temps réel sans décodage complet.

Objectif et conception réseau initiale

  • L’objectif était de créer un routeur basé sur FreeBSD et pfSense afin de bloquer, à l’échelle de tout le réseau, les publicités YouTube pre-roll, mid-roll et end-roll sur Apple TV et iPhone.
  • En plaçant un proxy man-in-the-middle entre l’Apple TV et Internet, il devient possible de déchiffrer le trafic HTTPS et de lire les données Protocol Buffer utilisées par Google pour remplir les publicités YouTube.
  • Après avoir mis en place le blocage des publicités YouTube pendant plusieurs mois, l’auteur a commencé à payer YouTube Premium, en précisant qu’« être capable de le faire » et « devoir le faire » sont deux choses différentes.
  • Les raisons invoquées pour bloquer les publicités et le tracking étaient le suivi des données personnelles, le gaspillage de bande passante, le clickbait et le cryptojacking.
    • Il estime que 25 à 40 % du trafic réseau peut être constitué de publicités, de scripts de tracking, de fingerprint.js, de googletagmanager.js et de loaders d’analytics temps réel comme Hotjar.
    • Il explique que du JavaScript de minage crypto comme CoinHive.js peut faire surchauffer un ordinateur ou l’exploiter abusivement pour gagner de petites sommes.

Matériel pfSense et configuration de base

  • Pour protéger tout un réseau SMB, des VM, images Docker ou Raspberry Pi n’offrent pas assez de performances ; il faut donc, selon lui, du matériel dédié chargé uniquement du routage des paquets, du déchiffrement et de la supervision.
  • Le matériel routeur utilisé était un mini PC doté du jeu d’instructions AES-NI, de RAM DDR4, d’un SSD mSATA et d’une clé USB pour flasher pfSense.
    • Un exemple de configuration est un mini PC J4125, 32 Gio de RAM DDR4 et un SSD mSATA de 128 Gio.
    • Il juge que 128 Go de stockage suffisent pour les logs, la réduction de l’usure du SSD, les captures de paquets et le cache edge NPM/Docker.
  • L’image d’installation de pfSense fait environ 360 Mo et peut être flashée sur une clé USB avec l’AppImage d’Etcher.
  • Après la première configuration, AES-NI apparaissait comme “Yes (inactive)” ; il l’a donc activé manuellement dans System › Advanced › Miscellaneous.
  • Pour exploiter les 32 Gio de RAM, il a largement alloué des RAM disks à /var et /tmp, et configuré des sauvegardes horaires des RAM disks vers le SSD de 128 Gio, en comptant sur le wear-leveling.
  • Il a ajouté un widget S.M.A.R.T. au Dashboard afin de détecter d’éventuelles anomalies du SSD.

Blocage DNS, segmentation réseau et pfBlockerNG

  • Auparavant, il utilisait Pi-hole sur Raspberry Pi comme bloqueur de publicité au niveau DNS ; avec pfSense, il a installé pfBlockerNG-devel pour tester le blocage des publicités, des contenus malveillants et le geo-blocking.
  • Si le service pfb_dnsbl ne démarre pas ou si l’onglet status affiche [ Missing CRON task ], il recommande d’essayer de supprimer le fichier vide /var/run/booting.
  • En utilisant les trois ports Gigabit du mini PC, il a créé de véritables réseaux physiques plutôt que des VLAN, et isolé du réseau principal les appareils qui “appellent à la maison”, comme Alexa et l’Apple TV.
    • Les appareils non fiables sont placés sur le réseau privé 172.31.1.0/24.
    • Le LAN de confiance reste en 192.168/16.
    • Le LAN matériel destiné à l’IoT passe par l’adblocker, et les requêtes DNS codées en dur vers 1.1.1.1, 9.9.9.9, etc. sont interceptées afin d’empêcher YouTube de contourner le bloqueur DNS.
  • Il a configuré des règles NAT pour que tous les clients derrière pfSense utilisent le serveur DNS Unbound local.
    • Il estime qu’il faut d’abord bloquer DNS over TLS pour pouvoir intercepter les requêtes DNS.
    • L’iPhone peut afficher un Privacy Warning concernant le blocage du trafic DNS chiffré, mais les requêtes DNS upstream sont, selon lui, chiffrées vers Cloudflare.
    • Le NAT reflection doit être désactivé afin d’empêcher Internet d’accéder au serveur DNS.
  • Il crée un alias firewall Non_WAN pour rediriger vers localhost le port 53 des requêtes DNS locales provenant des interfaces autres que le WAN.
  • Comme YouTube diffuse les publicités et la vidéo principale depuis les mêmes domaines, il était difficile de filtrer uniquement les publicités avec des bloqueurs par nom de domaine comme pfBlockerNG ou Pi-hole.

Expériences de contournement via VPN et points d’échec

  • Il a aussi mené des expériences visant, plutôt que de bloquer les publicités, à tromper l’algorithme publicitaire de YouTube afin de paraître moins intéressant pour les annonceurs.
    • Il voulait router le trafic de localisation de YouTube via un VPN vers une région comptant peu de spectateurs, à l’aide du routeur pfSense.
    • Il s’était fixé comme objectif que le compte YouTube soit perçu comme appartenant à un « homme de 70 ans vivant en Italie ».
  • Sur pfSense, il a utilisé WireGuard plutôt qu’OpenVPN pour réaliser une expérience de référence envoyant tout le trafic de l’Apple TV dans le VPN.
    • Il a installé le paquet FreeBSD WireGuard, puis ajouté et activé un tunnel.
    • Pour la configuration NordLynx, il a récupéré la private key dans une VM Linux avec sudo wg showconf nordlynx, puis l’a transférée dans pfSense.
  • Les tests ont montré que Google s’affichait en italien sur l’ordinateur portable, et que YouTube sur Apple TV passait également en italien.
    • Quelques publicités apparaissaient encore, mais moins qu’auparavant.
    • Netflix et Amazon Prime posaient problème, et des fichiers CSS ou de polices semblaient bloqués, ou des vignettes ne se chargeaient pas.
    • Il avertit qu’il ne faut pas envoyer tout le trafic de l’Apple TV dans le VPN, et estime que Netflix et Prime détectent bien les fournisseurs de VPN et le geofencing.
  • Il a ensuite configuré une règle de politique firewall ciblant www.youtube.com, youtube.com, googlevideo.com, accounts.google.com, googleapis.com, gstatic.com, etc., afin de ne faire passer dans le VPN que le trafic YouTube de l’Apple TV.
    • Au final, YouTube considère l’utilisateur comme situé à Milan, tandis que Netflix et Prime Video le voient au Canada.
    • Les publicités sont devenues très rares, “few and far between”.
  • Au bout d’une journée, une condition de course DNS est apparue.
    • Les alias de hostname pfSense sont résolus par défaut toutes les 300 secondes.
    • Le TTL DNS de YouTube peut être de 1 440 secondes, soit 24 minutes.
    • Si l’IP résolue par l’Alias Daemon diffère de celle réellement reçue par le client, la policy peut ne pas tunneler le trafic YouTube.
  • Certaines vidéos YouTube ne se lançaient pas, avec un 403 Forbidden.
    • Selon lui, YouTube intègre l’IP de l’utilisateur dans chaque requête googlevideo.com.
    • Si un domaine modifié comme r5---sn-hpa7kn76.googlevideo.com n’est pas tunnelé, la requête sort depuis la mauvaise IP et pose problème.
    • Ce qu’il faudrait, c’est un tunneling wildcard de *.googlevideo.com, mais les règles NAT et firewall fonctionnent avec des IP, pas avec des hostnames wildcard.

PoC de suivi d’IP basé sur les requêtes DNS

  • Pour router *.googlevideo.com via le VPN, l’idée était d’utiliser une méthode de détournement des requêtes DNS Google Video
    • Elle consistait à suivre périodiquement le journal des requêtes DNS et à ajouter les requêtes *.googlevideo.com à la liste d’alias
    • Si chaque vidéo utilise un domaine unique et modifié, cette méthode ne fonctionnerait probablement pas à moins d’actualiser à chaque vidéo
  • Le nouvel objectif était de surveiller les requêtes DNS avec Python 3 et l’API REST de pfSense pour capturer les IP, retenir brièvement la réponse, ajouter l’IP à la règle de tunneling VPN, puis libérer la réponse DNS
  • L’API REST de pfSense a été installée, puis une requête GET vers https://pfsense/api/v1/firewall/alias a permis de consulter l’alias VPN_domains
  • En explorant le module Python d’Unbound DNS Resolver, il a été possible de journaliser les messages de requête DNS
    • La version actuelle de Python était la 3.8
    • Les exemples du module Python d’Unbound étant basés sur Python 2.4, il a été estimé que 2to3 ou un reformatage pourrait être nécessaire
  • Le script PoC extrait les IP des enregistrements A/AAAA dans la réponse DNS et les ajoute à l’alias pfSense
    • Les enregistrements A sont traités avec ipaddress.IPv4Address(d.rr_data[j][2:]).exploded
    • Les enregistrements AAAA sont traités avec ipaddress.IPv6Address(d.rr_data[j][2:]).exploded
    • Le TTL de l’alias est fixé à 1 heure et sa capacité à 500
  • Le lendemain, Unbound DNS Resolver a subi un segfault, et comme chaque ajout d’IP imposait de recharger les règles pfSense, pfSense est devenu très lent

Passage de Squid à mitmproxy

  • Le nouvel objectif a consisté à étudier et installer un proxy de la famille Squid, créer un faux certificat CA de confiance, puis déchiffrer le trafic TLS
  • Lors des essais avec Squid, le proxy squid3 fourni comme package pfSense a été testé pour vérifier s’il répondait aux besoins
    • Un dossier dédié /squid_cache a été créé et la taille du cache fixée à 8 GiB
    • Le support HTTPS transparent était attendu
  • Après une journée de configuration de Squid et SquidGuard, l’approche a été abandonnée
    • C’était très lent
    • La configuration des ACL était laborieuse
    • Il y avait un problème lié à https://http/*
    • La mise à jour de la liste de filtrage d’URL de SquidGuard prenait énormément de temps
    • L’interface utilisateur de Squid était insuffisante
  • Le choix s’est ensuite porté sur mitmproxy, écrit en Python
    • mitmproxy a été préféré à SSLSplit pour l’extensibilité via des hooks Python et pour son UI
    • La version FreeBSD de pfSense était 12.2-Stable, en build 64 bits
  • Dans l’environnement par défaut de pfSense, les jails étant désactivées, ezjail a été installé manuellement et une jail dédiée à mitmproxy a été créée
    • La jail a été créée avec ezjail-admin create mitmproxy 'lo0|127.0.1.1'
    • allow.raw_sockets=1 a été configuré pour le mode proxy transparent
    • Si les raw sockets sont bloquées, des erreurs comme Transparent mode failure ou Cannot open connection, no hostname given. peuvent apparaître
  • L’exécution du binaire Linux en tarball a échoué sous FreeBSD
    • L’erreur ELF interpreter /lib64/ld-linux-x86-64.so.2 not found s’est produite
    • libdl.so.2, libz.so.1, libpthread.so.0 et libc.so.6 étaient également introuvables
  • Dans la jail, pkg install mitmproxy a été exécuté ; l’installation nécessitait 50 packages, 206 MiB d’espace supplémentaire et 33 MiB de téléchargement
  • Pour rendre MITMProxy accessible depuis le LAN, l’IP virtuelle 127.0.1.1 a été attachée à localhost, puis une règle NAT a temporairement redirigé [Private IPs]:8080 vers 127.0.1.1:8080
  • Le fichier PEM CA généré automatiquement par MITMProxy est ~/.mitmproxy/mitmproxy-ca-cert.pem, et ce certificat CA a été installé dans le Trusted Root Store de l’appareil de test
  • Même au repos, mitmproxy consommait beaucoup de CPU ; la génération en temps réel d’un certificat TLS par requête et le logging excessif semblaient fortement ralentir l’ensemble
    • mitmdump, qui omet l’UI et le logging excessif, semblait moins solliciter le CPU

Suppression des publicités JSON de YouTube Web

  • Le certificate pinning est une technique par laquelle le serveur ou le client connaît à l’avance l’empreinte du certificat attendu, ce qui rend inefficace la falsification de certificat par MITMProxy
  • Les hôtes problématiques peuvent contourner le proxy via l’option --ignore-hosts
    • Par exemple, apple.com:443 et icloud.com:443 ont été ignorés
  • Lors de l’accès à YouTube, des publicités de page sont apparues dans MITMProxy avec des en-têtes non chiffrés, et la possibilité d’un blocage par simple regex a été examinée
  • Pour appliquer le script de blocage des publicités YouTube, --scripts "youtube.py" a été ajouté à mitmdump
  • Le filtre de smoke-test bloque les requêtes publicitaires en fonction de sous-chaînes dans l’URL
    • youtube.com : /pagead/, /log_event?, /stats/ads, /stats/qoe?, /ptracking?, /generate_204, el=adunit, adformat=, /activeview?
    • google.com, google.ca : /pagead/
    • ggpht.com : .
  • Les requêtes ciblées semblaient bien bloquées dans MITMProxy et dans le panneau Network de DevTools, mais les publicités apparaissaient toujours, et il arrivait qu’elles se passent d’elles-mêmes ou que leur lecture échoue
  • Par la suite, un grand nombre d’URL liées aux publicités ont été trouvées dans le payload JSON
  • Après analyse de l’UI YouTube et du workflow HTTP, jusqu’aux cookies et service workers, il serait devenu possible de supprimer toutes les publicités pre-roll, post-roll et mid-roll
  • À ce stade, le routeur pouvait supprimer les publicités du payload JSON des publicités Web YouTube

YouTube iOS et le problème Protobuf

  • L’app iOS de YouTube expose, dans la version Protobuf du même appel d’API que la version web, des données très similaires
  • Dans Protobuf, les clés sont numériques et peuvent changer, il est donc impossible d’utiliser une méthode consistant à trouver la section publicitaire avec JSONPath
  • YouTube envoie dans le payload une grande liste de publicités à afficher prochainement, et une fois cette liste épuisée, une autre grande liste arrive rapidement
  • Dans le payload Protobuf, on voyait des chaînes comme « Telus », « Samsung TV », « Boxing Week », « Buy now »
  • Le protocole YouTube iOS était différent du trafic web
    • Dans la version web, il était possible de distinguer dans une certaine mesure la vidéo publicitaire de la vidéo souhaitée en observant l’URL et le paramètre de requête range
    • Le protocole iOS n’utilise ni le paramètre de requête range, ni l’en-tête Range, et emploie à la place des compteurs comme &nr=2, &nr=3 dans les chunks vidéo
    • Pour bloquer les publicités YouTube sur iOS, il fallait rétroconcevoir la réponse Protobuf
  • Dans le message Protobuf décodé, des entrées has_unlimited_entitlement: False et has_premium_lite_entitlement: False ont été trouvées, mais au lieu de les basculer, l’approche est revenue à des heuristiques
  • Décoder environ 500 KiB de Protobuf brut avec l’implémentation Python pure était très lent
    • Sur un ordinateur de bureau i7-6700, les résultats Python étaient d’environ 2,06 à 2,11 secondes
    • Sur un routeur pfSense, les résultats Python étaient d’environ 22,8 à 24,2 secondes
    • Le C++ protoc --decode_raw prenait environ 0,017 à 0,022 seconde sur l’ordinateur de bureau, et environ 0,12 à 0,14 seconde sur le routeur pfSense

Tentatives de décodage Protobuf et d’extraction du schéma

  • Comme Python ne prend pas en charge le décodage Protobuf brut, l’option retenue a été de communiquer avec le binaire C++ protoc via subprocess.Popen, plutôt que d’utiliser directement libprotobuf.so en C++
  • En fuzzant les réponses vidéo publicitaires, des essais ont été faits avec des 200 vides, des 404, des 503, des corps de réponse tronqués et la mise à null d’une partie de la vidéo publicitaire, mais l’app iOS ralentissait puis crashait, ou restait bloquée sur l’écran publicitaire
  • Le blocage d’URL déclenchait des comportements de repli de l’app, et les chunks de réponse vidéo contenaient aussi des métadonnées de session
  • blackboxprotobuf pour Burp Suite permet de décoder un message Protobuf wire brut, d’y injecter du contenu, puis de le réencoder afin de vérifier le comportement d’un endpoint Protobuf
    • Il est recommandé d’utiliser la version originale pour Burp Suite, et non le fork PyPI
    • Certains forks ont des problèmes de stack overflow ou de récursion infinie à cause d’une récursion profonde
    • Avec les bindings C++, environ 500 KiB de Protobuf brut peuvent être transcodés en quelques secondes
  • Le schéma généré n’était pas parfait, il était volumineux et profondément imbriqué, et son pretty-print était lent, mais il suffisait pour trouver les détails des publicités
  • Pour extraire les vrais fichiers .proto ou de schéma depuis l’APK Android de YouTube, PBTK, Apktool, dex2jar et Java Decompiler ont été essayés
    • PBTK n’a extrait qu’un fichier proto de 59 octets
    • Il y avait des classes Protobuf et des getter/setter dans le Java, mais les vrais fichiers de schéma n’ont pas pu être obtenus, ce qui a conduit à abandonner cette piste

Tournant final : modification d’un octet du tag de champ Protobuf

  • D’après le trafic réseau déchiffré et le fuzzing Protobuf, les publicités semblaient être enregistrées dans des slots associés à une vidéo donnée
    • Les types de slots incluaient pre-roll, mid-roll, end-roll, full-page et ad pods
    • Si l’URL publicitaire était bloquée, une erreur du type « une publicité inexistante a réservé un slot » survenait et provoquait une panique de l’UI
  • Sans schéma original, décoder, modifier puis réencoder produit un encodage modifié ; cela pose problème, car on ne sait pas si ZigZag est utilisé ni quels types numériques sont employés, comme int32, int64, sint32/64 ou varint, et l’ordre des champs d’objet est généralement non déterministe
  • Une possibilité de contournement a été trouvée dans la compatibilité ascendante de Protobuf et le comportement de UnknownFieldSet
    • Lorsqu’un ancien logiciel lit un message auquel un nouveau champ a été ajouté, un champ inconnu peut apparaître
    • En remplaçant la clé d’un champ donné par une autre valeur, toute la sous-structure contenant les informations publicitaires et de tracking peut devenir indisponible
  • L’idée proposée consiste par exemple à remplacer la clé de champ 49399797 par 49399796, afin de faire passer cette sous-structure publicité/tracking pour un champ inconnu
  • La clé de champ 49399797 ne peut pas être trouvée par une simple recherche hexadécimale ; il faut tenir compte de l’encodage varint/tag
    • Le wire type est 2, ce qui correspond à une chaîne ou un message imbriqué à longueur délimitée
    • La séquence d’octets du tag pour la clé de champ cible 49399797 devient AA FF B8 BC 01
    • En supprimant les 3 bits de wire type avec 395198378 >> 3, on obtient la clé de champ originale 49399797
  • Dans les octets Protobuf, une signature classique d’URL publicitaire comme /pagead/ a été recherchée pour délimiter la zone de recherche du champ, puis un retour en arrière depuis cette position a permis de trouver le tag de champ et la clé de champ à modifier
  • Dans l’exemple de journal d’interception, dans la réponse application/x-protobuf de 1,87 MiB d’une requête POST youtubei.googleapis.com:443/youtubei/v1/browse?key=..., la clé 49399797 a été trouvée à la position 4465 et la clé 50195462 à la position 4477
  • Lors d’un smoke test en O(n), les données Protobuf de 1,8 MiB ont été scannées une seule fois sans mémoire supplémentaire
    • La cible a été trouvée au 30 593e octet sur les 1,8 MiB
    • Un retour en arrière d’environ 600 octets a permis de trouver la clé de champ à dénaturer
  • Une fois cette méthode fonctionnelle, il n’a plus été nécessaire de bloquer les URL contenant *.googleadservices.com ou /pagead/, et les requêtes correspondantes ont cessé de se produire dès le départ

Structure du script add-on MITMProxy

  • Le script add-on MITMProxy est fourni comme proof of concept pour bloquer les publicités YouTube sur les appareils Apple en réseau
    • Le nom du fichier est youtube.py
    • Exemple d’exécution : mitmdump --listen-port 8080 --listen-host 127.0.0.1 -s "youtube.py"
    • Les prérequis FreeBSD sont pkg install protobuf, pkg install py38-pip, pip install jsonpath-ng
  • Le script inclut une fonction d’équité qui autorise 5 % des publicités afin de soutenir les créateurs de contenu
    • in_allowed_ads_window() saute le blocage des publicités si l’heure actuelle se situe entre la minute 0 et la minute 2 de chaque heure
  • YouTubeAdBlocker intercepte les domaines liés à YouTube et modifie les réponses JSON ou Protobuf pour supprimer les informations publicitaires
    • La regex des hôtes à intercepter est \.youtube\.com|google\.(com|ca)|googleapis\.com|googleadservices\.com|googlevideo\.com
    • La chaîne utilisée pour détecter les publicités Protobuf est b"/pagead/"
    • La limite de recherche est de 80_000 octets
    • Le tag du champ cible est 50195462
  • La liste de blocage à l’étape de requête inclut notamment pagead/, log_event?, stats/ads, stats/qoe?, ptracking?, generate_204, error_204, adformat=, activeview?, _ad_, ai?, sw.js sur les hôtes YouTube
  • Pour YouTube Web, les remplacements JSON suppriment ou désactivent les champs liés à la publicité
    • yt_ad est remplacé par "0"
    • adPlacements est remplacé par []
    • adPlacementRenderer, adPlacementConfig, playerAdParams, gutParams sont remplacés par {}
    • adVideoId est remplacé par ""
    • showCompanion, showInstream, useGut sont remplacés par False
  • Le hook load() désactive HTTP/2 et définit anticomp=True, mode="transparent"
  • Le hook running() met à jour allow_hosts afin que l’interception ne s’applique qu’aux domaines liés à YouTube
  • Le hook response() cherche /pagead/ dans les 80 000 premiers octets du corps de la réponse si le content-type contient protobuf
    • S’il le trouve, il crée les octets du tag cible avec TagBytes(self.target_field_tag, WIRETYPE_LENGTH_DELIMITED)
    • Il crée de nouveaux octets de tag pour target_field_tag - 1
    • Il effectue une recherche inversée avant la position de /pagead/ pour trouver le tag cible
    • Il remplace les octets à cet emplacement par ceux correspondant à target_field_tag - 1
    • Il réinjecte le contenu Protobuf modifié avec flow.response.set_content(bytes(body))
  • Les commentaires du code indiquent que ce PoC bloque déjà 90 % des publicités, et ajoutent qu’il existe d’autres clés de champ dans d’autres sections et qu’il peut y avoir plusieurs sections publicitaires à neutraliser

Performances, limites et utilisateurs visés

  • La technique finale exploite la capacité de Protobuf à autoriser les champs inconnus pour rester compatible avec les changements de schéma, ainsi que la sensibilité aux modifications d’un seul octet de son format compact
  • En modifiant 1 octet à un endroit critique pour faire passer une section profondément imbriquée pour un élément appartenant à une future version du schéma, Protobuf peut l’ignorer et supprimer les informations publicitaires
  • Google renvoie une grosse réponse Protobuf qui inclut même la mise en page de l’app iOS ; l’exemple de payload fait 1,8 Mio
  • Parser tout le payload nécessiterait du code natif comme C++/Swift, et le décodage en Python serait plusieurs ordres de grandeur plus lent, au point de provoquer des timeouts de connexion
  • Le JSON côté Web doit être entièrement parsé, modifié puis resérialisé, tandis que la technique Protobuf ne fait qu’un scan linéaire et un backtracking rapide : elle se traite en quelques microsecondes, convient au blocage publicitaire en temps réel et ne nécessite pas de blocklist
  • Toutes les URL *.googleadservices.com et /pagead/* sur les appareils Apple proviennent du payload Protobuf ; si les données publicitaires disparaissent du payload, ces requêtes disparaissent automatiquement elles aussi
  • L’app YouTube ne tente pas de récupérer les URL publicitaires, ce qui donne une impression de plus grande rapidité, et le contenu est lu immédiatement car aucune publicité n’est enregistrée dans le slot vidéo
  • Cette approche est présentée comme une technique très spécialisée pour bloquer les publicités YouTube sur appareils Apple ou le trafic de tracking d’Instagram, WhatsApp et Facebook
  • La charge CPU nécessaire pour déchiffrer/réchiffrer le trafic HTTPS dépasserait largement les performances d’un Raspberry Pi
  • Comme elle vise les propriétaires d’appareils Apple qui ne veulent pas compromettre leur OS, le public concerné est considéré comme encore plus restreint

YouTube Premium et expérimentation sur le coût publicitaire

  • L’auteur estime qu’il n’est pas certain que YouTube Premium soit raisonnable à CAD $9.99/mo ou CAD $11.99/mo, soit environ CAD $13.43/mo taxes incluses
  • Il a mené une expérience d’exposition publicitaire en regardant YouTube par intermittence pendant une journée sur un ordinateur portable propre, en navigation privée
    • D’après l’historique, seuls 10 vidéos ont été « vues »
    • Pendant le visionnage partiel de ces 10 vidéos, 8 publicités ont été affichées
    • Seules 2 publicités étaient ignorables, et toutes deux ont été ignorées
  • En supposant un CPV approximatif de USD $0.15, 8 publicités par jour représentent un coût annonceur de 8 x $0.15 = $1.20, soit environ USD $36/mo extrapolé sur un mois
  • Il présente aussi un calcul basé sur des données Statista, en divisant les dépenses publicitaires aux États-Unis par le nombre total de vues
    • En 2019, les annonceurs américains ont dépensé $15.1 billion sur YouTube
    • Les résidents américains auraient regardé 916 billion de vidéos
    • La moyenne est de $15.1B / 916B = USD $0.0165 per view
    • Dans son cas, il calcule que cela correspond à environ USD $0.13 par jour, soit environ USD $3.96 par mois de coût annonceur
  • Comme il avait coupé le son au niveau matériel et détournait souvent le regard pendant l’expérience, il estime que les dépenses publicitaires engagées sur lui ont été gaspillées
  • Il dit néanmoins vouloir soutenir les créateurs et tenter l’essai de 3 mois de Premium, tout en continuant à surveiller ce que Google suit à son sujet
  • Il s’inquiète du fait qu’à partir du moment où une réclamation DMCA est déposée, tous les revenus publicitaires puissent aller au plaignant plutôt qu’au créateur, et ajoute qu’il n’est pas surprenant que beaucoup de créateurs migrent vers Patreon

Conclusion

  • Il a configuré un routeur matériel à partir de zéro et séparé le LAN en zones trusted/untrusted
  • Il a mis en place un blocage publicitaire DNS traditionnel
  • Il a ajouté un proxy MITM transparent
  • Au final, il affirme avoir pu bloquer efficacement les publicités YouTube sur les appareils Apple connectés au réseau
  • Il conclut qu’il envisagera de payer YouTube Premium maintenant que la partie difficile est terminée, tout en ajoutant que les trackers restent fortement bloqués

1 commentaires

 
GN⁺ 2025-03-19
Avis sur Hacker News
  • Plutôt qu’un défaut du format Protobuf, il semble que l’auteur ait remplacé le numéro de champ par un grand numéro inutilisé.
    La méthode consiste à chercher, dans les octets Protobuf, des signatures d’URL publicitaires comme /pagead/ pour délimiter la plage du champ, puis à remonter en arrière afin de trouver le tag et la clé du champ cible pour le neutraliser ; ce n’est pas vraiment une faille, mais plutôt un comportement prévu.
    Si l’on fait l’effort de trouver le tag, lire la longueur varint juste à côté et sauter les octets correspondants ne représente pas beaucoup de travail supplémentaire. Il faudrait certes copier le buffer ou déplacer des octets, mais le script de PoC doit déjà effectuer une copie puisque les bytes renvoyés par l’API mitmproxy sont immuables.

    • Au niveau du protocole, cela fonctionne comme prévu, mais la faille semble venir du fait que, lorsqu’un champ inconnu apparaît dans la structure des données publicitaires, Google ne déclenche pas d’erreur et traite cela comme s’il n’y avait pas de publicité.
      Google déploierait d’abord une nouvelle application avant de modifier le protocole au point de supprimer toutes les publicités dans les anciennes versions ; un simple certificate pinning de base, ou un décodage moins tolérant en cas d’échec d’extraction des informations publicitaires, suffirait donc à bloquer immédiatement cette méthode. L’équipe YouTube considérerait probablement cela comme un défaut.
    • Les objets bytes sont immuables, mais les objets bytearray ne le sont pas.
  • Un petit proxy en C++/Go pourrait faire la même chose avec bien moins de surcharge. Pour une tâche aussi bien définie, ce serait plus stable et demanderait moins d’efforts que de se battre avec mitmproxy.
    Envoyer tout le trafic vers un proxy dégrade les performances, même avec de l’interception SNI. C’est pareil avec pfSense : un simple serveur Linux et quelques règles iptables suffiraient, sans avoir à lutter contre les couches d’abstraction de pfSense.
    Il suffit d’écrire dans un fichier .proto uniquement les champs rétro-ingéniérés nécessaires, de générer automatiquement le code et de changer le flag. Ce serait moins coûteux qu’une implémentation Python et plus facile à mettre à jour si le proto change. Ignorer les tags de champs inconnus est une fonctionnalité importante de Protobuf, qui permet des changements de schéma compatibles sans casser les déploiements existants.

    • Il est peut-être préférable de ralentir volontairement l’expérience YouTube et de rendre les changements de vidéo plus lents. Cela réduirait sans doute pas mal l’addiction aux Shorts.
    • J’attendrais avec intérêt un billet de blog expliquant en détail comment faire cela.
    • Ce serait bien que tu écrives toi-même un guide montrant où se trouvent les inefficacités et comment les atténuer avec un logiciel plus simple.
      L’auteur semblait déjà conscient d’une bonne partie des remarques faites en commentaire, et l’article est assez approfondi. Il a benchmarké Python et C++, et l’implémentation finale ne décode même pas Protobuf. Il a aussi essayé plusieurs solutions mitm, et pfSense n’est pas utilisé comme simple routeur de sécurité, mais pour cibler uniquement le trafic de l’Apple TV via VLAN et VPN.
      Ce commentaire donne une impression trop facile et dépréciative. L’article original ne l’est pas ; si tu veux le formuler ainsi, il faudrait le démontrer toi-même pour la communauté.
    • Je serais curieux d’avoir des recommandations de proxy léger pouvant tourner sur macOS et servir aussi les autres appareils de la maison.
  • Est-ce que payer YouTube Premium soutient les créateurs ? Si oui, je me demande dans quelle mesure par rapport à un soutien direct comme Patreon.

    • Ce n’est probablement pas grand-chose comparé à Patreon, mais on peut difficilement s’attendre à ce que quelqu’un s’abonne au Patreon de tous les YouTubeurs qu’il regarde.
      La part d’un abonnement YouTube Premium qui revient à un créateur individuel est minime, mais c’est tout de même mieux que de regarder ses vidéos avec un bloqueur de publicités.
    • On dit que les créateurs reçoivent une plus grande part pour une vue YouTube Premium que pour une vue avec publicité classique. Si la publicité est ignorée, il n’y a pas de revenu. Cela dit, comme les utilisateurs Premium sont peu nombreux, les limites restent importantes.
    • Les informations récentes sont rares, mais au lancement sous le nom Youtube Red, c’était généralement bien plus que le revenu publicitaire par vue.
    • Plus que la publicité, moins que Patreon.
      Comme c’est basé sur le temps de visionnage et non sur les impressions publicitaires, les créateurs de contenus longs sont avantagés.
  • Le compte YouTube de ma petite amie n’affiche étrangement aucune publicité, quel que soit l’appareil sur lequel elle se connecte. Cela inclut l’Apple TV, elle n’a pas Premium et ne l’a jamais eu.
    Je me demande quel flag interne a été activé pour désactiver les publicités.

    • Envoie-moi son nom d’utilisateur et son e-mail en DM, je peux vérifier et corriger ça.
    • Ta petite amie fait en pratique partie du groupe témoin pour les publicités. Cela peut servir à comprendre l’effet des publicités sur les utilisateurs, en comparant son comportement à celui des personnes qui en voient.
    • Elle fait peut-être partie d’une expérience de holdback. Il est fréquent de placer certains utilisateurs dans un groupe de retenue pour observer l’effet d’une fonctionnalité, comme l’exécution des publicités, sur les métriques ; j’ai aussi mené ce genre d’expériences quand je travaillais chez Google.
    • À une époque, un abonnement Google Music désactivait les publicités YouTube. Même après l’arrêt du service ou l’annulation de l’abonnement, les publicités YouTube ne sont pas réapparues pendant plus de six mois.
      C’est seulement à ce moment-là que j’ai compris pourquoi les gens se plaignaient.
    • J’ai la même expérience sur Twitch.
      Je n’utilise pas de bloqueur de publicités, mais dès que je suis connecté, je ne vois aucune publicité, ni sur le site web ni dans l’application mobile. Je n’ai pas Twitch Turbo, et je n’ai plus Amazon Prime. Je n’ai pas non plus les autres avantages de Turbo, donc mon compte n’est pas entièrement marqué comme Turbo.
      Je ne sais pas si mon profil de compte a été accidentellement cassé quand je bricolais diverses choses dans le cadre d’un bug bounty, mais si cela permet de conserver cet avantage, je peux fournir plus d’informations.
      Ce qui est étrange, c’est que je me souviens d’une fois à l’hôpital, sous médicaments et dans la douleur, où je voulais juste regarder la télé, mais les publicités Twitch étaient tellement envahissantes que j’ai presque craqué. Puis, un ou deux ans plus tard, je me suis soudain rendu compte que je n’avais pas vu de publicité depuis des années.
      Il existe probablement un vieux test A/B sans publicité oublié, qui est resté en place parce que ça ne valait pas la peine de le nettoyer. J’en profite depuis des années et je regarde Twitch plus que n’importe quelle autre plateforme. Au Royaume-Uni, Twitch Turbo coûte 12 £ par mois, soit environ 15,50 $, ce qui est cher même à l’échelle mondiale ; comparé aux 12 $/12 € aux États-Unis et en Europe, c’est un prix assez désavantageux.
  • J’ai trouvé assez surprenant qu’en plaçant un proxy MITM entre l’Apple TV et l’Internet extérieur, on puisse déchiffrer le trafic HTTPS.
    Je pensais que, normalement, ça ne devrait pas fonctionner ; puis j’ai été à nouveau surpris en apprenant qu’on pouvait ajouter une CA au magasin de certificats de l’Apple TV. C’était un article fouillé qui passe en revue toute la stack.

    • Si l’on cherche pourquoi Apple permet d’ajouter des certificats, il est très probable que ce soit pour s’aligner sur les besoins IT et de gestion des appareils dans les entreprises ou les environnements éducatifs où l’Apple TV sert de boîtier AirPlay.
      Par exemple, dans les universités, pour connecter un appareil au Wi‑Fi, il fallait parfois ajouter son adresse MAC à une liste d’autorisation ou installer un certificat.
    • Google pourrait facilement bloquer cette méthode dans l’app YouTube simplement en vérifiant la CA qui a signé le certificat SSL.
      Cela dit, en faisant ça, YouTube pourrait casser dans de nombreux environnements d’entreprise, donc je ne sais pas s’ils le feront vraiment. Malheureusement, ce serait tout de même très facile à bloquer.
    • Je ne m’attendais pas à ce qu’on puisse ajouter une CA à l’Apple TV. Je crois que je ne m’en étais pas rendu compte parce que je n’avais jamais essayé d’accéder depuis l’Apple TV à une ressource sans chaîne de certificats valide.
    • La plupart des appareils permettent d’ajouter des CA, mais aujourd’hui presque toutes les apps utilisent le certificate pinning et ignorent le magasin de certificats système. Le fait que YouTube ne le fasse pas est très surprenant.
    • Ironiquement, Android TV, au moins en version 7.x, ne le permet pas. Je l’ai appris à mes dépens en essayant de contourner un certificat Let’s Encrypt non approuvé.
  • J’ai essayé plusieurs fois de l’implémenter sur Apple TV, sans aucun succès. Il semble que YouTube ait désormais ajouté du certificate pinning dans l’app, ou quelque chose du genre. Je me demande si quelqu’un a réussi à faire fonctionner ça récemment.

    • Si tu es prêt à y consacrer du temps, tu peux creuser Frida [0]. Les certificats épinglés ne sont pas un problème non plus.
      [0] https://frida.re/docs/home/
  • J’aime toutes les tentatives de blocage à l’échelle du réseau de ces mauvais services en ligne qu’on se retrouve forcé d’utiliser.
    Bloquer les pubs, c’est bien, mais j’aimerais qu’il existe davantage de moyens, plus simples, de bloquer à l’échelle du réseau le scroll infini agressif comme YouTube Shorts ou Instagram Reels.
    Sur Instagram, je veux seulement voir les posts et les stories des gens que je suis, pas me faire recommander des vidéos idiotes conçues pour capter mon attention. C’est peut-être révélateur d’un manque de volonté de ma part, mais il m’arrive souvent d’en regarder quelques-unes et de perdre 15 minutes de ma vie.

    • On n’est pas forcé de les utiliser. On peut ne pas les utiliser, ou payer.
      Les internautes ont globalement choisi de ne pas vouloir payer, donc quelqu’un d’autre en supporte le coût. Dans l’ensemble, les internautes ne récompensent pas ceux qui ne leur montrent pas de publicité. Ils veulent du contenu, mais le veulent généralement gratuitement.
    • Il suffit de supprimer l’app, d’utiliser la page web et un navigateur qui autorise les user scripts.
      J’ai trouvé un script qui transforme la page Instagram en quelque chose de proche d’une simple balise image, pour ne voir que les photos : https://greasyfork.org/en/scripts/5014-un-instagram
    • Je pense que ces tactiques exploitent notre curiosité naturelle et toute l’esthétique qui l’entoure.
      Donc ce n’est pas tant un manque de volonté qu’une forme de désensibilisation que nous avons construite, et c’est assez mauvais. Les efforts et la créativité visant à faire en sorte que ce soit nous qui utilisions les plateformes, plutôt que les plateformes qui nous fassent les utiliser, méritent le respect.
    • En tant que parent, je m’y retrouve particulièrement. C’est difficile de voir ses enfants se faire happer par les algorithmes.
      J’en parle régulièrement avec eux, et ils reconnaissent aussi que c’est nocif, mais ils ont beaucoup de mal à résister. Même moi, je me fais parfois aspirer par le doomscrolling.
      Là où c’est possible, j’ai configuré un filtrage des pubs avec Pi-hole, mais je ne veux pas bloquer YouTube entièrement. Cela dit, pour protéger ma famille, je vais probablement devoir l’envisager sérieusement à l’avenir.
    • Cette app convenait très bien pour bloquer le scroll infini d’Instagram : https://www.distractionfreeapps.com/index.html
  • L’ingénierie est intéressante, mais c’est un peu triste de devoir aller aussi loin pour utiliser son propre matériel ou logiciel comme si on en était propriétaire, ne serait-ce qu’un peu.

    • Dans ce cas, on possède bien l’appareil. En revanche, je ne vois pas vraiment sur quelle base on pourrait prétendre posséder YouTube ou ses contenus.
    • C’est quelque chose qu’on pouvait déjà faire il y a presque dix ans avec une box Android à 30 dollars et le NewPipe APK installé dessus.
  • Il y a des pubs sur YouTube ? Mon navigateur les bloque si bien que je ne le savais pas.
    Le vrai problème, c’est que l’expérience Apple TV est bien pire que celle d’un navigateur web classique. Apple verrouille tellement le matériel que la structure profite davantage aux revenus publicitaires de YouTube qu’au consommateur final qui l’a payé.

    • Sous Linux, Windows et Android, je ne vois aucune publicité. Quand j’essaie parfois de regarder YouTube sur iPad, je suis surpris par la fréquence et le côté agaçant des pubs.
      C’est pareil quand je navigue sur le web avec l’iPad en dehors du réseau Pi-hole de la maison. Je ne comprends pas comment les gens supportent ça au quotidien.
      L’iPad est un appareil fourni par le travail, donc je ne l’utilise pas souvent à titre personnel, mais chaque utilisation me rappelle à quel point c’est pénible.
      Curieusement, avant de recevoir l’iPad, je pensais qu’il ne serait utile que pour consommer du contenu ; en réalité, il est très pratique pour accéder rapidement à distance à des ressources de travail, mais pour la navigation web générale et le streaming, c’est un appareil enfermé dans un désert couvert de pubs.
  • Si vous voulez YouTube sans publicité, vous pouvez utiliser https://yewtu.be ou une autre instance Invidious https://docs.invidious.io/instances/.
    Il existe une course aux armements entre YouTube et Invidious, et il arrive qu’Invidious ne fonctionne pas, mais l’équipe a toujours fini par trouver de nouvelles façons de contourner YouTube et de fournir les vidéos sans publicité.

    • Ce n’est pas pour rien que le titre contient “on AppleTV”. Les clients ou front-ends alternatifs ne fonctionnent pas dessus.
    • Sur des plateformes comme Roku TV, il n’y a pas de navigateur, donc cette méthode ne fonctionne pas.