JavaScript DRM의 허상: HotAudio 복사 보호를 3라운드 만에 무력화한 과정
(therantydev.com)- 브라우저에서 실행되는 JavaScript 기반 DRM은 복호화된 오디오 데이터가 결국 JavaScript 접근 가능한 영역을 통과해야 하므로 근본적으로 우회 가능함
- HotAudio는 NSFW ASMR 오디오 호스팅 플랫폼으로, MediaSource Extensions API를 활용한 자체 암호화·청크 전송 방식의 복사 보호를 구현
- 개발자의 반복적인 패치(전역 변수 제거, 해시 검증,
.toString()무결성 검사, iframe/Shadow DOM 격리)에 대해 공격자가 매번 프로토타입 후킹과 위장 기법으로 대응하는 3단계 공방 기록 - 실질적인 DRM은 Trusted Execution Environment(TEE) 기반의 하드웨어 보호(Widevine, FairPlay 등)가 필요하나, 소규모 플랫폼은 라이선스 비용과 인프라 문제로 접근 불가
- JavaScript DRM은 일반 사용자에게는 유효한 마찰(friction) 역할을 하지만, 숙련된 공격자를 막을 수 없으므로 "DRM"이라 부르기엔 기대치와 현실 사이에 큰 괴리 존재
배경: HotAudio와 JavaScript DRM의 태생적 한계
- HotAudio는 NSFW ASMR 오디오 호스팅 사이트로, 크리에이터를 위한 DRM 보호 기능을 제공한다고 주장하는 플랫폼
- 기존 Soundgasm, Mega 등의 호스팅 서비스가 ToS 강화로 제한되면서 대안 플랫폼으로 등장
- 개발자 fermaw가 Reddit에서 DRM 구현을 "재미있었다"고 언급한 것이 분석의 시작점
- JavaScript 코드는 본질적으로 "userland" 영역에 존재하며, 사용자가 접근·수정 가능한 코드를 배포하는 구조
- 아무리 정교한 키, nonce, 암호화 파일 포맷을 사용해도 결국 JavaScript 복호화 로직을 거친 데이터는 평문 상태로 브라우저 오디오 엔진에 전달되어야 함
Trusted Execution Environment(TEE)의 역할
- Microsoft 정의에 따르면 TEE는 "암호화로 보호된 CPU와 메모리의 격리 영역"으로, 외부 코드가 내부 데이터를 읽거나 변조할 수 없는 구조
- TEE는 하드웨어 기반 보안 영역(ARM TrustZone, Intel SGX 등)이며, 그 위에서 Content Decryption Module(CDM) 인 Widevine, FairPlay, PlayReady가 동작
- 이들 CDM은 암호화 키와 복호화된 미디어 버퍼가 호스트 OS에 노출되지 않도록 보장
- Widevine 라이선스 취득에는 Google과의 라이선스 계약, 네이티브 바이너리 통합, 인프라, 법적 절차, 상당한 비용이 필요
- 소규모 NSFW 오디오 플랫폼이 Widevine 라이선스를 확보하는 것은 현실적으로 불가능
HotAudio의 구현 방식과 "PCM 경계"
- HotAudio는 오디오를 암호화된 형태로 전송하고, MediaSource Extensions(MSE) API를 통해 청크 단위로 복호화·재생하는 JavaScript 기반 커스텀 복호화 방식 채택
- 이 방식은 일반 사용자의 우클릭 저장이나 네트워크 탭에서의 직접 다운로드를 차단하는 데는 효과적
- PCM(Pulse-Code Modulation) 은 스피커로 전달되는 최종 비압축 디지털 오디오 포맷으로, 모든 오디오 파이프라인의 종착점
- 실제 공격에서는 PCM까지 추적할 필요 없이, JavaScript가 접근 가능한 마지막 지점인
SourceBuffer.appendBuffer()메서드가 핵심 공격 대상 -
appendBuffer가 호출되는 시점에 데이터는 이미 JavaScript에 의해 복호화된 상태이며, 브라우저의 AAC/Opus 디코더는 HotAudio의 독자 암호화를 이해하지 못하므로 표준 코덱 형태의 복호화된 데이터만 수용 - 복호화 완료와 브라우저 미디어 엔진 전달 사이의 순간이 바로 인터셉트 가능한 "골든 모먼트"
Act 1: V1.0 — 전역 변수 노출과 프로토타입 후킹
- HotAudio 플레이어가
window.as라는 전역 변수로 오디오 소스 객체를 노출하고 있었음 - V1 확장 프로그램은 HotAudio가 항상 전송하는
nozzle.js파일을 네트워크 요청 단계에서 가로채 수정된 코드를 주입 -
SourceBuffer.prototype.appendBuffer를 몽키패치하여 복호화된 청크를 배열에 저장하면서 원래 함수도 정상 호출 -
window.as.el을 음소거하고 재생 속도를 16배(브라우저 최대치)로 설정하여 빠르게 전체 오디오를 버퍼링한 후,ended이벤트 발생 시Blob으로 결합해.m4a파일로 다운로드 - 브라우저 확장 API를 활용한 클라이언트 사이드 중간자 공격(MITM) 으로, HotAudio 서버는 변조 사실을 인지할 수 없음
-
fermaw의 첫 번째 대응
- 공개 릴리스 약 2주 후 fermaw가 패치 적용
-
window.as전역 변수 노출을 제거하고 초기화 코드를 클로저로 감싸 외부 접근 차단 -
nozzle.js에 대한 해시 검증 체크 도입(SRI, 커스텀 자체 해싱, 서버 사이드 nonce 시스템 중 하나로 추정)- 수정된 파일이 정규 해시와 불일치하면 플레이어가 초기화되지 않는 구조
Act 2: V2.0 — 위장 기법과 범용 후킹
-
fermaw의 인메모리 방어
- JavaScript에서 네이티브 함수에
.toString()을 호출하면"function appendBuffer() { [native code] }"를 반환하지만, 몽키패치된 함수는 실제 소스 코드를 반환하는 특성 활용 - fermaw는
SourceBuffer.prototype.appendBuffer.toString()에'[native code]'가 포함되지 않으면 재생을 거부하는 무결성 검사 추가 - 플레이어 초기화 과정도 난독화하여
AudioSource클래스를 폴링 루프로 찾기 어렵게 변경
- JavaScript에서 네이티브 함수에
-
mockToString — 무결성 검사를 속이는 위장 함수
- 후킹된 함수의
.toString()이"function 이름() { [native code] }"를 반환하도록 오버라이드 - fermaw의 무결성 검사가 false negative를 반환하게 만들어, 후킹 여부를 탐지 불가능하게 만듦
- 후킹된 함수의
-
HTMLMediaElement.prototype.play 후킹
-
window.as나 특정 클래스명을 찾는 대신,HTMLMediaElement.prototype.play를 후킹하는 범용 접근 채택 - 플레이어 객체의 이름이나 클로저 깊이에 관계없이
.play()호출 시점에 오디오 엘리먼트를 자동 포착 - 모바일 기기는 일반적으로 하나의 플레이어만 활성화하므로, 다수의
.play()로 역분석을 방해하기 어려움
-
-
Object.defineProperty를 통한 영구 고정
-
window.Audio를 하이재킹한 생성자로 교체한 뒤writable: false,configurable: false로 설정 - fermaw의 코드가 원래
Audio생성자를 복원하려 해도 브라우저가 TypeError를 발생시키는 구조 - 후킹이 페이지 수명 동안 영구적으로 유지
-
Act 3: V3.0 — 프로퍼티 디스크립터 레벨의 전면 후킹
-
fermaw의 iframe 및 Shadow DOM 격리 시도
-
<iframe>은 자체window,document, 독립된 프로토타입 체인을 가지므로 부모 window의 후킹이 iframe 내부에 적용되지 않음 -
Shadow DOM은 메인 문서의
querySelector로 내부 엘리먼트를 탐색할 수 없는 격리된 DOM 서브트리 -
srcObject를 통해MediaStream/MediaSource객체를 직접 할당하여 URL 기반 인터셉트를 우회하는 방식도 시도
-
-
V3의 대응: 브라우저 프로퍼티 디스크립터 수준 후킹
-
Object.getOwnPropertyDescriptor를 사용하여HTMLMediaElement.prototype의src와srcObjectsetter를 직접 후킹- 오디오 엘리먼트가 메인 문서, iframe, 웹 컴포넌트 어디에 존재하든 소스 할당 시 후킹 발동
-
document_start주입을 통해 iframe 초기화 이전에 후킹 설치
-
-
addSourceBuffer 후킹: 레이스 컨디션 해결
- 이전 버전에서
SourceBuffer.prototype.appendBuffer를 프로토타입 수준에서 후킹할 경우, fermaw의 코드가 후킹 설치 전에appendBuffer참조를 캐시하면 우회 가능했음 - V3에서는
MediaSource.prototype.addSourceBuffer를 후킹하여SourceBuffer인스턴스 생성 시점을 가로챔- 인스턴스가 반환되는 즉시 해당 인스턴스에 직접
appendBuffer후킹을 own property로 설치 - 페이지 코드가 인스턴스를 보기 전에 후킹이 완료되므로 캐시 우회가 원천적으로 불가능
- 인스턴스가 반환되는 즉시 해당 인스턴스에 직접
- 이전 버전에서
-
캡처 단계 이벤트 리스너 — 최후의 안전망
-
document.addEventListener에서useCapture: true(캡처 단계)로play,loadedmetadata이벤트 감시 - 브라우저 이벤트는 캡처 단계(루트→타겟)에서 먼저 전파되므로, HotAudio 코드의 이벤트 리스너보다 항상 먼저 실행
-
addSourceBuffer프로토타입 후킹 +src/srcObject프로퍼티 디스크립터 후킹 +play()후킹 + 캡처 단계 이벤트 리스너의 4중 레이어로 브라우저의 모든 미디어 재생 경로를 커버
-
자동화: 고속 다운로드 프로세스
- 포착된 오디오 엘리먼트를 음소거하고
playbackRate를 16배로 설정 후 처음부터 재생 - 브라우저가 재생 위치 앞의 버퍼를 채우기 위해 빠르게 fetch→복호화→
SourceBuffer전달을 반복하고, 모든 청크가 후킹된appendBuffer를 통해 수집됨 - Chrome은 재생 속도를 16배로 제한(HTML 스펙에 명시된 상한은 없으나 Chromium 구현 제약)
- fermaw는 버스트 트래픽에 대한 스로틀링(수백 KB/s → 약 50 KB/s)을 적용하나, 실시간 청취 대비 여전히 수배 빠른 속도
- 더 심한 제한은 정상 사용자의 스트리밍에도 끊김을 유발하므로 현실적으로 어려움
-
적응형 속도 제어
- V3에서 추가된 기능으로,
buffered타임 레인지를 모니터링하여 버퍼 상태에 따라 재생 속도를 동적 조절- 버퍼 여유가 15초 이상이면 속도 증가, 3초 미만이면 감속
- 느린 연결에서 브라우저 멈춤(stall)과
ended이벤트 미발생 문제 방지
- V3에서 추가된 기능으로,
-
최종 파일 생성
- 재생 완료(
ended이벤트 또는currentTime이duration에 근접) 시 수집된 청크를Blob으로 결합하여.m4a다운로드 - 버퍼 경계의 불완전한 청크로 인한 무음 패딩 아티팩트가 발생할 수 있으며,
ffmpeg후처리로 정리 가능
- 재생 완료(
V3의 spoof() 함수: 더 정교한 위장
- V2의
mockToString은 네이티브 코드 문자열을 하드코딩하여 반환했으나, 브라우저/플랫폼별로[native code]문자열의 공백·포맷이 미세하게 다를 수 있는 취약점 존재 - V3의
spoof()는 후킹 전 원본 함수에서 실제 네이티브 코드 문자열을 캡처한 뒤 그대로 반환하는 방식으로 완벽한 위조 달성 -
_call.call(_toString, original)형태로 스크립트 시작 시 캐시해둔Function.prototype.call과Function.prototype.toString참조를 사용- 이후 다른 코드에 의해
.toString이 변조되더라도 영향을 받지 않는 구조
- 이후 다른 코드에 의해
DRM의 본질적 한계와 윤리적 고찰
- DRM 역사 전체가 "잠긴 상자를 주면서 동시에 열쇠를 건네는" 문제의 반복
- 1999년 최초의 CSS 암호화 DVD 크래킹 이후, 영화·음악 산업은 이 싸움에서 계속 패배
- 가장 정교한 게임 DRM인 Denuvo도 대부분의 주요 게임에서 출시 수주 내에 크래킹됨
- 한때 유명 크래커 Empress 은퇴 후 크래킹 속도가 둔화되었으나, 하이퍼바이저 스타일 익스플로잇 등장으로 다시 크래킹이 활발해진 상황
- 콘텐츠와 복호화 키가 모두 클라이언트 머신에 존재하는 한, 충분한 동기와 도구를 가진 사용자의 인터셉트는 불가피
결론: JavaScript DRM은 "정교한 마찰"이지 진정한 DRM이 아님
- HotAudio의 DRM은 fermaw의 능력 부족 때문이 아니라, JavaScript 기반 DRM이 도달할 수 있는 최선
- 클라이언트 사이드 복호화, 청크 전송, 능동적 안티탬퍼 체크를 모두 구현했으며, 브라우저 확장을 모르는 대다수 사용자에게는 완전한 차단 효과
- 그러나 이를 "DRM"이라 부르면 하드웨어 TEE 기반의 진정한 DRM과 동일한 기대치를 설정하게 되어 문제
- ASMR 크리에이터의 열성 팬은 오프라인 복사본을 원할 정도로 헌신적이며, Patreon 같은 유료 채널이 제공되면 기꺼이 구매할 가능성이 있는 층
- 어떤 형태의 보호든 콘텐츠 제작자가 필요로 하는 것은 이해할 수 있으나, JavaScript로 구현하는 것은 근본적으로 부적합한 접근
NSFW (Not Safe For Work) ASMR 이면..
성인 사이트 해킹한 이야기를 아주 기술적으로 딥하게 풀어쓴거네요 ㅡ.ㅡ;
역시 기술의 진보는 모두 성인쪽에서 이뤄집니다...?
생각해보니 오디오에 drm을 건다는 건... 정말 어렵지 않나요?
복잡한 해킹을 하는 게 아니라 오디오를 가상 케이블로만 돌려도 뭔가 될 거 같은 느낌이에요