Débogage des Interceptions et Chiffrement Asymétrique en Rétro-ingénierie JavaScript

L'analyse des mécanismes de protection dans les applications web implique souvent de surmonter des points d'arrêt de débogage dynamiques et de déchiffrer des schémas de chiffrement complexes. Cet article explore des techniques pour contourner les protections de débogage basées sur JavaScript et la reproduction d'un processus de chiffrement asymétrique pour l'authentification des requêtes.

Contournement des Mécanismes Anti-Débogage

Lorsqu'on tente d'ouvrir les outils de développement du navigateur (F12), un point d'arrêt de débogage peut être déclenché. Une observation rapide de la pile d'appels révèle que ces points d'arrêt sont souvent générés dynamiquement, typiquement via le constructeur Function.

Le premier type de point d'arrêt que l'on rencontre généralement est créé par new Function("debugger"). Ce type est relativement simple à contourner en choisissant l'option "Ne jamais faire de pause ici" dans le panneau Sources de votre débogueur.

Cependant, des points d'arrêt plus sophistiqués peuvent apparaître. Il est possible de rencontrer des débogueurs générés dynamiquement dont la chaîne passée au constructeur Function est concaténée avec des variables telles qu'un horodatage. De même, des points d'arrêt peuvent être injectés via eval() en utilisant une construction de chaîne similaire. Ces méthodes de génération dynamique rendent l'option "Ne jamais faire de pause ici" inefficace.

En remontant la pile d'appels pour ces débogueurs dynamiques, on découvre souvent une source commune : un timer setInterval qui exécute une fonction de débogage toutes les 500 millisecondes. Plusieurs stratégies peuvent être envisagées pour neutraliser ce mécanisme :

  1. Modification directe du code source (souvent non recommandée car elle peut déclencher d'autres vérifications et rediriger la page).
  2. Interception de la fonction setInterval.
  3. Interception des fonctions Function et eval.

L'approche la plus stable consiste à intercepter la fonction setInterval. Voici un script d'interception (hook) pour ce faire :


const _originalSetInterval = setInterval;
setInterval = function (fonctionAExecuter, delaiMs) {
    if (fonctionAExecuter.toString().includes('debugger')) {
        // Intercepte les timers qui tentent de déclencher un débogueur
        return function() {}; // Retourne une fonction vide
    } else {
        return _originalSetInterval(fonctionAExecuter, delaiMs);
    }
};

// Modifie le comportement de toString pour masquer l'interception
Function.prototype.toString = function () {
    return `function ${this.name}() { [code natif] }`;
};

L'application de ce script d'interception doit être effectuée avec précision. D'abord, définissez un point d'arrêt d'exécution sur le premier script chargé par la page. Ensuite, forcez un rechargement complet du navigateur (Ctrl + F5). Une fois le point d'arrêt atteint, collez et exécutez le script d'interception dans la console des outils de développement. Enfin, désactivez le point d'arrêt de script et reprenez l'exécution. La page devrait désormais charger sans interruption de débogage.

Analyse du Chiffrement Asymétrique

Après avoir désactivé les protections anti-débogage, l'étape suivante consiste à examiner les requêtes réseau. On constate rapidement que les paramètres de requête et les en-têtes (notamment X-Auth-Key, X-Signature, data, verify, et t) sont chiffrés. En recherchant les mots-clés pertinents dans le code source JavaScript, on localise le segment de code responsable du traitement des requêtes :


return l.interceptors.request.use(function(e) {
        var n, t, r = e.url.match(/\/page\/(\d+)\//);
        if (r) {
            var c = parseInt(r[1])
              , a = new URLSearchParams(e.url.split("?")[1] || "").get("challenge_type") || "fsymmetry_challenge"
              , o = Date.now()
              , s = "".concat(c, "_").concat(a, "_").concat(o);
            e.headers["X-Auth-Key"] = u(s),
            e.headers["X-Signature"] = (n = s,
            (t = new JSEncrypt).setPrivateKey("-----BEGIN RSA PRIVATE KEY-----\nMIICXAIBAAKBgQC1vKwZUIv7pgpJUXXPpDlD4+VEon3a0ANOrNmqAESrcGfkmYzD\nCo2JeuYezhBGjBNjwVmSct/Y3BBOCRGT2bvtCJGdS12RMvHbFcdbwS/Adh48+rhL\niMNYXLm7pI3e2k6TlScxKa7EeeZpVtew/Cv5z6ol0llNPp6BdqAlOa8DwIDAQAB\nAoGAS0GaWI9AsFAFEXBgoz/jkMf14DKTgEFEJVexeNLMnNuawhCNuBSOIMCaO2Zk\nWfpWaygdUeYs6M3UGKRruXhf92g/BRmJK5FzR0kWW4qw6WwlYob3TPc3c9MFOjmp\nVtWQ0VSeEPrnBNoQRccKl0dGBnToHGuV+KEuKx8oWZc/JM0CQQDH/cvlx0BKz2zN\n6PM8FidAvc+Wgon8YW81KJgC7iJIrK9FOpctOE3L1pdF7guOQNVGRqN4HCIgLfHE\ncqxWJKJtAkEA6KIkwHe/Q23uWH5GP8DHtVkLVfohTumYkpb0rk05EYQ0dsWSNzWH\nXDH/kD6ayNq+fscnS8g+59onzvfhJ0bq6wJBAKNFkDEHenWY4js481sauvEgBVnb\nOMvSv/emLHQ39cVfNbhPHRzN2rWPe/CbZtO8GmJFSS/FyBZ9a+P1uryZLAECQAaw\nApZ12s25b0yj9KkIhbU05hqGokZ+eKBeLpKELcvPHSL88wMbStTfqxUed5ymjStf\n1kVbcFOB9fsBLTvP0hkCQFCON0l1VjFli+vqfN0lypgIqCf85V6FZFN19creGCCd\n76pX/X2FIBbUSDN1z48SM5I/RKdCkTx7FY+509q2Mek=\n-----END RSA PRIVATE KEY-----"),
            t.sign(n, CryptoJS.SHA256, "sha256") || "");
            var i = function(e) {
                return CryptoJS.HmacSHA256(e, "dsa_secret_key_2025").toString()
            }(s)
              , l = u(s + "_param")
              , f = e.url.includes("?") ? "&" : "?";
            e.url += "".concat(f, "data=").concat(encodeURIComponent(l), "&verify=").concat(i, "&t=").concat(o)
        }
        return e
    }

Ce code montre la génération des paramètres de sécurité :

  • Une chaîne de base s est construite à partir du numéro de page, du type de défi ("fsymmetry_challenge") et de l'horodatage actuel.
  • X-Auth-Key est généré par un chiffrement RSA avec une clé publique (via la fonction u) de la chaîne s.
  • X-Signature est une signature RSA de la chaîne s, utilisant une clé privée et l'algorithme SHA256.
  • verify est un hachage HMAC-SHA256 de la chaîne s avec une clé secrète fixe.
  • data est un chiffrement RSA avec clé publique de la chaîne s + "_param".
  • L'horodatage o est également ajouté aux paramètres d'URL.

Pour reproduire ces calculs dans un environnement Node.js, il est nécessaire d'extraire et d'adapter le code JavaScript. Les bibliothèques JSEncrypt et CryptoJS sont essentielles. Assurez-vous d'avoir les fichiers jsencrypt.min.js et CryptoJS (ou modules équivalents) disponibles localement.


// Assurez-vous que les bibliothèques JSEncrypt et CryptoJS sont disponibles
// Pour un environnement Node.js, `globalThis` peut servir de `window` si nécessaire.
// Exemple : const JSEncrypt = require('./jsencrypt.min.js');
// const CryptoJS = require('./CryptoJS/rollups/sha256'); // ou le chemin approprié pour votre installation de CryptoJS

// Fonction utilitaire de chiffrement RSA avec clé publique
function crypterClePubliqueRSA(chaine) {
    const encryptor = new JSEncrypt();
    encryptor.setPublicKey("-----BEGIN PUBLIC KEY-----\nMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC1vKwZUIv7pgpJUXXPpDlD4+VE\non3a0ANOrNmqAESrcGfkmYzDCo2JeuYezhBGjBNjwVmSct/Y3BBOCRGT2bvtCJGd\nS12RMvHbFcdbwS/Adh48+rhLiMNYXLm+7pI3e2k6TlScxKa7EeeZpVtew/Cv5z6o\nl0llNPp6BdqAlOa8DwIDAQAB\n-----END PUBLIC KEY-----");
    return encryptor.encrypt(chaine) || "";
}

/**
 * Génère les paramètres de sécurité nécessaires pour les requêtes paginées.
 * @param {number} numeroPage - Le numéro de la page actuelle.
 * @param {number} horodatage - L'horodatage actuel en millisecondes.
 * @returns {object} Un objet contenant les en-têtes et paramètres de requête chiffrés.
 */
function obtenirClesSecurisees(numeroPage, horodatage) {
    const resultat = {};
    const typeDefi = "fsymmetry_challenge";
    const chaineBase = `${numeroPage}_${typeDefi}_${horodatage}`;

    // Calcul de X-Auth-Key (chiffrement RSA par clé publique)
    resultat["X-Auth-Key"] = crypterClePubliqueRSA(chaineBase);

    // Calcul de X-Signature (signature RSA par clé privée)
    const signataire = new JSEncrypt();
    signataire.setPrivateKey("-----BEGIN RSA PRIVATE KEY-----\nMIICXAIBAAKBgQC1vKwZUIv7pgpJUXXPpDlD4+VEon3a0ANOrNmqAESrcGfkmYzD\nCo2JeuYezhBGjBNjwVmSct/Y3BBOCRGT2bvtCJGdS12RMvHbFcdbwS/Adh48+rhL\niMNYXLm+7pI3e2k6TlScxKa7EeeZpVtew/Cv5z6ol0llNPp6BdqAlOa8DwIDAQAB\nAoGAS0GaWI9AsFAFEXBgoz/jkMf14DKTgEFEJVexeNLMnNuawhCNuBSOIMCaO2Zk\nWfpWaygdUeYs6M3UGKRruXhf92g/BRmJK5FzR0kWW4qw6WwlYob3TPc3c9MFOjmp\nVtWQ0VSeEPrnBNoQRccKl0dGBnToHGuV+KEuKx8oWZc/JM0CQQDH/cvlx0BKz2zN\n6PM8FidAvc+Wgon8YW81KJgC7iJIrK9FOpctOE3L1pdF7guOQNVGRqN4HCIgLfHE\ncqxWJKJtAkEA6KIkwHe/Q23uWH5GP8DHtVkLVfohTumYkpb0rk05EYQ0dsWSNzWH\nXDH/kD6ayNq+fscnS8g+59onzvfhJ0bq6wJBAKNFkDEHenWY4js481sauvEgBVnb\nOMvSv/emLHQ39cVfNbhPHRzN2rWPe/CbZtO8GmJFSS/FyBZ9a+P1uryZLAECQAaw\nApZ12s25b0yj9KkIhbU05hqGokZ+eKBeLpKELcvPHSL88wMbStTfqxUed5ymjStf\n1kVbcFOB9fsBLTvP0hkCQFCON0l1VjFli+vqfN0lypgIqCf85V6FZFN19creGCCd\n76pX/X2FIBbUSDN1z48SM5I/RKdCkTx7FY+509q2Mek=\n-----END RSA PRIVATE KEY-----");
    resultat["X-Signature"] = signataire.sign(chaineBase, CryptoJS.SHA256, "sha256") || "";

    // Calcul de 'verify' (HMAC-SHA256)
    resultat["verify"] = CryptoJS.HmacSHA256(chaineBase, "dsa_secret_key_2025").toString();
    
    // Calcul de 'data' (chiffrement RSA par clé publique avec suffixe)
    resultat["data"] = crypterClePubliqueRSA(chaineBase + "_param");

    return resultat;
}

Exécution des Requêtes Sécurisées

Avec les fonctions de chiffrement et de signature prêtes en JavaScript, nous pouvons maintenant utiliser un script Python pour orchestrer les requêtes HTTP, en utilisant execjs pour appeler les fonctions JavaScript et générer les paramètres dynamiques.


import time
import requests
import execjs

# En-têtes HTTP de base, les champs X-Auth-Key et X-Signature seront mis à jour dynamiquement
headers_requete = {
    "accept": "application/json, text/plain, */*",
    "accept-language": "fr-FR,fr;q=0.9",
    "user-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36",
    "x-auth-key": "",
    "x-signature": ""
}

# Les cookies doivent inclure un sessionid valide
cookies_requete = {
    "sessionid": "VOTRE_SESSION_ID_ICI" # Remplacez par un sessionid valide
}

# Modèle d'URL pour les requêtes paginées
url_modele = "https://www.spiderdemo.cn/authentication/api/fsymmetry_challenge/page/{}/"

# Charge et compile le script JavaScript contenant les fonctions de chiffrement
# Assurez-vous que le fichier 'parametres_secu.js' inclut JSEncrypt et CryptoJS,
# et la fonction 'obtenirClesSecurisees'.
contexte_js = execjs.compile(open('./parametres_secu.js', 'r', encoding='utf-8').read())

somme_totale = 0

# Boucle pour récupérer les données de 100 pages
for numero_page in range(1, 101):
    horodatage_actuel = int(time.time() * 1000) # Horodatage en millisecondes

    # Appel de la fonction JavaScript pour obtenir les paramètres chiffrés
    resultat_chiffrement = contexte_js.call("obtenirClesSecurisees", numero_page, horodatage_actuel)

    # Mise à jour des en-têtes avec les valeurs générées
    headers_requete["x-auth-key"] = resultat_chiffrement["X-Auth-Key"]
    headers_requete["x-signature"] = resultat_chiffrement["X-Signature"]

    # Préparation des paramètres d'URL supplémentaires
    donnees_chiffrees = resultat_chiffrement["data"]
    verification_hmac = resultat_chiffrement["verify"]
    
    parametres_url = {
        "challenge_type": "fsymmetry_challenge",
        "data": donnees_chiffrees,
        "verify": verification_hmac,
        "t": str(horodatage_actuel) # L'horodatage est passé comme chaîne
    }

    # Exécution de la requête GET
    reponse = requests.get(
        url_modele.format(numero_page),
        headers=headers_requete,
        cookies=cookies_requete,
        params=parametres_url
    )

    # Traitement de la réponse
    donnees_page = reponse.json().get("page_data", [])
    somme_totale += sum(donnees_page)
    print(f"Page {numero_page}: {reponse.text}")

print(f"La somme des données de 100 pages est : {somme_totale}")


Étiquettes: JavaScript Rétro-ingénierie débogage Chiffrement Asymétrique RSA

Publié le 8 juillet à 05h52