Contourner une protection anti‑scraping basée sur un leurre de l’objet window et un btoa personnalisé

Identification du point d’entrée

En observant les échanges réseau, on constate que la valeur m est calculée dynamiquement. L’inspecteur de source (Initiator) pointe vers window.request. En posant un point d’arrêt, on voit que :

  • t (le timestamp) est obtenu via Date.parse(new Date).toString()
  • m est produit par btoa(p_s) après simplification

Analyse de la fonction btoa

La fonction btoa n’est pas la fonction native. Elle utilise une table de caractères personnalisée et une obfuscation poussée. Après extraction, sa signature peut être réduite à :

function btoa(e) {
  var r = {
    // de nombreuses propriétés issues de u()
  };
  var i = r;
  var f = "U9876543210zyxwvutsrqpomnlkjihgfdecbaZXYWVUTSRQPONABHICESQWK2Fi+9876543210zyxwvutsrqpomnlkjihgfdecbaZXYWVUTSRQPONABHICESQWK2Fi";
  if (/([^\u0000-\u00ff])/.test(e)) throw new Error(i.TGmSp);

  var o, a, s, l = 0, c = [];
  while (l < e.length) {
    a = e.charCodeAt(l);
    s = i.kukBH(l, 6);
    switch (s) {
      case 0: /* ... */ break;
      case 1: /* gestion d’erreur selon n.g */ break;
      case 2: /* combinaisons avec f.charAt */ break;
      // ... cas 3,4,5
    }
    o = a;
    l++;
  }
  // finalisation avec d() et md5 du tableau c
  return i.aQCDK(d(15), md5(c.join(''))) + i(d, 10);
}

Plusieurs fonctions auxiliaires sont nécessaires : u, d, md5 et un objet n.

Restauration des dépendances manquantes

L’exécution hors contexte échoue car de nombreux symboles ne sont pas définis.

1. La fonction u et le tableau _0x34e7

u est une simple indirection qui renvoie _0x34e7[e - 188]. L’ordre des chaînes dans _0x34e7 doit être identique à celui de la page originale. Voici un extrait du tableau :

var _0x34e7 = [
  "split", "ABHICESQWK", "FKByN", "U987654321", "lmHcG", "dICfr",
  "Szksx", "Bgrij", "iwnNJ", "jihgfdecba", "GfTek", "gfdecbaZXY",
  /* ... */
];
function u(e, t) { return _0x34e7[e - 188]; }

2. La table de caractères f

Elle est directemant récupérée depuis le code source :

var f = "U9876543210zyxwvutsrqpomnlkjihgfdecbaZXYWVUTSRQPONABHICESQWK2Fi+" +
        "9876543210zyxwvutsrqpomnlkjihgfdecbaZXYWVUTSRQPONABHICESQWK2Fi";

3. La fonction d (générateur de chaîne aléatoire)

Elle s’appuie également sur u :

function d(e) {
  var t = u, n = {};
  n[t(227)] = "ABCDEFGHJKMNPQRSTWXYZabcdefhijkmnprstwxyz2345678";
  // ... logique de génération aléatoire
  return c;
}

4. Le hachage MD5

L’appel à window.md5(c.join('')) nécessite une implémentation indépendante. On remplace window.md5 par une fonction md5 récupérée du contexte de la page, elle‑même dépendante d’un autre tableau _0x4c28 et d’une fonction n.

var _0x4c28 = [
  "Rtsed","SUrST","nsaps","vyNVU","2|29|23|64",
  "0|43|57|4|", /* ... */
];
function n(e, t) { return _0x4c28[e - 0]; }

var md5 = function(e) {
  var t = n, r = { /* nombreuses propriétés */ };
  // ... implémentation complète de l’algorithme MD5
  return resultat;
};

L’objet n (qui diffère de la fonction n ci‑dessus) correspond à des propriétés globales, notamment n.g.

Correction du flux d’exécution : le piège window

À l’intérieur du case 1 du switch de btoa, la ligne try { "WhHMm" === i[198] || n.g && c.push(...) } catch(e) { ... } pose problème. En environnement page, n.g est défini (true), donc la branche correcte est exécutée. Hors navigateur, n.g est undefined, la condition évalue à false et le code passe dans le catch, modifiant le résultat. La solution consiste à forcer n.g = true; avant l’appel.

Script d’extraction des données

Une fois la fonction btoa entièrement reconstituée, on l’exporte via execjs. Le code JavaScript final appelle simplement btoa_change(timestamp) puis combine la valeur de hachage avec le timestamp.

Le script Python effectuant les requêtes :

import requests
import execjs
import re

pattern = r'"value": (\d+)'
total = 0

with open("btoa_bundle.js", "r") as f:
    js_source = f.read()

ctx = execjs.compile(js_source)

for page in range(1, 6):
    ts, token = ctx.call("btoa")
    url = f"https://match.yuanrenxue.cn/api/match/16?page={page}&m={token}&t={ts}"
    headers = {"User-Agent": "Mozilla/5.0 ..."}
    cookies = {"sessionid": "votre_session"}
    resp = requests.get(url, headers=headers, cookies=cookies)
    for match in re.findall(pattern, resp.text):
        total += int(match)

print("Somme totale :", total)

La boucle itère sur les 5 pages, calcule un nouveau couple (t, m) pour chaque appel (le timestamp évolue), récupère les valeurs JSON et les additionne.

Résultat

L’approche permet de contourner le pot de miel basé sur l’objet window et la fonction btoa modifiée, et d’obtenir la somme attendue.

Étiquettes: rétro-ingénierie JavaScript btoa personnalisée leurre window anti-scraping execjs

Publié le 25 septembre à 11h37