- 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.as adlı bir global değişken olarak dışarı açıyordu
- V1 eklentisi, HotAudio’nun her zaman gönderdiği
nozzle.js dosyası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 edildi
window.as.el sessize alınıp oynatma hızı 16x’e (tarayıcı üst sınırı) getirildi; böylece tüm ses hızla tamponlandı ve ended olayı geldiğinde Blob olarak birleştirilip .m4a dosyası 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.as global değişkenini kaldırdı ve başlatma kodunu closure içine alarak dış erişimi kapattı
nozzle.js iç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
AudioSource sınıfını polling döngüsüyle bulmak zorlaştırıldı
-
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
-
HTMLMediaElement.prototype.play hook’lama
window.as veya 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 ve writable: false, configurable: false olarak ayarlandı
- Böylece fermaw’ın kodu özgün
Audio constructor’ı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>, kendi window, document ve bağımsız prototip zincirine sahip olduğundan, üst pencereye yapılan hook’lar iframe içine uygulanmaz
- Shadow DOM, ana belgedeki
querySelector ile iç öğelerine erişilemeyen yalıtılmış bir DOM alt ağacıdır
srcObject üzerinden MediaStream/MediaSource nesnelerini doğrudan atayarak URL tabanlı yakalamayı atlatma yöntemi de denendi
-
V3’ün yanıtı: tarayıcı property descriptor düzeyinde hook’lama
Object.getOwnPropertyDescriptor kullanılarak HTMLMediaElement.prototype üzerindeki src ve srcObject setter’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_start enjeksiyonu sayesinde hook’lar iframe başlatılmadan önce kuruluyordu
-
addSourceBuffer hook’lama: race condition çözümü
- Önceki sürümde
SourceBuffer.prototype.appendBuffer prototip düzeyinde hook’landığında, fermaw’ın kodu hook kurulmadan önce appendBuffer referansını önbelleğe alırsa bunu aşabiliyordu
- V3’te
MediaSource.prototype.addSourceBuffer hook’lanarak SourceBuffer örneğinin oluşturulduğu an yakalandı
- Örnek döner dönmez ilgili örneğin üzerine doğrudan
appendBuffer hook’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
-
Capture aşaması olay dinleyicileri — son güvenlik ağı
document.addEventListener içinde useCapture: true ile play ve loadedmetadata olayları 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
addSourceBuffer prototip hook’u + src/srcObject property 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
playbackRate 16x olarak ayarlanıyor ve baştan oynatılıyor
- Tarayıcı, oynatma konumunun önündeki tamponu doldurmak için hızla fetch → çözme →
SourceBuffer aktarı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
buffered zaman 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
ended olayının hiç gelmemesi önleniyor
-
Nihai dosya oluşturma
- Oynatma tamamlandığında (
ended olayı veya currentTime değerinin durationa yaklaşması), toplanan parçalar Blob içinde birleştirilip .m4a olarak indiriliyor
- Tampon sınırlarında eksik parçalar nedeniyle sessizlik dolgusu artifaktları oluşabiliyor; bunlar
ffmpeg ile sonradan temizlenebiliyor
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.call ve Function.prototype.toString referansları _call.call(_toString, original) biçiminde kullanıldı
- Böylece daha sonra başka kodlar
.toString üzerinde oynasa bile etkilenmeyen bir yapı elde edildi
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
> JavaScript'te yerel bir fonksiyonda
.toString()çağrıldığındafunction appendBuffer() { [native code] }döner; ancak monkey patch uygulanmış fonksiyonlar gerçek kaynak kodunu döndürür ve bu özellikten yararlanılmış.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.