4P by GN⁺ 5시간전 | ★ favorite | 댓글 3개
  • 브라우저에서 실행되는 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 클래스를 폴링 루프로 찾기 어렵게 변경
  • 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.prototypesrcsrcObject setter를 직접 후킹
      • 오디오 엘리먼트가 메인 문서, 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중 레이어로 브라우저의 모든 미디어 재생 경로를 커버

자동화: 고속 다운로드 프로세스

  • 포착된 오디오 엘리먼트를 음소거하고 playbackRate16배로 설정 후 처음부터 재생
  • 브라우저가 재생 위치 앞의 버퍼를 채우기 위해 빠르게 fetch→복호화→SourceBuffer 전달을 반복하고, 모든 청크가 후킹된 appendBuffer를 통해 수집됨
  • Chrome은 재생 속도를 16배로 제한(HTML 스펙에 명시된 상한은 없으나 Chromium 구현 제약)
  • fermaw는 버스트 트래픽에 대한 스로틀링(수백 KB/s → 약 50 KB/s)을 적용하나, 실시간 청취 대비 여전히 수배 빠른 속도
    • 더 심한 제한은 정상 사용자의 스트리밍에도 끊김을 유발하므로 현실적으로 어려움
  • 적응형 속도 제어

    • V3에서 추가된 기능으로, buffered 타임 레인지를 모니터링하여 버퍼 상태에 따라 재생 속도를 동적 조절
      • 버퍼 여유가 15초 이상이면 속도 증가, 3초 미만이면 감속
      • 느린 연결에서 브라우저 멈춤(stall)과 ended 이벤트 미발생 문제 방지
  • 최종 파일 생성

    • 재생 완료(ended 이벤트 또는 currentTimeduration에 근접) 시 수집된 청크를 Blob으로 결합하여 .m4a 다운로드
    • 버퍼 경계의 불완전한 청크로 인한 무음 패딩 아티팩트가 발생할 수 있으며, ffmpeg 후처리로 정리 가능

V3의 spoof() 함수: 더 정교한 위장

  • V2의 mockToString은 네이티브 코드 문자열을 하드코딩하여 반환했으나, 브라우저/플랫폼별로 [native code] 문자열의 공백·포맷이 미세하게 다를 수 있는 취약점 존재
  • V3의 spoof()는 후킹 전 원본 함수에서 실제 네이티브 코드 문자열을 캡처한 뒤 그대로 반환하는 방식으로 완벽한 위조 달성
  • _call.call(_toString, original) 형태로 스크립트 시작 시 캐시해둔 Function.prototype.callFunction.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을 건다는 건... 정말 어렵지 않나요?
복잡한 해킹을 하는 게 아니라 오디오를 가상 케이블로만 돌려도 뭔가 될 거 같은 느낌이에요

JavaScript에서 네이티브 함수에 .toString()을 호출하면 "function appendBuffer() { [native code] }"를 반환하지만, 몽키패치된 함수는 실제 소스 코드를 반환하는 특성 활용

와 근데 되게 재밌게 주고 받았네요 ㅋㅋㅋㅋ AI는 절대 생각하지 않았을 꼼수들을 생각한 모습이 보입니다.