2 पॉइंट द्वारा GN⁺ 2025-02-13 | 1 टिप्पणियां | WhatsApp पर शेयर करें
  • Unicode के variation selector को जोड़ते जाने से, स्क्रीन पर न दिखने वाली लेकिन copy-paste के साथ चलने वाली byte sequence को एक अक्षर के पीछे छिपाया जा सकता है
  • VS-1 से VS-256 तक 256 variation selector होते हैं, इसलिए 1-byte range से बिल्कुल मेल खाने वाली mapping बनाई जा सकती है
  • 😊 के पीछे hello के bytes [0x68, 0x65, 0x6c, 0x6c, 0x6f] जोड़ने पर भी बाहर से यह एक सामान्य इमोटिकॉन जैसा दिखता है
  • decoding में U+FE00..U+FE0F और U+E0100..U+E01EF ranges ढूंढकर उन्हें फिर से bytes में बदला जाता है, और base character का इमोटिकॉन होना जरूरी नहीं है
  • यह तरीका Unicode का दुरुपयोग है, और human content filter को bypass करने या text में watermark डालने के लिए गलत इस्तेमाल हो सकता है

अदृश्य डेटा एक अक्षर से कैसे जुड़ता है

  • Unicode text को codepoint की sequence के रूप में दिखाया जाता है, और आम तौर पर U+XXXX format में लिखा जाता है
  • सरल Latin अक्षरों में codepoint और स्क्रीन पर दिखने वाले अक्षर का 1:1 mapping होता है
    • उदाहरण: U+0067 अक्षर g को दर्शाता है
  • दूसरे writing systems में स्क्रीन पर दिखने वाला एक अक्षर कई codepoints से मिलकर बन सकता है
    • उदाहरण: देवनागरी में की के रूप में पढ़ा जाने वाला अक्षर U+0915 और U+0940 की लगातार pair से दर्शाया जाता है

Variation selector को data store की तरह इस्तेमाल करना

  • Unicode 256 variation selector codepoints define करता है, जिनके नाम VS-1 से VS-256 तक हैं
  • variation selector खुद स्क्रीन पर नहीं दिखता, और इससे पिछले अक्षर के display style को बदलने के लिए इस्तेमाल किया जाता है
  • अधिकतर Unicode अक्षरों के साथ कोई जुड़ा हुआ variant नहीं होता, लेकिन Unicode future compatibility को लक्ष्य बनाता है, इसलिए meaning न समझने वाले processing code को भी variation selector preserve करना चाहिए
    • U+0067(g) के बाद U+FE01(VS-2) जोड़ने पर भी स्क्रीन पर यह lowercase g जैसा दिखता है
    • copy-paste करने पर variation selector भी साथ चलता है
  • 256 variation selector ठीक 1 byte को represent करने के लिए पर्याप्त संख्या है, इसलिए किसी भी Unicode codepoint के पीछे 1-byte data छिपाया जा सकता है
  • Unicode specification कई variation selector की लगातार sequence को विस्तार से handle नहीं करता, और संकेत देता है कि rendering के दौरान इन्हें ignore किया जाना चाहिए
  • कई variation selector को जोड़ते जाने से arbitrary byte sequence को एक अक्षर के पीछे represent किया जा सकता है

bytes को variation selector में encode करना

  • variation selector दो codepoint ranges में बंटे होते हैं
    • U+FE00 .. U+FE0F: पहले 16
    • U+E0100 .. U+E01EF: बाकी 240
  • byte को variation selector में बदलने का rule सरल है
    • अगर byte 16 से छोटा है, तो 0xFE00 + byte
    • वरना 0xE0100 + (byte - 16)
  • encoding में पहले एक base character डाला जाता है, और उसके बाद हर byte को variation selector में convert करके जोड़ा जाता है
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 को दिखाने वाले bytes [0x68, 0x65, 0x6c, 0x6c, 0x6f] को 😊 के पीछे जोड़ने पर बाहर से यह सामान्य इमोटिकॉन जैसा दिखने वाला string बन जाता है
  • सामान्य output में छिपे हुए अक्षर नहीं दिखते, लेकिन Rust के debug format से print करने पर \u{e0158} जैसे छिपे codepoints दिख जाते हैं
"😊\u{e0158}\u{e0155}\u{e015c}\u{e015c}\u{e015f}"

छिपे हुए bytes को फिर से पढ़ने का तरीका

  • decoding में characters पर iterate करते हुए variation selector range में मौजूद codepoints को फिर से bytes में convert किया जाता है
  • U+FE00..U+FE0F range को variation_selector - 0xFE00 से restore किया जाता है
  • U+E0100..U+E01EF range को variation_selector - 0xE0100 + 16 से restore किया जाता है
  • पहले variation selector से पहले आने वाले सामान्य अक्षरों को base character माना जाता है और ignore किया जाता है
  • अगर कोई non-variation selector character मिले और result पहले से मौजूद हो, तो decoding खत्म कर दी जाती है
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
    }
}
  • उसी encoding result को decode करने के बाद UTF-8 के रूप में interpret करें, तो "hello" मिलता है
  • base character का इमोटिकॉन होना जरूरी नहीं है, और सामान्य अक्षरों में भी variation selector processing समान होती है
  • इमोटिकॉन इस्तेमाल करने की वजह बस यह है कि यह ज्यादा मजेदार है

दुरुपयोग की संभावना

  • यह तरीका Unicode abuse है और इसका इस्तेमाल नहीं करना चाहिए
  • rendered result में data नहीं दिखता, इसलिए human moderator या reviewer के लिए छिपे data की मौजूदगी समझना मुश्किल होता है
  • human content filter को पार करके data छिपाने के तरीके के रूप में इसका दुरुपयोग हो सकता है
  • text watermarking के लिए भी इसका इस्तेमाल किया जा सकता है
    • message को कई लोगों को भेजने के बाद अगर leak हो जाए, तो मूल recipient को trace किया जा सकता है
    • variation selector sequence अधिकतर copy-paste में बची रहती है
    • यह arbitrary data density की अनुमति देता है, और चाहें तो हर character में watermark डाला जा सकता है

क्या LLM छिपे हुए data को process कर सकते हैं

  • Hacker News पर पोस्ट होने के बाद यह सवाल उठा कि LLM इस तरह के छिपे data को कैसे handle करते हैं
  • आम तौर पर tokenizer variation selector को token के रूप में preserve करता दिखता है, इसलिए theoretically model इन्हें access कर सकता है
  • OpenAI tokenizer इसे verify करने के लिए इस्तेमाल किया जा सकने वाला checking tool है
  • कुल मिलाकर models internally सीधे decoding की कोशिश नहीं करते दिखते
  • code interpreter के साथ इस्तेमाल करने पर कुछ models छिपे data को खोल सकते हैं
    • Gemini 2 Flash ने Codename Goose और foreverVM का इस्तेमाल करके 7 सेकंड में एक example solve किया
    • Claude द्वारा एक example solve करने का लंबा video भी है

1 टिप्पणियां

 
GN⁺ 2025-02-13
Hacker News की राय
  • Unicode के दुरुपयोग के लिहाज से यह तो बस हिमशैल का सिरा है। मिलती-जुलती तकनीकों से Unicode strings लेने वाले कई सिस्टमों में buffer overflow कराया जा सकता है, और आमतौर पर बात error या crash पर खत्म होती है, लेकिन किस्मत अच्छी हो तो काफी दिलचस्प behavior भी मिल सकता है
    Python 3 से पहले के दौर में penetration testing करते समय, मैंने सिर्फ diacritical marks के जरिए एक character को कई bytes में बढ़ाकर backend web server का buffer overflow कराया था। तब बस crash और automatic restart हुआ था, लेकिन गहराई से खंगाला जाए तो यह किसी खास system या software exploit में काम आ सकता है

    • Google CTF quals 2024 की "encrypted runner" समस्या इसी विचार पर आधारित थी
    • सही। Zalgo text website input fields के लिए एक आम test case है, लेकिन आमतौर पर कुछ दिलचस्प नहीं होता। कभी-कभी database length limit exception छू जाता है, और आम तौर पर process मरता भी नहीं; exception मौजूदा thread के अंदर ही खत्म हो जाता है
      आधुनिक forms में भी सिर्फ JavaScript बंद करके कुछ ऐसा ही कराया जा सकता है, और सबसे अच्छे मामले में debug चालू हो तो stack trace या query print हो जाती है और थोड़ी जानकारी leak हो जाती है। एक और आम गलती text strings में \n और \r\n की length गलत गिनना है; JavaScript आमतौर पर carriage return को 1 byte गिनता है, लेकिन HTTP spec 2 bytes मांगता है
      unescape(encodeURIComponent("ç")).length JavaScript में byte length जल्दी जांचने का एक मोटा-मोटा तरीका है, और \r\n वाली समस्या length गिनने से पहले string को normalize/clean करके हल की जा सकती है
    • मैं beginner हूं, क्या कोई और समझा सकता है कि यह कैसे होता है या आपने कैसे किया? यह test करके देखने लायक loophole जैसा लग रहा है
  • यह cute है, लेकिन जरूरी नहीं। Unicode में PUA(private use area) नाम का एक बड़ा range है, और इस range के codes किसी भी character से mapped नहीं हैं और आगे भी नहीं होंगे, इसलिए इन्हें internal/user-defined purposes के लिए इस्तेमाल किया जाता है
    उदाहरण के लिए, fish-shell में tokens को string के रूप में सुरक्षित तरीके से parse करते समय, unescaped special characters को string के अंदर दूसरे Unicode code points की तरह बदल दिया जाता है लेकिन PUA area में रखा जाता है, और बाद में pipeline में intercept किया जाता है। इन्हें API boundary के बाहर expose नहीं करना चाहिए, लेकिन सामने आने पर इन्हें जस का तस pass through करना recommended है और ज्यादातर systems और libraries भी ऐसा ही करते हैं। यह साफ तौर पर leakage path बन सकता है, लेकिन कई सामान्य developers Unicode के बारे में “internationalization समस्याओं से बचना हो तो हमेशा Unicode इस्तेमाल करें” से ज्यादा नहीं जानते, इसलिए यह अक्सर खुला रह जाता है

    • खुद test करके देखा तो private use characters मेरे environment में boxes के रूप में render होते हैं(󰀀)। यहां मुख्य बात यह है कि encode ऐसा हो कि copy-paste करते समय छिपा रहे और किसी दूसरे character का “हिस्सा” माना जाए
    • फर्क यह है कि PUA characters आमतौर पर किसी न किसी तरह काफी दिखाई देकर render होते हैं, लेकिन variation selector ऐसा नहीं करता
    • एक संदर्भ छूट गया है: यह idea Open Heart Protocol submission के आसपास हुई चर्चा से आया था
      https://news.ycombinator.com/item?id=42791378
      API सिर्फ emoji स्वीकार करता है, इस restriction की वजह से तुरंत ही criminal use की संभावना पर चर्चा हुई। उस use case में PUA इस्तेमाल नहीं कर सकते, emoji के अंदर ही encode करना पड़ता है
    • यह private-use area के बजाय designated noncharacter use case के ज्यादा करीब नहीं है? PUA का इस्तेमाल कभी-कभी Unicode में अभी शामिल नहीं हुई scripts की unofficial encoding या Apple logo जैसी चीजों के लिए भी होता है, इसलिए इस तरह इस्तेमाल करने पर collision की चिंता होती है
      designated noncharacters में 0xFFFF, 0xFFFE या हर plane के आखिरी दो code points ही नहीं, बल्कि Arabic Presentation Forms के बीच का एक area भी शामिल है। मेरी जानकारी में बाद में list में इन्हें इसलिए जोड़ा गया था ताकि लोगों के पास इस तरह इस्तेमाल करने के लिए और noncharacters हों
    • सच कहूं तो मैंने यह comment दिए गए decoder में paste करके देखा। लगा था कि कोई इतने स्तर पर point miss नहीं कर सकता और अंदर कोई hidden message होगा, लेकिन लगता है सचमुच miss किया है या यह website उन्हें remove कर रही है
      अनपहचाने PUA characters से arbitrary characters को अदृश्य रूप से watermark नहीं किया जा सकता। वजह यह है कि उन्हें combining characters की तरह treat नहीं किया जाता। इसके बजाय अलग से render होने वाला placeholder box दिखाई देता है। उदाहरण: — हां, अगर आप private use area को खुद निजी तौर पर use कर रहे हैं तो शायद box न दिखे
  • करीब 10 साल पहले Windows filenames के बीच में U+202D LEFT-TO-RIGHT OVERRIDE डालकर मैंने coworkers को चौंकाया था। funnypicturegnp.exe ऐसा दिखता था जैसे funnypictureexe.png हो
    अगर photo preview जैसा दिखने वाला custom icon भी लगा दें तो यह काफी believable लगता था

    • मैं phishing detection में काम करता था, और attackers यह pattern अक्सर इस्तेमाल करते थे। .exe ज्यादातर automatically block हो जाता है, लेकिन आजकल malicious extension अक्सर .html होता है, और obfuscated window.location redirect के जरिए fake login page खोलता है
      cute-cat-lmth.png जैसे RTL abuse relatively common थे, लेकिन detect करना भी बहुत आसान था, और ऐसे emails को तुरंत phishing के रूप में mark कर देते थे
    • इसका source code version CVE-2021-42574 है, और इसकी website भी है
      https://trojansource.codes/
      मूल रूप से आप ऐसा code छिपा सकते हैं जो comment जैसा दिखता है, लेकिन compile होने पर code की तरह काम करता है। हालांकि मुझे याद है कि CVE status विवादित था, क्योंकि कई text editors पहले से ही ऐसे suspicious comments को visible बना देते हैं
    • इस specific trick के बारे में नहीं पता था, लेकिन खुशी है कि दशकों से potentially suspicious media files को हमेशा “right-click → open with” से खोलने वाली मेरी paranoid habit सही साबित हुई
    • मैंने कभी guitar_tab.txt नाम की एक bat file बनाई थी
  • वास्तविक उपयोग के उदाहरण के तौर पर, Sanity ने इस ट्रिक का इस्तेमाल करके Content Source Maps को “preview mode” में वेबपेज पर दिए जाने वाले असली टेक्स्ट के अंदर encode किया था0. Editor सिर्फ उस टेक्स्ट या content पर क्लिक करके गहरी content structure के भीतर original location तक आसानी से trace कर सकते हैं
    कमियां और सीमाएं भी हैं। उदाहरण के लिए date·timestamp, URL, ID जैसी values जिन्हें जस का तस parse या use करना हो, उनमें इसे add होने से रोकना होगा। फिर भी यह काफी मजेदार trick है
    0 https://www.sanity.io/docs/stega
    [1] https://github.com/sanity-io/content-source-maps

  • मुझे इसे LLM output watermarking के लिए इस्तेमाल करने का idea पसंद है। यह बिल्कुल सही जगह है। वैसे भी सिर्फ copy-paste करने वाले 99% low-quality generators बच नहीं पाएंगे, और बाकी core use cases पर लगभग कोई असर नहीं पड़ेगा
    यह भी जानना दिलचस्प होगा कि हर character या output token में कितना डाला जाएगा। User ID, prompt reference, date, token number जैसी चीजें होंगी क्या? Terminal में इसे कैसे interpret किया जाएगा, यह भी जानना चाहूंगा; सच में बढ़िया है

    • समझ नहीं आता कि सबको क्यों लगता है कि AI watermarking काम करेगी। कोई भी watermarking तुरंत और आसानी से हटाई जा सकती है, इसलिए वह कभी ठीक से काम नहीं करेगी
      असली AI defense तो सिर्फ यह है कि हर human interaction के लिए वास्तविक identity से verified key signature मांगा जाए, लेकिन वह भी A: कभी होने वाला नहीं है, और B: भ्रष्ट सरकारों वाले देशों में या private industry के भारी प्रभाव वाली भ्रष्ट सरकारों वाले देशों में, जैसे अमेरिका, misuse हो सकता है
    • Dataset में डालने से पहले इतनी ज्यादा preprocessing होती है कि ऐसी शरारत वाकई काम करेगी तो हैरानी होगी
    • ज्यादातर Linux terminals में जो pass किया जाता है वह बस byte sequence के रूप में जस का तस आगे चला जाता है। यह तकनीक UTF-8 के अनुकूल है और extra glyphs का इस्तेमाल नहीं करती, इसलिए Unicode-compliant terminals में इंसानी आंखों को दिखाई नहीं देती। मैंने कुछ में test करके देखा है
      बेशक अगर वाक्य को xxd में भेजेंगे तो दिखेगा। अभी top-level comment में दिया गया PUA suggestion तुरंत visible होने वाली चीज से अलग है
      कुछ और tests करने पर, terminal में paste करने के बाद xxd में message पूरी तरह बिना बदले गुजरता है, लेकिन terminal से select करके फिर paste करने पर mate terminal और konsole की X selection में सिर्फ कुछ words बचे और बाकी कट गया। कटना terminal की वजह से है या X की, पता नहीं। xterm में आखिरी e बदल गया और selected content और ज्यादा कट गया
      File में वाक्य बिना बदले record हो जाता है। इसलिए लगता है कि terminal से बाहर copy करते समय कुछ data गिर जाता है। मैंने वाक्य को test file में echo किया और browser में खोलकर text copy करके check किया
    • LLM watermarking के लिए इससे कहीं ज्यादा मजबूत और detect करना कठिन दूसरे approaches भी हैं। वे इस बात का फायदा उठाते हैं कि LLM हर possible next token के लिए probability देने वाला probability distribution बनाता है, और फिर उनमें से random sampling करके output बनाता है
      Generation के समय sampling method में कुछ छेड़छाड़ डाल दें, तो बाद में LLM को फिर चलाकर output pattern observe करके fingerprint detect किया जा सकता है। जैसे high-probability tokens और low-probability tokens को बारी-बारी चुनना। असली implementation जाहिर है काफी ज्यादा sophisticated होगा, लेकिन idea इसी दिशा का है
  • दिलचस्प बात यह है कि screen readers character-by-character move करते समय ऐसे variation selector को detect कर सकते हैं। Example के ऊपर arrows से move करने पर “Smiling face with smiling eyes”, “Symbol e zero one five five”, “Symbol e zero one five c” जैसा पढ़ता है
    हालांकि यह इस्तेमाल हो रहे speech synthesizer पर निर्भर करता है, और अगर document को बस सामान्य रूप से पढ़ रहे हों तो ऐसे characters मौजूद हैं या नहीं, पता नहीं चलता, इसलिए कुल मिलाकर कोई बड़ा फायदा नहीं है

    • Online text कुल मिलाकर ऐसे characters से contaminated है जो दिखते नहीं लेकिन सुनने में irritating होते हैं, इसलिए मैं अपने screen reader में non-ASCII characters को पूरी तरह हटाने वाली script इस्तेमाल करता हूं
  • StegCloak0 भी इसी तरह की category का है, और hidden payload को AES-256-CTR से encrypt करके इस idea को एक कदम और आगे ले जाता है। काफी बढ़िया छोटी trick है
    0 https://github.com/KuroLabs/stegcloak

    • लगता है Better Discord plugins में से कोई इस या मिलती-जुलती method का इस्तेमाल करता है। आप ऐसा fully encrypted message भेज सकते हैं जो दूसरों को कुछ भी नहीं जैसा दिखता है
      हालांकि सामने वाले को decode करने के लिए password secret value share करनी होगी
    • Cloudflare DNS TXT record में test करने की कोशिश की थी, लेकिन Cloudflare ने smartly TXT field में paste करते समय ही उसे decode कर दिया
  • Title थोड़ा misleading है। उसमें लिखा है, “base character का emoji होना जरूरी नहीं है, और variation selector की handling normal characters में भी वही रहती है। Emoji से बस यह ज्यादा मजेदार हो जाता है”
    इस method को non-emoji characters पर इस्तेमाल करें तो यह ज्यादा stealthy और ज्यादा inconvenient हो जाता है

    • लगता है इतना भी inconvenient नहीं है। Detector आसानी से बनाया जा सकता है। जिन characters में असली variation नहीं है, उनके साथ variant जुड़ा हो तो दिखा दें। उल्टा यह signature use case के लिए भी इस्तेमाल हो सकता है
  • साधारण LLM output watermarking से भी बढ़कर, यह logprobs data को साथ में package करने का साफ-सुथरा तरीका हो सकता है
    मूल रूप से generated हर token की probability information शामिल करके generation process में थोड़ी transparency देना। OpenAI API spec में भी यह शामिल है, और llama.cpp जैसे कई engines भी यह जानकारी देते हैं। आम तौर पर यह अलग field के रूप में जुड़ती है, लेकिन mikupad0 जैसी visualization methods भी हैं
    शायद यह खराब idea होगा, फिर भी दिमाग में खटकता रहने वाला विचार है

  • शानदार तकनीक है। यह ASCII को दर्शाती है और इसमें UI elements, खासकर web apps में अक्सर न दिखने वाले Unicode Tag characters भी हैं
    Tag characters की खास बात यह है कि कुछ LLM छिपे हुए text को ASCII के रूप में interpret करके निर्देशों का पालन करते हैं, और उन्हें सीधे लिखा भी जा सकता है
    https://embracethered.com/blog/posts/2024/hiding-and-finding...
    Microsoft द्वारा Copilot में fix किए गए एक वास्तविक exploit का proof of concept भी है
    https://embracethered.com/blog/posts/2024/m365-copilot-promp...