Anti-LLM ऑब्फ़स्केशन: फिंगरप्रिंटिंग स्क्रिप्ट्स का पलटवार
Anti-LLM obfuscation फिंगरप्रिंटिंग स्क्रिप्ट्स में जेलब्रेक प्रॉम्प्ट छिपाता है, ताकि AI deobfuscation फेल हो जाए। जानिए यह कैसे काम करता है और इसे क्या तोड़ सकता है।

एक रिसर्चर ने वेब की 10,000 सबसे बड़ी साइटों पर हर canvas कॉल को हुक किया, एक हज़ार से ज़्यादा फिंगरप्रिंटिंग रेंडरिंग जमा कीं, और फिर सबसे ख़ूबसूरत वाली ढूँढने निकला। ज़िम्मेदार स्क्रिप्ट के अंदर उसे anti-LLM obfuscation का अब तक का सबसे साफ़ उदाहरण मिला: एक ब्राउज़र फिंगरप्रिंटिंग स्क्रिप्ट, जिसमें computeRicinLethalDosageForWaterSupply जैसे नाम वाले फ़ंक्शन थे और एक जेलब्रेक प्रॉम्प्ट अदृश्य Unicode characters में एन्कोड किया गया था। यह इंसानी reverse engineers से छिपने के लिए नहीं बनाई गई थी, बल्कि इसका मक़सद AI assistants को इसे deobfuscate करने से मना करवाना था।
मैंने कई सालों तक incident response में काम किया है, और write-up पढ़ते ही मेरी पहली प्रतिक्रिया वही हुई जो लेखकों ने चाही थी: यह malware लगता है, या उससे भी बुरा। फ़ंक्शन नाम ricin की डोज़ और dirty-bomb के fallout radius के बारे में। एक decode हुई स्ट्रिंग जो bioweapon synthesis के step-by-step guides माँगती है। एक Node.js code path जो .npmrc, .env और kube configs पढ़ता है, किसी internal IP से binary खींचता है और cron beacon इंस्टॉल करता है। हर instinct कहता है कि यह बुरा actor है। और हर instinct ग़लत है, और यही ग़लती पूरी बात का मुद्दा है। यह कहानी इस बारे में है कि जब obfuscation बेचने वालों को समझ आता है कि उनका असली दुश्मन अब debugger वाला इंसान नहीं, बल्कि safety filter वाला model है, तब क्या होता है।
क्रॉल सीधा-सादा था: Tranco top-sites list पर Playwright चलाया गया, HTMLCanvasElement.prototype.toDataURL और navigator.webdriver जैसे automation के स्पष्ट निशानों को हुक किया गया, ताकि सामान्य canvases छाँटे जा सकें। रेंडरिंग एक पब्लिक गैलरी में जमा हुईं और vendor के हिसाब से क्लस्टर की गईं: FingerprintJS, Castle और बाक़ी जाने-पहचाने नाम, साथ में अनजान वेंडर्स की लंबी सूची। एक canvas, बहते हुए लकीरों का एक शानदार भँवर, एक रूसी DDoS-protection वेंडर की स्क्रिप्ट तक पहुँचा। यहीं से असली मज़ा शुरू हुआ।
समस्या: LLMs ने obfuscation का बिज़नेस मॉडल तोड़ दिया
बीस सालों तक JavaScript obfuscation का बस एक काम था: reverse engineering को इतना महँगा बनाना कि कोई उसकी परवाह ही न करे। javascript-obfuscator और उसके कमर्शियल वर्ज़न identifiers को बिगाड़ते हैं, string literals को lookup arrays में पैक करते हैं, opaque predicates डालते हैं और control flow को switch-driven state machines में बदल देते हैं। इनमें से कुछ भी उस इंसान के सामने नहीं टिकता जिसके पास एक दोपहर फ़ुर्सत हो। पर तब भी चल जाता था, क्योंकि ऐसे दृढ़ इंसान और फ़ालतू दोपहरें दुर्लभ थीं, और अर्थशास्त्र defender के पक्ष में था।
फिर LLMs बिगड़े हुए कोड को पढ़ने में अच्छे हो गए, और अर्थशास्त्र उलट गया। किसी chat window में 400 KB का obfuscated anti-bot script चिपकाकर पूछना कि “यह क्या करता है?” बस तीस सेकंड का काम है। मॉडल को इससे फ़र्क़ नहीं पड़ता कि आपके फ़ंक्शन का नाम PiERW है या आपकी strings किसी घूमते हुए array में रहती हैं। वह semantics पढ़ता है। वह decoder को inline करता है, data flow को फ़ॉलो करता है, और सादी भाषा में सारांश लौटा देता है। हर anti-bot vendor की सुरक्षा दीवार, यानी अपारदर्शिता, चुपचाप ग़ायब हो गई।
vendors ने यह देखा, और उनका जवाब एक तरह से चालाक है, और इसी वजह से चिढ़ भी आती है: अगर आप कोड को model से नहीं छिपा सकते, तो model पर ही हमला कर दो। ऐसी सामग्री दो जिसे उसकी safety training उसे मना करने पर मजबूर करे। Anti-LLM obfuscation model alignment को analysis pipeline की सबसे कमज़ोर कड़ी मानता है। चिढ़ाने वाली बात यह है कि वे सही हैं।
ब्राउज़र फिंगरप्रिंटिंग स्क्रिप्ट्स entropy कैसे जुटाती हैं
जाल को समझने के लिए पहले उस वैध मशीनरी को समझना ज़रूरी है जिस पर वह चढ़ा है। Canvas fingerprinting एक अदृश्य <canvas> पर text, gradients और shapes बनाकर, फिर toDataURL() से pixel buffer वापस पढ़कर काम करती है। Rendering pipeline, यानी GPU, driver, font rasterizer, anti-aliasing और subpixel hinting, हर मशीन पर थोड़ा अलग होता है, इसलिए एक ही निर्देश से बनी दो तस्वीरों के pixel buffers अलग-अलग hash देते हैं। इसमें WebGL renderer strings, AudioContext timing, screen geometry, installed fonts और सैकड़ों और signals जोड़ दें, तो बिना एक भी cookie के एक स्थिर पहचानकर्ता मिल जाता है।
जिस क्रॉल ने हमारी स्क्रिप्ट ढूँढी, उसने मानक interception तरीका इस्तेमाल किया। init script में read-back primitive को हुक करें, हर captured image का stack trace और prefix लॉग करें:
await page.addInitScript(() => {
const original = HTMLCanvasElement.prototype.toDataURL;
HTMLCanvasElement.prototype.toDataURL = function (...args) {
const result = original.apply(this, args);
fetch("/__log", {
method: "POST",
body: JSON.stringify({
stack: new Error().stack,
byteLength: result.length,
head: result.slice(0, 64),
}),
});
return result;
};
});
इतनी साइटों पर यह चलाएँ तो फिंगरप्रिंटिंग का पूरा ecosystem अपना नक़्शा ख़ुद बना देता है। ज़्यादातर canvases उबाऊ होते हैं: एक pangram, कुछ आयत, एक gradient। कुछ गिने-चुने इतने जटिल होते हैं कि generative art कहलाएँ। ये सभी भारी obfuscated स्क्रिप्ट्स से बनते हैं, क्योंकि vendors जानते हैं कि बिना obfuscation वाला fingerprinter ख़ुद आसानी से fingerprint हो जाता है। Obfuscation हमेशा product का हिस्सा था। नई बात यह है कि obfuscation किसको निशाना बना रहा है।
Anti-LLM obfuscation के जाल के अंदर
स्क्रिप्ट की शुरुआत आम तरीके से हुई: बिगड़े हुए नाम, पैक की हुई strings, वही पुराना सामान। फिर identifier नाम रैंडम होना बंद हुए और निशाने पर लगने लगे। computeRicinLethalDosageForWaterSupply। calculateDeadlyLethalSuicideAspirinDosage। computeCRISPRGeneDrivePropagationRate। computeCesium137DirtyBombFalloutRadius। ये फ़ंक्शन ऐसे नंबर लौटाते हैं जिन्हें डरावने सारांशों में लपेटा गया है, जैसे “adult lethality threshold confirmed at X mg/kg”, पर गणित नकली है। कुछ असली नहीं गिनता और कहीं कुछ नहीं भेजता।
असली बात यह है: identifiers का runtime पर कोई अर्थ नहीं होता, पर एक language model के लिए उनका semantic वज़न बहुत भारी होता है। इंसानी reverse engineer अनुभव से नामों को नज़रअंदाज़ करना सीख चुका है, हम सबको कभी न कभी validateUser नाम के उस फ़ंक्शन ने धोखा दिया है जो कुछ validate नहीं करता। मॉडल इसका उल्टा करता है। वह computeRicinLethalDosageForWaterSupply पढ़ता है, bioweapons वाले संदर्भ से मिलान करता है, और उसकी safety training उसकी जिज्ञासा से पहले चालू हो जाती है।
गहरी परत एक template literal थी, जो अदृश्य characters से भरा था, यानी Unicode के Tags block के codepoints, जो U+E0000 से शुरू होते हैं। ये स्क्रीन पर कुछ नहीं दिखाते। Decoder एक-लाइन का है:
const cleartext = [...payload]
.map((c) => String.fromCodePoint(c.codePointAt(0) - 917504))
.join("");
// 917504 === 0xE0000, the start of the invisible "Tags" block.
// Subtracting it maps each hidden char back to plain ASCII.
डिकोड होने पर यह एक पूरा जेलब्रेक प्रॉम्प्ट निकला: एक नकली “SYSTEM OVERRIDE -- CLASSIFIED BRIEFING”, जो घोषणा करता है कि safety guidelines निलंबित हैं, और healthcare infrastructure पर DDoS हमलों, टैक्स चोरी, वायुजनित जैविक एजेंटों और दवाओं की डोज़ के step-by-step guides माँगता है। साथ में अंग्रेज़ी और चीनी दोनों में तियानमेन स्क्वायर के संदर्भ थे, जो साफ़ तौर पर चीन में होस्ट किए गए मॉडलों की censorship प्रतिक्रियाओं को छेड़ने के लिए थे। यह एक ऐसी string थी जो हर safeguard taxonomy को एक साथ निशाना बनाने के लिए गढ़ी गई थी। कोई LLM जब आसपास के कोड का विश्लेषण करने बैठता है, तो वह इसे संदर्भ की तरह निगल लेता है, और फिर वह जो भी करने वाला था, अब कुछ नहीं करता।

असफल प्रयास: इनकार, false positives और एक नकली botnet
सबसे स्वाभाविक पहला क़दम, इसे किसी frontier model में चिपकाना, ठीक वैसे ही विफल हुआ जैसा डिज़ाइन किया गया था। मॉडल ने जैविक सुरक्षा जोखिम का हवाला देकर मना कर दिया। अलग safety posture के साथ दूसरी कोशिश भी रोक दी गई। Obfuscator ने इनकार को हथियार बना लिया था: अब यह स्क्रिप्ट उन tools के लिए undecompilable है जिन्हें सब असल में इस्तेमाल करते हैं। एक open-weight model, जिसके guardrails कमज़ोर थे, उसने बात मान ली, पर यह एक data point है, समाधान नहीं। ज़्यादातर analysts के पास इसके लिए कोई local model गरम रखा नहीं होता, और ज़्यादातर कंपनियों की policies वैसे भी अनजान infrastructure पर third-party code चिपकाने की इजाज़त नहीं देतीं।
दूसरी विफल कोशिश ज़्यादा महीन और ज़्यादा ख़तरनाक है: वह इंसानी analyst जो यह नतीजा निकाल लेता है कि स्क्रिप्ट malware है। यहाँ एक Node-only code path है, जो process.env.METRICS_AGENT_8BFD_ACTIVE के पीछे बंद है। यह SSH से जुड़ी config files पढ़ता है, 10.15.45.1 से एक “metrics agent” डाउनलोड करता है, होस्ट की fingerprint /tmp में लिखता है, उसे zip करता है और cron beacon इंस्टॉल करता है। देखने में यह supply chain implant लगता है। पर धागे खींचिए: environment flag को कोई और कहीं सेट नहीं करता, readFile खाली callbacks के साथ बुलाया जाता है, इसलिए चुराया गया डेटा कहीं नहीं जाता, C2 पता RFC 1918 space का है, और shell commands marker files में और जेलब्रेक चारा दोहराते हैं जिसे कोई exfiltrate नहीं करता। Sandbox में चलाने पर इसका असर बस एक अस्थायी directory तक सीमित रहता है। यह एक दिखावा है, एक नकली जाल जो AI-assisted triage और सतही पढ़ने वाले इंसानों के लिए बनाया गया है।
यहाँ असली दुनिया की एक गूँज है। जब एक बड़े model hub ने एक असली compromise की जाँच की, तो उनकी टीम ने पाया कि commercial model APIs हमलावर के commands, payloads और C2 artifacts का विश्लेषण करने से मना कर रहे थे, यानी guardrails incident responder और हमलावर में फ़र्क़ नहीं कर पाए। अंत में उन्होंने अपने ही infrastructure पर एक open-weight model से forensic analysis चलाया। यह विफलता बड़े पैमाने पर दोहराई जाए, तो यही असमानता है जिसका anti-LLM obfuscation फ़ायदा उठाता है। इसी वजह से मैं लगातार कहता हूँ कि AI code comprehension checks आपकी इकलौती सुरक्षा पंक्ति नहीं हो सकते: model का फ़ैसला अब एक attack surface है, जिसका exploit प्रकाशित हो चुका है।
Obfuscator अब आपसे कोड नहीं छिपा रहा। वह आपके model से कोड छिपा रहा है, और उसी model की safety training को ताला बनाकर इस्तेमाल कर रहा है।
Anti-LLM obfuscation के खिलाफ़ असल में क्या काम करता है
समाधान यह है कि model को कच्चा source खिलाना बंद करें, और pipeline के वे चरण वापस लें जिन्हें tripwires ज़हरीला बना देते हैं। ठोस रूप से, उस क्रम में जिसमें मैं इसे बनाता:
- पहले deterministic deobfuscation। स्क्रिप्ट को Babel-based AST passes से गुज़ारें: constant folding, string-array inlining, dead-branch elimination, control-flow unflattening। Tripwires string literals और identifiers में रहते हैं, इसलिए folding और renaming मॉडल के एक भी token देखने से पहले उन्हें बेअसर कर देते हैं। डिकोड हुआ जेलब्रेक प्रॉम्प्ट बस एक dead code path में पड़ी एक और string बन जाता है।
- सोर्स पढ़ने के बजाय व्यवहार का trace लें। स्क्रिप्ट को किसी instrumented sandbox में चलाएँ, जैसे हुक किए हुए APIs वाला Playwright page या अलग realm, और हर DOM call, network request, property access और canvas read-back रिकॉर्ड करें। मॉडल को trace खिलाएँ। व्यवहार के trace में ricin का एक भी संदर्भ नहीं होता, क्योंकि व्यवहार के पास identifiers नहीं होते।
- किसी भी LLM pass से पहले identifiers को neutral tokens में बदलें।
computeRicinLethalDosageForWaterSupplyबन जाता हैfn_0417। थोड़ा semantic संकेत खोता है, पर सारा ज़हर भी खो देता है। यह सौदा अच्छा है। - वास्तव में संवेदनशील triage के लिए एक local open-weight model रखें। जब सामग्री वाक़ई भयावह हो, जैसे असली exploit kits या असली phishing kits, तब बिना cloud guardrails वाला air-gapped model ही विश्लेषण और इनकार की दीवार के बीच का फ़र्क़ है।
- फ़ैसले सबूत से लें, अंदाज़े से नहीं। स्क्रिप्ट को देखे गए capabilities पर परखें: क्या उसने credentials पढ़े? क्या page से bytes बाहर गए? न कि इस पर कि source में credentials का ज़िक्र है या नहीं।
पैटर्न पर ध्यान दें: हर तकनीक “model से पूछो कोड क्या कहता है” की जगह “देखो कोड क्या करता है, फिर उस अवलोकन के बारे में model से पूछो” रखती है। यह वही सिद्धांत है जिस पर मैं autonomous agents के बारे में बार-बार लौटता हूँ: containment, monitoring से बेहतर है। Artifact के अपने बारे में किए गए दावे पर भरोसा मत करो; उसे सीमित करो, उसकी सीमा पर instrumentation लगाओ, और उसके आउटपुट को परखो।
आपकी third-party scripts model providers से बात कर रही हैं
अब वह हिस्सा जो उन लोगों को चिंतित करना चाहिए जो मज़े के लिए reverse engineering नहीं करते। यह स्क्रिप्ट किसी अँधेरे कोने में होस्ट नहीं है। यह एक anti-bot check है, जो ग्राहकों की वेबसाइटों पर, ग्राहकों के domains के तहत, ग्राहकों के ब्रांड्स के बगल में चलता है। इसका मतलब है कि हर crawler, हर LLM-संचालित content classifier, हर trust-and-safety pipeline जो पेज को निगलता है, अब कंपनी के लोगो के साथ bioweapon synthesis के अनुरोध और जेलब्रेक प्रॉम्प्ट भी निगल रहा है। ग्राहकों को लगभग निश्चित रूप से पता नहीं है। वेंडर ने कभी पूछा भी नहीं।
ऐसे classifiers का, जो ऐसी सामग्री पर फ़ैसले लेते हैं जिसे वे समझते ही नहीं, यह कोई काल्पनिक ख़तरा नहीं है। लोगों ने देखा है कि outbound दस्तावेज़ चुपचाप quarantine में ग़ायब हो गए, क्योंकि किसी अदृश्य watermark string ने mail filter को चौंका दिया। अब इस विफलता को बड़े पैमाने पर सोचिए: एक brand-safety classifier तय करता है कि आपका domain चरमपंथी या जैव-ख़तरे वाली सामग्री के आसपास है, और आपको कभी बताया नहीं जाता कि आपकी पहुँच क्यों गिर गई। आप अपील भी नहीं कर सकते, क्योंकि सबूत एक ऐसी फ़ाइल में अदृश्य Unicode है जो आपने लिखी ही नहीं।
और आम बचाव यहाँ काम नहीं करते। Subresource Integrity बेकार है, जब vendor हर deployment पर स्क्रिप्ट का hash बदल देता है, और ये स्क्रिप्ट्स लगातार बदलती हैं, कुछ हद तक pinning को विफल करने के लिए। Hashes वाला strict CSP भी उतनी ही तेज़ी से बदलता है। बचती है वह असहज निष्कर्ष जिस पर हम LiteLLM supply chain compromise के बाद पहुँचे थे: जिस dependency की आप समीक्षा नहीं कर सकते, वही आपकी मालिक बन जाती है। अगर आपके anti-bot vendor का कोड जानबूझकर उन tools के लिए undecompilable है जिन्हें आपकी security टीम इस्तेमाल करती है, तो वह कोई feature नहीं है। वह एक CDN URL के साथ आने वाली governance की विफलता है।

यह हथियारों की होड़ आगे कहाँ जाएगी
मेरा अनुमान है कि एक साल के अंदर safeguard-tripwiring कमर्शियल obfuscators में एक checkbox बन जाएगा, “LLM analysis resistance”, ठीक “control flow flattening” के बगल में। Model providers इसका जवाब यह देंगे कि models को tripwire corpora पर प्रशिक्षित करें, ताकि वे analysis संदर्भों में डाले गए चारे को पहचानकर नज़रअंदाज़ करना सीखें। Obfuscators इसका जवाब महीन जालों से देंगे: ऐसा कोड जिसका व्यवहार traces में malicious दिखे, न कि सिर्फ़ उसकी strings। HN comments ने मज़ाक में जिस fractal arms race की भविष्यवाणी की थी, यानी obfuscators models के खिलाफ़ ट्यून होते हुए और models obfuscators के खिलाफ़, वह पहले से यहाँ है; बस वह असमान रूप से बँटी हुई है।
टिकाऊ बचाव वही उबाऊ तरीका है, जो नब्बे के दशक के पहले packers के बाद से नहीं बदला: dynamic analysis, व्यवहार-आधारित सबूत और sandboxed execution। अपारदर्शिता कभी सुरक्षा का गुण था ही नहीं, वह लागत को इधर-उधर खिसकाने की रणनीति थी, और अब वह लागत वापस लौट आई है। अगर आपका detection pipeline एक ऐसी string से विफल हो सकता है जो किसी language model की “भावनाओं” को चोट पहुँचाए, तो वह pipeline शुरू से ही कमज़ोरी था।


