2 puan yazan GN⁺ 2025-02-13 | 1 yorum | WhatsApp'ta paylaş
  • Unicode’un variation selector’ları art arda eklenerek, ekranda görünmeyen ama kopyala-yapıştırla birlikte taşınan bir bayt dizisi tek bir karakterin arkasına gizlenebilir
  • VS-1’den VS-256’ya kadar 256 variation selector vardır; bu da 1 baytlık aralıkla bire bir eşleşen bir eşleme oluşturmayı mümkün kılar
  • 😊 arkasına hello’nun baytları olan [0x68, 0x65, 0x6c, 0x6c, 0x6f] eklense bile dışarıdan bakıldığında sıradan tek bir emoji gibi görünür
  • Çözme işlemi U+FE00..U+FE0F ve U+E0100..U+E01EF aralıklarını bulup yeniden bayta çevirir; temel karakterin emoji olması gerekmez
  • Bu yöntem Unicode’un kötüye kullanımıdır ve insan denetimli içerik filtrelerini aşmak ya da metne filigran gömmek için kötüye kullanılabilir

Görünmez verinin tek bir karaktere eklenme biçimi

  • Unicode metin, kod noktası (codepoint) dizileriyle temsil edilir ve genellikle U+XXXX biçiminde gösterilir
  • Basit Latin karakterlerinde kod noktası ile ekranda görünen karakter bire bir eşleşir
    • Örnek: U+0067, g karakterini temsil eder
  • Diğer yazı sistemlerinde ekrandaki tek bir karakter birden fazla kod noktasından oluşabilir
    • Örnek: Devanagari’de ki diye okunan karakter, U+0915 ve U+0940 ardışık çiftiyle temsil edilir

Variation selector’ları veri deposu gibi kullanmak

  • Unicode, VS-1’den VS-256’ya kadar adlandırılan 256 variation selector kod noktası tanımlar
  • Variation selector’ın kendisi ekranda gösterilmez; önceki karakterin gösterim biçimini değiştirmek için kullanılır
  • Çoğu Unicode karakterinin ilişkili bir varyasyonu yoktur; ancak Unicode geleceğe dönük uyumluluğu hedeflediğinden, anlamını bilmeyen işleme kodlarının da variation selector’ı koruması gerekir
    • U+0067 (g) arkasına U+FE01 (VS-2) eklense bile ekranda küçük harf g gibi görünür
    • Kopyala-yapıştır yapıldığında variation selector da birlikte taşınır
  • 256 variation selector, tam olarak 1 baytı temsil etmeye yetecek sayıdadır; bu nedenle herhangi bir Unicode kod noktasının arkasına 1 bayt veri gizlenebilir
  • Unicode belirtimi, birden fazla variation selector’ın art arda geldiği dizileri ayrıntılı olarak ele almaz ve işleme sırasında bunların yok sayılması gerektiğini ima eder
  • Birden fazla variation selector art arda eklenirse, rastgele bir bayt dizisi tek bir karakterin arkasında temsil edilebilir

Baytları variation selector olarak kodlama

  • Variation selector’lar iki kod noktası aralığına ayrılır
    • U+FE00 .. U+FE0F: ilk 16’sı
    • U+E0100 .. U+E01EF: kalan 240’ı
  • Bir baytı variation selector’a çevirme kuralı basittir
    • Bayt 16’dan küçükse 0xFE00 + byte
    • Aksi halde 0xE0100 + (byte - 16)
  • Kodlama, önce bir temel karakter (base character) ekleyip ardından her baytı bir variation selector’a dönüştürerek peş peşe ekler
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
}
  • hello’yu temsil eden [0x68, 0x65, 0x6c, 0x6c, 0x6f] baytları 😊 arkasına eklenirse, dışarıdan sıradan bir emoji gibi görünen bir dizge oluşur
  • Normal çıktıda gizli karakterler görünmez; ancak Rust’ın debug formatıyla yazdırıldığında \u{e0158} gibi gizli kod noktaları ortaya çıkar
"😊\u{e0158}\u{e0155}\u{e015c}\u{e015c}\u{e015f}"

Gizli baytları yeniden okuma yöntemi

  • Çözme işlemi, karakterleri dolaşırken variation selector aralıklarındaki kod noktalarını yeniden bayta dönüştürür
  • U+FE00..U+FE0F aralığı variation_selector - 0xFE00 ile geri yüklenir
  • U+E0100..U+E01EF aralığı variation_selector - 0xE0100 + 16 ile geri yüklenir
  • İlk variation selector’a kadar olan normal karakterler temel karakter kabul edilip yok sayılır
  • Variation selector olmayan bir karakterle karşılaşılır ve halihazırda sonuç varsa çözme işlemi sonlandırılır
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
    }
}
  • Aynı kodlama sonucu çözüldükten sonra UTF-8 olarak yorumlandığında "hello" elde edilir
  • Temel karakterin emoji olması gerekmez; normal karakterlerde de variation selector işleme biçimi aynıdır
  • Emoji kullanılmasının nedeni yalnızca daha eğlenceli olmasıdır

Kötüye kullanım olasılığı

  • Bu yöntem Unicode’un kötüye kullanımıdır ve kullanılmamalıdır
  • İşlenmiş sonuçta veri görünmediği için insan moderatörlerin veya inceleyicilerin gizli verinin varlığını fark etmesi zordur
  • İnsan denetimli içerik filtrelerinden geçerek veri gizleme yöntemi olarak kötüye kullanılabilir
  • Metin filigranlama için de kullanılabilir
    • Mesaj birden fazla kişiye gönderildikten sonra sızdırılırsa asıl alıcı takip edilebilir
    • Variation selector dizileri çoğu kopyala-yapıştır işleminden sağ çıkar
    • İstenilen veri yoğunluğuna izin verir; istenirse her karaktere filigran eklenebilir

LLM gizli veriyi işleyebilir mi

  • Hacker News’te yayımlandıktan sonra, LLM’lerin bu tür gizli verileri nasıl ele aldığına dair bir soru ortaya çıktı
  • Genel olarak tokenizer’ların variation selector’ları token olarak koruduğu ve teorik olarak modelin bunlara erişebildiği görülüyor
  • OpenAI tokenizer, bunu doğrulamak için kullanılabilecek bir kontrol aracıdır
  • Genel olarak modellerin dahili olarak doğrudan çözmeyi denemediği görülüyor
  • Kod yorumlayıcıyla birlikte kullanıldığında bazı modeller gizli veriyi çözebiliyor

1 yorum

 
GN⁺ 2025-02-13
Hacker News yorumları
  • Unicode’un kötüye kullanımında bu sadece buzdağının görünen kısmı. Benzer tekniklerle Unicode dizgesi alan pek çok sistemde tampon taşırılabiliyor; genelde hata ya da çökme ile sonuçlanıyor ama şanslıysanız epey ilginç davranışlar da çıkabiliyor.
    Python 3 öncesi dönemde sızma testi yaparken, yalnızca aksan/ayırıcı işaretlerle tek bir karakteri çok bayta genişletip arka uç web sunucusunun tamponunu taşırmıştım. O zaman sonuç sadece çökme ve otomatik yeniden başlatmaydı, ama yeterince kurcalanırsa belirli sistem ya da yazılım exploit’lerinde kullanılabilecek gibi görünüyor.

    • Google CTF quals 2024’teki “encrypted runner” sorusu bu fikre dayanıyordu.
    • Evet. Zalgo metni web sitesi giriş alanları için yaygın bir test örneği, ama çoğu zaman ilginç bir şey olmuyor. Ara sıra veritabanı uzunluk sınırı istisnalarını tetikliyor; genelde süreç ölmez ve istisna mevcut thread içinde kalır.
      Modern formlarda bile yalnızca JavaScript’i kapatarak benzer durumlar tetiklenebilir; en iyi ihtimalle debug açıktır da stack trace veya sorgular yazdırılır ve biraz bilgi sızar. Bir diğer yaygın hata da metin dizgilerinde \n ile \r\n uzunluğunu yanlış saymaktır; JavaScript genelde carriage return’ü 1 bayt sayar, ama HTTP spesifikasyonu 2 bayt ister.
      unescape(encodeURIComponent("ç")).length, JavaScript’te bayt uzunluğunu hızlıca kontrol etmek için kabaca kullanılan bir yöntemdir; \r\n sorunu da uzunluğu saymadan önce dizgeyi temizleyerek çözülür.
    • Acemiyim de, bunun nasıl olduğunu ya da nasıl yaptığını biraz daha açıklayabilir misin? Deneyebilecek bir açık gibi geliyor.
  • Bu sevimli ama aslında gerekli değil. Unicode’da PUA (private use area) denen geniş bir aralık var; bu aralıktaki kodlar hiçbir karaktere eşlenmez ve gelecekte de eşlenmeyecekleri için dahili/kullanıcı tanımlı amaçlarla kullanılır.
    Örneğin fish-shell’de token’ları güvenli biçimde dizge olarak ayrıştırırken, escape edilmemiş özel karakterleri dizge içindeki başka Unicode kod noktaları gibi değiştirir ama PUA bölgesine koyar ve daha sonra pipeline’da yakalar. API sınırının dışına sızdırılmamalıdır, ama karşılaşıldığında aynen geçirilmesi önerilir; çoğu sistem ve kütüphane de böyle yapar. Bariz bir sızıntı yolu olabilir; çünkü pek çok sıradan geliştirici Unicode hakkında “uluslararasılaştırma sorunlarından kaçınmak için her zaman Unicode kullan”dan fazlasını bilmediğinden bu yol çoğu zaman açık kalır.

    • Kendim test ettim; private use karakterleri benim ortamımda kutu olarak render ediliyor (󰀀). Buradaki ana nokta, kopyala-yapıştır sırasında gizli kalacak ve başka bir karakterin “parçası” gibi ele alınacak şekilde kodlamak.
    • Fark şu: PUA karakterleri çoğu zaman bir şekilde epey görünür biçimde render edilirken, variation selector’lar öyle değildir.
    • Eksik bırakılan bir bağlam var: Bu fikir, Open Heart Protocol gönderisi etrafındaki tartışmadan çıktı.
      https://news.ycombinator.com/item?id=42791378
      API’nin yalnızca emoji kabul etmesi kısıtı nedeniyle suç amaçlı kullanım ihtimali hemen tartışılmaya başlandı. O kullanımda PUA kullanılamaz; emojinin içine kodlamak gerekir.
    • Bu, private-use area’dan çok atanmış karakter olmayanlar (noncharacter) kullanımına daha yakın değil mi? PUA, Unicode’a henüz girmemiş yazı sistemlerinin gayriresmî kodlamaları ya da Apple logosu gibi şeyler için de kullanılıyor; böyle kullanıldığında çakışma endişesi doğuyor.
      Atanmış noncharacter’lar arasında 0xFFFF, 0xFFFE ve her düzlemin son iki kod noktası olduğu gibi, Arabic Presentation Forms’un ortasında bir bölge de var. İnsanların bu şekilde kullanabileceği daha fazla noncharacter olsun diye listenin sonradan genişletildiğini biliyorum.
    • Açıkçası bu yorumu verilen decoder’a yapıştırıp denedim. Bu kadar bariz noktayı kaçırmış olamaz diye, içinde gizli bir mesaj vardır sandım; ama ya gerçekten kaçırmış ya da bu web sitesi onları temizliyor gibi görünüyor.
      Tanınmayan PUA karakterleriyle rastgele karakterleri görünmez şekilde watermark’lamak mümkün değil. Çünkü bunlar birleşen karakter olarak ele alınmaz. Bunun yerine ayrı render edilen yer tutucu kutular görünür. Örn: — tabii private use area’yı gerçekten kendi özel kullanımınız için kullanıyorsanız kutu olmayabilir.
  • Yaklaşık 10 yıl önce Windows dosya adının ortasına U+202D LEFT-TO-RIGHT OVERRIDE koyarak iş arkadaşlarımı şaşırtmıştım. funnypicturegnp.exe, funnypictureexe.png gibi görünüyordu.
    Fotoğraf önizlemesi gibi görünen özel bir ikon da ekleyince epey inandırıcı oluyordu.

    • Phishing tespiti işi yapıyordum; saldırganların sık kullandığı bir kalıptı. .exe çoğu zaman otomatik engellenir, ama günümüzde zararlı uzantı çoğunlukla .html oluyor ve obfuscate edilmiş window.location yönlendirmesiyle sahte giriş sayfası açıyorlar.
      cute-cat-lmth.png gibi RTL kötüye kullanımları nispeten yaygındı ama tespitleri de çok kolaydı; böyle e-postaları anında phishing olarak işaretliyorduk.
    • Bunun kaynak kodu versiyonu CVE-2021-42574 ve bir web sitesi de var.
      https://trojansource.codes/
      Temelde yorum gibi görünen ama derlendiğinde kod olarak çalışan kodu gizleyebiliyorsunuz. Yine de birçok metin editörü bu tür şüpheli yorumları zaten görünür kıldığı için CVE statüsünün tartışmalı olduğunu hatırlıyorum.
    • Bu spesifik numarayı bilmiyordum ama onlarca yıldır potansiyel olarak şüpheli medya dosyalarını hep “sağ tık → birlikte aç” ile açma şeklindeki paranoyak alışkanlığımın haklı çıkmasına sevindim.
    • guitar_tab.txt adlı bir bat dosyası oluşturmuştum
  • Gerçek bir kullanım örneği olarak Sanity, bu numarayı kullanarak Content Source Maps’i “önizleme modu”nda web sayfasına sunulan gerçek metnin içine kodlamış0. Editörler yalnızca ilgili metne veya içeriğe tıklayarak derin içerik yapısı içindeki özgün konuma kolayca iz sürebiliyor
    Dezavantajları ve sınırlamaları da var. Örneğin tarihler, zaman damgaları, URL’ler, ID’ler gibi olduğu gibi ayrıştırılması veya kullanılması gereken değerlere eklenmesini engellemek gerekiyor. Yine de oldukça eğlenceli bir numara
    0 https://www.sanity.io/docs/stega
    [1] https://github.com/sanity-io/content-source-maps

  • Bunu LLM çıktısı filigranlama için kullanma fikri hoşuma gitti. Tam doğru nokta. Zaten sadece kopyala-yapıştır yapan düşük kaliteli üreticilerin %99’u mecburen yakalanır; diğer temel kullanım senaryolarını da neredeyse hiç etkilemez
    Her karaktere ya da çıktı token’ına ne kadar yerleştirileceğini de merak ediyorum. Kullanıcı ID’si, prompt referansı, tarih, token numarası gibi şeyler mi? Terminalde nasıl yorumlandığını da merak ediyorum; gerçekten harika

    • Herkesin neden yapay zeka filigranlamasının işe yarayacağını düşündüğünü anlamıyorum. Her tür filigran anında ve kolayca kaldırılabildiği için asla doğru düzgün çalışmayacak
      Gerçek yapay zeka savunması, tüm insan etkileşimlerinde gerçek kimlikle doğrulanmış anahtar imzası istemekten ibaret; o da A: asla gerçekleşmeyecek, B: yozlaşmış hükümetlerin olduğu ülkelerde veya özel sektörün güçlü etkisi altındaki yozlaşmış hükümetlerin olduğu ülkelerde, örneğin ABD’de, kötüye kullanılabilir
    • Veri kümesine koymadan önce yapılan ön işleme o kadar çok ki, böyle bir oyunun pratikte işe yaraması şaşırtıcı olurdu
    • Çoğu Linux terminalinde ilettiğiniz şey olduğu gibi bir bayt dizisi olarak geçer. Bu teknik UTF-8’e uygundur ve ek glif kullanmaz; dolayısıyla Unicode’a uyan terminallerde insan gözüyle görünmez. Birkaçında denedim
      Elbette cümleyi xxd’ye gönderirseniz görünür. Şu anki en üst yorumdaki PUA önerisi ise hemen görünür olmasından farklı
      Ek test yaptığımda, terminale yapıştırdıktan sonra xxd içinde mesaj tamamen bozulmadan geçiyor; ancak terminalde seçip tekrar yapıştırınca mate terminal ve konsole’un X selection’ında yalnızca birkaç kelime kalıp kesildi. Kesilmenin terminalden mi X’ten mi kaynaklandığını bilmiyorum. xterm’de son e değişti ve seçilen içerik daha da fazla kesildi
      Dosyaya cümle bozulmadan yazılıyor. Bu yüzden sorun, terminal dışına kopyalarken bazı verilerin düşmesine daha yakın görünüyor. Cümleyi bir test dosyasına echo edip tarayıcıda açarak metni kopyalayıp kontrol ettim
    • LLM filigranlaması için çok daha sağlam ve tespit edilmesi daha zor başka yaklaşımlar da var. LLM’in olası her sonraki token için olasılık verdiği bir olasılık dağılımı oluşturup, çıktıyı bunların içinden rastgele örnekleyerek üretmesi gerçeğinden yararlanıyor
      Üretim sırasında örnekleme yöntemine bir oyun eklerseniz, daha sonra LLM’i tekrar çalıştırıp çıktı desenini gözlemleyerek parmak izini tespit edebilirsiniz. Örneğin yüksek olasılıklı token’lar ile düşük olasılıklı token’ları dönüşümlü seçmek gibi. Gerçek uygulama elbette çok daha incelikli olurdu, ama fikir bu yönde
  • İlginç bir nokta olarak, ekran okuyucular karakter karakter ilerlerken bu variation selector’ları algılayabiliyor. Örneğin üzerinde ok tuşlarıyla ilerleyince “Smiling face with smiling eyes”, “Symbol e zero one five five”, “Symbol e zero one five c” diye okuyor
    Ancak bu, kullanılan konuşma sentezleyicisine göre değişiyor ve belgeyi normal biçimde okurken böyle karakterler olup olmadığını anlayamadığınız için genel olarak büyük bir avantaj değil

    • Çevrimiçi metinlerin geneli görünmeyen ama dinlerken sinir bozucu karakterlerle kirlenmiş durumda; bu yüzden ekran okuyucumda tüm ASCII olmayan karakterleri kaldıran bir betik kullanıyorum
  • StegCloak0 da benzer aileden ve gizli payload’u AES-256-CTR ile şifreleyerek bu fikri bir adım daha ileri taşıyor. Oldukça hoş küçük bir numara
    0 https://github.com/KuroLabs/stegcloak

    • Better Discord eklentilerinden birinde bunun ya da benzer bir yöntemin kullanıldığını sanıyorum. Başkalarına hiçbir şey yokmuş gibi görünen tamamen şifreli mesajlar gönderebiliyorsunuz
      Ancak karşı tarafın çözebilmesi için parola sırrını paylaşmanız gerekiyor
    • Cloudflare DNS TXT kaydı üzerinde denemek istemiştim, ama Cloudflare akıllıca davranıp TXT alanına yapıştırırken bunu decode ediyor
  • Başlık biraz yanıltıcı. “Temel karakterin emoji olması gerekmez; variation selector işleme normal karakterlerde de aynıdır. Emojiyle yapmak sadece daha eğlencelidir” deniyor
    Bu yöntemi emoji olmayan karakterlerde kullanırsanız daha gizli ve daha rahatsız edici olur

    • O kadar da rahatsız edici değil gibi. Bir algılayıcıyı yapmak zor olmaz. Gerçek bir varyasyonu olmayan karaktere variant eklenmişse gösterirsiniz. Hatta imza amacıyla bile kullanılabilir gibi görünüyor
  • Basit LLM çıktısı filigranlamasından ziyade, bu logprobs verisini birlikte paketlemenin temiz bir yolu olabilir gibi görünüyor
    Temelde, üretilen tüm token’ların olasılık bilgisini dahil ederek üretim sürecine bir miktar şeffaflık katmak. OpenAI API spesifikasyonunda da var ve llama.cpp gibi birçok motor da bu bilgiyi sağlıyor. Genellikle ayrı bir alan olarak ekleniyor, ama mikupad0 gibi görselleştirme yöntemleri de var
    Muhtemelen kötü bir fikir, ama yine de aklı kurcalayan bir düşünce

  • Harika bir teknik. ASCII’yi yansıtıyor ve UI öğeleri, özellikle de web uygulamalarında pek sık görünmeyen Unicode Tag karakterleri de var
    Tag karakterlerinin ilginç yanı, bazı LLM’lerin gizli metni ASCII olarak yorumlayıp talimatları izlemesi; hatta bunların doğrudan yazılabilmesi
    https://embracethered.com/blog/posts/2024/hiding-and-finding...
    Microsoft’un Copilot’ta düzelttiği gerçek bir exploit kavram kanıtı da var
    https://embracethered.com/blog/posts/2024/m365-copilot-promp...