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 :
- Modification directe du code source (souvent non recommandée car elle peut déclencher d'autres vérifications et rediriger la page).
- Interception de la fonction
setInterval. - Interception des fonctions
Functioneteval.
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
sest construite à partir du numéro de page, du type de défi ("fsymmetry_challenge") et de l'horodatage actuel. X-Auth-Keyest généré par un chiffrement RSA avec une clé publique (via la fonctionu) de la chaînes.X-Signatureest une signature RSA de la chaînes, utilisant une clé privée et l'algorithme SHA256.verifyest un hachage HMAC-SHA256 de la chaînesavec une clé secrète fixe.dataest un chiffrement RSA avec clé publique de la chaînes + "_param".- L'horodatage
oest é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}")