background blurbackground mobile blur

1/1/1970

Bagaimana Saya Buat Foony Berfungsi di Sebalik Proksi

Hai semua! Saya dah lama tahu yang proksi web menyebabkan masalah keserasian untuk laman web. Namun, sokongan Foony pada proksi memang terkenal teruk, dan menyelesaikan masalah keserasian proksi Foony agak rumit.

Ini bukan isu "Foony guna API yang pelik" pun (walaupun memang kami guna). Ia gabungan:

  • Proksi yang membuat penulisan semula rentetan secara agresif di tempat yang tak sepatutnya langsung.
  • Proksi yang melayan domain utama laman secara berbeza daripada domain "lain" (CDN, hos aset, dan sebagainya).
  • Dan realiti pahit yang sesetengah proksi memang tak boleh sokong aplikasi web moden (ketepatan HTTPS, WebSockets, dan sebagainya).

Kami tak berfungsi dengan setiap proksi, tapi sekarang kami berfungsi dengan sekurang-kurangnya croxyproxy dan proxyorb, yang merupakan matlamat asal.

Di bawah, saya jelaskan apa yang rosak, kenapa ia rosak, dan pembaikan yang betul-betul penting.


Pusingan 1: Shader Three.js yang Sah Tapi Rosak

Gejalanya

Bila saya cuba croxyproxy, saya tak boleh main 8 Ball Pool atau permainan three.js Foony yang lain. Saya asyik dapat kegagalan kompilasi shader dalam Three.js dengan ralat seperti:

  • "Shader Error 1282 - VALIDATE_STATUS false"

Mesej tu hampir tak berguna langsung. Biasanya ia bermaksud "shader awak tak sah, semoga berjaya." Bagus betul. Kalau awak tertanya-tanya kenapa saya selalu guna mesej ralat unik untuk setiap ralat di Foony, inilah sebabnya. Ia membantu mengenal pasti masalah daripada hanya "kod rosak, pergi baiki."

Tapi kenapa shader three.js yang sah betul-betul rosak? Apa hal pula ni?

Punca sebenar: proksi merosakkan layout(location = N)

Three.js menghasilkan GLSL dengan kelayakan susun atur seperti:

layout(location = 0) in vec3 position;

Sesetengah proksi cuba menulis semula apa-apa yang nampak macam API JavaScript location dengan melakukan penggantian rentetan global secara naif. Itu sendiri pun dah teruk dalam JS, tapi mereka juga buat begitu di dalam rentetan sumber shader. Saya rasa parsing AST terlalu mahal untuk mereka.

Jadi sumber shader jadi rosak menjadi sesuatu seperti:

layout(__cpLocation = 0) in vec3 position;

Pengecam itu mesti location di situ. Apa-apa lain adalah GLSL yang tak sah, dan pengkompil akan menolaknya. (Kelayakan Susun Atur dalam GLSL)

Ini hanya masalah Three.js dalam erti kata Three.js menghasilkan shader secara dinamik, dan kami menghantarnya ke WebGL semasa runtime. Pepijat sebenarnya adalah strategi penulisan semula proksi.

Kenapa saya tak "baiki proksi tu"

Pendekatan naif adalah mencari rentetan penggantian location croxyproxy, iaitu __cpLocation, dan menggantikannya dengan location. Namun, proksi yang berbeza menggunakan nama penggantian yang berbeza. Sesetengah guna __cpLocation, ada yang guna pengecam pelik lain. Jadi mengekod tetap pembaikan seperti "ganti __cpLocation kembali kepada location" adalah rapuh.

Saya perlukan:

  • Pembaikan yang generik (tiada pengekodan tetap pengecam proksi).
  • Pembaikan yang berfungsi walaupun proksi sedang menulis semula perkataan location dalam JavaScript saya juga.

Helah base64: menyembunyikan perkataan location daripada proksi

Kalau proksi menulis semula setiap location literal yang ia nampak, langkah paling mudah adalah dengan tidak menggunakan location langsung. Senang je. Saya pernah jumpa helah ini sebelum ini dalam Lua semasa saya merekayasa balik sistem perlindungan panduan RestedXP (kalau saya tak silap, mereka mengaburkan penggunaan BNGetInfo mereka, contohnya _G("\x42\x4E\x47\x65\x74\x49\x6E\x66\x6F")).

Helah ini berfungsi dalam JavaScript juga, tentulah. Dalam client/index.html, saya menyahkod yang berikut semasa runtime:

// Sebab proksi-proksi ini cuba menggantikan setiap `location`, kami menggunakan rentetan yang dikodkan dalam base64.
const suffix = 'pb24=';
const locStr = atob('bG9jYXR' + suffix); // "location"
const loc = window[locStr]; // window.location

atob() itu berlaku selepas proksi telah pun melakukan penulisan semula HTML/JS-nya, jadi ia tak boleh "rosakkan-awal" rentetan tu. Saya pecahkan rentetan kepada dua untuk lebih sukar dikesan, dan saya guna 'atob' sebab saya boleh, tapi String.fromCharCode atau hex-escaping window['\x6c\x6f\x63\x61\x74\x69\x6f\x6e'] juga mungkin berfungsi.

Corak shader yang rosak sentiasa sama secara struktur:

layout(<sesuatu> = <nombor>)

Jadi saya padankan secara generik dan gantikan <sesuatu> dengan pengecam yang betul:

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

Cangkuk WebGL: tampal shaderSource (WebGL1 + WebGL2)

Memandangkan Three.js memanggil gl.shaderSource(shader, source), saya tampal shaderSource itu sendiri:

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,
});

Dan saya gunakan tampalan yang sama pada WebGL2RenderingContext jika ia wujud.

Setelah itu dilaksanakan, ralat kompilasi shader pun lenyap. Pada ketika ini, croxyproxy dah berfungsi tapi proxyorb masih gagal. Apesal pula?! Bukankah ia sepatutnya berfungsi sama?


Pusingan 2: Masalah Kedua (Domain) dan Kenapa Membuang foony.io Memudahkan Segalanya

Foony secara sejarahnya menggunakan dua domain, sekurang-kurangnya untuk sebulan yang lalu:

  • foony.com untuk laman utama
  • foony.io untuk aset statik

Sebab asalnya adalah praktikal: menyajikan aset dari domain tanpa kuki mengelakkan kembungan muat naik header kuki pada setiap permintaan fail statik. Ini bagus, tapi tak seperlu yang awak fikirkan memandangkan HTTP/2 menggunakan HPACK untuk mengurangkan bait yang dihantar melalui wayar untuk header.

Ia adalah pengoptimuman yang sah dalam pelayaran biasa.

Di sebalik proksi, ia menjadi sumber kerosakan utama. Dan pengguna Foony memang suka proksi. Aduhai.

Proksi melayan "laman utama" berbeza daripada "laman lain"

Banyak proksi dioptimumkan untuk "proksi satu halaman / domain ini." Mereka akan berjaya memuatkan HTML utama, menyuntik skrip, mendaftarkan ServiceWorker mereka sendiri, dan sebagainya.

Tapi bila aplikasi mula menarik aset dari asal yang berbeza (seperti foony.io), awak akan masuk ke pelbagai jenis kerosakan yang menyeronokkan:

  • Kegagalan pintasan ServiceWorker seperti:
    • "ServiceWorker intercepted the request and encountered an unexpected error"
    • "Loading failed for the module with source"
  • Parameter pertanyaan diperlukan oleh infrastruktur proksi (dan mudah dibuang secara tak sengaja).
  • Permintaan aset kehilangan metadata penghalaan dalaman proksi.
  • Proksi pelik yang menggantikan keseluruhan permintaan dengan generic-php-slug.php?someQueryParam=hugeEncodedString (ya, saya tak kisah pun nak sokong yang itu).

Mekanisme dalaman proksi-proksi ini bergantung pada parameter pertanyaan / pengekodan URL, dan ia agak rapuh.

Salah satu contoh yang ketara adalah URL aset seperti:

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

?__pot=... itu adalah penghalaan/keadaan proksi sendiri yang memberitahu proksi untuk domain mana permintaan itu. Kalau awak buang ia, proksi tak boleh selesaikan permintaan dengan betul, dan awak akan berakhir dengan laluan ralat ServiceWorker.

"Pertukaran sumber" untuk menyelamatkan keadaan (dan kenapa ia jadi rumit dengan cepat)

Pada satu ketika, saya cuba penyelesaian: kesan "kami diproksi," kemudian tukarkan mana-mana URL sumber foony.io kepada asal semasa supaya proksi akan melihat segalanya sebagai asal-yang-sama.

Itu bunyinya munasabah, dan ia berfungsi untuk croxyproxy, tapi ia menambah banyak kerumitan:

  • Awak perlu menggantikan tag link dan script yang sudah wujud dalam HTML.
  • Awak perlukan MutationObserver untuk mengendalikan tag yang disuntik secara dinamik (modulepreload, stylesheet, dan sebagainya).
  • Awak kena mengekalkan parameter pertanyaan proksi, atau awak akan rosakkan penghalaan mereka. Dan proksi yang berbeza melakukan ini secara berbeza. Sebab memang macam tu lah.
  • Dan awak tetap kena pastikan logik tu generik (tiada global khusus proksi) supaya kod tak menjadi tempat sampah yang membengkak.

Di sinilah juga "helah base64" muncul lagi: walaupun dalam JavaScript saya sendiri, saya kena berhati-hati tentang rentetan literal location sebab proksi mungkin menulisnya semula.

Merekayasa balik skrip yang disuntik CroxyProxy

Pada ketika ini saya jadi ingin tahu: apa sebenarnya yang proksi tu buat pada halaman saya? Adakah ia menyuntik iklannya sendiri? Sesuatu yang lebih teruk?

Skrip sisi-pelanggan CroxyProxy dikaburkan dengan teruk.

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

Yang bila dijalankan, menghasilkan:

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

Berdasarkan ini, nampaknya croxyproxy menggunakan Obfuscator.io untuk pengkaburan ini. Yang mana, syukurlah, cukup mudah untuk dinyahkaburkan dengan webcrack.

Ini menghasilkan JavaScript yang lebih mudah dibaca:

((_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)) {

Bagus. Sekarang kita boleh nampak apa yang ia buat. Dan... nampaknya kebanyakannya OK. Saya rasa pengkaburan tu lebih kepada membantu mengelakkan pengesanan proksi. Kebanyakannya.

Kita dapat sedikit suntikan iklan / 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;
    }

Ada beberapa tempat lain, tapi pada dasarnya ia hanya menunjukkan iklan, termasuk iklan jenis pop-under. Ia juga menggunakan FuckAdBlock.

Penggantian sebenar untuk rentetan, walau bagaimanapun, berlaku di sisi pelayan. Dan siapa yang tahu apa semua yang ia buat.

Walau apa pun, awak memang tak patut menggunakan proksi web jika awak peduli tentang keselamatan akaun awak. Kalau terpaksa, elakkan memasukkan apa-apa PII / akaun / maklumat pembelian awak.

"Pertukaran sumber" dibuang ke tong sampah

Saya buat keputusan yang kerumitan dari pertukaran sumber, digabungkan dengan kerumitan di bahagian lain kod untuk sokongan foony.io, tak berbaloi dengan penjimatan rangkaian kecil daripada permintaan tanpa kuki yang cantik. Kami juga melihat penurunan yang tak dapat dijelaskan dalam penukaran permainan kami sejak menerima pakai foony.io, jadi saya mengesyaki ada isu lain dengan foony.io yang kami tak sedar.

Jadi saya buang foony.io. Sekurang-kurangnya buat masa ini.

Setelah saya padamkan logik CDN foony.io dan menyeragamkan semuanya pada foony.com, sokongan proksi menjadi jauh lebih ringkas:

  • Pemuatan aset asal-yang-sama.
  • Lebih sedikit "kes khas" untuk dijelaskan kepada ServiceWorker proksi.
  • Kurang penulisan semula.
  • Kod yang kurang rapuh.

Pendek kata, membuang foony.io adalah pemudahan seni bina yang mengurangkan kawasan permukaan untuk tingkah laku proksi yang pelik.


Pusingan 3: Apa yang Berfungsi, Apa yang Tak, dan Kenapa

Proksi yang disahkan berfungsi

Pada ketika ini, Foony berfungsi di sebalik:

  • croxyproxy
  • proxyorb

Beberapa proksi lain mungkin berfungsi. Saya bertaruh kebanyakannya masih tak. Tapi sekurang-kurangnya yang penting yang orang gunakan untuk bermain permainan nampaknya berfungsi.

Kenapa bukan "semua proksi"?

Sesetengah proksi memang tak boleh sokong aplikasi web berbilang pemain moden. Contoh:

  • Proksi yang tak menyokong HTTPS dengan betul.
  • Proksi yang merosakkan atau menyekat WebSockets (Foony menggunakan rangkaian masa nyata). Secara teknikalnya awak boleh atasi ini, tapi ia akan menambah kerumitan.
  • Proksi yang mempunyai terlalu banyak sekatan tentang permintaan rentas-asal, header, atau ServiceWorkers.

Pengajaran utama

Proksi web sangat tak selamat

Mereka adalah middleware yang:

  • menulis semula HTML
  • menulis semula JavaScript
  • kadang-kadang menyuntik ServiceWorker
  • dan sering bergantung pada parameter pertanyaan / pengekodan URL untuk menghala permintaan
  • boleh mengusik halaman awak dalam pelbagai cara

Saya terkejut betapa dalamnya sesetengah proksi pergi: mereka akan menulis semula rentetan sumber shader, komen, dan entah apa lagi.

Kadang-kadang pembaikan terbaik adalah seni bina

Tampalan WebGL membuat permainan dipaparkan semula, tetapi membuang strategi CDN berbilang-domain membuat sokongan proksi kekal stabil.

Ini adalah peringatan yang baik: pengoptimuman pintar boleh jadi munasabah sehingga ia berlanggar dengan middleware yang bermusuhan. Atau sambungan pelayar pengguna. Atau Safari. Atau tetapan bahasa. Atau ciri kebolehcapaian. Atau letupan suria. Atau apa-apa pun, sebenarnya.


Kesimpulan

Foony kini berfungsi di sebalik proksi yang penting (croxyproxy dan proxyorb), tanpa menjadikan kod sumber sebagai serabai khusus-proksi:

  • Pembaikan shader Three.js yang generik (tiada pengecam khusus-proksi).
  • Strategi domain yang lebih ringkas (foony.com di mana-mana).
8 Ball Pool online multiplayer billiards icon