background blurbackground mobile blur

1/1/1970

मैंने Foony को प्रॉक्सी के पीछे काम करने लायक कैसे बनाया

नमस्ते! मुझे काफी समय से पता है कि वेब प्रॉक्सी वेबसाइटों के लिए कम्पैटिबिलिटी समस्याएँ पैदा करते हैं। हालाँकि, Foony का प्रॉक्सी में सपोर्ट कुख्यात रूप से खराब रहा है, और Foony की प्रॉक्सी कम्पैटिबिलिटी को हल करना काफी पेचीदा था।

यह कोई "Foony विदेशी APIs का उपयोग करता है" वाली समस्या भी नहीं है (हालाँकि हम करते हैं)। यह कई चीजों का मिश्रण था:

  • प्रॉक्सी ऐसी जगहों पर आक्रामक स्ट्रिंग रीराइटिंग कर रहे हैं जहाँ उन्हें बिल्कुल नहीं करनी चाहिए।
  • प्रॉक्सी मुख्य साइट डोमेन को "अन्य" डोमेन (CDNs, asset hosts, आदि) से अलग तरीके से ट्रीट करते हैं।
  • और कड़वी सच्चाई कि कुछ प्रॉक्सी आधुनिक वेब ऐप्स को सपोर्ट ही नहीं कर सकते (HTTPS शुद्धता, WebSockets, आदि)।

हम हर प्रॉक्सी के साथ काम नहीं करते, लेकिन अब हम कम से कम croxyproxy और proxyorb के साथ काम करते हैं, जो लक्ष्य था।

नीचे मैं समझाता हूँ कि क्या टूटा, क्यों टूटा, और कौन से फिक्स वास्तव में मायने रखते थे।


पास 1: मान्य लेकिन टूटे हुए Three.js shaders

लक्षण

जब मैंने croxyproxy आजमाया, तो मैं 8 Ball Pool या Foony के किसी भी अन्य three.js गेम्स नहीं खेल पाया। मुझे Three.js में लगातार shader कम्पाइलेशन फेलियर मिल रहा था जैसे एरर:

  • "Shader Error 1282 - VALIDATE_STATUS false"

वह संदेश लगभग पूरी तरह से बेकार था। इसका आमतौर पर मतलब है "आपका shader अमान्य है, गुड लक।" वाह। अगर आप कभी सोचते हैं कि मैं Foony पर हर एक एरर के लिए हमेशा यूनिक एरर मैसेज क्यों इस्तेमाल करता हूँ, तो यही वजह है। यह "कोड टूटा है, जाओ ठीक करो" के बजाय समस्याओं को पिनपॉइंट करने में मदद करता है।

लेकिन बिल्कुल मान्य three.js shaders क्यों टूट रहे थे? आखिर माजरा क्या है?

असली कारण: प्रॉक्सी layout(location = N) को करप्ट कर रहे थे

Three.js layout qualifiers के साथ GLSL emit करता है जैसे:

layout(location = 0) in vec3 position;

कुछ प्रॉक्सी हर उस चीज को रीराइट करने की कोशिश करते हैं जो JavaScript location API जैसी दिखती है, और वे साधारण ग्लोबल स्ट्रिंग रिप्लेसमेंट करते हैं। यह JS में पहले से ही बुरा है, लेकिन वे यह shader source strings के अंदर भी कर रहे थे। मुझे लगता है AST पार्सिंग उनके लिए बहुत महंगी है।

तो shader source कुछ ऐसा करप्ट हो गया:

layout(__cpLocation = 0) in vec3 position;

वहाँ identifier must location ही होना चाहिए। कुछ भी और invalid GLSL है, और कम्पाइलर इसे रिजेक्ट कर देता है। (GLSL में Layout Qualifiers)

यह Three.js की समस्या केवल इस अर्थ में है कि Three.js shaders डायनामिक रूप से जनरेट करता है, और हम उन्हें रनटाइम पर WebGL को पास करते हैं। असली बग प्रॉक्सी की रीराइटिंग रणनीति है।

मैंने "प्रॉक्सी को फिक्स" क्यों नहीं किया

एक भोला तरीका होगा croxyproxy की location रिप्लेसमेंट स्ट्रिंग, __cpLocation, को खोजना और उसे location से बदलना। हालाँकि, अलग-अलग प्रॉक्सी अलग-अलग रिप्लेसमेंट नाम इस्तेमाल करते हैं। कुछ __cpLocation का उपयोग करते हैं, अन्य अजीब identifiers का। तो "__cpLocation को वापस location से बदलें" जैसा हार्डकोडेड फिक्स नाजुक है।

मुझे चाहिए था:

  • एक जेनरिक फिक्स (कोई प्रॉक्सी identifiers हार्डकोड नहीं)।
  • एक फिक्स जो तब भी काम करे जब प्रॉक्सी मेरे JavaScript में शब्द location को भी रीराइट कर रहा हो।

base64 ट्रिक: प्रॉक्सी से शब्द location को छिपाना

अगर प्रॉक्सी हर लिटरल location को रीराइट करता है जो उसे दिखता है, तो सबसे आसान तरीका है location का उपयोग न करना। काफी आसान। मैंने इस तरह की ट्रिक्स पहले Lua में देखी हैं जब मैंने RestedXP के गाइड प्रोटेक्शन सिस्टम को रिवर्स इंजीनियर किया था (अगर मुझे सही याद है, तो वे BNGetInfo के उपयोग को obfuscate करते हैं, जैसे _G("\x42\x4E\x47\x65\x74\x49\x6E\x66\x6F"))।

यह ट्रिक JavaScript में भी काम करती है, बेशक। client/index.html में, मैं रनटाइम पर निम्नलिखित को decode करता हूँ:

// Because these proxies try to replace every `location`, we use a base64 encoded string.
const suffix = 'pb24=';
const locStr = atob('bG9jYXR' + suffix); // "location"
const loc = window[locStr]; // window.location

वह atob() उसके बाद होता है जब प्रॉक्सी पहले ही अपनी HTML/JS रीराइटिंग कर चुका होता है, इसलिए वह स्ट्रिंग को "प्री-करप्ट" नहीं कर सकता। मैंने स्ट्रिंग को दो में विभाजित कर दिया ताकि इसे डिटेक्ट करना और भी मुश्किल हो, और मैं 'atob' का उपयोग करता हूँ क्योंकि मैं कर सकता हूँ, लेकिन String.fromCharCode या hex-escaping window['\x6c\x6f\x63\x61\x74\x69\x6f\x6e'] भी काम कर सकता है।

टूटा हुआ shader पैटर्न हमेशा संरचनात्मक रूप से समान होता है:

layout(<something> = <number>)

तो मैं उसे जेनरिक तरीके से मैच करता हूँ और <something> को सही identifier से बदल देता हूँ:

source.replace(/layout\s*\(\s*[^=)]+\s*=\s*(\d+)\s*\)/g, 'layout(' + locStr + ' = $1)');

WebGL हुक: shaderSource को पैच करें (WebGL1 + WebGL2)

चूँकि Three.js gl.shaderSource(shader, source) कॉल करता है, मैं shaderSource को ही पैच करता हूँ:

const originalShaderSource = WebGLRenderingContext.prototype.shaderSource;

Object.defineProperty(WebGLRenderingContext.prototype, 'shaderSource', {
  value: function (shader, source) {
    return originalShaderSource.call(this, shader, fixCorruptedShaderSource(source));
  },
  writable: true,
  configurable: true,
});

और मैं वही पैच WebGL2RenderingContext पर भी लागू करता हूँ अगर वह मौजूद है।

एक बार वह जगह पर आ गया, तो shader कम्पाइलेशन एरर गायब हो गए। इस बिंदु पर, croxyproxy काम कर रहा था लेकिन proxyorb अभी भी फेल हो रहा था। क्यों?! क्या वह वैसे ही काम नहीं करना चाहिए?


पास 2: दूसरी समस्या (डोमेन) और foony.io को हटाने से सब कुछ आसान क्यों हो गया

Foony ऐतिहासिक रूप से दो डोमेन उपयोग करता था, कम से कम पिछले महीने से:

  • मुख्य साइट के लिए foony.com
  • स्टैटिक एसेट्स के लिए foony.io

मूल कारण व्यावहारिक था: कुकी-रहित डोमेन से एसेट्स सर्व करने से हर स्टैटिक फ़ाइल रिक्वेस्ट पर कुकी हेडर अपलोड ब्लोट से बचा जा सकता है। यह बढ़िया है, लेकिन उतना आवश्यक नहीं है जितना आप सोचेंगे, यह देखते हुए कि HTTP/2 HPACK का उपयोग करके हेडर के लिए वायर पर भेजे गए बाइट्स को कम करता है।

यह सामान्य ब्राउज़िंग में एक मान्य ऑप्टिमाइजेशन है।

प्रॉक्सी के पीछे, यह टूटने का एक प्रमुख स्रोत बन गया। और Foony का यूजरबेस प्रॉक्सी को बहुत पसंद करता है। आह

प्रॉक्सी "मुख्य साइट" को "अन्य साइट्स" से अलग ट्रीट करते हैं

कई प्रॉक्सी "इस एक पेज / डोमेन को प्रॉक्सी करें" के लिए ऑप्टिमाइज़्ड हैं। वे सफलतापूर्वक मुख्य HTML लोड करेंगे, स्क्रिप्ट इंजेक्ट करेंगे, अपना खुद का ServiceWorker रजिस्टर करेंगे, आदि।

लेकिन जब ऐप अलग origin (जैसे foony.io) से एसेट्स खींचना शुरू करता है, तो आप हर तरह की मजेदार टूट-फूट में फंस जाते हैं:

  • ServiceWorker इंटरसेप्शन फेलियर जैसे:
    • "ServiceWorker intercepted the request and encountered an unexpected error"
    • "Loading failed for the module with source"
  • क्वेरी पैरामीटर्स जो प्रॉक्सी इन्फ्रास्ट्रक्चर द्वारा आवश्यक हैं (और गलती से स्ट्रिप करना आसान है)।
  • एसेट रिक्वेस्ट प्रॉक्सी का इंटरनल रूटिंग मेटाडेटा खो रहे हैं।
  • अजीब प्रॉक्सी जो पूरी रिक्वेस्ट को generic-php-slug.php?someQueryParam=hugeEncodedString से रिप्लेस कर देते हैं (हाँ, मुझे उसे सपोर्ट करने की परवाह नहीं थी)।

इन प्रॉक्सी के आंतरिक तंत्र क्वेरी पैरामीटर्स / URL एनकोडिंग पर निर्भर करते हैं, और वे काफी नाजुक हैं।

एक स्पष्ट उदाहरण एक एसेट URL था जैसे:

https://<proxy-ip>/assets/firebase-<hash>.js?__pot=aHR0cHM6Ly9mb29ueS5jb20

वह ?__pot=... प्रॉक्सी का अपना रूटिंग/स्टेट है जो प्रॉक्सी को बताता है कि रिक्वेस्ट किस डोमेन के लिए है। अगर आप इसे स्ट्रिप कर देते हैं, तो प्रॉक्सी रिक्वेस्ट को सही ढंग से रिज़ॉल्व नहीं कर सकते, और आप ServiceWorker एरर पाथ में फंस जाते हैं।

"Resource swapping" बचाव में (और यह तेजी से जटिल क्यों हो गया)

एक बिंदु पर, मैंने एक वर्कअराउंड आजमाया: "हम प्रॉक्सी हो रहे हैं" डिटेक्ट करें, फिर किसी भी foony.io रिसोर्स URLs को करंट origin पर स्वैप करें ताकि प्रॉक्सी सब कुछ same-origin के रूप में देखे।

यह उचित लगता है, और यह croxyproxy के लिए काम करता था, लेकिन इसने बहुत जटिलता जोड़ी:

  • आपको उन link और script टैग्स को बदलना होगा जो पहले से HTML में मौजूद हैं।
  • आपको डायनामिक रूप से इंजेक्ट किए गए टैग्स (modulepreload, stylesheet, आदि) को हैंडल करने के लिए एक MutationObserver चाहिए।
  • आपको प्रॉक्सी के क्वेरी पैरामीटर्स को संरक्षित करना होगा, या आप उनकी रूटिंग तोड़ देंगे। और अलग-अलग प्रॉक्सी इसे अलग तरीके से करते हैं। बेशक वे ऐसा करते हैं।
  • और आपको लॉजिक को अभी भी जेनरिक रखना होगा (कोई प्रॉक्सी-विशिष्ट ग्लोबल नहीं) ताकि कोड एक फूला हुआ डंपस्टर-फायर न बन जाए।

यहीं पर "base64 ट्रिक" फिर से सामने आई: यहां तक कि अपने स्वयं के JavaScript में भी, मुझे लिटरल स्ट्रिंग location के बारे में सावधान रहना पड़ा क्योंकि प्रॉक्सी इसे रीराइट कर सकता है।

CroxyProxy की इंजेक्टेड स्क्रिप्ट को रिवर्स-इंजीनियर करना

इस बिंदु पर मुझे जिज्ञासा हुई: प्रॉक्सी वास्तव में मेरे पेज के साथ क्या कर रहा है? क्या वह अपने विज्ञापन इंजेक्ट कर रहा है? कुछ बदतर?

CroxyProxy की क्लाइंट-साइड स्क्रिप्ट भारी रूप से ऑब्फस्केटेड है।

(new Function(new TextDecoder('utf-8').decode(new Uint8Array((atob('NjY3NTZlN...')).match(/.{1,2}/g).map(b => parseInt(b, 16))))))();

जिसे चलाने पर, परिणाम मिलता है:

function a0_0x5ebf(_0x213dc9,_0x1c49b6){var _0x4aa7c1=a0_0x4274();return a0_0x5ebf=function(_0x159600,_0x51d898){_0x159600=...

इसके आधार पर, ऐसा लगता है कि croxyproxy इस ऑब्फस्केशन के लिए Obfuscator.io का उपयोग कर रहा है। जिसे शुक्र है webcrack के साथ डीऑब्फस्केट करना काफी आसान है।

इसका परिणाम बहुत अधिक पठनीय JavaScript है:

((_0x15ca2c, _0x489eaa) => {
  if (typeof module == "object" && module.exports) {
    module.exports = _0x489eaa(require("./punycode"), require("./IPv6"), require("./SecondLevelDomains"));
  } else if (typeof define == "function" && define.amd) {
    define(["./punycode", "./IPv6", "./SecondLevelDomains"], _0x489eaa);
  } else {
    _0x15ca2c.URI = _0x489eaa(_0x15ca2c.punycode, _0x15ca2c.IPv6, _0x15ca2c.SecondLevelDomains, _0x15ca2c);
  }
})(this, function (_0x724467, _0x275183, _0x219d84, _0x2dcc2c) {
  var _0x114c1e = _0x2dcc2c && _0x2dcc2c.URI;
  function _0x5a9187(_0x2fd4ea, _0x3bd460) {
    var _0x23a83f = arguments.length >= 1;
    if (!(this instanceof _0x5a9187)) {

बढ़िया। अब हम देख सकते हैं कि यह क्या कर रहा है। और... यह ज्यादातर ठीक लगता है। मुझे लगता है ऑब्फस्केशन ज्यादातर प्रॉक्सी का पता लगाने से बचाने में मदद करने के लिए है। ज्यादातर

हमारे पास कुछ ऐड / UI इंजेक्शन है:

    Bi(_0x308e2f) {
      console.log("Ads: " + _0x331b11.showAds);
      console.log("Ad codes: " + !!_0x331b11.adsJson);
      console.log("Adblock: " + _0x308e2f);
      _0x34fa18.document.body.insertAdjacentHTML("afterbegin", _0x331b11.modal);
      if (_0x331b11.header) {
        if (_0x331b11.header) {
          this.Ri(_0x308e2f);
        }
        [...document.querySelectorAll("#__cpsHeader a")].forEach(_0xcce595 => {
          _0xcce595.addEventListener("click", function (_0x3ef5f1) {
            if (this.target === "_blank") {
              _0x34fa18.open(this.href, "_blank").focus();
            } else {
              _0x34fa18.location.href = this.href;
            }
            _0x3ef5f1.stopImmediatePropagation();
            _0x3ef5f1.preventDefault();
          }, true);
        });
      }
      return this;
    }

कुछ अन्य जगहें भी हैं, लेकिन मूल रूप से यह सिर्फ विज्ञापन दिखा रहा है, जिसमें पॉप-अंडर स्टाइल विज्ञापन शामिल हैं। यह FuckAdBlock का भी उपयोग करता है।

स्ट्रिंग्स के लिए असली रिप्लेसमेंट, हालाँकि, सर्वर-साइड पर हो रहा है। और कौन जाने वह सब क्या कर रहा है।

किसी भी तरह, अगर आप अपनी अकाउंट सिक्योरिटी की परवाह करते हैं तो आपको बिल्कुल नहीं वेब प्रॉक्सी का उपयोग करना चाहिए। अगर आपको करना ही पड़े, तो अपनी कोई भी PII / अकाउंट / खरीद जानकारी दर्ज करने से बचें।

"Resource swapping" कूड़ेदान में

मैंने तय किया कि resource swapping से जटिलता, साथ ही foony.io सपोर्ट के लिए कोड के अन्य हिस्सों में जटिलता, सुंदर, कुकीरहित रिक्वेस्ट्स की छोटी नेटवर्क बचत के लायक नहीं थी। हम foony.io अपनाने के बाद से अपने गेमप्ले रूपांतरणों में एक अस्पष्ट गिरावट भी देख रहे थे, इसलिए मुझे संदेह है कि foony.io के साथ अन्य समस्याएँ थीं जिनके बारे में हमें पता नहीं था।

तो मैंने foony.io हटा दिया। कम से कम अभी के लिए।

एक बार जब मैंने foony.io CDN लॉजिक हटा दिया और हर चीज को foony.com पर मानकीकृत कर दिया, तो प्रॉक्सी सपोर्ट नाटकीय रूप से सरल हो गया:

  • Same-origin एसेट लोड्स।
  • प्रॉक्सी ServiceWorker को समझाने के लिए कम "विशेष मामले"।
  • कम रीराइटिंग।
  • कम नाजुक कोड।

संक्षेप में, foony.io को हटाना एक आर्किटेक्चरल सरलीकरण था जिसने अजीब प्रॉक्सी व्यवहार के लिए सरफेस एरिया को कम कर दिया।


पास 3: क्या काम करता है, क्या नहीं, और क्यों

पुष्टि किए गए काम करने वाले प्रॉक्सी

इस बिंदु पर, Foony इनके पीछे काम करता है:

  • croxyproxy
  • proxyorb

कुछ अन्य प्रॉक्सी शायद काम करते हैं। मुझे शर्त है कि अधिकांश अभी भी नहीं करते। लेकिन कम से कम वे महत्वपूर्ण प्रॉक्सी जो लोग गेम्स खेलने के लिए उपयोग करते हैं, काम करते दिखते हैं।

"सभी प्रॉक्सी" क्यों नहीं?

कुछ प्रॉक्सी बस एक आधुनिक मल्टीप्लेयर वेब ऐप को सपोर्ट नहीं कर सकते। उदाहरण:

  • प्रॉक्सी जो HTTPS को ठीक से सपोर्ट नहीं करते।
  • प्रॉक्सी जो WebSockets को तोड़ते या ब्लॉक करते हैं (Foony रियल-टाइम नेटवर्किंग का उपयोग करता है)। तकनीकी रूप से आप इसके आसपास काम कर सकते हैं, लेकिन इससे जटिलता बढ़ेगी।
  • प्रॉक्सी जिनमें cross-origin requests, headers, या ServiceWorkers के आसपास बहुत अधिक प्रतिबंध हैं।

मुख्य निष्कर्ष

वेब प्रॉक्सी बहुत असुरक्षित हैं

वे मिडलवेयर हैं जो:

  • HTML को रीराइट करते हैं
  • JavaScript को रीराइट करते हैं
  • कभी-कभी ServiceWorker इंजेक्ट करते हैं
  • और अक्सर रिक्वेस्ट्स को रूट करने के लिए क्वेरी पैरामीटर्स / URL एनकोडिंग पर निर्भर करते हैं
  • आपके पेजों के साथ कई तरीकों से छेड़छाड़ कर सकते हैं

मुझे आश्चर्य हुआ कि कुछ प्रॉक्सी कितनी गहराई तक जाते हैं: वे shader source strings, comments, और भगवान जाने और क्या रीराइट करेंगे।

कभी-कभी सबसे अच्छा फिक्स आर्किटेक्चरल होता है

WebGL पैच ने गेम्स को फिर से रेंडर करवाया, लेकिन मल्टी-डोमेन CDN रणनीति को हटाने ने प्रॉक्सी सपोर्ट को स्थिर रहने दिया।

यह एक अच्छा रिमाइंडर है: चतुर ऑप्टिमाइजेशन तब तक पूरी तरह से उचित हो सकते हैं जब तक वे शत्रुतापूर्ण मिडलवेयर से नहीं टकराते। या यूजर के ब्राउज़र एक्सटेंशन से। या Safari से। या भाषा सेटिंग्स से। या एक्सेसिबिलिटी फीचर्स से। या सोलर फ्लेयर्स से। या किसी भी चीज से, सच में।


निष्कर्ष

Foony अब उन प्रॉक्सी के पीछे काम करता है जो मायने रखते हैं (croxyproxy और proxyorb), बिना codebase को प्रॉक्सी-विशिष्ट गड़बड़ी में बदले:

  • एक जेनरिक Three.js shader फिक्स (कोई प्रॉक्सी-विशिष्ट identifiers नहीं)।
  • एक सरल डोमेन रणनीति (हर जगह foony.com)।
8 Ball Pool online multiplayer billiards icon