บล็อกโฆษณา YouTube บน Apple TV ด้วยการถอดรหัสและแก้ไข Protobuf
(ericdraken.com)- ทดลองตั้งแต่ pfSense, การบล็อก DNS, การทำ VPN routing, Squid, MITMProxy ไปจนถึง การแก้ไข response ของ Protobuf เพื่อบล็อกโฆษณา YouTube บน Apple TV และ iPhone ในระดับเครือข่าย
- โฆษณา YouTube ถูกส่งมาจากโดเมนเดียวกับวิดีโอปกติ จึงแยกคอนเทนต์กับโฆษณาอย่างเสถียรได้ยากด้วยแค่ การบล็อก DNS อย่าง Pi-hole หรือ pfBlockerNG
- หลังถอดรหัส TLS traffic ด้วย MITMProxy แล้ว ลบฟิลด์โฆษณาใน JSON ของ YouTube บนเว็บ และในแอป iOS ก็ค้นหาและแก้ไขโครงสร้างโฆษณาภายใน response แบบ application/x-protobuf โดยตรง
- การถอดรหัส Protobuf ทั้งหมดด้วย Python ใช้เวลาประมาณ 23 วินาทีบนเราเตอร์ pfSense แต่เมื่อใช้วิธี linear scan รอบ ๆ
/pagead/ก็ทำให้ payload ขนาด 1.8MiB ประมวลผลแบบเรียลไทม์ได้ - วิธีนี้ต้องติดตั้ง CA ที่เชื่อถือได้และต้องมี CPU ที่แรงพอ และแม้จะบล็อกโฆษณาบนอุปกรณ์ Apple ที่เชื่อมต่อกับเครือข่ายได้ แต่ภายหลังก็ทำให้เริ่มพิจารณาจ่ายเงินสมัคร YouTube Premium
เราเตอร์ pfSense และการแยกเครือข่าย
- เป้าหมายคือสร้างเราเตอร์ที่ใช้ FreeBSD และ pfSense เพื่อบล็อกโฆษณา YouTube แบบ pre-roll, mid-roll, end-roll บน Apple TV และ iPhone ทั่วทั้งเครือข่าย
- ฮาร์ดแวร์ที่ใช้คือ mini PC รุ่น J4125 ที่มีชุดคำสั่ง AES-NI, RAM DDR4, SSD mSATA และไดรฟ์ USB สำหรับติดตั้ง pfSense
- ตัวอย่างสเปกคือ RAM 32GiB และ SSD mSATA 128GB
- มองว่าพื้นที่เก็บข้อมูล 128GB มีประโยชน์สำหรับ log, ลดการสึกหรอของ SSD, packet capture และพื้นที่ edge cache
- หลังติดตั้ง pfSense ตั้งค่า LAN 1 เป็น IP แบบ static
192.168.1.3ที่อยู่นอกช่วง DHCP เดิม และเข้าเว็บพอร์ทัลผู้ดูแลระบบด้วยบัญชีadmin/pfsense - หลังตรวจดูข้อความ
AES-NI CPU Crypto: Yes (inactive)บนแดชบอร์ด pfSense แล้ว จึงเปิดใช้ AES-NI เองที่ System › Advanced › Miscellaneous - ใช้ RAM 32GiB โดยจัดสรร RAM disk ให้
/varและ/tmpไว้ค่อนข้างมาก และตั้งค่าให้สำรองข้อมูล RAM-disk ทุกชั่วโมง
การบล็อก DNS และการแยกเครือข่ายทางกายภาพ
- แทนที่จะใช้ Pi-hole เดิม ได้ติดตั้ง pfBlockerNG-devel ซึ่งเป็นแพ็กเกจของ pfSense เพื่อบล็อกโฆษณาและคอนเทนต์อันตราย รวมถึงตั้งค่าการบล็อกตามภูมิภาค
- พื้นที่ติดตั้งเพิ่มขึ้นประมาณ 20MiB
- หาก service
pfb_dnsblไม่เริ่มทำงาน หรือเห็นข้อความ[ Missing CRON task ]ให้ลองลบไฟล์เปล่า/var/run/booting
- พอร์ต Gigabit 3 พอร์ตของเราเตอร์ pfSense ถูกใช้สำหรับ การแยก LAN ทางกายภาพ แทน VLAN
- นำอุปกรณ์ที่มักโทรกลับบ้านอย่าง Alexa และ Apple TV ไปไว้ใน LAN ฮาร์ดแวร์แยกต่างหาก
- ต้องการแยก LAN สำคัญออกจากอุปกรณ์ smart device และ Wi‑Fi เพื่อปกป้องอุปกรณ์ที่ใช้ทำธุรกรรมธนาคาร ซื้อขายหุ้น และใช้ crypto wallet
- เครือข่ายสำหรับ smart device ถูกแยกเป็น
172.31.1.0/24ส่วน LAN ที่เชื่อถือมากกว่ายังคงไว้ที่192.168/16- มองว่าหากไม่มี route ระหว่างเครือข่าย ก็ช่วยลดผลกระทบบางส่วนจากกฎ iptables ที่ตั้งค่าผิดได้
- ต้องเปิดใช้ DHCP resolver บน NIC ทางกายภาพ เพื่อให้อุปกรณ์เครือข่ายใหม่ได้รับ address
- AC1200 Archer C5 ถูกเลิกใช้เพราะไม่มี AP mode, มีปัญหา remote access, stock firmware เก่า และ chipset Broadcom รองรับ OpenWRT/DD-WRT/Tomato ได้ไม่ดี
- หลังจากนั้นใช้ Nighthawk R7000 เป็น AP สำหรับ Apple/Amazon/TV และเป็น AP สำหรับ Trusted Wireless Network
- ใน Trusted Wireless Network ตัดสินใจปิด 2.4GHz และใช้เฉพาะ 5GHz
- มองว่า 5GHz ถูกผนังและคอนกรีตบังได้ง่ายกว่า จึงช่วยหลีกเลี่ยงการ snooping ระยะกลางได้ดีกว่า
บังคับให้ DNS ทั้งหมดไปที่ pfSense
- เพิ่มกฎ NAT เพื่อให้ client ทั้งหมดหลัง pfSense ใช้เซิร์ฟเวอร์ Unbound DNS ในเครื่อง
- จุดประสงค์คือไม่ให้แอปและ home assistant หลบเลี่ยงไปใช้ DNS server ของตัวเองหรือ DNS server ที่ hardcode ไว้
- เพื่อให้ pfBlockerNG แทรกแซงคำขอ DNS ได้ ต้องบล็อก DNS over TLS
- เดิมสร้างกฎ NAT ในแต่ละ interface ยกเว้น WAN ก่อนจะทำให้ง่ายขึ้นด้วย firewall alias ชื่อ
Non_WAN- redirect local DNS query ที่พอร์ต 53 ไปยัง localhost ทั้ง IPv4 และ IPv6
- ต้องปิด NAT reflection เพื่อไม่ให้อินเทอร์เน็ตภายนอกเข้าถึง local DNS server ได้
- เพิ่ม
server: log-queries: yesใน Services › DNS Resolver › Display Custom Options เพื่อ log คำขอ DNS ที่ถูกดักไว้ - ใน DNS log พบว่า Windows พยายามเข้าถึง Google Tag Manager และคำขอนั้นถูก blackhole ไปยัง IP ที่ไม่มีอยู่จริง
10.10.10.1
ทดลองเลี่ยงการกำหนดเป้าหมายโฆษณา YouTube ด้วย VPN
- เพราะโฆษณา YouTube มาจากโดเมนเดียวกับวิดีโอปกติ จึงยากที่จะเลือกบล็อกเฉพาะโฆษณาด้วยตัวบล็อกโดเมนอย่าง pfBlockerNG หรือ Pi-hole
- มองว่าการบล็อก
googleadservices.comจะมีความหมายก็ต่อเมื่อดูวิดีโอโฆษณาแล้วคลิกโฆษณานั้นภายหลัง - บนเบราว์เซอร์ uBlock Origin แทรกแซง JavaScript ได้ แต่ในแอป YouTube บน iPhone มองว่าจำกัดโฆษณาได้ยากหากไม่ jailbreak
- มองว่าการบล็อก
- แทนที่จะบล็อกโฆษณาโดยตรง ได้ทดลองทำให้อัลกอริทึมโฆษณาของ YouTube มองผู้ใช้เป็นเป้าหมายโฆษณาที่ไม่น่าสนใจนัก
- แนวคิดคือส่ง traffic ของ Apple TV ผ่าน VPN และใช้ VPN endpoint ในภูมิภาคที่มีผู้ชม YouTube น้อย
- เป้าหมายคือทำให้ผู้ใช้ดูเหมือนผู้ชายอายุ 70 ปีที่อาศัยอยู่ในอิตาลี
- ตั้งค่า WireGuard บน pfSense และนำ private key ของ NordLynx/WireGuard มาจาก Linux VM เพื่อตั้งค่า
- ระบุว่าหากใส่
1.0.0.0และ subnet mask0ตอนป้อน tunnel address ผลลัพธ์ใน UI จะแสดงเป็น0.0.0.0/0
- ระบุว่าหากใส่
- เมื่อส่ง traffic ทั้งหมดของ Apple TV ผ่าน VPN YouTube แสดงเป็นภาษาอิตาลีและจำนวนโฆษณาลดลง แต่เกิดปัญหากับ Netflix และ Amazon Prime
- ดูเหมือน CSS หรือไฟล์ font ถูกบล็อก และ thumbnail ไม่โหลด
- เตือนว่า Netflix และ Prime ทำ geofencing กับผู้ให้บริการ VPN ได้ดี
- หลังจากนั้นพยายาม route เฉพาะ FQDN ที่เกี่ยวกับ YouTube ผ่าน VPN โดยเลือก
www.youtube.com,youtube.com,googlevideo.com,accounts.google.com,googleapis.com,gstatic.comเป็นต้นเป็น candidate- หลังตั้งค่า YouTube มองว่าผู้ใช้อยู่ใน Milan ส่วน Netflix และ Prime Video มองว่าอยู่ใน Canada
- โฆษณาพบน้อยลง และเมื่อมีโฆษณาก็เป็นโฆษณาภาษาอิตาลี
ปัญหา DNS race condition และโดเมน wildcard
- หลังจากผ่านไปหนึ่งวัน พบ DNS race condition ที่ pfSense hostname alias กับ DNS cache ของไคลเอนต์มองเห็นชุด IP ของ YouTube ต่างกัน
- ค่า resolve interval เริ่มต้นของ pfSense hostname alias คือ 300 วินาที
- สังเกตพบว่า YouTube DNS TTL อยู่ที่ 1,440 วินาที
- หาก IP ที่ Alias Daemon resolve จาก FQDN ไม่ตรงกับ IP ที่ Apple TV ได้รับภายหลัง ทราฟฟิกอาจไม่ผ่านอุโมงค์ VPN
- วิธีบรรเทาคือทำให้ pfSense เพิกเฉยต่อ TTL ของปลายทาง และ cache alias entry ไว้นานขึ้น
- subdomain รูปแบบต่าง ๆ ของ
googlevideo.comจำเป็นต้องใช้การ routing แบบ wildcard แต่ NAT และ firewall rule ทำงานบนพื้นฐานของ IP และไม่สามารถจัดการ wildcard hostname ได้โดยตรง - เขียน PoC โดยใช้ Unbound Python module และ pfSense REST API เพื่อดักจับ IP ใน DNS response แล้วเพิ่มเข้าไปใน alias
VPN_wildcardsแบบไดนามิก- ตั้งค่า TTL ของ
VPN_wildcardsเป็น 1 ชั่วโมง และ capacity เป็น 500 - parse A record ด้วย
ipaddress.IPv4Addressและ AAAA record ด้วยipaddress.IPv6Address
- ตั้งค่า TTL ของ
- เมื่อตรวจสอบตอนเช้า พบว่า Unbound DNS Resolver อยู่ในสถานะ segfault และทุกครั้งที่เพิ่ม IP ต้อง reload rule ของ pfSense ทำให้ pfSense ช้ามาก
ถอดรหัส HTTPS ด้วย Squid และ MITMProxy
- เป้าหมายใหม่คือการติดตั้ง proxy คล้าย Squid และเพิ่ม fake-but-trusted CA certificate ลงในอุปกรณ์ เพื่อทำ การถอดรหัสทราฟฟิก TLS
- ติดตั้ง Squid เป็นแพ็กเกจของ pfSense และทำ SSL Filtering smoke test สำเร็จ แต่ก็เลิกใช้
- ประสิทธิภาพช้ามาก
- การตั้งค่า ACL ยุ่งยาก
- มี issue เกี่ยวกับ
https://http/* - การ update SquidGuard URL filter list ใช้เวลานานมาก
- มองว่า Squid UI ยังไม่เพียงพอ
- หลังจากนั้นเลือก MITMProxy
- มองว่ามี Python scripting และ UI และสามารถขยายให้เหมาะกับการบล็อกโฆษณา YouTube ได้
- mitmproxy 7.0.4 Linux tarball ไม่สามารถรันบน FreeBSD ได้ เนื่องจาก
ELF interpreter /lib64/ld-linux-x86-64.so.2 not foundและขาด library
- ติดตั้งในสภาพแวดล้อม FreeBSD jail ของ pfSense ด้วย
pkg install mitmproxy- แพ็กเกจเป้าหมายที่จะติดตั้งมี 50 รายการ ใช้พื้นที่เพิ่ม 206MiB และปริมาณดาวน์โหลด 33MiB
- เมื่อรัน
mitmproxyภายใน jail จะเปิด UI ขึ้นมา
- เพื่อทดลอง MITMProxy ได้ผูก virtual IP
127.0.1.1เข้ากับ localhost ใน pfSense และใช้ NAT rule ส่งต่อ[Private IPs]:8080ไปยัง127.0.1.1:8080- เมื่อตั้งค่า proxy ของโน้ตบุ๊กที่ใช้เป็นเหยื่อเป็น
192.168.20.1:8080คำขอจากเบราว์เซอร์ก็แสดงใน log ของ MITMProxy UI
- เมื่อตั้งค่า proxy ของโน้ตบุ๊กที่ใช้เป็นเหยื่อเป็น
- ไฟล์ MITMProxy CA PEM คือ
~/.mitmproxy/mitmproxy-ca-cert.pem- ให้บริการ
cert.pemด้วยเว็บเซิร์ฟเวอร์ Python 3 - MITMProxy ให้บริการ CA certificate เดียวกันผ่าน
mitm.itด้วย - เพิ่ม certificate ลงในโน้ตบุ๊กสะอาดและ iPhone
- ให้บริการ
การใช้งาน MITMProxy และการรับมือ certificate pinning
- บนเราเตอร์
mitmproxyใช้ CPU สูงแม้ขณะว่าง โดยมองว่าสาเหตุมาจากงานสร้าง TLS certificate แบบทันทีต่อคำขอ และการ logging มากเกินไปของ UI - มองว่า
mitmdumpลดภาระ CPU ได้ เพราะตัด UI และการ logging แบบสุดโต่งออก- ตอนรันใช้ตัวเลือก เช่น
--anticomp,--mode regular,--listen-port 8080,--listen-host 127.0.1.1
- ตอนรันใช้ตัวเลือก เช่น
- Certificate Pinning คือเทคนิคที่เซิร์ฟเวอร์หรือไคลเอนต์รู้ fingerprint ของ certificate ที่คาดไว้ล่วงหน้า ทำให้การปลอม certificate ของ MITMProxy ใช้ไม่ได้
- วิธีเลี่ยงคือใช้
--ignore-hostsเพื่อให้โฮสต์อย่างapple.com:443,icloud.com:443ข้าม proxy ไป
- วิธีเลี่ยงคือใช้
- patch
next_layer.pyของ MITMProxy 7.0.4 เพื่อให้--allowed-hostsใน Transparent Proxy Mode ทำงานตาม SNI ได้ดีขึ้น- เดิมมองว่าหลายกรณีใช้เฉพาะ server IP ในการ match
- patch เพิ่ม
server.sniเข้าไปเป็น hostname candidate นอกเหนือจากserver.address[0]
- หลัง patch ระบุว่าสามารถดักจับบางโฮสต์ได้อย่างเสถียร และปล่อยโฮสต์ที่เหลือให้ผ่านไปได้
ลบโฆษณา JSON ของ YouTube บนเว็บ
- ใน smoke test ของ MITMProxy บล็อก URL โฆษณาและติดตามของ YouTube ด้วยสคริปต์ขนาดเล็ก
- บน
youtube.comบล็อก/pagead/,/log_event?,/stats/ads,/stats/qoe?,/ptracking?,/generate_204,el=adunit,adformat=,/activeview?เป็นต้น - สำหรับ
google.comและgoogle.caบล็อก/pagead/
- บน
- ในการทดสอบช่วงแรก คำขอที่อยู่ในเป้าหมายการบล็อกถูกบล็อกจริงแม้ใน DevTools Network panel
- รายการ
(failed)เกิดจากสคริปต์ - มองว่าความล้มเหลว
502เป็นผลจาก pfBlockerNG จัดการคำขอแบบ black-hole - ปิดใช้งาน HTTP/2 เพื่อไม่ให้คำขอถัดไปใน channel เดียวกันผ่านไปได้
- รายการ
- การบล็อก URL อย่างเดียวไม่ได้ทำให้โฆษณาหายไปทั้งหมด จึงตรวจสอบ YouTube HTML·JavaScript และ filter ของ uBlock Origin เพื่อไล่ดูความเป็นไปได้ที่ข้อมูลโฆษณาอยู่ใน body ของ JSON response
- ใน JSON response ที่ MITMProxy capture ได้ พบข้อมูลโฆษณาและการติดตามในโครงสร้าง
playerAdsและplaybackTrackingplayerAdsมีplayerLegacyDesktopWatchAdsRenderer,playerAdParams,gutParams.tag,showCompanion,showInstream,useGutเป็นต้นplaybackTrackingมีvideostatsPlaybackUrl,ptrackingUrl,qoeUrl,atrUrlเป็นต้นyoutubeRemarketingUrlอยู่ในรูปแบบwww.youtube.com/pagead/viewthroughconversion/...
- บน YouTube เว็บ สามารถลบข้อมูลโฆษณาออกจาก JSON payload เพื่อกำจัดโฆษณาบนเว็บผ่านเราเตอร์ได้
การวิเคราะห์ iOS YouTube และ Protobuf
- แอป YouTube บน iOS ใช้ข้อมูลในรูปแบบ Protocol Buffer(Protobuf) แทน JSON สำหรับการเรียก API ที่คล้ายกับเวอร์ชันเว็บ
- ใน Protobuf คีย์เป็นตัวเลขและอาจเปลี่ยนได้ จึงหา section โฆษณาด้วยวิธีแบบ JSONPath ได้ยาก
- พบสตริงโฆษณาอย่าง “Telus”, “Samsung TV”, “Boxing Week”, “Buy now” ใน payload
- ทราฟฟิกเครือข่ายของ iOS YouTube แตกต่างจากทราฟฟิกเว็บ
- ในเวอร์ชันเว็บ สามารถประมาณผู้เข้าข่ายเป็นโฆษณาได้ระดับหนึ่งจากพารามิเตอร์
rangeหรือclenของ chunk วิดีโอ - โปรโตคอล iOS ไม่ใช้พารามิเตอร์ query
rangeหรือเฮดเดอร์Rangeแต่ใช้ตัวนับอย่าง&nr=2,&nr=3
- ในเวอร์ชันเว็บ สามารถประมาณผู้เข้าข่ายเป็นโฆษณาได้ระดับหนึ่งจากพารามิเตอร์
- เมื่อถอดรหัส response Protobuf แล้ววิเคราะห์แบบออฟไลน์ พบรายการอย่าง
has_unlimited_entitlement: False,has_premium_lite_entitlement: False- วิธีเปลี่ยนค่าเหล่านี้รู้สึกเหมือนเป็น “cheating” จึงกลับไปใช้แนวทาง heuristic อีกครั้ง
- การทดลองบล็อก URL โฆษณาทำให้แอป iOS เกิดลูปไม่สิ้นสุด, UI ผิดพลาด และแครช
- การคืน
200พร้อม body ว่าง,404,503, response body ที่ถูกตัดทอน, หรือการทำให้บางส่วนของ ad video เป็น null ทำให้แอปช้าลงหรืออยู่ในสภาพเสียแล้วแครช - endpoint รายงานข้อผิดพลาด
/error_204/แสดง “dev assertion failed” จึงบล็อกไว้
- การคืน
- ดูเหมือนว่าโฆษณาจะถูกลงทะเบียนไว้ใน slot ภายในวิดีโอบางตัว
- ประเภทของ slot มี pre-roll, mid-roll, end-roll, full-page และ ad pod
- หากบล็อกเฉพาะ URL โฆษณา จะเกิดข้อผิดพลาดทำนองว่า “โฆษณาที่ไม่มีอยู่ได้จอง slot ไว้” และ UI จะเข้าสู่สถานะ panic
ปัญหาประสิทธิภาพของ Protobuf และ blackboxprotobuf
- การถอดรหัส raw Protobuf ขนาดประมาณ 500KiB ให้เป็นข้อความที่มนุษย์อ่านได้ด้วย Python เพียงอย่างเดียวนั้นช้ามาก
- บนเดสก์ท็อป CPU i7-6700 ใช้เวลาประมาณ 2.06~2.11 วินาที
- บนเราเตอร์ pfSense ใช้เวลาประมาณ 22.8~24.2 วินาที
- C++
protoc --decode_rawเร็วกว่ามาก- บนเดสก์ท็อป CPU i7-6700 ใช้เวลาประมาณ 0.017~0.022 วินาที
- บนเราเตอร์ pfSense อยู่ที่ประมาณ 0.12~0.14 วินาที
- เนื่องจาก Python ไม่รองรับ raw decoding จึงเลือกวิธีสื่อสารโดยตรงกับไบนารี C++
protocผ่านsubprocess.Popen blackboxprotobufสำหรับ Burp Suite สามารถ decode raw Protobuf wire message, ฉีดค่า แล้ว re-encode ได้- แนะนำให้ใช้เวอร์ชัน Burp Suite ต้นฉบับ ไม่ใช่ fork บน PyPI
- fork บางตัวอาจทำให้เกิด stack overflow จาก deep recursion
- หากตั้งค่า
os.environ["PROTOCOL_BUFFERS_PYTHON_IMPLEMENTATION"] = "cpp"ก่อน importprotobufจะใช้ implementation C++libprotobuf.soเมื่อเป็นไปได้
protobuf_to_json(data)ของblackboxprotobufสามารถสร้าง schema.protoแบบคาดเดาที่ดีที่สุดได้ แต่ผลลัพธ์มีขนาดใหญ่มาก ซ้อนลึก และไม่สมบูรณ์แบบ- dump schema ใน Python มีความยาวประมาณ 250,000 ตัวอักษรขึ้นไป
- มองว่าเพียงพอสำหรับดึงรายละเอียดโฆษณาออกมา
ทำให้ section โฆษณาไร้ผลด้วยการเปลี่ยน 1 ไบต์
- หาก decode/edit/re-encode Protobuf Wire Format โดยไม่มี schema ต้นฉบับ encoding อาจเปลี่ยนไปได้
- สาเหตุที่ยกมาคือ การใช้ ZigZag encoding หรือไม่, การไม่สามารถจำแนกชนิดตัวเลขได้ และความไม่แน่นอนของลำดับ object field
- วิธีแก้คือใช้ backward compatibility ของ Protobuf เพื่อทำให้ section โฆษณาดูเหมือน field ที่ไม่รู้จัก
- ใช้พฤติกรรมที่ซอฟต์แวร์เก่าเพิกเฉยต่อ field ที่ไม่รู้จักเมื่ออ่าน field ใหม่
- หากเปลี่ยน 1 ไบต์ในจุดสำคัญ ทำให้ section ที่ซ้อนลึกดูเหมือนเป็นของ future schema version, Protobuf จะเพิกเฉยต่อส่วนนั้น
- ต้องหา target field key
49399797ด้วย varint tag scanning ไม่ใช่การค้นหาสตริงแบบง่าย- wire type คือ
2หมายถึง nested string/message แบบ length-delimited - target tag คำนวณได้เป็น
AA FF B8 BC 01 - เมื่อ shift out บิต wire type 3 บิต จะได้ field key
49399797กลับมา
- wire type คือ
- การค้นหาจริงเริ่มจากการหา signature ของ URL โฆษณาอย่าง
/pagead/ใน raw Protobuf bytes ก่อน แล้วถอยกลับไปแถว ๆ นั้นเพื่อหา target field tag และ field key- ตัวอย่างเป้าหมายสำหรับ intercept คือ
POST youtubei.googleapis.com:443/youtubei/v1/browse?key=... - response คือ
200,application/x-protobuf,1.87m - ใน log ตัวอย่างพบ
49399797ที่ position4465และ50195462ที่ position4477
- ตัวอย่างเป้าหมายสำหรับ intercept คือ
- ใน smoke test แบบ O(n) สแกน Protobuf data ขนาด 1.8MiB หนึ่งครั้งโดยไม่ใช้หน่วยความจำเพิ่ม แล้วการลบโฆษณาทำงานได้
- พบ target ที่ byte ลำดับที่ 30,593
- ใช้การ backtracking ประมาณ 600 byte เพื่อหา field key ที่จะทำให้ผิดรูป
- ไม่จำเป็นต้องบล็อก URL ที่มี
*.googleadservices.comหรือ/pagead/อีกต่อไป และ request ดังกล่าวจะไม่ถูกสร้างขึ้นตั้งแต่แรก
โครงสร้างแอดออน MITMProxy
- สคริปต์ PoC บันทึกเป็น
youtube.pyและรันด้วยmitmdump --listen-port 8080 --listen-host 127.0.0.1 -s "youtube.py"- ระบุ prerequisite สำหรับ FreeBSD คือ
pkg install protobuf,pkg install py38-pip,pip install jsonpath-ng
- ระบุ prerequisite สำหรับ FreeBSD คือ
- สคริปต์ประกอบด้วยคลาส
Logger,trunc,KilledError,JSONPathReplacement,ProtobufDebugParser,YouTubeAdBlocker - host regex ที่
YouTubeAdBlockerใช้เป็นเป้าหมายในการ intercept คือ\.youtube\.com|google\.(com|ca)|googleapis\.com|googleadservices\.com|googlevideo\.com- search string ของ URL โฆษณา Protobuf คือ
b"/pagead/" - search limit คือ
80_000 - target field tag คือ
50195462
- search string ของ URL โฆษณา Protobuf คือ
- rule สำหรับบล็อก request จะตรวจสอบสตริง partial URL แยกตาม host แล้ว kill flow
- สำหรับ
youtube.comมีpagead/,log_event?,stats/ads,stats/qoe?,ptracking?,generate_204,error_204,adformat=,activeview?,_ad_,ai?,sw.jsเป็นต้น - มีคอมเมนต์ที่
sw.jsว่าเป็นการปฏิเสธ service workers
- สำหรับ
- ใน JSON response จะใช้ JSONPath replacement หลายรายการ
$.responseContext.serviceTrackingParams[*].params[?(@.key == 'yt_ad')].valueถูกเปลี่ยนเป็น"0"$..adPlacementsถูกเปลี่ยนเป็น[]$..adPlacementRenderer,$..adPlacementConfig,$..playerAdParams,$..gutParamsถูกเปลี่ยนเป็น{}$..adVideoIdถูกเปลี่ยนเป็นสตริงว่าง$..showCompanion,$..showInstream,$..useGutถูกเปลี่ยนเป็นFalse
- ใน Protobuf response เมื่อ content type มีคำว่า
protobufจะสร้าง body เป็นbytearrayแล้วค้นหา/pagead/ภายใน 80,000 ไบต์แรก- สร้าง target tag bytes ด้วย
TagBytes(self.target_field_tag, WIRETYPE_LENGTH_DELIMITED)และสร้าง bytes ใหม่ของtarget_field_tag - 1 - ค้นหาย้อนกลับจนถึงตำแหน่งก่อนหน้า
/pagead/เพื่อหา target tag bytes - หากพบ จะเขียนทับ byte เหล่านั้นด้วย bytes ใหม่ แล้วแทนที่ response body ด้วย
flow.response.set_content(bytes(body))
- สร้าง target tag bytes ด้วย
- ในคอมเมนต์ระบุว่า PoC นี้บล็อกโฆษณาได้ 90%
- section อื่นก็มี field key อื่น ๆ และอาจมี ad section หลายจุดที่ต้องทำให้เสียรูป
ขอบเขตการใช้งานและข้อจำกัด
- เทคนิคนี้ถูกสรุปว่าเป็น highly specialized technique สำหรับบล็อกโฆษณา YouTube บนอุปกรณ์ Apple หรือ tracker traffic อย่าง Instagram, WhatsApp, Facebook
- มองว่าความต้องการ CPU สำหรับ decrypt/re-encrypt HTTPS traffic นั้นเกินกว่าที่ Raspberry Pi จะรับไหวอย่างมาก
- หลัง jailbreak Apple TV แล้วเพิ่ม pfSense root certificate จะทำให้ pfSense gateway ถอดรหัสทราฟฟิกของ Apple TV และตรวจสอบ hostname โฆษณาใน request header เพื่อบล็อกได้
- อย่างไรก็ตาม ยังใช้กับโฆษณาบน iPhone ไม่ได้ และการ jailbreak iPhone ทำได้ยากกว่า อีกทั้ง banking app อาจตรวจพบแล้วไม่ทำงาน
- ประเมินว่าการ jailbreak เองเป็นวิธีที่สุดโต่งเกินไป
- หากทำ fake trusted CA ได้ ก็สามารถถอดรหัส TLS packet เป็นข้อความล้วนและใช้ URL blocking ได้
- ยกตัวอย่าง URL ที่บล็อกเป็นเส้นทาง
/pagead/viewthroughconversion/...และ/pagead/conversion/...ของ YouTube
- ยกตัวอย่าง URL ที่บล็อกเป็นเส้นทาง
- สุดท้าย ตั้งค่า hardware router ตั้งแต่ต้น แบ่ง LAN เป็นโซนที่เชื่อถือและไม่เชื่อถือ เพิ่ม DNS ad blocking และ transparent MITM proxy แล้วบล็อกโฆษณา YouTube บนอุปกรณ์ Apple ที่เชื่อมต่อเครือข่ายได้ด้วยประสิทธิภาพดี
YouTube Premium และการสนับสนุนครีเอเตอร์
- หลังจากบล็อกโฆษณา YouTube อยู่หลายเดือน ก็เริ่มจ่ายเงิน YouTube Premium เพราะอยากสนับสนุนผู้สร้างคอนเทนต์
- ระบุเงื่อนไขไว้ว่า “แค่ทำได้ ไม่ได้แปลว่าควรทำ”
- ราคา YouTube Premium ถูกกล่าวถึงว่าเพิ่มจาก CAD
$9.99/moเป็น$11.99/moและรวมภาษีแล้วประมาณ$13.43/mo - ในการทดลองดูโฆษณา ใช้แล็ปท็อปสะอาดและโหมด private browsing ดู YouTube เป็นครั้งคราวตลอดหนึ่งวัน
- ตามบันทึกมีวิดีโอที่ดู 10 รายการ
- พบโฆษณา 8 รายการ และในนั้นมีเพียง 2 รายการที่กดข้ามได้
- หากสมมติ CPV เป็น USD
$0.15ต้นทุนโฆษณาต่อวันคือ$1.20และเมื่อนำไปประมาณทั้งเดือนจะอยู่ที่ราว USD$36/mo - ในการคำนวณอีกแบบที่ใช้ตัวเลขจาก Statista ระบุว่าในปี 2019 ผู้ลงโฆษณาในสหรัฐฯ ใช้เงินกับ YouTube
$15.1 billionและผู้อยู่อาศัยในสหรัฐฯ ดูวิดีโอ916 billionรายการ ทำให้เฉลี่ยต่อการรับชมเป็น USD$0.0165- หากใช้การคำนวณนี้ ต้นทุนต่อวันจะอยู่ที่ประมาณ USD
$0.13และเมื่อนำไปประมาณทั้งเดือนจะอยู่ที่ราว USD$3.96 - มองว่าค่านี้ไม่ได้ใกล้เคียงกับ Premium ที่ประมาณ USD
$10
- หากใช้การคำนวณนี้ ต้นทุนต่อวันจะอยู่ที่ประมาณ USD
- หากมี DMCA claim รายได้โฆษณาอาจไปยังผู้ยื่น claim อย่าง Sony หรือ Viacom แทนที่จะเป็นครีเอเตอร์
- ด้วยเหตุนี้ คุณอาจไม่ได้ให้อะไรแก่ช่องที่ชอบเลยโดยไม่รู้ตัว
- ประเมินว่าไม่น่าแปลกใจที่ครีเอเตอร์จำนวนมากย้ายไป Patreon
2 ความคิดเห็น
ต้นฉบับยาวมาก แต่กระบวนการก็น่าสนใจ โดยประเด็นสำคัญคือสุดท้ายผู้เขียนก็ลงเอยด้วยการจ่าย YouTube Premium แล้วใช้งานอยู่ดี
ความคิดเห็นจาก Hacker News
โดยรวมเป็นการแฮ็กที่เจ๋ง แต่บางคำอธิบายเกี่ยวกับ Protobuf ฟังดูแปลก ๆ
สิ่งที่ทำคือจงใจทำให้แท็กของฟิลด์บางตัวใน Protobuf เสียหาย และการที่มันมองข้ามหมายเลขแท็กที่ไม่รู้จักนั้นไม่ใช่ “ข้อบกพร่อง” แต่เป็นการออกแบบหลักเพื่อความสามารถในการขยาย
1.87MiB ก็ไม่ได้ใหญ่ขนาดนั้น และข้อความแบบนี้ก็คงไม่ได้ถูกสตรีมต่อเนื่องตลอดเวลา ดังนั้นคำอธิบายว่าเป็นกำแพงด้านประสิทธิภาพจึงยังไม่ค่อยน่าเชื่อ
การเข้ารหัสของ Protobuf ไม่ได้ถูกออกแบบมาให้ถอดรหัสแพงขึ้น แต่ตรงกันข้าม ถูกออกแบบมาให้ถอดรหัสได้อย่างมีประสิทธิภาพ และแม้ไม่มีสคีมา
.protoต้นฉบับ ก็ยังถอดรหัสเองได้ด้วยUnknownFieldSetวิธีที่ดีกว่าน่าจะเป็นการใช้สคีมา
.protoปลอมที่มีเฉพาะฟิลด์เดียวที่ต้องการลบออก วิธีสแกนสตริงมีโอกาสผิดพลาดง่ายกว่า เพราะลำดับไบต์เดียวกันอาจบังเอิญไปปรากฏในข้อมูลอื่นได้หากลำดับฟิลด์เปลี่ยน ผลลัพธ์ไบต์หลังเข้ารหัสใหม่อาจต่างออกไป แต่ฝั่งรับควรต้องประมวลผลเป็นข้อความเดียวกัน และดูไม่น่าเป็นไปได้ที่แอป YouTube จะตรวจจับการเปลี่ยนลำดับฟิลด์
จากมุมมองของคนที่เคยทำงานกับ Protobuf มาก่อน ดูเหมือนผู้เขียนจะเข้าใจส่วนนี้ผิด
ผมใช้ชีวิตส่วนใหญ่มาตลอดในพื้นที่ชนบท และถ้าไม่ใช่ Wi‑Fi ของผมเอง ขนาดเท่านี้ก็ถือเป็นการดาวน์โหลดที่ใหญ่ในทางปฏิบัติแล้ว บนมือถืออาจมีวิธีเลี่ยงได้บ้าง แต่ Wi‑Fi ในชนบทยังรับมือกับโครงสร้างแบบ Web 2.0 ได้ลำบาก และมักใช้งานกันที่ความเร็วระดับ 2~4G
ในเขตเมืองที่มีประชากรพอรองรับโครงสร้างพื้นฐาน 1.87MB โดยทั่วไปอาจกลายเป็นไฟล์เล็กไปแล้ว แต่ช่วงประมาณ 6 โมงเย็นที่คนบนสายเคเบิลพากันสตรีมก็อาจเป็นข้อยกเว้นได้
without the C++ source proto filesผมทำโปรเจกต์ชื่อ protodump ที่สร้างไฟล์ซอร์ส.protoจากไบนารีได้มันสร้างคำจำกัดความของ message และ field ขึ้นมาใหม่ พร้อมชื่อเดิมด้วย ขอแค่ดึงไบนารีออกมาจากกล่อง Apple TV ก็พอ
https://github.com/arkadiyt/protodump
Protobuf เป็นเทคโนโลยีที่ทำให้ผมรู้จัก IDL เป็นครั้งแรก และในตอนนั้นมันดูเหมือนไอเดียเวทมนตร์เลย ยิ่งหลังจากผมเคยสร้าง IDL แบบลวก ๆ เองแล้วมาค้นพบ Protobuf ก็ยิ่งทึ่งเข้าไปใหญ่
ถ้าไม่อยากลาก dependency ของ
protocทั้งก้อนเข้ามา ก็เขียนตัวถอดรหัส Protobuf แบบง่าย ๆ ไม่กี่ร้อยบรรทัดเองได้: https://github.com/kubernetes/test-infra/blob/master/guberna... https://github.com/kubernetes/test-infra/blob/master/guberna...ตั้งแต่เจอ The Proxomitron เมื่อกว่า 20 ปีก่อน ผมก็ใช้พร็อกซีแบบ man-in-the-middle จัดการทราฟฟิกมาโดยตลอด เพื่อเอาโฆษณาออกและเขียนหน้าเว็บใหม่ด้วยสิ่งอย่าง CSS ของผู้ใช้
ที่อย่าง CloudFlare มักจัดผมเป็น “บ็อต” แต่เรื่องนั้นก็มีวิธีเลี่ยง แม้จะไม่ง่ายก็ตาม กรณีแบบนี้ยังแสดงให้เห็นด้วยว่า remote attestation อันตรายต่อเสรีภาพของผู้ใช้อย่างไร
ดูเหมือนจะมีโปรเจกต์ “สืบทอด” อยู่หลายตัว และก็สงสัยด้วยว่ายังจำกัดเฉพาะ Windows หรือไม่ ผมกำลังมองหาพร็อกซีง่าย ๆ แบบเบา ๆ ที่แทรกลิงก์โลคัลเข้าไปในคอนเทนต์จากระยะไกลได้
ตอนนี้ผมถอด pi-hole ที่เคยรันบน Pi 4 ออกแล้ว ใช้เวลาหลายชั่วโมงพยายามทำให้มันทำงานได้ดีร่วมกับหลายบริการ แต่สุดท้ายก็ยอมแพ้ และมันไม่คุ้มค่ากับเวลาที่ต้องทุ่มให้เครือข่ายในบ้าน
สิ่งที่ใช้ได้จริงคือ ad blocker บนเบราว์เซอร์กับตัวแพตช์แอปอย่าง ReVanced พอเงินเก็บเพิ่มขึ้น ผมก็เอนเอียงไปทางจ่ายค่าบริการแบบไม่มีโฆษณาสำหรับกรณีที่สองอย่างนั้นแก้ไม่ได้ เช่น YouTube Premium, Hulu, Netflix, Max
ผมไม่เคยใช้เพื่อวัตถุประสงค์แบบที่พูดถึงตรงนี้ แต่สำหรับใช้เป็นพร็อกซีหลังไฟร์วอลล์บริษัทนี่เยี่ยมมาก สมัยก่อนไฟร์วอลล์ต้องการข้อมูลล็อกอินสำหรับการเชื่อมต่อออกภายนอก ทำให้โปรแกรมจำนวนมากเข้าอินเทอร์เน็ตไม่ได้
ทำให้นึกถึง Privaxy ที่แพ็กเกจมาเป็น Docker เป็นพร็อกซีแบบคนกลางที่เข้ากันได้กับรายการบล็อกของ UBlock Origin
พอเห็นว่าผลิตภัณฑ์อัจฉริยะ โดยเฉพาะทีวี มีโฆษณาและสคริปต์ติดตามมากแค่ไหนก็น่าตกใจ จากที่ทดสอบมาจนถึงตอนนี้ ทราฟฟิกที่ไม่จำเป็นมีเกิน 40% และการทดลองลอกโฆษณาออกจากแอปสมาร์ททีวีก็สนุกทีเดียว
https://github.com/deetungsten/webui-privaxy เป็นฟอร์กที่ทำเป็น Docker ของ https://github.com/Barre/privaxy
เคยพยายามแก้ฟอร์กไม่ให้ใช้ที่อยู่
0.0.0.0ที่ฮาร์ดโค้ดไว้ในฟรอนต์เอนด์ เพื่อให้แยก Docker container ได้จริง ๆ แต่ชีวิตก็แทรกเข้ามา เคยลองบน Apple TV ไหม?https://github.com/AdguardTeam/urlfilter
บทความนี้เป็นคำตอบที่ยอดเยี่ยมสำหรับคำถามที่เจอบ่อย ๆ ว่า “ถ้าอยากเป็นแฮกเกอร์ควรเรียนอย่างไร?”
มันแสดงให้เห็นได้ดีถึง กระบวนการคิด และงานที่ต้องกัดไม่ปล่อยซึ่งอยู่เบื้องหลัง exploit ใด ๆ
มีส่วนที่บอกว่า “มาใช้ WireGuard กัน — มีชุดคำสั่งเข้ารหัส Intel AES-NI” แต่เท่าที่ผมรู้ WireGuard ไม่ได้ใช้ AES
โดยรวมแล้วดูเหมือนผู้เขียนจะประเมินความต้องการ CPU ของการเข้ารหัส TLS สูงเกินไปอยู่บ้าง หรือไม่ก็ประเมินประสิทธิภาพของคอมพิวเตอร์บอร์ดเดี่ยวสมัยใหม่ต่ำเกินไป
คำอธิบายที่ว่าความต้องการ CPU สำหรับถอดรหัสและเข้ารหัสทราฟฟิก HTTPS ใหม่บน Raspberry Pi นั้นเกินขีดจำกัดไปมากก็น่าสงสัย ถ้าการทำ TLS แบบคนกลางบน RPi 4 เป็นไปไม่ได้จริง ๆ ผมคงค่อนข้างแปลกใจ และแม้ใช้ RSA แบบซอฟต์แวร์ล้วนก็เช่นกัน
ในบรรดามือถือ Android ที่ยังใช้อยู่ มีบางเครื่องที่มี CPU อ่อนกว่า RPi 4 และเครื่องเหล่านี้ก็ใช้ TLS
มือถือ Android สเปกอ่อน ๆ ต่อให้จัดการทราฟฟิก TLS ได้แค่ 50Mb/s ในการใช้งานจริงก็อาจไม่ใช่ปัญหาใหญ่ เพราะมือถือที่ช้ามักเชื่อมต่อกับเครือข่ายที่ช้าอยู่แล้ว
แต่ถ้าที่บ้านมีอินเทอร์เน็ตกิกะบิต แล้วเกิดคอขวดที่ 50Mb/s เพราะมีอุปกรณ์สเปกอ่อนคั่นอยู่ระหว่างคอมพิวเตอร์ทั้งหมดกับอินเทอร์เน็ต นั่นเป็นปัญหาใหญ่
ความต้องการ CPU ของ TLS ขึ้นอยู่กับแบนด์วิดท์เป้าหมายอย่างมาก ที่แบนด์วิดท์สูง ๆ การ offload ไปยังตัวเร่งความเร็วแทบจะเป็นสิ่งจำเป็น ค่าใช้จ่ายของ handshake ก็ไม่ควรมองข้าม และอาจจำกัดจำนวนการเชื่อมต่อต่อวินาทีได้ บนอุปกรณ์เครื่องเดียวอาจไม่ค่อยเป็นปัญหา แต่ในเครือข่ายของอุปกรณ์ทั้งหมดอาจกลายเป็นเรื่องใหญ่ขึ้นได้
เป็นบทความที่ยอดเยี่ยม ผมคาดหวังว่าจะได้เห็นวิธีทำ คนกลาง กับอุปกรณ์ที่ไม่อนุญาตให้ติดตั้ง CA แบบกำหนดเอง
ผมมีอุปกรณ์ IoT ที่ไม่เปิด local API และแสดงข้อมูลผ่านคลาวด์เท่านั้น เลยอยากดักจับทราฟฟิกระหว่างอุปกรณ์กับคลาวด์
สุดท้ายคงไม่มีทางอื่นนอกจาก dump หน่วยความจำแฟลช เปลี่ยน CA แล้วแฟลชกลับขึ้นไปหรือเปล่า?
มีบทความดี ๆ สำหรับลองวิธีดักอุปกรณ์ IoT โดยไม่ต้องยุ่งกับฮาร์ดแวร์หรือเฟิร์มแวร์:
https://robertheaton.com/2019/11/21/how-to-man-in-the-middle...
ถ้าทำได้ก็เท่ากับอาศัยข้อบกพร่องในการใช้งานจริง
พูดตรง ๆ ตอนนี้แม้แต่อุปกรณ์บางรุ่นที่ยังติดตั้งใบรับรองที่เชื่อถือเองได้ ผมก็คิดว่าวันแบบนั้นคงเหลืออีกไม่นาน
วิธีใหม่ ๆ ในการบล็อกโฆษณาบน YouTube หรือแพลตฟอร์มใด ๆ มักจะโผล่มาเสมอ แต่ไม่กี่เดือนต่อมาก็ถูกเปลี่ยนจนใช้ไม่ได้
ถ้าแทนที่จะทำแบบนั้น ลอง โจมตีผู้ลงโฆษณา ล่ะ? YouTube/Google ดูเหมือนจะติดตามแค่ “คลิก” แล้วติดตามไปถึงการซื้อจริงด้วยไหม?
ในทางทฤษฎี ถ้ามีบอตปลอมจำนวนมากพอและผู้ใช้จริงคลิกโฆษณาแต่ไม่ซื้ออะไรเลย ก็สามารถเผางบโฆษณาให้หมดได้ เมื่อเวลาผ่านไป ฝ่ายการตลาดจะเห็นว่าบนแพลตฟอร์มนั้นจำนวนคลิกสูงเป็นประวัติการณ์ แต่ conversion rate เมื่อเทียบกับคลิกหรือ impression ต่ำมาก และสุดท้ายอาจถอนตัวจากแพลตฟอร์มนั้น
เป็นบทความที่น่าทึ่ง ทันทีที่เห็นขั้นตอน mitm patch ก็คิดเลยว่าน่าจะเป็นบทความพิเศษ แล้วก็เป็นอย่างนั้นจริง ๆ
ประทับใจกับหัวข้อในสารบัญที่ว่า “เป้าหมายใหม่: ทำให้ YouTube เชื่อว่าผมเป็นผู้ชายอายุ 70 ปีที่อาศัยอยู่ในอิตาลี”
ไม่รู้ว่าเกิดขึ้นได้อย่างไร แต่ครั้งหนึ่งการกำหนดเป้าหมายโฆษณาเคยทำให้ระบบเชื่อว่าผมเป็นคนที่กำลังจะซื้อ ชุดนอนผ้าไหมซักได้ราคา 500 ดอลลาร์ ให้แฟน
ตัวโฆษณาเองยอดเยี่ยมมาก แต่ก็สงสัยว่าเขาจ่ายต่อ impression เท่าไร
หลังจากเปลี่ยนมาใช้ Apple TV ส่วนใหญ่จะได้โฆษณาท้องถิ่นที่กำหนดพื้นที่ผิด โดยเฉลี่ยแล้วบางทีแบบนั้นอาจดีกว่าก็ได้
นี่ไม่ใช่ “ข้อบกพร่อง” ของ Protobuf การที่เมื่อแก้ไขไบต์แล้วถูกถอดรหัสเป็นฟิลด์ในตำแหน่งอื่นนั้นคือการ ทำงานตามที่ออกแบบไว้
Protobuf เดิมทีเป็นโปรโตคอลที่อิงหมายเลขฟิลด์และคำนำหน้าความยาว และตั้งอยู่บนสมมติฐานที่สมเหตุสมผลว่าไบต์จะไม่ถูกเปลี่ยนระหว่างส่ง ส่วนความถูกต้องสมบูรณ์ของข้อมูลปล่อยให้ฝั่งที่อ่านเป็นผู้รับผิดชอบ
ต่อให้เรียกว่าเป็นข้อบกพร่องได้ ก็ไม่น่าจะเป็นของ Protobuf แต่เป็นของแอป YouTube สำหรับ iOS และจริง ๆ แล้วก็ไม่ใช่ข้อบกพร่อง จึงยากที่จะเรียกว่า “exploit” ได้ เว้นแต่ว่าหมายถึงกรณีที่ในการแลกเปลี่ยน Protobuf ของแอป YouTube บน iOS ไม่มีการตรวจสอบแฮชของ payload ที่ส่งกลับมา
หลังจากบทความนี้ อาจจะมีการตรวจสอบแล้วก็ได้
มันไม่ใช่ข้อบกพร่อง แต่เป็นการทำงานตามการออกแบบ
ส่วนที่บอกว่า “Google ทำให้การ decode, แก้ไข และ re-encode มีต้นทุนการคำนวณสูง หากไม่มีไฟล์ proto ต้นฉบับภาษา C++” ก็แปลกเช่นกัน ถ้าทำด้วยโค้ด Python ที่ไม่ได้ optimize ก็อาจจะแพง แต่ถ้าเขียนด้วย C หรือภาษา compiled อื่น ๆ การสแกน Protobuf ขนาด 1.8MB ไม่ว่าจะมีไฟล์ proto ต้นฉบับหรือไม่ ก็เป็นเรื่องเล็กน้อย
ไม่น่าจะใช่เป้าหมายการออกแบบที่ทำให้ไฟล์ Protobuf ถอดรหัสได้ยากเมื่อไม่มี source ถ้านั่นเป็นเป้าหมายจริง ก็คงทำได้แย่มาก