1 điểm bởi GN⁺ 4 giờ trước | 1 bình luận | Chia sẻ qua WhatsApp
  • Một lập trình viên đã xem favicon, biểu tượng trên tab trình duyệt, như một kho lưu trữ byte theo pixel và thực hiện thí nghiệm nhét một HTML nhỏ vào các kênh RGB của ảnh
  • Cách mã hóa là gắn thêm phần đầu 4 byte chứa độ dài trước các byte UTF-8 của HTML, rồi ghi lần lượt từng byte vào các giá trị R·G·B của pixel
  • Payload demo dài 208 byte và là 212 byte tính cả phần đầu, nên chỉ cần 71 pixel lưu trữ theo từng nhóm 3 byte và một ảnh PNG 9×9 là đủ
  • Việc khôi phục được thực hiện bằng cách vẽ ảnh favicon lên canvas, sau đó JavaScript đọc dữ liệu pixel và lắp lại các giá trị RGB thành mảng byte để giải mã về HTML
  • Cấu trúc này không phải là một website có thể tự chạy chỉ với favicon; nó vẫn cần bootstrap JavaScript riêng, nên gần với một thử nghiệm khám phá ranh giới hơn là thứ thực dụng

Cách xử lý favicon như một kho lưu trữ

  • favicon là biểu tượng nhỏ hiển thị trên tab trình duyệt, nhưng về bản chất nó là một tệp ảnh được tạo nên từ pixel và byte
  • Điểm xuất phát của thử nghiệm là steganography, nhưng trong bản demo trọng tâm không phải làm cho nó trông như một biểu tượng mà là dùng nó như không gian lưu trữ thuần túy
  • Đối tượng được lưu là một payload HTML nhỏ
Website in a Favicon

Everything you're reading right now was decoded from favicon pixels.

  • Quy trình mã hóa khá đơn giản
    • Dùng TextEncoder để chuyển HTML thành các byte UTF-8
    • Gắn phần đầu 4 byte chứa độ dài payload ở phía trước
    • Vì có thể còn pixel thừa, phần đầu độ dài được dùng để xác định điểm kết thúc thực sự của payload
    • Byte đầu tiên được lưu vào kênh red của pixel đầu tiên, byte thứ hai vào green, byte thứ ba vào blue
    • Các pixel sau đó cũng được lấp theo cùng thứ tự để toàn bộ tài liệu HTML đi vào các giá trị màu
  • Ảnh kết quả nhìn trực quan giống như nhiễu

Kích thước và quy trình khôi phục

  • Kích thước cuối cùng của bản demo rất nhỏ
    • Payload: 208 bytes
    • Tổng dung lượng gồm cả header: 212 bytes
    • Pixel cần thiết: 71 pixels
    • Kích thước ảnh: 9×9 pixels
    • Dung lượng: 239 bytes
    • Tỷ lệ sử dụng: 87% {p:87}
  • Việc khôi phục chỉ dùng các tính năng của trình duyệt
    • Tải favicon như một ảnh
    • Vẽ ảnh lên canvas
    • Đọc toàn bộ pixel bằng Canvas API
    • Tái tạo các giá trị RGB thành mảng byte
    • Đọc độ dài payload từ 4 byte đầu tiên
    • Trích xuất payload và giải mã thành văn bản UTF-8
  • Trên trang demo, khi nhấn nút "Render Website", favicon sẽ được đọc, HTML được khôi phục rồi nội dung trang được thay thế

Giới hạn và các phương án thay thế

  • Ràng buộc lớn nhất là favicon không thể một mình chạy toàn bộ website
    • favicon chứa nội dung của website
    • vẫn cần một trình nạp JavaScript nhỏ riêng để giải mã
    • nếu không có JavaScript, favicon chỉ là một PNG chứa nội dung website mà thôi
  • Tính thực dụng thấp
    • lượng dữ liệu có thể lưu rất nhỏ
    • trang phải được bootstrap bằng JavaScript
    • có nhiều cách tốt hơn để phân phối các tài liệu HTML nhỏ
  • Các phương án thay thế gồm chèn trực tiếp markup vào SVG favicon, dùng các comment chunk tEXt, zTXt, iTXt của PNG, hoặc dùng định dạng tệp ico có thể chứa biểu tượng ở nhiều độ phân giải
  • Trang demo: https://www.timwehrle.de/labs/favicon-site/
  • Mã triển khai: https://github.com/timwehrle/favicon

1 bình luận

 
Ý kiến trên Hacker News
  • Có vẻ như chẳng cần đi qua pixel, chỉ cần dùng SVG favicon rồi lưu trực tiếp markup vào trong đó và trích xuất ra là được
    Có thể đặt nội dung như hello HN! vào favicon.svg, dùng nó làm SVG favicon, rồi trích xuất và chèn vào phần thân tài liệu

    • Có lẽ nên xem đây là “một biến thể thú vị nữa” hơn là hỏi “tại sao không dùng cách thay thế này”. Cả hai đều là kiểu nghịch công nghệ vì vui, vì tò mò, vì muốn khám phá, và cách lưu trong pixel có cái thú kiểu cỗ máy Rube Goldberg
    • Tôi là tác giả đây, và đúng là cách này thực tế hơn. Nhưng tôi muốn payload “sống” bên trong chính dữ liệu pixel thật sự, thay vì chỉ là văn bản ẩn trong một tệp XML nên mới làm vậy :)
    • Regex à, ôi. Chỉ cần mã hóa đúng bằng XML trong namespace chuẩn rồi đọc ra như vậy là được
      Hoặc cứ phân phối nguyên tệp SVG và nhúng HTML vào trong đó cũng được. Về mặt lý thuyết thì phải có thể định nghĩa rồi dùng được, nhưng tiếc là cả Firefox lẫn Chromium dường như không xử lý tử tế bên trong favicon
    • Nhân tiện nói về cối xay gió mà cá nhân tôi cứ thích lao vào, [\s\S] có thể viết ngắn hơn và chính xác hơn thành [^]
    • SVG có thể nhúng ảnh raster dưới dạng các byte mã hóa base64
      Vì vậy có thể xếp thêm một lớp thí nghiệm nữa: favicon là SVG, bên trong có raster đã mã hóa, và trong các byte đó lại mã hóa HTML. Ít nhất cũng đủ để thành một màn CTF gây choáng váng
  • Dĩ nhiên đây không phải ý tưởng mới. Ví dụ, hồi năm 2000 đã có người lưu deCSS vào favicon
    https://web.archive.org/web/20010408040524if_/http://decss.z...
    Có thể trích xuất bằng dd bs=1 skip=2238 < favicon.ico

  • Không hẳn là “vẫn cần một bootstrap loader nhỏ để giải mã ảnh”. Nếu dùng HTML/PNG polyglot thì có thể xử lý mọi thứ bằng một tệp duy nhất, và ngày nay với các định dạng mới như WebP thì tỷ lệ nén còn có thể tốt hơn
    https://web.archive.org/web/20120801001616/http://daeken.com...

  • Nếu chuyển hướng người dùng qua nhiều domain thì cache favicon cũng có thể dùng làm nơi lưu trữ. Từng có đề xuất về điều này như một rủi ro fingerprinting tiềm tàng[0], và nếu trình duyệt ngây thơ tái sử dụng cache ngay cả trong chế độ ẩn danh thì nó có thể bị lạm dụng để theo dõi người dùng giữa các profile trình duyệt
    [0]: https://www.schneier.com/blog/archives/2021/02/browser-track...

    • Chẳng phải cái này đã được sửa rồi, hoặc ít nhất phần lớn là vậy sao?
    • Vừa đọc bài gốc xong là tôi lập tức nghĩ “cái này chắc dùng cho fingerprinting được đây”. Không rõ các biện pháp anti-fingerprinting có tính đến cả tổ hợp favicon và Canvas API hay không
      Tiếc là link tới trang supercookie giờ đã chết
  • PNG có các chunk chú thích tEXt, zTXt, iTXt. Có thể nhét vào đó bao nhiêu nội dung tùy thích trong một tệp ảnh trông hoàn toàn bình thường. Tất nhiên sẽ bớt vui hơn một chút

  • Cái thời điểm này là ngẫu nhiên à? Mới đúng 1 giờ trước, chính xác là 30 phút trước bài này, tôi vừa đăng một trang lưu danh mục cổ phiếu vào URL + favicon mà tôi làm
    https://news.ycombinator.com/item?id=48606396

  • Cách tư duy này thật sự rất hợp: màn hình cũng là lưu trữ, bàn phím cũng là lưu trữ, và bài đăng trên diễn đàn cũng là lưu trữ
    Nếu theo thời gian bạn chèn vào các chỉnh sửa là những biến thể mà Markov sẽ chấp nhận thì sẽ có khá nhiều dung lượng lưu trữ. Thêm nữa, bình luận đôi khi còn thú vị về mặt xã hội nên thành ra là một dạng lưu trữ hai mục đích.
    Không ai biết liệu công thức gà casserole của ai đó thực ra có phải là tay cầm của một GUID được sắp đặt tinh vi hay không, hay đùa thôi nhưng biết đâu nó lại trỏ tới một nghìn bài đăng diễn đàn khác nhau. Không rõ tác giả có biết PoC||GTFO không, nhưng đây chắc chắn là kiểu kỹ thuật có thể tìm thấy sâu trong cuốn thánh thư của Alchemist Owls

    • Code trong code. Bánh xe trong bánh xe
  • Văn phong ngắt mạnh, gắt gỏng, trông rõ kiểu do LLM tạo ra, khiến tôi đọc rất mệt

    • Vài tháng trước tôi từng phàn nàn về kiểu văn này trên Medium. Tác giả của bài đó trả lời rằng đây là kiểu được ưa chuộng nếu người ta dự đoán nó sẽ được đọc trên màn hình điện thoại nhỏ. Cũng có phần hợp lý. Tôi không biết bài đó hay bài này có phải AI tạo hay không
    • Đến khoảng giữa bài, tôi còn chắc mẩm cuối bài sẽ có cú lật là “thật ra chính bài viết này đã được lưu trong favicon của trang”, qua đó giải thích các câu ngắn và cụt. Khi nhận ra không phải vậy, tôi thực sự thất vọng. Đúng là cơ hội bị bỏ lỡ
    • Tôi lại thích kiểu viết này. Bản thân tôi cũng đôi khi viết tương tự, và chưa từng dùng LLM để tạo bài của mình. Ở chỗ làm tôi cũng đã từng viết đúng như vậy
      Với tôi thì tác giả chỉ đơn giản là muốn đi thẳng vào ý chính. Có vẻ họ biết rằng nếu bài dài quá thì mọi người sẽ bắt đầu đọc lướt
    • Lâu rồi tôi mới không đồng ý với nhận định văn phong do AI tạo trên HN. Cùng lắm có thể tác giả dùng LLM để lên bản nháp, nhưng kết quả cuối cùng trông khá con người
      it’s/its bị dùng sai, But. thành câu chỉ có một từ, HTML không được viết hoa, và có chữ “okayy” trong ngoặc. Tôi không chê tác giả đâu, ngược lại chính những chút không hoàn hảo này làm nên bài blog và khiến tôi thích hơn
    • Bài viết cuốn hút và đọc rất vui
  • Làm tôi nhớ tới real pixel coding của Inigo: https://www.youtube.com/watch?v=FvS_DG8yIqQ
    Đó là một intro 256 byte được tạo bằng cách đặt pixel trong Photoshop rồi lưu thành exe

  • Một sự thật thú vị: bất kỳ SVG inline nào cũng có thể dùng làm favicon và giữ nguyên trong tài liệu HTML
    Làm vậy thì cũng có thể dùng emoji trực tiếp làm favicon. Trên HN thì emoji không hiển thị

    • Nhân tiện, nếu làm như vậy mà muốn dùng mã màu #rrggbb hoặc liên kết url(#id) thì phải escape # thành %23. Nếu không nó sẽ bị parse như URL fragment và mã SVG sẽ bị cắt ở điểm đó