JavaScript DRM का भ्रम: HotAudio की कॉपी प्रोटेक्शन को सिर्फ़ 3 राउंड में निष्प्रभावी करने की प्रक्रिया
(therantydev.com)- ब्राउज़र में चलने वाला 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 हो जाए; फिरendedevent पर chunks कोBlobमें जोड़कर.m4aफ़ाइल के रूप में डाउनलोड किया गया- यह browser extension API का उपयोग करने वाला client-side man-in-the-middle(MITM) attack था, इसलिए HotAudio server को tampering का पता नहीं चल सकता था
-
fermaw की पहली प्रतिक्रिया
- public release के लगभग 2 हफ़्ते बाद fermaw ने patch लागू किया
window.asglobal 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 से
AudioSourceclass ढूँढना मुश्किल हो जाए
- JavaScript में native function पर
-
mockToString — integrity check को धोखा देने वाला spoof function
- hooked function की
.toString()को override करके उसे"function नाम() { [native code] }"लौटाने के लिए मजबूर किया गया - इससे fermaw की integrity check false negative देने लगी और hooking detect नहीं हो पाया
- hooked function की
-
HTMLMediaElement.prototype.playhookingwindow.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के ज़रिए स्थायी fixationwindow.Audioको hijacked constructor से replace किया गया और उसेwritable: false,configurable: falseके साथ set किया गया- fermaw का code original
Audioconstructor 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/MediaSourceobjects को सीधे assign करके URL-based interception से बचने का प्रयास भी किया गया
-
V3 की प्रतिक्रिया: browser property descriptor स्तर पर hooking
Object.getOwnPropertyDescriptorका उपयोग करHTMLMediaElement.prototypeकेsrcऔरsrcObjectsetter को सीधे hook किया गया- audio element main document, iframe या web component कहीं भी हो, source assign होते ही hooking सक्रिय हो जाती थी
document_startinjection के ज़रिए iframe initialization से पहले ही hooks install कर दिए जाते थे
-
addSourceBufferhooking: race condition का समाधान- पुराने version में
SourceBuffer.prototype.appendBufferको prototype स्तर पर hook करने पर fermaw का code hook install होने से पहलेappendBufferreference cache कर ले तो bypass संभव था - V3 में
MediaSource.prototype.addSourceBufferको hook करकेSourceBufferinstance creation के क्षण को intercept किया गया- instance return होते ही उसी instance पर direct
appendBufferhook को own property के रूप में install कर दिया गया - page code के instance देखने से पहले ही hook लग जाता था, इसलिए cache-based bypass मूल रूप से असंभव हो गया
- instance return होते ही उसी instance पर direct
- पुराने version में
-
capture phase event listener — अंतिम safety net
document.addEventListenerमेंuseCapture: trueके साथplay,loadedmetadataevents को monitor किया गया- browser events capture phase (root→target) में पहले propagate होते हैं, इसलिए HotAudio के event listeners से हमेशा पहले execution मिलता था
addSourceBufferprototype hooking +src/srcObjectproperty 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 →
SourceBuffertransfer दोहराता रहा, और सभी chunks hookedappendBufferके माध्यम से 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
bufferedtime range को monitor कर buffer की स्थिति के अनुसार playback speed dynamic रूप से adjust करता है- यदि buffer margin 15 सेकंड से अधिक हो तो speed बढ़ती है, और 3 सेकंड से कम होने पर घटती है
- धीमे connection पर browser stall और
endedevent न आने की समस्या इससे टाली जा सकती थी
- V3 में जोड़ा गया feature
-
final file generation
- playback समाप्त होने पर (
endedevent याcurrentTimeकाdurationके क़रीब पहुँचना) collected chunks कोBlobमें जोड़कर.m4adownload बनाया गया - buffer boundary पर अधूरे chunks के कारण silent padding artifacts आ सकते थे, जिन्हें
ffmpegpost-processing से साफ़ किया जा सकता है
- playback समाप्त होने पर (
V3 का spoof() function: और अधिक परिष्कृत छलावा
- V2 का
mockToStringnative 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.toStringreferences का उपयोग किया गया- बाद में किसी अन्य code द्वारा
.toStringबदल दिए जाने पर भी इस पर असर नहीं पड़ता था
- बाद में किसी अन्य code द्वारा
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 से इसे लागू करना मूल रूप से अनुपयुक्त तरीका है
4 टिप्पणियां
लगता है दोनों के बीच सच में काफ़ी दिलचस्प भिड़ंत हुई होगी।
मुझे भी पहले ऐसा हुआ था कि API response अचानक encrypted होकर आने लगा, तो मैंने सोचा कि अगर encrypted value मिल रही है तो client कहीं न कहीं उसे decrypt भी कर रहा होगा। इसी सोच से मैंने bundled JavaScript code को पूरा का पूरा कॉपी किया, decryption code के ठीक पहले
console.logकी एक लाइन जोड़ दी, और उसे सीधे developer console में paste कर दिया। हैरानी की बात यह थी कि वह बस चल गया। वैसे भी, एक बार encryption key पता चल गई तो उसके बाद सब आसान हो गया। API के दूसरे responses में वही key लेकर इस्तेमाल की जा रही थी, हाहा।अगर यह NSFW (Not Safe For Work) ASMR है..
तो यह किसी adult site को hack करने की कहानी को बहुत technical और गहराई से लिखकर समझाया गया है -.-;
आखिरकार, technology की प्रगति तो सब adult क्षेत्र में ही होती है...?
सोचता हूँ कि ऑडियो पर DRM लगाना... क्या यह सच में बहुत मुश्किल नहीं है?
जटिल हैकिंग करने की बात नहीं, सिर्फ़ ऑडियो को virtual cable से घुमा दें तब भी लगता है कि कुछ न कुछ हो ही जाएगा।
वैसे, इसमें आगे-पीछे जो आदान-प्रदान हुआ वह काफ़ी मज़ेदार था, हाहाहा। लगता है उन्होंने ऐसे जुगाड़ सोचे जिनके बारे में AI ने शायद कभी सोचा भी नहीं होगा।