JavaScript DRM yanılsaması: HotAudio’nun kopya koruması 3 turda nasıl etkisiz hale getirildi
(therantydev.com)- Tarayıcıda çalışan JavaScript tabanlı DRM, şifresi çözülmüş ses verisi eninde sonunda JavaScript’in erişebildiği bir alandan geçmek zorunda olduğu için temelde aşılabilir
- HotAudio, NSFW ASMR ses barındırma platformu olarak, MediaSource Extensions API kullanan kendi şifreleme ve parça aktarım yöntemine dayalı bir kopya koruma sistemi uyguladı
- Geliştiricinin tekrarlanan yamalarına (global değişkenin kaldırılması, hash doğrulaması,
.toString()bütünlük kontrolü, iframe/Shadow DOM yalıtımı) karşı saldırganın her seferinde prototip hook’lama ve sahtecilik teknikleriyle yanıt verdiği 3 aşamalı mücadele kaydı - Gerçek anlamda DRM için Trusted Execution Environment(TEE) tabanlı donanım koruması (Widevine, FairPlay vb.) gerekir; ancak küçük platformlar lisans maliyeti ve altyapı sorunları nedeniyle buna erişemez
- JavaScript DRM, sıradan kullanıcılar için etkili bir sürtünme (friction) unsuru olsa da yetkin saldırganları durduramaz; bu yüzden buna "DRM" demek beklenti ile gerçeklik arasında büyük bir uçurum yaratır
Arka plan: HotAudio ve JavaScript DRM’in doğuştan gelen sınırları
- HotAudio, içerik üreticileri için DRM koruma özelliği sunduğunu iddia eden bir NSFW ASMR ses barındırma sitesi
- Mevcut barındırma hizmetleri olan Soundgasm ve Mega’nın ToS sıkılaştırmalarıyla kısıtlanmasının ardından alternatif bir platform olarak ortaya çıktı
- Geliştirici fermaw’ın Reddit’te DRM uygulamasının "eğlenceli" olduğunu söylemesi analizin başlangıç noktası oldu
- JavaScript kodu özünde "userland" alanında bulunur; yani kullanıcının erişip değiştirebildiği kod dağıtılmış olur
- Ne kadar sofistike anahtarlar, nonce’lar ve şifreli dosya biçimleri kullanılırsa kullanılsın, JavaScript çözme mantığından geçen veri sonunda düz metin halinde tarayıcının ses motoruna iletilmek zorundadır
Trusted Execution Environment(TEE)’nin rolü
- Microsoft’un tanımına göre TEE, "şifreleme ile korunan CPU ve belleğin yalıtılmış alanı"dır; dış kod bu alan içindeki veriyi okuyamaz veya değiştiremez
- TEE, donanım tabanlı bir güvenlik alanıdır (ARM TrustZone, Intel SGX vb.) ve Content Decryption Module(CDM) olan Widevine, FairPlay ve PlayReady bunun üzerinde çalışır
- Bu CDM’ler, şifreleme anahtarlarının ve çözülmüş medya tamponlarının ana işletim sistemine açığa çıkmamasını sağlar
- Widevine lisansı almak için Google ile lisans sözleşmesi, native binary entegrasyonu, altyapı, hukuki süreçler ve ciddi maliyet gerekir
- Küçük ölçekli bir NSFW ses platformunun Widevine lisansı alması pratikte mümkün değildir
HotAudio’nun uygulama şekli ve "PCM sınırı"
- HotAudio, sesi şifreli biçimde aktarıp MediaSource Extensions(MSE) API üzerinden parça parça çözüp oynatan JavaScript tabanlı özel bir çözme yöntemi kullanıyor
- Bu yöntem, sıradan kullanıcıların sağ tıkla kaydetmesini veya ağ sekmesinden doğrudan indirmesini engellemede etkili
- PCM(Pulse-Code Modulation), hoparlöre iletilen nihai sıkıştırılmamış dijital ses biçimidir ve tüm ses hattının son durağıdır
- Gerçek saldırıda PCM’e kadar iz sürmeye gerek kalmadan, JavaScript’in erişebildiği son nokta olan
SourceBuffer.appendBuffer()yöntemi asıl hedef haline geliyor appendBufferçağrıldığı anda veri zaten JavaScript tarafından çözülmüş durumdadır; tarayıcının AAC/Opus çözücüsü HotAudio’nun özel şifrelemesini anlayamadığından yalnızca standart codec biçimindeki çözülmüş veriyi kabul eder- Çözmenin tamamlanması ile verinin tarayıcı medya motoruna teslim edilmesi arasındaki an, yakalanabilen asıl **"altın an"**dır
Act 1: V1.0 — global değişken sızıntısı ve prototip hook’lama
- HotAudio oynatıcısı, ses kaynağı nesnesini
window.asadlı bir global değişken olarak dışarı açıyordu - V1 eklentisi, HotAudio’nun her zaman gönderdiği
nozzle.jsdosyasını ağ isteği aşamasında yakalayıp değiştirilmiş kod enjekte etti SourceBuffer.prototype.appendBuffer, çözülmüş parçaları bir diziye kaydederken özgün işlevi de normal biçimde çağıracak şekilde monkey patch edildiwindow.as.elsessize alınıp oynatma hızı 16x’e (tarayıcı üst sınırı) getirildi; böylece tüm ses hızla tamponlandı veendedolayı geldiğindeBlobolarak birleştirilip.m4adosyası halinde indirildi- Bu, tarayıcı uzantı API’leriyle yapılan istemci taraflı bir ortadaki adam saldırısı(MITM) idi; HotAudio sunucusu değişikliği fark edemiyordu
-
fermaw’ın ilk karşı hamlesi
- Açık yayından yaklaşık 2 hafta sonra fermaw bir yama uyguladı
window.asglobal değişkenini kaldırdı ve başlatma kodunu closure içine alarak dış erişimi kapattınozzle.jsiçin hash doğrulama kontrolü ekledi (SRI, özel hashleme veya sunucu taraflı nonce sistemi olduğu tahmin ediliyor)- Değiştirilmiş dosya beklenen hash ile eşleşmezse oynatıcı başlatılmıyordu
Act 2: V2.0 — sahtecilik teknikleri ve genel amaçlı hook’lama
-
fermaw’ın bellek içi savunması
- JavaScript’te native bir işlevde
.toString()çağrıldığında"function appendBuffer() { [native code] }"döner; monkey patch uygulanmış işlev ise gerçek kaynak kodu döndürür - fermaw,
SourceBuffer.prototype.appendBuffer.toString()çıktısında'[native code]'yoksa oynatmayı reddeden bir bütünlük denetimi ekledi - Oynatıcı başlatma süreci de obfuscate edilerek
AudioSourcesınıfını polling döngüsüyle bulmak zorlaştırıldı
- JavaScript’te native bir işlevde
-
mockToString — bütünlük kontrolünü kandıran sahte işlev
- Hook’lanmış işlevin
.toString()çıktısı"function ad() { [native code] }"dönecek şekilde override edildi - Böylece fermaw’ın bütünlük denetimi false negative veriyor ve hook’lama tespit edilemiyordu
- Hook’lanmış işlevin
-
HTMLMediaElement.prototype.play hook’lama
window.asveya belirli sınıf adlarını aramak yerine,HTMLMediaElement.prototype.playüzerinden genel amaçlı bir yaklaşım benimsendi- Oynatıcı nesnesinin adı veya closure derinliği ne olursa olsun,
.play()çağrıldığı anda ses öğesi otomatik olarak yakalanıyordu - Mobil cihazlarda genellikle tek bir oynatıcı aktif olduğundan, çok sayıda
.play()çağrısıyla tersine mühendisliği zorlaştırmak kolay değildi
-
Object.defineProperty ile kalıcı sabitleme
window.Audio, ele geçirilmiş bir constructor ile değiştirildi vewritable: false,configurable: falseolarak ayarlandı- Böylece fermaw’ın kodu özgün
Audioconstructor’ını geri yüklemeye çalışsa bile tarayıcı TypeError üretiyordu - Hook, sayfa yaşam döngüsü boyunca kalıcı kaldı
Act 3: V3.0 — property descriptor düzeyinde tam kapsamlı hook’lama
-
fermaw’ın iframe ve Shadow DOM yalıtımı denemesi
<iframe>, kendiwindow,documentve bağımsız prototip zincirine sahip olduğundan, üst pencereye yapılan hook’lar iframe içine uygulanmaz- Shadow DOM, ana belgedeki
querySelectorile iç öğelerine erişilemeyen yalıtılmış bir DOM alt ağacıdır srcObjectüzerindenMediaStream/MediaSourcenesnelerini doğrudan atayarak URL tabanlı yakalamayı atlatma yöntemi de denendi
-
V3’ün yanıtı: tarayıcı property descriptor düzeyinde hook’lama
Object.getOwnPropertyDescriptorkullanılarakHTMLMediaElement.prototypeüzerindekisrcvesrcObjectsetter’ları doğrudan hook’landı- Ses öğesi ana belgede, iframe içinde ya da web component içinde nerede olursa olsun, kaynak atandığında hook devreye giriyordu
document_startenjeksiyonu sayesinde hook’lar iframe başlatılmadan önce kuruluyordu
-
addSourceBuffer hook’lama: race condition çözümü
- Önceki sürümde
SourceBuffer.prototype.appendBufferprototip düzeyinde hook’landığında, fermaw’ın kodu hook kurulmadan önceappendBufferreferansını önbelleğe alırsa bunu aşabiliyordu - V3’te
MediaSource.prototype.addSourceBufferhook’lanarakSourceBufferörneğinin oluşturulduğu an yakalandı- Örnek döner dönmez ilgili örneğin üzerine doğrudan
appendBufferhook’u own property olarak kuruldu - Sayfa kodu örneği görmeden hook tamamlandığı için önbellek yoluyla atlatma artık yapısal olarak imkânsız hale geldi
- Örnek döner dönmez ilgili örneğin üzerine doğrudan
- Önceki sürümde
-
Capture aşaması olay dinleyicileri — son güvenlik ağı
document.addEventListeneriçindeuseCapture: trueileplayveloadedmetadataolayları izlendi- Tarayıcı olayları capture aşamasında (kök→hedef) önce yayıldığı için, HotAudio kodunun olay dinleyicilerinden her zaman önce çalışıyordu
addSourceBufferprototip hook’u +src/srcObjectproperty descriptor hook’u +play()hook’u + capture aşaması olay dinleyicileri şeklindeki dört katmanlı yapı, tarayıcıdaki tüm medya oynatma yollarını kapsadı
Otomasyon: yüksek hızlı indirme süreci
- Yakalanan ses öğesi sessize alınıp
playbackRate16x olarak ayarlanıyor ve baştan oynatılıyor - Tarayıcı, oynatma konumunun önündeki tamponu doldurmak için hızla fetch → çözme →
SourceBufferaktarımı döngüsünü tekrar ediyor; tüm parçalar hook’lanmışappendBufferüzerinden toplanıyor - Chrome, oynatma hızını 16x ile sınırlandırıyor (HTML standardında açık bir üst sınır yok, ancak Chromium uygulamasında kısıt var)
- fermaw, ani trafik artışına karşı throttling uyguluyor (yüzlerce KB/s → yaklaşık 50 KB/s); yine de bu, gerçek zamanlı dinlemeye kıyasla birkaç kat daha hızlı
- Daha sert sınırlama normal kullanıcıların akışında da kesintiye yol açacağından pratik değil
-
Uyarlamalı hız kontrolü
- V3’e eklenen bu özellikte
bufferedzaman aralıkları izlenerek tampon durumuna göre oynatma hızı dinamik biçimde ayarlanıyor- Tampon boşluğu 15 saniyeden fazlaysa hız artırılıyor, 3 saniyeden azsa düşürülüyor
- Yavaş bağlantılarda tarayıcının takılması ve
endedolayının hiç gelmemesi önleniyor
- V3’e eklenen bu özellikte
-
Nihai dosya oluşturma
- Oynatma tamamlandığında (
endedolayı veyacurrentTimedeğerinindurationa yaklaşması), toplanan parçalarBlobiçinde birleştirilip.m4aolarak indiriliyor - Tampon sınırlarında eksik parçalar nedeniyle sessizlik dolgusu artifaktları oluşabiliyor; bunlar
ffmpegile sonradan temizlenebiliyor
- Oynatma tamamlandığında (
V3’ün spoof() işlevi: daha gelişmiş sahtecilik
- V2’deki
mockToString, native code dizesini hardcode ederek döndürüyordu; ancak tarayıcıya/platforma göre[native code]dizesinin boşlukları ve biçimi küçük farklılıklar gösterebiliyordu - V3’teki
spoof(), hook kurulmadan önce özgün işlevden gerçek native code dizesini yakalayıp aynen döndürerek kusursuz taklit sağladı - Bunun için script başında önbelleğe alınmış
Function.prototype.callveFunction.prototype.toStringreferansları_call.call(_toString, original)biçiminde kullanıldı- Böylece daha sonra başka kodlar
.toStringüzerinde oynasa bile etkilenmeyen bir yapı elde edildi
- Böylece daha sonra başka kodlar
DRM’in yapısal sınırları ve etik değerlendirme
- Tüm DRM tarihi, "kilitli kutuyu verip anahtarı da aynı anda teslim etme" sorununun tekrarından ibaret
- 1999’da CSS şifrelemeli ilk DVD’nin kırılmasından bu yana film ve müzik endüstrisi bu mücadelede sürekli kaybetti
- En sofistike oyun DRM’lerinden Denuvo bile büyük oyunların çoğunda çıkıştan sonraki haftalar içinde kırıldı
- Bir dönem ünlü kırıcı Empress’in emekliliği sonrası tempo yavaşlamış olsa da, hypervisor tarzı exploit’lerin ortaya çıkmasıyla kırma faaliyeti yeniden hızlandı
- İçerik ve çözme anahtarı aynı istemci makinede bulunduğu sürece, yeterli motivasyona ve araçlara sahip kullanıcıların veriyi yakalaması kaçınılmazdır
Sonuç: JavaScript DRM, "sofistike bir sürtünme"dir; gerçek DRM değildir
- HotAudio’nun DRM’i, fermaw’ın yetersizliğinden değil, JavaScript tabanlı DRM’in ulaşabileceği en iyi noktayı temsil etmesinden dolayı bu kadar ileri gidebildi
- İstemci tarafı çözme, parça aktarımı ve aktif anti-tamper kontrollerinin tümü uygulanmıştı; tarayıcı uzantılarını bilmeyen çoğu kullanıcı için bu fiilen tam engel anlamına geliyordu
- Ancak buna "DRM" demek, donanım TEE tabanlı gerçek DRM ile aynı beklentiyi yaratıyor ve sorun da burada başlıyor
- ASMR içerik üreticilerinin sadık hayranları, çevrimdışı kopya isteyecek kadar bağlı bir kitle; Patreon benzeri ücretli bir kanal sunulursa isteyerek satın alma ihtimali yüksek
- İçerik üreticilerinin bir tür koruma istemesi anlaşılır; ancak bunu JavaScript ile yapmak yapısal olarak uygunsuz bir yaklaşım
4 yorum
Birbirlerine karşı gerçekten çok eğlenceli bir kapışma olmuştur.
Benim de eskiden API yanıtları bir anda şifreli gelmeye başlamıştı; “madem şifreli değer geliyor, istemci bir yerde bunu çözüyor olmalı” diye düşünüp bundle edilmiş JavaScript kodunun tamamını olduğu gibi kopyalamış, çözme kodunun önüne bir satır
console.logekleyip aynen geliştirici konsoluna yapıştırmıştım. Beklenmedik şekilde doğrudan çalışmıştı. Neyse, şifreleme anahtarını öğrenince sonrası kolay oldu. API’nin başka bir yanıt alanından anahtarı alıp kullanıyormuş hahaNSFW (Not Safe For Work) ASMR ise..
Yetişkin bir sitenin hacklenmesi hikâyesini teknik açıdan oldukça derinlemesine anlatmışlar -.-;
Demek ki teknolojideki ilerleme yine hep yetişkin tarafında gerçekleşiyor...?
Düşününce, sese DRM uygulamak... gerçekten zor değil mi?
Karmaşık bir hack yapmak yerine, sesi sadece sanal bir kablo üzerinden yönlendirmek bile bir şekilde işe yarayacakmış gibi geliyor
Ama yine de karşılıklı atışma gerçekten çok eğlenceliymiş hahaha Yapay zekanın asla aklına gelmeyecek türden kurnaz numaralar düşündükleri görülüyor.