- Dengan merangkai variation selector Unicode, dimungkinkan menyembunyikan deretan byte di belakang satu karakter yang tidak terlihat di layar tetapi tetap ikut saat disalin-tempel
- Ada 256 variation selector, dari VS-1 hingga VS-256, sehingga bisa dibuat pemetaan yang pas dengan rentang 1 byte
- Bahkan jika byte
hello [0x68, 0x65, 0x6c, 0x6c, 0x6f] ditempelkan setelah 😊, tampilannya tetap seperti satu emoji biasa
- Decoding dilakukan dengan mencari rentang
U+FE00..U+FE0F dan U+E0100..U+E01EF, lalu mengubahnya kembali menjadi byte, dan karakter dasar tidak harus berupa emoji
- Metode ini adalah penyalahgunaan Unicode, dan dapat disalahgunakan untuk melewati filter konten manusia atau menyisipkan watermark ke dalam teks
Cara data tak terlihat menempel pada satu karakter
- Teks Unicode direpresentasikan sebagai urutan code point, biasanya ditulis dalam format
U+XXXX
- Pada huruf Latin sederhana, code point dan karakter yang terlihat di layar biasanya berkorespondensi 1:1
- Contoh:
U+0067 merepresentasikan karakter g
- Pada sistem tulisan lain, satu karakter yang tampak di layar bisa tersusun dari beberapa code point
- Contoh: dalam Devanagari, karakter yang dibaca sebagai
키 direpresentasikan sebagai pasangan berurutan U+0915 dan U+0940
Menggunakan variation selector seperti media penyimpanan data
- Unicode mendefinisikan 256 code point variation selector, dengan nama dari VS-1 sampai VS-256
- Variation selector sendiri tidak ditampilkan di layar, melainkan digunakan untuk mengubah cara karakter sebelumnya dirender
- Sebagian besar karakter Unicode tidak memiliki variasi terkait, tetapi karena Unicode menargetkan kompatibilitas masa depan, kode pemroses yang tidak memahami maknanya tetap harus mempertahankan variation selector
- Menambahkan
U+FE01 (VS-2) setelah U+0067 (g) tetap membuatnya terlihat seperti huruf kecil g di layar
- Saat disalin-tempel, variation selector juga ikut terbawa
- Karena 256 variation selector tepat berjumlah cukup untuk merepresentasikan 1 byte, data 1 byte dapat disembunyikan di belakang code point Unicode arbitrer
- Spesifikasi Unicode tidak secara rinci membahas urutan beberapa variation selector yang beruntun, dan mengisyaratkan bahwa urutan semacam itu seharusnya diabaikan saat rendering
- Dengan merangkai beberapa variation selector, deretan byte arbitrer bisa direpresentasikan di belakang satu karakter
Menyandikan byte menjadi variation selector
- Variation selector terbagi ke dalam dua rentang code point
U+FE00 .. U+FE0F: 16 yang pertama
U+E0100 .. U+E01EF: 240 sisanya
- Aturan untuk mengubah byte menjadi variation selector cukup sederhana
- Jika byte lebih kecil dari 16, gunakan
0xFE00 + byte
- Selain itu, gunakan
0xE0100 + (byte - 16)
- Proses encoding dimulai dengan memasukkan satu karakter dasar (base character), lalu setiap byte setelah itu diubah menjadi variation selector dan dirangkai di belakangnya
fn byte_to_variation_selector(byte: u8) -> char {
if byte < 16 {
char::from_u32(0xFE00 + byte as u32).unwrap()
} else {
char::from_u32(0xE0100 + (byte - 16) as u32).unwrap()
}
}
fn encode(base: char, bytes: &[u8]) -> String {
let mut result = String::new();
result.push(base);
for byte in bytes {
result.push(byte_to_variation_selector(*byte));
}
result
}
- Jika byte yang merepresentasikan
hello, yaitu [0x68, 0x65, 0x6c, 0x6c, 0x6f], ditempelkan setelah 😊, hasilnya adalah string yang secara kasatmata tampak seperti emoji biasa
- Pada output normal, karakter tersembunyi itu tidak terlihat, tetapi jika dicetak dengan format debug Rust, code point tersembunyi seperti
\u{e0158} akan tampak
"😊\u{e0158}\u{e0155}\u{e015c}\u{e015c}\u{e015f}"
Cara membaca kembali byte yang tersembunyi
- Decoding dilakukan dengan mengiterasi karakter dan mengubah kembali code point dalam rentang variation selector menjadi byte
- Rentang
U+FE00..U+FE0F dipulihkan dengan variation_selector - 0xFE00
- Rentang
U+E0100..U+E01EF dipulihkan dengan variation_selector - 0xE0100 + 16
- Karakter biasa sebelum variation selector pertama dianggap sebagai karakter dasar dan diabaikan
- Jika menemukan karakter yang bukan variation selector setelah hasil decoding sudah mulai terbentuk, proses decoding dihentikan
fn variation_selector_to_byte(variation_selector: char) -> Option<u8> {
let variation_selector = variation_selector as u32;
if (0xFE00..=0xFE0F).contains(&variation_selector) {
Some((variation_selector - 0xFE00) as u8)
} else if (0xE0100..=0xE01EF).contains(&variation_selector) {
Some((variation_selector - 0xE0100 + 16) as u8)
} else {
None
}
}
- Jika hasil encoding yang sama didekode lalu diinterpretasikan sebagai UTF-8, hasilnya adalah
"hello"
- Karakter dasar tidak harus emoji; pada karakter biasa pun variation selector diproses dengan cara yang sama
- Emoji dipakai hanya karena terasa lebih menyenangkan
Potensi penyalahgunaan
- Metode ini adalah penyalahgunaan Unicode dan sebaiknya tidak digunakan
- Karena data tidak terlihat pada hasil render, moderator atau reviewer manusia akan sulit mengetahui keberadaan data tersembunyi
- Ini bisa disalahgunakan untuk menyembunyikan data sambil lolos dari filter konten manusia
- Teknik ini juga dapat dipakai untuk watermarking teks
- Setelah pesan dikirim ke beberapa orang, jika bocor maka penerima aslinya bisa dilacak
- Urutan variation selector cenderung tetap bertahan dalam sebagian besar proses salin-tempel
- Kepadatan data arbitrer dimungkinkan, dan jika diinginkan watermark dapat ditanamkan pada setiap karakter
Bisakah LLM memproses data tersembunyi?
- Setelah diposting ke Hacker News, muncul pertanyaan tentang bagaimana LLM menangani data tersembunyi seperti ini
- Secara umum, tokenizer tampaknya mempertahankan variation selector sebagai token, sehingga secara teori model dapat mengaksesnya
- OpenAI tokenizer adalah alat pemeriksaan yang bisa digunakan untuk memverifikasi hal ini
- Secara keseluruhan, model tampaknya tidak mencoba melakukan decoding langsung secara internal
- Jika digunakan bersama code interpreter, beberapa model bisa membuka data tersembunyi
1 komentar
Komentar Hacker News
Untuk penyalahgunaan Unicode, ini hanyalah puncak gunung es. Dengan teknik serupa, buffer bisa dibuat overflow di berbagai sistem yang menerima string Unicode; biasanya berakhir sebagai error atau crash, tetapi kalau beruntung bisa menghasilkan perilaku yang cukup menarik
Saat melakukan penetration testing pada masa sebelum Python 3, saya pernah membuat buffer backend web server overflow dengan memperpanjang satu karakter menjadi beberapa byte hanya memakai tanda diakritik. Waktu itu hanya crash dan restart otomatis, tetapi jika digali cukup dalam, tampaknya bisa dipakai untuk exploit pada sistem atau software tertentu
Di form modern pun hal serupa bisa dipicu hanya dengan mematikan JavaScript, dan skenario terbaiknya debug sedang aktif sehingga stack trace atau query tercetak dan sedikit informasi bocor. Kesalahan umum lain adalah salah menghitung panjang
\ndan\r\ndalam string teks; JavaScript biasanya menghitung carriage return sebagai 1 byte, tetapi spesifikasi HTTP mensyaratkan 2 byteunescape(encodeURIComponent("ç")).lengthadalah cara kira-kira yang cepat untuk memeriksa panjang byte di JavaScript, dan masalah\r\nbisa diselesaikan dengan membersihkan string sebelum menghitung panjangnyaIni lucu, tetapi sebenarnya tidak perlu. Unicode punya rentang besar bernama PUA (private use area), dan kode di rentang ini tidak dipetakan ke karakter apa pun serta tidak akan dipetakan di masa depan, sehingga digunakan untuk keperluan internal/kustom
Misalnya di fish-shell, saat mem-parsing token menjadi string dengan aman, karakter khusus yang tidak di-escape diubah seperti code point Unicode lain di dalam string tetapi ditempatkan di area PUA, lalu dicegat belakangan di pipeline. Ini tidak boleh terekspos keluar dari batas API, tetapi saat ditemui, rekomendasinya adalah membiarkannya lewat apa adanya, dan sebagian besar sistem serta library juga begitu. Ini bisa menjadi jalur kebocoran yang jelas, dan sering tetap terbuka karena banyak developer normal tidak tahu banyak tentang Unicode selain “selalu pakai Unicode agar terhindar dari masalah internasionalisasi”
). Poin utamanya di sini adalah mengenkode agar tersembunyi saat disalin-tempel dan diperlakukan sebagai “bagian” dari karakter lainhttps://news.ycombinator.com/item?id=42791378
Karena API dibatasi hanya menerima emoji, potensi pemanfaatan kriminal langsung dibahas. Untuk kegunaan itu PUA tidak bisa dipakai, dan harus dienkode di dalam emoji
Noncharacter yang ditetapkan mencakup
0xFFFF,0xFFFE, dan dua code point terakhir di setiap plane, serta satu area di tengah Arabic Presentation Forms. Seingat saya, daftar itu kemudian ditambah agar orang punya lebih banyak noncharacter untuk dipakai seperti iniAnda tidak bisa membuat watermark karakter arbitrer secara tak terlihat dengan karakter PUA yang tidak dikenali. Sebab karakter itu tidak diperlakukan sebagai combining character. Sebagai gantinya akan muncul kotak placeholder yang dirender terpisah. Contoh:
— tentu saja jika Anda memang memakai private use area secara pribadi, mungkin saja bukan kotakSekitar 10 tahun lalu saya pernah mengagetkan rekan kerja dengan menyisipkan U+202D LEFT-TO-RIGHT OVERRIDE di tengah nama file Windows.
funnypicturegnp.exeterlihat sepertifunnypictureexe.pngJika ditambah ikon kustom yang tampak seperti preview foto, hasilnya cukup meyakinkan
.exekebanyakan diblokir otomatis, tetapi ekstensi berbahaya masa kini sering berupa .html, lalu menampilkan halaman login palsu lewat redirectwindow.locationyang diobfuscatePenyalahgunaan RTL seperti
cute-cat-lmth.pngcukup umum, tetapi juga sangat mudah dideteksi, dan email seperti itu langsung kami tandai sebagai phishinghttps://trojansource.codes/
Pada dasarnya, ini memungkinkan menyembunyikan kode yang tampak seperti komentar tetapi berfungsi sebagai kode saat dikompilasi. Namun, saya ingat status CVE-nya diperdebatkan karena banyak editor teks sudah membuat komentar mencurigakan seperti ini terlihat
guitar_tab.txtSebagai contoh penggunaan nyata, Sanity memakai trik ini untuk mengenkode Content Source Maps ke dalam teks aktual yang disajikan di halaman web dalam “mode pratinjau”0. Editor dapat dengan mudah melacak hingga lokasi asal di dalam struktur konten yang dalam hanya dengan mengeklik teks atau konten tersebut
Ada juga kekurangan dan batasannya. Misalnya, harus dicegah agar ini tidak ditambahkan ke nilai yang perlu diparse atau digunakan apa adanya, seperti tanggal·timestamp, URL, atau ID. Meski begitu, ini trik yang cukup menarik
0 https://www.sanity.io/docs/stega
[1] https://github.com/sanity-io/content-source-maps
Saya suka ide memakai ini untuk watermarking keluaran LLM. Posisinya pas. Bagaimanapun, 99% generator berkualitas rendah yang cuma copy-paste mau tak mau akan ketahuan, dan hampir tidak berdampak pada use case inti lainnya
Saya juga penasaran berapa banyak yang akan disisipkan untuk tiap huruf atau token keluaran. Apakah hal-hal seperti ID pengguna, referensi prompt, tanggal, nomor token? Juga penasaran bagaimana ini ditafsirkan di terminal; benar-benar keren
Satu-satunya pertahanan AI yang nyata adalah mewajibkan tanda tangan kunci yang diverifikasi dengan identitas asli untuk semua interaksi manusia, tetapi itu pun A: tidak akan pernah terjadi, dan B: bisa disalahgunakan di negara dengan pemerintah korup atau negara dengan pemerintah korup yang sangat dipengaruhi industri swasta, misalnya AS
Tentu saja kalau kalimatnya dikirim ke
xxd, itu akan terlihat. Berbeda dengan usulan PUA pada komentar teratas saat ini yang langsung terlihatSetelah pengujian tambahan, jika ditempelkan ke terminal lalu dilihat dengan
xxd, pesannya lewat sepenuhnya tanpa perubahan, tetapi jika dipilih dari terminal lalu ditempelkan lagi, pada X selection di mate terminal dan konsole hanya tersisa beberapa kata dan terpotong. Saya tidak tahu apakah pemotongan itu karena terminal atau karena X. Di xterm, hurufeterakhir berubah dan isi pilihan terpotong lebih banyakDi file, kalimat tercatat tanpa perubahan. Jadi sepertinya sebagian data hilang saat disalin keluar dari terminal. Saya mengujinya dengan
echokalimat ke file uji, membukanya di browser, lalu menyalin teksnyaJika saat generasi kita mengutak-atik cara sampling, nantinya kita bisa menjalankan LLM lagi dan mengamati pola keluarannya untuk mendeteksi sidik jari. Misalnya dengan memilih token berprobabilitas tinggi dan token berprobabilitas rendah secara bergantian. Implementasi nyatanya tentu akan jauh lebih canggih, tetapi idenya mengarah ke sana
Yang menarik, pembaca layar dapat mendeteksi variation selector seperti ini saat berpindah per karakter. Jika bergerak dengan tombol panah di atas contoh, ia akan membacakan seperti “Smiling face with smiling eyes”, “Symbol e zero one five five”, “Symbol e zero one five c”
Namun ini bergantung pada speech synthesizer yang digunakan, dan jika dokumen hanya sedang dibaca biasa, kita tidak akan tahu ada karakter semacam itu, jadi secara keseluruhan bukan keuntungan besar
StegCloak0 juga satu keluarga yang mirip, dan mendorong ide ini selangkah lebih jauh dengan mengenkripsi payload tersembunyi menggunakan AES-256-CTR. Trik kecil yang cukup bagus
0 https://github.com/KuroLabs/stegcloak
Namun agar pihak lain dapat mendekodenya, Anda harus berbagi nilai rahasia kata sandi
Judulnya agak menyesatkan. Tertulis bahwa “karakter dasar tidak harus berupa emoji, dan pemrosesan variation selector sama saja pada karakter biasa. Dengan emoji hanya jadi lebih menyenangkan”
Jika cara ini dipakai pada karakter non-emoji, hasilnya menjadi lebih tersembunyi dan lebih merepotkan
Dibanding sekadar watermarking keluaran LLM, ini tampak bisa menjadi cara rapi untuk membungkus data logprobs bersama teks
Pada dasarnya, ini memasukkan informasi probabilitas dari semua token yang dihasilkan untuk memberi sedikit transparansi pada proses generasi. Ini juga ada di spesifikasi OpenAI API, dan beberapa engine seperti
llama.cppjuga menyediakan informasi ini. Biasanya ditempelkan sebagai field terpisah, tetapi ada juga cara visualisasi seperti mikupad0Mungkin ini ide buruk, tetapi tetap saja gagasannya menggelitik
Teknik yang keren. Ini mencerminkan ASCII dan juga ada karakter Unicode Tag yang jarang terlihat di elemen UI, terutama aplikasi web.
Keunikan karakter Tag adalah sebagian LLM menafsirkan teks tersembunyi sebagai ASCII dan mengikuti instruksinya, bahkan bisa menuliskannya secara langsung.
https://embracethered.com/blog/posts/2024/hiding-and-finding...
Ada juga proof-of-concept eksploit nyata yang telah diperbaiki Microsoft di Copilot.
https://embracethered.com/blog/posts/2024/m365-copilot-promp...