- ब्राउज़र में चलने वाला JavaScript-आधारित DRM मूल रूप से बायपास किया जा सकता है, क्योंकि डिक्रिप्ट किया गया ऑडियो डेटा अंततः JavaScript की पहुँच वाले हिस्से से होकर गुजरता है
- HotAudio एक NSFW ASMR ऑडियो होस्टिंग प्लेटफ़ॉर्म है, जिसने MediaSource Extensions API का उपयोग कर अपने खुद के encryption और chunk transfer तरीके पर आधारित कॉपी प्रोटेक्शन लागू किया
- डेवलपर के बार-बार किए गए patches (global variable हटाना, hash verification,
.toString() integrity checks, iframe/Shadow DOM isolation) के जवाब में attacker ने हर बार prototype hooking और spoofing techniques से प्रतिक्रिया दी — यह 3 चरणों की तकनीकी भिड़ंत का रिकॉर्ड है
- वास्तविक DRM के लिए Trusted Execution Environment(TEE) आधारित hardware protection (Widevine, FairPlay आदि) की ज़रूरत होती है, लेकिन छोटे प्लेटफ़ॉर्म के लिए licensing cost और infrastructure समस्याओं के कारण यह व्यावहारिक नहीं है
- JavaScript DRM आम users के लिए प्रभावी friction का काम कर सकता है, लेकिन skilled attackers को नहीं रोक सकता, इसलिए इसे "DRM" कहना अपेक्षा और वास्तविकता के बीच बड़े अंतर को छिपाता है
पृष्ठभूमि: HotAudio और JavaScript DRM की जन्मजात सीमाएँ
- HotAudio एक NSFW ASMR ऑडियो होस्टिंग साइट है, जो creators के लिए DRM protection features देने का दावा करती है
- Soundgasm, Mega जैसी मौजूदा hosting services पर ToS सख़्त होने के बाद यह एक वैकल्पिक प्लेटफ़ॉर्म के रूप में उभरा
- डेवलपर fermaw ने Reddit पर DRM implementation को "मज़ेदार" बताया, और वहीं से इस विश्लेषण की शुरुआत हुई
- JavaScript कोड स्वभाव से "userland" में मौजूद होता है, यानी ऐसा कोड जो user तक पहुँचता है और जिसे user access या modify कर सकता है
- चाहे कितनी भी जटिल key, nonce या encrypted file format क्यों न इस्तेमाल की जाए, JavaScript decryption logic से गुजरने के बाद डेटा को अंततः plain text स्थिति में browser audio engine तक पहुँचना ही पड़ता है
Trusted Execution Environment(TEE) की भूमिका
- Microsoft की परिभाषा के अनुसार TEE एक "cryptography से सुरक्षित CPU और memory का isolated area" है, जहाँ बाहरी कोड अंदर के data को पढ़ या बदल नहीं सकता
- TEE hardware-based security area है (ARM TrustZone, Intel SGX आदि), और इसी के ऊपर Content Decryption Module(CDM) जैसे Widevine, FairPlay, PlayReady चलते हैं
- ये CDM इस बात की गारंटी देते हैं कि encryption keys और decrypted media buffers host OS के सामने expose न हों
- Widevine license लेने के लिए Google के साथ licensing agreement, native binary integration, infrastructure, legal process और काफ़ी अधिक लागत की ज़रूरत होती है
- किसी छोटे NSFW ऑडियो प्लेटफ़ॉर्म के लिए Widevine license हासिल करना वास्तविक रूप से लगभग असंभव है
HotAudio का implementation और "PCM boundary"
- HotAudio encrypted form में ऑडियो भेजता है और MediaSource Extensions(MSE) API के ज़रिए chunk स्तर पर decrypt और playback करने वाला JavaScript-आधारित custom decryption तरीका अपनाता है
- यह तरीका सामान्य users के लिए right-click save या network tab से direct download रोकने में काफ़ी प्रभावी है
- PCM(Pulse-Code Modulation) वह अंतिम uncompressed digital audio format है जो speaker तक भेजा जाता है, यानी हर audio pipeline का अंतिम पड़ाव
- वास्तविक attack में PCM तक trace करने की ज़रूरत नहीं पड़ी; JavaScript की पहुँच वाले आख़िरी बिंदु
SourceBuffer.appendBuffer() method पर हमला करना पर्याप्त था
appendBuffer के call होने तक data पहले ही JavaScript द्वारा decrypt हो चुका होता है, और browser का AAC/Opus decoder HotAudio की proprietary encryption को समझ नहीं सकता, इसलिए वह सिर्फ़ standard codec form में decrypted data ही स्वीकार करता है
- decryption पूरा होने और browser media engine तक data पहुँचने के बीच का यही क्षण intercept करने योग्य "golden moment" है
Act 1: V1.0 — global variable exposure और prototype hooking
- HotAudio player ऑडियो source object को
window.as नाम के global variable के रूप में expose कर रहा था
- V1 extension ने HotAudio द्वारा हमेशा लोड की जाने वाली
nozzle.js फ़ाइल को network request चरण पर intercept करके उसमें modified code inject किया
SourceBuffer.prototype.appendBuffer को monkey-patch करके decrypted chunks को array में store किया गया, जबकि original function को भी सामान्य रूप से call होने दिया गया
window.as.el को mute करके playback speed को 16x (browser maximum) पर सेट किया गया, ताकि पूरा ऑडियो तेज़ी से buffer हो जाए; फिर ended event पर chunks को Blob में जोड़कर .m4a फ़ाइल के रूप में डाउनलोड किया गया
- यह browser extension API का उपयोग करने वाला client-side man-in-the-middle(MITM) attack था, इसलिए HotAudio server को tampering का पता नहीं चल सकता था
-
fermaw की पहली प्रतिक्रिया
- public release के लगभग 2 हफ़्ते बाद fermaw ने patch लागू किया
window.as global exposure को हटा दिया और initialization code को closure में wrap कर बाहरी access बंद कर दिया
nozzle.js के लिए hash verification check जोड़ा गया (संभावित रूप से SRI, custom self-hashing, या server-side nonce system में से कोई एक)
- modified file का hash यदि अपेक्षित hash से मेल न खाए, तो player initialize नहीं होता था
Act 2: V2.0 — spoofing techniques और generic hooking
-
fermaw की in-memory defense
- JavaScript में native function पर
.toString() call करने पर "function appendBuffer() { [native code] }" मिलता है, जबकि monkey-patched function अपना वास्तविक source code लौटाता है — fermaw ने इसी गुण का उपयोग किया
- fermaw ने
SourceBuffer.prototype.appendBuffer.toString() में '[native code]' न मिलने पर playback reject करने वाली integrity check जोड़ दी
- player initialization को भी obfuscate कर दिया गया, ताकि polling loop से
AudioSource class ढूँढना मुश्किल हो जाए
-
mockToString — integrity check को धोखा देने वाला spoof function
- hooked function की
.toString() को override करके उसे "function नाम() { [native code] }" लौटाने के लिए मजबूर किया गया
- इससे fermaw की integrity check false negative देने लगी और hooking detect नहीं हो पाया
-
HTMLMediaElement.prototype.play hooking
window.as या किसी specific class name को खोजने के बजाय, HTMLMediaElement.prototype.play को hook करने वाला generic तरीका अपनाया गया
- इससे player object के नाम या closure depth की परवाह किए बिना
.play() call के समय audio element अपने-आप पकड़ा जा सकता था
- mobile devices पर आमतौर पर एक समय में एक ही player active होता है, इसलिए कई
.play() calls से reverse engineering रोकना आसान नहीं था
-
Object.defineProperty के ज़रिए स्थायी fixation
window.Audio को hijacked constructor से replace किया गया और उसे writable: false, configurable: false के साथ set किया गया
- fermaw का code original
Audio constructor restore करने की कोशिश करता तो browser TypeError फेंक देता
- इस तरह hooking page lifetime भर स्थायी रूप से कायम रही
Act 3: V3.0 — property descriptor स्तर पर full hooking
-
fermaw का iframe और Shadow DOM isolation प्रयास
<iframe> का अपना window, document और स्वतंत्र prototype chain होता है, इसलिए parent window की hooking iframe के भीतर लागू नहीं होती
- Shadow DOM एक isolated DOM subtree है, जिसके अंदर के elements को main document के
querySelector से नहीं खोजा जा सकता
srcObject के ज़रिए MediaStream/MediaSource objects को सीधे assign करके URL-based interception से बचने का प्रयास भी किया गया
-
V3 की प्रतिक्रिया: browser property descriptor स्तर पर hooking
Object.getOwnPropertyDescriptor का उपयोग कर HTMLMediaElement.prototype के src और srcObject setter को सीधे hook किया गया
- audio element main document, iframe या web component कहीं भी हो, source assign होते ही hooking सक्रिय हो जाती थी
document_start injection के ज़रिए iframe initialization से पहले ही hooks install कर दिए जाते थे
-
addSourceBuffer hooking: race condition का समाधान
- पुराने version में
SourceBuffer.prototype.appendBuffer को prototype स्तर पर hook करने पर fermaw का code hook install होने से पहले appendBuffer reference cache कर ले तो bypass संभव था
- V3 में
MediaSource.prototype.addSourceBuffer को hook करके SourceBuffer instance creation के क्षण को intercept किया गया
- instance return होते ही उसी instance पर direct
appendBuffer hook को own property के रूप में install कर दिया गया
- page code के instance देखने से पहले ही hook लग जाता था, इसलिए cache-based bypass मूल रूप से असंभव हो गया
-
capture phase event listener — अंतिम safety net
document.addEventListener में useCapture: true के साथ play, loadedmetadata events को monitor किया गया
- browser events capture phase (root→target) में पहले propagate होते हैं, इसलिए HotAudio के event listeners से हमेशा पहले execution मिलता था
addSourceBuffer prototype hooking + src/srcObject property descriptor hooking + play() hooking + capture phase event listeners की चार-स्तरीय layer से browser के लगभग सभी media playback paths कवर हो गए
automation: high-speed download process
- पकड़े गए audio element को mute किया गया और
playbackRate को 16x पर सेट कर शुरुआत से playback शुरू किया गया
- browser playback position से आगे buffer भरने के लिए तेज़ी से fetch → decrypt →
SourceBuffer transfer दोहराता रहा, और सभी chunks hooked appendBuffer के माध्यम से collect हो गए
- Chrome playback speed को 16x तक सीमित करता है (HTML spec में स्पष्ट upper bound नहीं है, लेकिन Chromium implementation limitation मौजूद है)
- fermaw ने burst traffic के लिए throttling लागू किया (सैकड़ों KB/s → लगभग 50 KB/s), लेकिन यह फिर भी real-time listening की तुलना में कई गुना तेज़ था
- इससे अधिक कड़ी limit सामान्य users की streaming में भी रुकावट पैदा कर सकती थी, इसलिए व्यवहारिक रूप से कठिन था
-
adaptive speed control
- V3 में जोड़ा गया feature
buffered time range को monitor कर buffer की स्थिति के अनुसार playback speed dynamic रूप से adjust करता है
- यदि buffer margin 15 सेकंड से अधिक हो तो speed बढ़ती है, और 3 सेकंड से कम होने पर घटती है
- धीमे connection पर browser stall और
ended event न आने की समस्या इससे टाली जा सकती थी
-
final file generation
- playback समाप्त होने पर (
ended event या currentTime का duration के क़रीब पहुँचना) collected chunks को Blob में जोड़कर .m4a download बनाया गया
- buffer boundary पर अधूरे chunks के कारण silent padding artifacts आ सकते थे, जिन्हें
ffmpeg post-processing से साफ़ किया जा सकता है
V3 का spoof() function: और अधिक परिष्कृत छलावा
- V2 का
mockToString native code string को hardcode करके लौटाता था, लेकिन browser/platform के अनुसार [native code] string के spacing या formatting में मामूली अंतर हो सकता था, जो एक कमजोरी थी
- V3 का
spoof() hook लगने से पहले original function से वास्तविक native code string capture करके वही वापस लौटाता था, जिससे spoofing लगभग पूर्ण हो गई
_call.call(_toString, original) जैसे रूप में script start पर cache किए गए Function.prototype.call और Function.prototype.toString references का उपयोग किया गया
- बाद में किसी अन्य code द्वारा
.toString बदल दिए जाने पर भी इस पर असर नहीं पड़ता था
DRM की बुनियादी सीमाएँ और नैतिक विचार
- DRM का पूरा इतिहास मूल रूप से "ताला लगा डिब्बा देना और साथ में उसकी चाबी भी थमा देना" वाली समस्या की पुनरावृत्ति है
- 1999 में CSS-encrypted DVD की शुरुआती cracking के बाद से फ़िल्म और संगीत उद्योग इस लड़ाई में लगातार हारते रहे हैं
- सबसे परिष्कृत game DRM माने जाने वाले Denuvo को भी अधिकतर बड़े games में release के कुछ हफ़्तों के भीतर crack कर लिया गया
- एक समय प्रसिद्ध cracker Empress के रिटायर होने के बाद cracking धीमी पड़ी थी, लेकिन hypervisor-style exploits आने के बाद गतिविधि फिर तेज़ हो गई
- जब तक content और decryption key दोनों client machine पर मौजूद रहेंगे, पर्याप्त motivation और tools रखने वाले users द्वारा interception अपरिहार्य रहेगा
निष्कर्ष: JavaScript DRM "परिष्कृत friction" है, वास्तविक DRM नहीं
- HotAudio का DRM fermaw की क्षमता की कमी नहीं, बल्कि JavaScript-आधारित DRM की अधिकतम व्यावहारिक सीमा को दिखाता है
- इसमें client-side decryption, chunk transfer और active anti-tamper checks सब लागू थे, और browser extensions से अनजान अधिकांश users के लिए यह लगभग पूरी तरह रोकथाम जैसा असर देता था
- लेकिन इसे "DRM" कहने से hardware TEE-आधारित वास्तविक DRM जैसी समान अपेक्षाएँ बन जाती हैं, और यहीं समस्या है
- ASMR creators के समर्पित प्रशंसक अक्सर offline copy चाहते हैं, और यदि Patreon जैसे paid channels उपलब्ध हों तो वे खुशी-खुशी खरीदने को भी तैयार हो सकते हैं
- content creators को किसी न किसी रूप में protection की ज़रूरत होना समझ में आता है, लेकिन JavaScript से इसे लागू करना मूल रूप से अनुपयुक्त तरीका है
अभी कोई टिप्पणी नहीं है.