17 คะแนน โดย GN⁺ 2026-03-12 | 4 ความคิดเห็น | แชร์ทาง WhatsApp
  • DRM ที่อิง JavaScript ซึ่งรันอยู่ในเบราว์เซอร์ สามารถถูกเลี่ยงได้โดยพื้นฐาน เพราะท้ายที่สุดข้อมูลเสียงที่ถูกถอดรหัสแล้วต้องผ่านพื้นที่ที่ JavaScript เข้าถึงได้
  • HotAudio เป็นแพลตฟอร์มโฮสต์เสียง ASMR แบบ NSFW ที่ใช้ MediaSource Extensions API สร้างระบบป้องกันการคัดลอกแบบเข้ารหัสและส่งเป็นชังก์ของตนเอง
  • เป็นบันทึกการปะทะกัน 3 รอบที่นักพัฒนาแพตช์ซ้ำ ๆ (ลบตัวแปรโกลบอล, ตรวจสอบแฮช, ตรวจสอบความสมบูรณ์ด้วย .toString(), แยกด้วย iframe/Shadow DOM) ขณะที่ฝั่งโจมตีก็ตอบโต้ทุกครั้งด้วย การ hook โปรโตไทป์และเทคนิคอำพราง
  • DRM ที่ใช้งานได้จริงจำเป็นต้องมีการป้องกันฮาร์ดแวร์บนฐาน Trusted Execution Environment(TEE) (เช่น Widevine, FairPlay) แต่แพลตฟอร์มขนาดเล็กเข้าถึงได้ยากเพราะต้นทุนไลเซนส์และปัญหาโครงสร้างพื้นฐาน
  • JavaScript DRM มีประโยชน์ในฐานะ แรงเสียดทาน (friction) สำหรับผู้ใช้ทั่วไป แต่ไม่อาจหยุดผู้โจมตีที่ชำนาญได้ จึงมีช่องว่างอย่างมากระหว่างความคาดหวังกับความเป็นจริงหากจะเรียกมันว่า “DRM”

พื้นหลัง: HotAudio และข้อจำกัดโดยกำเนิดของ JavaScript DRM

  • HotAudio เป็นเว็บไซต์โฮสต์เสียง ASMR แบบ NSFW และอ้างว่าเป็นแพลตฟอร์มที่มี ฟีเจอร์ป้องกัน 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 ส่งเสียงในรูปแบบเข้ารหัส และใช้วิธีถอดรหัสแบบกำหนดเองบน JavaScript โดยถอดรหัสและเล่นเป็นชังก์ผ่าน MediaSource Extensions(MSE) API
  • วิธีนี้มีประสิทธิภาพในการป้องกันการกดคลิกขวาเพื่อบันทึกหรือดาวน์โหลดตรงจากแท็บเครือข่ายของผู้ใช้ทั่วไป
  • PCM(Pulse-Code Modulation) คือฟอร์แมตเสียงดิจิทัลแบบไม่บีบอัดขั้นสุดท้ายที่ถูกส่งไปยังลำโพง และเป็นปลายทางของทุก pipeline เสียง
  • แต่ในการโจมตีจริง ไม่จำเป็นต้องไล่ไปถึง PCM เพราะจุดสุดท้ายที่ JavaScript ยังเข้าถึงได้คือเมธอด SourceBuffer.appendBuffer() ซึ่งเป็นเป้าหมายหลัก
  • ตอนที่ appendBuffer ถูกเรียก ข้อมูลอยู่ในสถานะที่ JavaScript ถอดรหัสแล้ว และตัวถอดรหัส AAC/Opus ของเบราว์เซอร์ไม่เข้าใจการเข้ารหัสเฉพาะของ HotAudio จึงรับได้เฉพาะ ข้อมูลที่ถูกถอดรหัสแล้วในรูปโค้ดกมาตรฐาน
  • ช่วงเวลาระหว่างการถอดรหัสเสร็จสิ้นกับการส่งต่อให้เอนจินสื่อของเบราว์เซอร์ คือ “golden moment” ที่สามารถดักจับได้

Act 1: V1.0 — การเปิดเผยตัวแปรโกลบอลและการ hook โปรโตไทป์

  • ตัวเล่นของ HotAudio เปิดเผยออบเจ็กต์แหล่งเสียงผ่าน ตัวแปรโกลบอล ชื่อ window.as
  • ส่วนขยาย V1 ดัก ไฟล์ nozzle.js ที่ HotAudio ส่งมาเสมอในขั้นตอนคำขอเครือข่าย แล้วฉีดโค้ดที่แก้ไขเข้าไป
  • มีการ monkey patch SourceBuffer.prototype.appendBuffer เพื่อเก็บชังก์ที่ถอดรหัสแล้วลงในอาร์เรย์ พร้อมเรียกฟังก์ชันเดิมตามปกติ
  • จากนั้นปิดเสียง window.as.el ตั้งความเร็วเล่นเป็น 16 เท่า (ค่าสูงสุดของเบราว์เซอร์) เพื่อบัฟเฟอร์เสียงทั้งหมดอย่างรวดเร็ว และเมื่อเกิดอีเวนต์ ended ก็รวมเป็น Blob เพื่อดาวน์โหลดเป็นไฟล์ .m4a
  • นี่คือ การโจมตีแบบ MITM ฝั่งไคลเอนต์ โดยใช้ browser extension API ซึ่งทำให้เซิร์ฟเวอร์ของ HotAudio ไม่อาจรู้ได้ว่าถูกแก้ไข
  • การตอบโต้ครั้งแรกของ fermaw

    • fermaw ออกแพตช์ประมาณ 2 สัปดาห์หลังปล่อยสู่สาธารณะ
    • ลบ การเปิดเผยตัวแปรโกลบอล window.as และห่อโค้ดเริ่มต้นไว้ใน closure เพื่อกันการเข้าถึงจากภายนอก
    • เพิ่ม การตรวจสอบแฮช สำหรับ nozzle.js (คาดว่าเป็น SRI, แฮชแบบกำหนดเอง หรือระบบ nonce ฝั่งเซิร์ฟเวอร์)
      • ถ้าไฟล์ที่ถูกแก้ไขไม่ตรงกับแฮชมาตรฐาน ตัวเล่นจะไม่เริ่มทำงาน

Act 2: V2.0 — เทคนิคอำพรางและการ hook แบบทั่วไป

  • การป้องกันในหน่วยความจำของ fermaw

    • ใน JavaScript เมื่อเรียก .toString() กับฟังก์ชันเนทีฟ จะได้ "function appendBuffer() { [native code] }" แต่ฟังก์ชันที่ถูก monkey patch จะคืนค่าเป็น ซอร์สโค้ดจริง ซึ่งถูกนำมาใช้เป็นจุดตรวจจับ
    • fermaw เพิ่มการตรวจสอบความสมบูรณ์ว่า ถ้า SourceBuffer.prototype.appendBuffer.toString() ไม่มี '[native code]' ก็จะ ปฏิเสธการเล่น
    • ขั้นตอนเริ่มต้นตัวเล่นก็ถูกทำให้สับสนมากขึ้น จนยากจะหา class AudioSource ด้วย polling loop
  • mockToString — ฟังก์ชันอำพรางที่หลอกการตรวจสอบความสมบูรณ์

    • override ให้ .toString() ของฟังก์ชันที่ถูก hook คืนค่าเป็น "function ชื่อ() { [native code] }"
    • ทำให้การตรวจสอบของ fermaw ได้ false negative และไม่อาจตรวจพบว่าถูก hook อยู่
  • การ hook HTMLMediaElement.prototype.play

    • แทนที่จะมองหา window.as หรือชื่อคลาสเฉพาะ ก็เปลี่ยนมาใช้วิธีทั่วไปด้วยการ hook HTMLMediaElement.prototype.play
    • ไม่ว่าชื่อออบเจ็กต์ตัวเล่นหรือความลึกของ closure จะเป็นอย่างไร เมื่อมีการเรียก .play() ก็สามารถ จับออบเจ็กต์เสียงได้อัตโนมัติ
    • บนอุปกรณ์พกพามักมีตัวเล่นเดียวที่ทำงานอยู่ จึงยากจะใช้ .play() หลายครั้งเพื่อขัดขวางการวิเคราะห์ย้อนกลับ
  • การตรึงถาวรผ่าน Object.defineProperty

    • มีการแทนที่ window.Audio ด้วย constructor ที่ถูก hijack แล้วตั้ง writable: false, configurable: false
    • ต่อให้โค้ดของ fermaw พยายาม กู้คืน constructor Audio เดิม เบราว์เซอร์ก็จะโยน TypeError
    • ทำให้ hook ถูก คงอยู่ถาวร ตลอดอายุของหน้า

Act 3: V3.0 — การ hook เต็มรูปแบบในระดับ property descriptor

  • ความพยายามของ fermaw ในการแยกด้วย iframe และ Shadow DOM

    • <iframe> มี window, document และ prototype chain ที่แยกอิสระ ของตัวเอง ดังนั้น hook ของ parent window จึงไม่ส่งผลภายใน iframe
    • Shadow DOM เป็น DOM subtree แบบแยกที่ไม่สามารถสำรวจองค์ประกอบภายในได้ด้วย querySelector ของเอกสารหลัก
    • ยังมีความพยายามใช้ srcObject เพื่อกำหนดออบเจ็กต์ MediaStream/MediaSource โดยตรง และหลบเลี่ยงการดักจับแบบอิง URL
  • การตอบโต้ของ V3: hook ระดับ browser property descriptor

    • ใช้ Object.getOwnPropertyDescriptor เพื่อ hook setter ของ src และ srcObject บน HTMLMediaElement.prototype โดยตรง
      • ไม่ว่าออบเจ็กต์เสียงจะอยู่ในเอกสารหลัก, iframe หรือ web component เมื่อมีการกำหนด source ก็จะถูก hook ทันที
      • ติดตั้ง hook ก่อน iframe เริ่มต้นทำงานด้วยการฉีดตั้งแต่ document_start
  • การ hook addSourceBuffer: แก้ race condition

    • ในเวอร์ชันก่อน หาก hook SourceBuffer.prototype.appendBuffer ในระดับโปรโตไทป์ แล้วโค้ดของ fermaw แคช reference ของ appendBuffer ไว้ก่อนติดตั้ง hook ก็สามารถหลบได้
    • ใน V3 จึงเปลี่ยนไป hook MediaSource.prototype.addSourceBuffer เพื่อดัก จังหวะสร้างอินสแตนซ์ SourceBuffer
      • ทันทีที่อินสแตนซ์ถูกคืนค่า ก็จะติดตั้ง hook appendBuffer ลงบนอินสแตนซ์นั้นโดยตรงเป็น own property
      • เพราะ hook เสร็จก่อนที่โค้ดของหน้าจะเห็นอินสแตนซ์ จึง ไม่สามารถหลบด้วยการแคชได้โดยสิ้นเชิง
  • ตัวรับฟังอีเวนต์ใน capture phase — ตาข่ายนิรภัยชั้นสุดท้าย

    • ใช้ document.addEventListener พร้อม useCapture: true เพื่อตรวจจับอีเวนต์ play, loadedmetadata ใน capture phase
    • อีเวนต์ของเบราว์เซอร์จะแพร่จาก capture phase (root→target) ก่อนเสมอ จึงทำงาน ก่อน event listener ของโค้ด HotAudio ทุกครั้ง
    • ด้วย 4 ชั้น ได้แก่ การ hook โปรโตไทป์ addSourceBuffer + การ hook property descriptor ของ src/srcObject + การ hook play() + ตัวรับฟังอีเวนต์ใน capture phase จึงครอบคลุมทุกเส้นทางการเล่นสื่อของเบราว์เซอร์

ระบบอัตโนมัติ: กระบวนการดาวน์โหลดความเร็วสูง

  • ปิดเสียงออบเจ็กต์เสียงที่จับได้ ตั้ง playbackRate เป็น 16 เท่า แล้วเล่นจากต้นจนจบ
  • เบราว์เซอร์จะเร่งวนลูป fetch→ถอดรหัส→ส่งเข้า SourceBuffer เพื่อเติมบัฟเฟอร์ด้านหน้าตำแหน่งเล่น และทุกชังก์ก็จะถูกเก็บผ่าน appendBuffer ที่ถูก hook
  • Chrome จำกัดความเร็วเล่นไว้ที่ 16 เท่า (แม้ HTML spec จะไม่ได้กำหนดเพดานไว้ แต่เป็นข้อจำกัดของ Chromium)
  • fermaw ใช้ throttling กับทราฟฟิกแบบ burst (จากหลายร้อย KB/s เหลือราว 50 KB/s) แต่ก็ยังเร็วกว่าการฟังแบบเรียลไทม์หลายเท่า
    • การจำกัดที่แรงกว่านี้ทำให้สตรีมของผู้ใช้ปกติกระตุก จึงทำได้ยากในทางปฏิบัติ
  • การควบคุมความเร็วแบบปรับตัว

    • เป็นฟีเจอร์ที่เพิ่มใน V3 โดยตรวจสอบช่วงเวลา buffered และ ปรับความเร็วเล่นแบบไดนามิกตามสถานะบัฟเฟอร์
      • ถ้ามีบัฟเฟอร์เผื่อเกิน 15 วินาทีจะเพิ่มความเร็ว และถ้าน้อยกว่า 3 วินาทีจะลดความเร็ว
      • ป้องกันปัญหาเบราว์เซอร์ค้าง (stall) และไม่เกิดอีเวนต์ ended บนการเชื่อมต่อช้า
  • การสร้างไฟล์สุดท้าย

    • เมื่อเล่นจบ (ended หรือ currentTime เข้าใกล้ duration) จะรวมชังก์ที่เก็บไว้เป็น Blob แล้วดาวน์โหลดเป็น .m4a
    • อาจเกิดอาร์ติแฟกต์เป็น การเติมความเงียบ จากชังก์ที่ไม่สมบูรณ์ตรงขอบบัฟเฟอร์ ซึ่งทำความสะอาดต่อได้ด้วย ffmpeg

ฟังก์ชัน spoof() ของ V3: การอำพรางที่แนบเนียนยิ่งขึ้น

  • mockToString ใน V2 คืนสตริง native code แบบ ฮาร์ดโค้ด แต่มีจุดอ่อนตรงที่รูปแบบช่องว่างหรือการจัดวางของสตริง [native code] อาจต่างกันเล็กน้อยในแต่ละเบราว์เซอร์หรือแพลตฟอร์ม
  • spoof() ใน V3 จึงเปลี่ยนมาจับ สตริง native code จริงจากฟังก์ชันต้นฉบับก่อนถูก hook แล้วคืนค่านั้นตรง ๆ ทำให้ปลอมได้สมบูรณ์
  • ใช้อ้างอิง Function.prototype.call และ Function.prototype.toString ที่แคชไว้ตั้งแต่เริ่มสคริปต์ในรูป _call.call(_toString, original)
    • ดังนั้นต่อให้ .toString ถูกโค้ดอื่นแก้ไขภายหลัง ก็ ไม่ได้รับผลกระทบ

ข้อจำกัดโดยเนื้อแท้ของ DRM และข้อพิจารณาด้านจริยธรรม

  • ประวัติศาสตร์ทั้งหมดของ DRM คือการวนซ้ำของปัญหาแบบ “ให้กล่องที่ล็อกไว้พร้อมยื่นกุญแจให้ในเวลาเดียวกัน
  • นับตั้งแต่การแคร็ก DVD ที่เข้ารหัส CSS ครั้งแรกในปี 1999 อุตสาหกรรมภาพยนตร์และเพลงก็แพ้การต่อสู้นี้มาโดยตลอด
  • แม้แต่ DRM เกมที่ซับซ้อนที่สุดอย่าง Denuvo ก็ยังถูกแคร็กในเกมใหญ่ส่วนมากภายในไม่กี่สัปดาห์หลังวางจำหน่าย
    • ช่วงหนึ่งความเร็วในการแคร็กลดลงหลัง Empress แคร็กเกอร์ชื่อดังวางมือ แต่เมื่อมี exploit สไตล์ไฮเปอร์ไวเซอร์ ปรากฏขึ้น การแคร็กก็กลับมาคึกคักอีกครั้ง
  • ตราบใดที่ทั้งคอนเทนต์และคีย์ถอดรหัสยังอยู่บนเครื่องไคลเอนต์ การดักจับโดยผู้ใช้ที่มีแรงจูงใจและเครื่องมือเพียงพอย่อม หลีกเลี่ยงไม่ได้

บทสรุป: JavaScript DRM เป็นเพียง “แรงเสียดทานที่ซับซ้อน” ไม่ใช่ DRM ที่แท้จริง

  • DRM ของ HotAudio ไม่ได้สะท้อนว่า fermaw ไร้ความสามารถ แต่เป็น ขีดสูงสุดที่ JavaScript DRM ทำได้แล้ว
  • มันมีทั้งการถอดรหัสฝั่งไคลเอนต์, การส่งแบบชังก์ และการตรวจจับการแก้ไขเชิงรุกครบถ้วน ซึ่งสำหรับผู้ใช้ส่วนใหญ่ที่ไม่รู้จัก browser extension ก็ให้ผล เหมือนปิดกั้นได้สมบูรณ์
  • แต่การเรียกสิ่งนี้ว่า “DRM” ทำให้เกิด ความคาดหวังแบบเดียวกับ DRM แท้ที่อิงฮาร์ดแวร์ TEE ซึ่งเป็นปัญหา
  • แฟนตัวยงของครีเอเตอร์ ASMR มักทุ่มเทมากพอที่จะอยากได้สำเนาออฟไลน์ และหากมีช่องทางเสียเงินอย่าง Patreon ก็เป็นกลุ่มที่ น่าจะยอมจ่ายอย่างเต็มใจ
  • แม้จะเข้าใจได้ว่าผู้สร้างคอนเทนต์ต้องการรูปแบบการปกป้องบางอย่าง แต่การทำสิ่งนี้ด้วย JavaScript นั้น เป็นแนวทางที่ไม่เหมาะสมโดยพื้นฐาน

4 ความคิดเห็น

 
joyfui 2026-03-13

คงเป็นการต่อสู้เชิงเทคนิคที่สนุกกันทั้งสองฝ่ายจริง ๆ
เมื่อก่อนผมก็เคยเจอเหมือนกันว่าอยู่ ๆ API ส่งค่าที่ถูกเข้ารหัสมาให้ เลยคิดว่าถ้าฝั่งไคลเอนต์ได้รับค่าที่เข้ารหัส อย่างน้อยก็น่าจะต้องมีที่ไหนสักแห่งที่ถอดรหัสมันอยู่ ก็เลยก๊อบ JavaScript bundle ทั้งก้อนมาตรงนั้น เพิ่ม console.log หนึ่งบรรทัดไว้หน้าส่วนโค้ดถอดรหัส แล้วแปะลงใน developer console ไปตรง ๆ เลย ปรากฏว่าดันทำงานได้เฉยเลย? ยังไงก็ตาม พอรู้คีย์เข้ารหัสแล้ว หลังจากนั้นก็ง่ายเลยครับ ปรากฏว่ามันไปรับคีย์จาก response อื่นของ API มาใช้อีกที 555

 
xguru 2026-03-12

ถ้าเป็น ASMR แบบ NSFW (Not Safe For Work) ล่ะก็..
นี่คือการเล่าเรื่องการแฮ็กเว็บผู้ใหญ่แบบลงลึกมากในเชิงเทคนิคเลยนะ -.-;
สุดท้ายแล้วความก้าวหน้าทางเทคโนโลยีก็มักเกิดขึ้นจากฝั่งผู้ใหญ่ทั้งนั้น...?

 
crawler 2026-03-12

พอมาคิดดูแล้ว การใส่ DRM กับเสียงนี่... มันยากมากจริง ๆ ไม่ใช่เหรอ?
ไม่ใช่ว่าต้องแฮ็กอะไรซับซ้อน แค่ส่งเสียงผ่านสายสัญญาณเสมือนก็น่าจะทำอะไรได้แล้วเหมือนกัน

 
crawler 2026-03-12

> ใน JavaScript เมื่อเรียก .toString() กับฟังก์ชันเนทีฟ จะคืนค่าเป็น function appendBuffer() { [native code] } แต่ฟังก์ชันที่ถูก monkey patch จะคืนซอร์สโค้ดจริงออกมา ซึ่งเป็นการอาศัยคุณสมบัตินี้

แต่ก็น่าสนุกดีนะที่โต้ตอบกันไปมาแบบนั้น 55555 ดูออกเลยว่ามีการคิดลูกเล่นแยบยลที่ AI คงไม่มีทางนึกถึงแน่ ๆ