- Le DRM basé sur JavaScript exécuté dans le navigateur reste fondamentalement contournable, car les données audio déchiffrées doivent inévitablement transiter par une zone accessible à JavaScript
- HotAudio est une plateforme d’hébergement audio ASMR NSFW qui a mis en place une protection contre la copie maison via l’API MediaSource Extensions, avec chiffrement et transfert par chunks
- L’article retrace un affrontement en 3 étapes où, face aux correctifs successifs du développeur (suppression des variables globales, vérification de hash, contrôle d’intégrité via
.toString(), isolation par iframe/Shadow DOM), l’attaquant a répondu à chaque fois par du hooking de prototypes et des techniques d’usurpation
- Un DRM réellement efficace exige une protection matérielle fondée sur un Trusted Execution Environment (TEE) comme Widevine ou FairPlay, mais ce type de solution reste hors de portée des petites plateformes à cause des coûts de licence et d’infrastructure
- Le DRM JavaScript peut servir de friction utile contre les utilisateurs ordinaires, mais il ne peut pas arrêter un attaquant expérimenté, d’où un fort décalage entre l’étiquette « DRM » et la réalité
Contexte : HotAudio et les limites intrinsèques du DRM JavaScript
- HotAudio est un site d’hébergement audio ASMR NSFW, présenté comme une plateforme offrant des fonctions de protection DRM aux créateurs
- La plateforme s’est imposée comme alternative après le durcissement des conditions d’utilisation de services d’hébergement comme Soundgasm ou Mega
- L’analyse est partie d’un commentaire du développeur fermaw sur Reddit, où il disait que l’implémentation du DRM avait été « amusante »
- Le code JavaScript existe par nature dans le « userland », c’est-à-dire dans un espace où l’utilisateur peut accéder au code et le modifier
- Même avec des clés, nonces et formats de fichiers chiffrés très sophistiqués, les données qui passent par la logique de déchiffrement JavaScript doivent finir par être transmises en clair au moteur audio du navigateur
Le rôle du Trusted Execution Environment (TEE)
- Selon la définition de Microsoft, un TEE est une « zone isolée du CPU et de la mémoire protégée par chiffrement », conçue pour empêcher un code externe de lire ou modifier les données qu’elle contient
- Le TEE est une zone de sécurité matérielle, comme ARM TrustZone ou Intel SGX, sur laquelle s’exécutent des Content Decryption Modules (CDM) tels que Widevine, FairPlay et PlayReady
- Ces CDM garantissent que les clés de chiffrement et les buffers média déchiffrés ne sont pas exposés au système d’exploitation hôte
- Obtenir une licence Widevine suppose un accord de licence avec Google, l’intégration de binaires natifs, une infrastructure adaptée, des procédures juridiques et des coûts importants
- Pour une petite plateforme audio NSFW, obtenir une licence Widevine est en pratique irréaliste
L’implémentation de HotAudio et la « frontière PCM »
- HotAudio transmet l’audio sous forme chiffrée et adopte une méthode de déchiffrement personnalisée en JavaScript avec lecture et déchiffrement par chunks via l’API MediaSource Extensions (MSE)
- Cette approche est efficace pour bloquer l’enregistrement par clic droit ou le téléchargement direct depuis l’onglet réseau pour les utilisateurs ordinaires
- Le PCM (Pulse-Code Modulation) est le format audio numérique final non compressé envoyé aux haut-parleurs, et constitue le point d’aboutissement de toute la chaîne audio
- Dans l’attaque réelle, il n’est pas nécessaire de remonter jusqu’au PCM : la cible clé est la méthode
SourceBuffer.appendBuffer(), dernier point accessible à JavaScript
- Au moment où
appendBuffer est appelée, les données ont déjà été déchiffrées par JavaScript, et comme les décodeurs AAC/Opus du navigateur ne comprennent pas le chiffrement propriétaire de HotAudio, ils ne peuvent accepter que des données déchiffrées dans un format de codec standard
- L’instant entre la fin du déchiffrement et la remise au moteur média du navigateur est précisément ce « moment en or » où l’interception devient possible
Acte 1 : V1.0 — exposition de variables globales et hooking de prototypes
- Le lecteur HotAudio exposait l’objet source audio via une variable globale appelée
window.as
- L’extension V1 interceptait au stade de la requête réseau le fichier
nozzle.js, toujours envoyé par HotAudio, afin d’y injecter un code modifié
SourceBuffer.prototype.appendBuffer était patché à la volée pour stocker les chunks déchiffrés dans un tableau tout en continuant d’appeler normalement la fonction d’origine
- En coupant le son de
window.as.el et en réglant la vitesse de lecture sur 16x — le maximum du navigateur —, l’extension mettait rapidement tout l’audio en buffer, puis assemblait les données dans un Blob téléchargé au format .m4a lors de l’événement ended
- Il s’agissait d’une attaque MITM côté client reposant sur les API d’extensions de navigateur, sans que les serveurs de HotAudio puissent détecter l’altération
-
Première réponse de fermaw
- Environ deux semaines après la sortie publique, fermaw a déployé un correctif
- L’exposition de la variable globale
window.as a été supprimée, et le code d’initialisation enfermé dans une closure pour empêcher l’accès externe
- Une vérification de hash a été ajoutée sur
nozzle.js — probablement via SRI, un hachage personnalisé ou un système de nonce côté serveur
- Si le fichier modifié ne correspondait pas au hash attendu, le lecteur ne s’initialisait pas
Acte 2 : V2.0 — usurpation et hooking générique
-
Défense en mémoire de fermaw
- En JavaScript, appeler
.toString() sur une fonction native renvoie "function appendBuffer() { [native code] }", alors qu’une fonction patchée renvoie son véritable code source
- fermaw a ajouté un contrôle d’intégrité refusant la lecture si
SourceBuffer.prototype.appendBuffer.toString() ne contenait pas '[native code]'
- L’initialisation du lecteur a aussi été davantage obfusquée pour rendre plus difficile la recherche de la classe
AudioSource par boucle de polling
-
mockToString — une fonction d’usurpation qui trompe le contrôle d’intégrité
- La méthode
.toString() de la fonction hookée a été redéfinie pour renvoyer "function nom() { [native code] }"
- Le contrôle d’intégrité de fermaw produisait alors un faux négatif, rendant la détection du hooking impossible
-
Hooking de HTMLMediaElement.prototype.play
- Au lieu de chercher
window.as ou un nom de classe spécifique, l’attaquant a adopté une approche générique en hookant HTMLMediaElement.prototype.play
- Quel que soit le nom de l’objet lecteur ou la profondeur de la closure, l’élément audio était capturé automatiquement au moment de l’appel à
.play()
- Sur mobile, un seul lecteur est généralement actif, ce qui rend difficile l’obfuscation par multiplication des appels à
.play()
-
Verrouillage permanent via Object.defineProperty
window.Audio a été remplacé par un constructeur détourné, puis défini avec writable: false et configurable: false
- Si le code de fermaw essayait de restaurer le constructeur
Audio d’origine, le navigateur levait alors une TypeError
- Le hooking restait ainsi permanent pendant toute la durée de vie de la page
Acte 3 : V3.0 — hooking généralisé au niveau des descripteurs de propriétés
-
Tentative d’isolation par iframe et Shadow DOM chez fermaw
- Un
<iframe> possède son propre window, son propre document et une chaîne de prototypes indépendante, de sorte que les hooks du window parent ne s’appliquent pas à son contenu
- Le Shadow DOM forme un sous-arbre DOM isolé, dont les éléments internes ne sont pas accessibles via le
querySelector du document principal
- Une tentative a aussi consisté à assigner directement des objets
MediaStream/MediaSource via srcObject, afin de contourner l’interception fondée sur les URL
-
Réponse de V3 : hooking au niveau des descripteurs de propriétés du navigateur
- En utilisant
Object.getOwnPropertyDescriptor, V3 hooke directement les setters src et srcObject de HTMLMediaElement.prototype
- Dès qu’une source est affectée à un élément audio, le hook se déclenche, que l’élément se trouve dans le document principal, un iframe ou un composant web
- L’injection en
document_start permet d’installer les hooks avant même l’initialisation des iframes
-
Hooking de addSourceBuffer : résolution de la race condition
- Dans les versions précédentes, hooker
SourceBuffer.prototype.appendBuffer au niveau du prototype pouvait être contourné si le code de fermaw mettait en cache une référence à appendBuffer avant l’installation du hook
- V3 hooke
MediaSource.prototype.addSourceBuffer pour intercepter le moment exact de création d’une instance de SourceBuffer
- Dès que l’instance est renvoyée, un hook
appendBuffer est installé directement sur cette instance comme propriété propre
- Le hook est donc en place avant même que le code de la page ne voie l’instance, rendant tout contournement par cache impossible à la source
-
Écouteurs d’événements en phase de capture — dernier filet de sécurité
document.addEventListener surveille les événements play et loadedmetadata avec useCapture: true
- Les événements du navigateur se propagent d’abord en phase de capture (racine → cible), de sorte que ces hooks s’exécutent toujours avant les écouteurs du code de HotAudio
- La combinaison du hooking de prototype
addSourceBuffer, du hooking des descripteurs de propriétés src/srcObject, du hooking de play() et des écouteurs en phase de capture crée une couverture en 4 couches de toutes les voies de lecture média du navigateur
Automatisation : le processus de téléchargement accéléré
- L’élément audio capturé est mis en sourdine, sa
playbackRate est réglée sur 16x, puis la lecture repart depuis le début
- Le navigateur enchaîne alors rapidement fetch → déchiffrement → envoi au
SourceBuffer pour remplir le buffer devant la position de lecture, et tous les chunks sont collectés via le hook appendBuffer
- Chrome limite la vitesse de lecture à 16x — une contrainte de l’implémentation Chromium, sans plafond explicite dans la spécification HTML
- fermaw applique un throttling contre les pics de trafic — de plusieurs centaines de Ko/s à environ 50 Ko/s —, mais cela reste plusieurs fois plus rapide qu’une écoute en temps réel
- Des limites plus strictes provoqueraient des coupures même pour les utilisateurs légitimes, ce qui les rend difficiles à mettre en œuvre en pratique
-
Contrôle adaptatif de la vitesse
- Fonction ajoutée dans V3 : le suivi des plages temporelles
buffered permet d’ajuster dynamiquement la vitesse de lecture selon l’état du buffer
- Si plus de 15 secondes sont d’avance, la vitesse augmente ; si l’avance tombe sous 3 secondes, elle ralentit
- Cela évite les blocages (
stall) du navigateur et les cas où l’événement ended ne se déclenche pas sur connexion lente
-
Génération du fichier final
- Une fois la lecture terminée — via l’événement
ended ou lorsque currentTime approche duration — les chunks collectés sont assemblés en Blob puis téléchargés en .m4a
- Des artefacts de padding silencieux peuvent apparaître à cause de chunks incomplets en bordure de buffer, mais ils peuvent être nettoyés avec un post-traitement
ffmpeg
La fonction spoof() de V3 : une usurpation plus sophistiquée
- Dans V2,
mockToString renvoyait une chaîne de code natif codée en dur, avec une fragilité potentielle car les espaces et le format de [native code] varient légèrement selon les navigateurs et plateformes
- Dans V3,
spoof() capture la véritable chaîne de code natif de la fonction originale avant le hooking, puis la renvoie telle quelle, ce qui permet une imitation parfaite
- La forme
_call.call(_toString, original) s’appuie sur des références à Function.prototype.call et Function.prototype.toString mises en cache au démarrage du script
- Ainsi, même si un autre code modifie ensuite
.toString, le mécanisme reste intact
Les limites fondamentales du DRM et la réflexion éthique
- Toute l’histoire du DRM répète le même problème : « on donne une boîte verrouillée tout en fournissant aussi la clé »
- Depuis le premier crack des DVD chiffrés CSS en 1999, les industries du cinéma et de la musique ont continuellement perdu ce combat
- Même Denuvo, le DRM de jeu le plus sophistiqué, finit le plus souvent cracké quelques semaines après la sortie des grands titres
- Le rythme des cracks avait ralenti après la retraite d’Empress, célèbre crackeuse, mais l’apparition d’exploits de type hyperviseur a relancé le phénomène
- Tant que le contenu et la clé de déchiffrement résident tous deux sur la machine cliente, l’interception par un utilisateur suffisamment motivé et outillé reste inévitable
Conclusion : le DRM JavaScript est une « friction sophistiquée », pas un vrai DRM
- Le DRM de HotAudio ne reflète pas une insuffisance de fermaw, mais plutôt la meilleure limite atteignable par un DRM fondé sur JavaScript
- Il implémente bien un déchiffrement côté client, un transfert par chunks et des contrôles anti-tampering actifs, et il constitue une barrière réellement efficace pour la majorité des utilisateurs qui ne connaissent pas les extensions de navigateur
- Mais l’appeler « DRM » pose problème, car cela crée le même niveau d’attente qu’un véritable DRM fondé sur un TEE matériel
- Les fans les plus investis d’un créateur ASMR sont précisément ceux qui veulent souvent une copie hors ligne ; s’ils ont accès à un canal payant comme Patreon, ils sont susceptibles de payer volontairement
- On peut comprendre que les créateurs de contenu aient besoin d’une forme de protection, mais une implémentation en JavaScript reste fondamentalement une approche inadaptée
Aucun commentaire pour le moment.