
Documentation et rétro-ingénierie de reCAPTCHA
Ce dépôt contient une analyse technique de l'antibot de Google (reCAPTCHA), en se concentrant sur :
@g_recaptcha@lyxlobyxreCAPTCHA est l'un des systèmes anti-bot dotés des techniques d'obscurcissement les plus sophistiquées, employant une série de transformations qui rendent le code moins lisible et plus difficile à rétro-ingénier. La plupart des obscurcissements peuvent être facilement manipulés à l'aide de l'arbre syntaxique abstrait (AST), mais certains sont traités au moment de l'exécution, rendant l'AST inutile dans ce cas. Le polymorphisme est également appliqué au code pour modifier sa structure dans chaque version du script. Par exemple, le code n'effectue pas l'action directement. Au lieu de cela, il utilise des objets ou des fonctions qui changent de forme
Expressions de séquence
Le code est aplati en convertissant chaque bloc d'instructions en une expression continue séparée par des virgules. Cela peut apparaître dans les instructions if, les arguments de fonction, et même à l'intérieur d'objets

Arithmétique booléenne mixte
Entremêle des opérations arithmétiques (addition, soustraction, multiplication) avec des opérations bit à bit (ET, OU, XOR, NON) pour masquer la logique originale, par exemple -2 * ~(h & H) + -2 + (h ^ H)
Table de fonctions indirecte
Chaque fonction est construite dans une table et est appelée en utilisant son index, comme functions[index](https://github.com/elyelysiox/recaptcha/blob/main/args)
Tableau de constantes en ligne Un littéral de tableau local est affecté en ligne, au milieu d'une expression. Le tableau regroupe des constantes (nombres, chaînes) qui sont réutilisées dans tout le corps de la fonction via un accès par index
// b = [14, 1, "call"] assigné en ligne dans une expression de séquence
function(Y, Q, c, l, G, X, W, J, b, P) {
(Y & 94) == Y && (b = [14, 1, "call"], ...)
W[b[2]](J, G) // W.call(J, G)
Y >> b[1] & b[0] // Y >> 1 & 14
}
Multiplexage de fonctions Plusieurs fonctions logiquement distinctes sont fusionnées en une seule, en utilisant un paramètre numérique comme sélecteur de bloc. Le bloc actif est déterminé en évaluant le paramètre par rapport à des conditions bit à bit. Les appelants passent un littéral numérique comme sélecteur.
function(N, y, U, Y, h, H, m, C, u) {
C = [26, 47, 6];
// bloc 1
if ((N - 2 ^ 14) < N && (N - C[2] | 28) >= N) {
// logique de conversion de valeur en chaîne
}
// bloc 2
if ((N + 4 & 40) >= N && (N + 5 & C[0]) < N) {
Y = bB();
throw Error(Y === void 0 ? "valeur inattendue " + U + y : Y);
}
return u;
}
Branchement par opérateurs logiques
Remplace les blocs if et if/else par l'évaluation en court-circuit des opérateurs logiques, convertissant le flux de contrôle en expressions. Combiné avec des expressions de séquence, plusieurs branches apparaissent comme une seule expression continue séparée par des virgules
// if (a) { bloc }
a && (bloc)
// if (!a) { bloc }
a || (bloc)
// if (a) { x } else { y }
a ? x : y
// combiné avec CFF et expressions de séquence :
(Y | 1) & 14 || (c = Q.O, J = c.O.length + c.g.length),
(Y ^ 59) >> 3 == 3 && (Q.classList
? Q.classList.add(c)
: Z[31](31, Q, c) || (l = f[0](84, "string", "", Q), ...)),
Liaison de constantes de méthodes natives Lie les méthodes natives du navigateur à leurs récepteurs d'origine, les stockant en tant que constantes pour empêcher toute modification
LO = (Tw = self) == null ? void 0 :
(K9 = Tw.Math) == null ? void 0 :
(v4 = K9.floor) == null ? void 0 :
(mF = v4.bind) == null ? void 0 :
mF.call(v4, Math) // Math.floor.bind(Math)
LO(x) // Math.floor(x)
U4() // Math.random()
Ge(obj, prop) // Object.defineProperty(obj, prop)
Code mort Des blocs de code inaccessibles ou inutilisés sont injectés dans tout le fichier, augmentant sa taille à plus de 60 000 lignes, ce qui rend l'analyse statique et la rétro-ingénierie par LLM difficiles
Aplatissement du flux de contrôle Transforme chaque partie du code (déclarations et boucles) en une machine d'état plate. Cela masque la logique d'exécution d'origine en acheminant tous les blocs de code via un bloc « répartiteur » central
Les répartiteurs peuvent changer de forme ; certains ont 2-3 variables d'état, et le type de boucle/condition change. Ils ressemblent à ceci

Ceci est un CFF avec 2 variables d'état, l'une gère le bloc catch et l'autre le bloc try.
Pool de chaînes chiffrées Tous les littéraux de chaîne (API DOM, propriétés du navigateur, valeurs CSS, messages d'erreur, etc.) sont chiffrés en un seul pool de chaînes massif. Une fonction de déchiffrement utilise une graine et un chiffrement XOR basé sur LCG pour extraire chaque chaîne au moment de l'exécution
Il y a plus de 1990 sites d'appel répartis dans le code. La fonction de déchiffrement utilise une clé courante qui accumule les points de code décodés, rendant chaque caractère dépendant de tous les précédents
X = function(J, b, P, F, U) {
U = ["codePointAt", 127, "pool chiffré de chars"];
for (F = (P = 0, b = "", l); P < Q; P++)
J = (U[2][U[0]](c + P) ^ F) & U[1], // XOR avec clé courante
b += String.fromCodePoint(J),
F += J; // accumule la clé
return G = b;
}
// les sites d'appel passent une graine pour localiser et déchiffrer chaque chaîne
Z[23](64, 4, 54961, 103)() // → "lang"
Z[23](66, 4, 54961, 103)() // → "addEventListener"
Z[23](32, 12, 20287, 852)() // → "inline-block"

Itérateur de valeurs avec état reCAPTCHA utilise une fonction avec état qui renvoie une séquence d'objets et de valeurs d'exécution (comme window, document.body, des constantes numériques) dans un ordre fixe ; chaque appel avance un curseur interne. L'appeler dans le désordre ou trop de fois corrompt toutes les lectures ultérieures. Un mécanisme d'expiration invalide l'état après un intervalle fixe, renvoyant null pour toutes les lectures tardives
// les appels séquentiels renvoient des valeurs différentes :
c() // → window
c() // → document.body
c() // → 123
c() // → null (expiration dépassée)
l(c(), G[2], G[W[1]], G[1]) + l(c(), G[2], G[W[1]], 12)
// ↑ window ↑ window
10 * l(c(), G[2], G[W[1]], G[1]) + l(c(), G[2], G[W[1]], 12))
c().querySelectorAll(a[X[2]](98, X[1], X[1]))
// ↑ document.body
Table de fonctions calculée
Ceci est similaire à la Table de fonctions indirecte, mais ici l'index de la fonction à obtenir est calculé au moment de l'exécution avec une graine, en utilisant XOR et Modulo
c = ((Q ^ no | U[1]) >> 5) + no
A = mN[(c % U[2] + U[2]) % U[2]] // mN est la table de fonctions (50+ fonctions)
q[29](5, 6977) // seed=6977 → l'index se résout en fonction à mN[X]
q[29](53, 6187) // seed=6187 → index différent, fonction différente

Chiffrement des valeurs d'exécution
Certaines valeurs (paramètres de configuration du captcha, paramètres d'ancrage, etc.) ne sont jamais stockées en texte clair ; elles sont chiffrées immédiatement après la collecte et déchiffrées uniquement au moment de l'utilisation, ont un préfixe B au début

Obscurcissement du flux de contrôle asynchrone
La logique synchrone est convertie en machines d'état basées sur des générateurs enveloppées dans des chaînes de Promesses récursives. Toute trace d'une valeur dans le débogueur force à parcourir plusieurs gestionnaires asynchrones, perdant le contexte d'exécution d'origine à chaque frontière .then()
Composants pour initialiser reCAPTCHA``` ar: <WidgetInit?> k: co: hl: v: size: sa: anchor-ms: execute-ms: cb:
### Response
La réponse contient :
- Conception de la fenêtre iframe CAPTCHA
- Jeton d'ancrage utilisé pour la validation de la charge utile (/reload)
- La configuration principale pour l'initialisation, exécutée dans la méthode `recaptcha.anchor.Main.init` pour la recevoir dans recaptcha_en.js
### Structure :
Note : Recaptcha BotGuard a été supprimé le `04/01/2026`, vous pouvez voir quelques exemples [ici](https://github.com/elyelysiox/recaptcha-payload/tree/main/botguard_scripts)```javascript
[
"ainput",
[
"bgdata",
"",
"LyogQW50aS1zcGFtLiBXYW50IHRvIHNheSBoZWxsbz8gQ29ud...", // BotGuard Script Base64-Encoded
"YWVtZ0h5MGNCWXJDQ2lidGh0RW13RmhWdlc3aU5yeVpzSmx6U...", // BotGuard VM Bytecode Double Base64-Encoded
"wrB8fsOVU8K0YAzDsyQpw7ZHw6jDnMK1AMO6SRcvw53CsGlQw6/DuMO0wqr" // VM Config Bytecode Base64-Encoded
],
null,
[
"conf",
null,
"6LfTV4gkAAAAACDVrUvp9_DalxUPvFSU7M2HJDO-", // Website Key
0,
null,
null,
null,
1,
// Indexes for fingerprinting or something else
[16, 21, 125, 63, 73, 95, 87, 41, 43, 42, 83, 102, 105, 109, 121],
[-7614991, 137],
0,
null,
null,
null,
null,
0,
null,
0,
null,
700, // Time Start for Time Variances of Fingerprint Values
1,
null,
0,
// Encoded metadata for the VM's main bytecode constructs
"CvsCEg8I8ajhFRgAOgZUOU5CNWISDwjmjuIVGAA6BlFCb29IYxIPCMfm1DgYADoGZHhkTmlkEg8Is4qgOBgBOgZMV0o1a2ISDwiB7OgVGAA6Bkh1dlBqZhIPCK6e6zcYADoGR2JpT1FkEg8I94jmNxgAOgZvaWxlRGQSDwjwzeMVGAE6BmZJVkloYhIPCOLKoDcYAToGZ0xOQ0hjEg8I3r+3NxgBOgZlYXp1NmQSDwjY0oEyGAA6BkFxNzd2ZhIOCLjllDIYAToFUUJPRDASDwio7784GAA6BkdGVU5jZhIPCLWzqzgYAToGb3JZVjZkEg8I0tuVNxgAOgZmZmFXQWUSDwiV2JQyGAE6BlBxNjBuZBIPCKuRyjgYADoGQVo0TmJiEg8IxfOINxgAOgZhWG9pYWMSDwiNyoYyGAE6Bk93TjR0ZhIOCIr9iDcYADoFTWZJSTQaJwgDEiMdJv3NgTUZp4oJGYQKGZzijAIZzPMRGa+VQBnqsCYZ5a8MGQ==",
0,
1,
null,
null,
1,
null,
null,
0,
null,
null,
0,
0,
"cf5e3c3a6ab6c494c829a7932d9577c1e6de862dd4912e55dc7fe2e40a1f7604"
],
"https://who.clickbait.team:443", // Website URL:Port
null,
[
3,
1,
1
],
null,
null,
null,
1,
3600,
[
"https://www.google.com/intl/es/policies/privacy/",
"https://www.google.com/intl/es/policies/terms/"
],
"vQWNjTpMwdevQPWmE8akDzp8jsOZfa1VqTzVHaPiQ/c=",
1,
0,
null,
1,
1777669303203, // Fingerprint Data Encryption Key
0,
0,
[203], // Config Bytecode Key
null,
[185], // Config Bytecode Key
"RC--rg1OSRnOD0ayA",
null,
null,
null,
null,
null,
// Bft Token, Used in reCAPTCHA V2
"0dAFcWeA4RepD9zDjeMQE73pAT27pXZ7Nz_419U2K36QNqzHaKtLIDkwZQLi-Ud8OvSZHbDEcxQxusBQnsF5QQErMRpJMcUplaFg",
1777752103288 // "oc" Data Encryption Key
]
Les valeurs sont basées sur les valeurs déchiffrées de la première empreinte d'échantillon, regardez ici
Chaque sous-champ des valeurs d'empreinte a un format de base :
[value, key, elapsed]
Où key est la clé de chiffrement, et elapsed est le temps qu'il a fallu au collecteur pour obtenir et chiffrer la valeur. Cela permet à reCAPTCHA de détecter :
Le système utilise un planificateur interne :``` [42, 45, 53, 30, 28, 54, 29, 31, 32, 33, 34, 35, 37, 36, 38, 39, 43, 40, 41, 46, 48, 57, 58, 60, 61, 62, 63, 64, 66, 68, 69, 71, 72, 79, 55]
Cela représente :
- Ordre d'exécution
- Pipeline de collecte
- Séquence de génération de signaux
### Codes de signaux d'empreinte et dérivation de clé
Transforme les signaux d'empreinte en clés de chiffrement déterministes via un pipeline de dérivation en plusieurs étapes
Chaque valeur d'empreinte est :
- Normalisée
- Encodée en un code de signal compact
- Convertie en une clé de chiffrement numérique
- Chiffrée à l'aide de la clé dérivée
Le processus est déterministe, ce qui signifie que la même valeur d'entrée génère toujours le même code de signal et la même clé de chiffrement```
Raw Value
↓
Signal Code Derivation
↓
Compact Signal Code
↓
Numeric Key Derivation
↓
Encryption Key
↓
Value Encryption
Signal d'entrée``` "BUTTON,195a81c9"
***Étape 1 - Dériver le code de signal***```javascript
deriveSignalCode("BUTTON,195a81c9")
// → "wg"
Le code de signal généré est un identifiant déterministe compact pour la valeur d'origine.
Étape 2 - Dérivation de la clé numérique```javascript deriveKey("wg") // → 3792
Le code du signal est transformé en une clé de chiffrement numérique.
***Étape 3 — Chiffrer la valeur***```javascript
encryptValueWithKey(3792, "wgia1z9pwq")
// → "bYVbh6BUsE_5pLA"
La valeur suivante sera chiffrée avec la clé dérivée, en respectant l'ordre du collecteur.
Tous les codes de signal générés sont agrégés dans une structure sérialisée compacte.
Processus``` value -> "BUTTON,195a81c9" seed code -> wg key -> 3792 value -> "wgia1z9pwq" seed code -> 21 key -> 1599 value -> 1 seed code -> p1 key -> 3521 value -> "8cc68d83" seed code -> ld key -> 3448 value -> "https://nextcaptcha.com/demo..." seed code -> 9p key -> 1879 value -> 4 seed code -> op key -> 3553 value -> false seed code -> 1r key -> 1633 value -> 7 seed code -> qf key -> 3605 value -> "jYAQSHAEAI" seed code -> 1z key -> 1641 value -> "" seed code -> 80 key -> 1784 value -> 2 seed code -> jk key -> 3393 value -> "AAAAAAAAAA" seed code -> 1z key -> 1641 value -> "h3" seed code -> 9p key -> 1879 value -> "0,BUTTON,195a81c9" seed code -> ia key -> 3352 value -> -1 seed code -> 1u key -> 1636 value -> 0 seed code -> wq key -> 3802 value -> "74,fbfbc5b3" seed code -> 6z key -> 1796 value -> 0 seed code -> wq key -> 3802 value -> 0 seed code -> wq key -> 3802 value -> 2 seed code -> jk key -> 3393 value -> 0 seed code -> wq key -> 3802 value -> "-1,-1" seed code -> 1m key -> 1628 value -> "static.cloudflareinsights.co,..." seed code -> 1g key -> 1622 value -> "" seed code -> 80 key -> 1784 value -> "https://nextcaptcha.com,https...." seed code -> pd key -> 3572 value -> "[4,"CABIBQAggA","BAgAAgAAgA"]" seed code -> 1n key -> 1629 value -> "Demostración empresarial de reCAPTCHA v3..." seed code -> 1k key -> 1626 value -> "[2,76852,77850,77351]" seed code -> 9b key -> 1865 value -> "sha384-TNm" seed code -> 23 key -> 1601 value -> "[1680,1050,876,1166,844,876]" seed code -> 13 key -> 1570 value -> "[300,null,1778502450481]" seed code -> ex key -> 3251 value -> "[null,null,"",""]" seed code -> rx key -> 3654 value -> "[2147483648,70806416,65303240]" seed code -> 2r key -> 1664 value -> "GA1.1.354395113.1778502448" seed code -> 1g key -> 1622 value -> false seed code -> 1r key -> 1633 value -> "[[[1,"wg"],..." seed code -> 85 key -> 1789
**Final**```json
[
[
[1, "wg"], [1, "21"], [1, "p1"],
[1, "ld"], [1, "9p"], [1, "op"],
[1, "1r"], [1, "qf"], [1, "1z"],
[1, "80"], [1, "jk"], [1, "1z"],
[1, "9p"], [1, "ia"], [1, "1u"],
[1, "wq"], [1, "6z"], [1, "wq"],
[1, "wq"], [1, "jk"], [1, "wq"],
[1, "1m"], [1, "1g"], [1, "80"],
[1, "pd"], [1, "1n"], [1, "1k"],
[1, "9b"], [1, "23"], [1, "13"],
[1, "ex"], [1, "rx"], [1, "2r"],
[1, "1g"], [1, "1r"]
],
"54"
]
Idx 4 (string)
Valeur : 3ccb
Haché : true
Description : Génère une clé HMAC à partir de window.localStorage.getItem("rc::a") + "6d" converti en octets, calcule HMAC-SHA256(siteKey) avec cette clé, et retourne les 4 premiers caractères hexadécimaux du résultat.
La valeur de rc::a est un base64 généré aléatoirement.
Idx 5 (integer)
window.localStorage.length * 2falsewindow.localStorage multipliée par 2Idx 16 (string)
Valeur : yM5Us/j/fn6EDgtmPlP4Pxj605nMJN9dRYHyy5Mn
Haché : true
Description : Empreinte de filtre Bloom en base64 de 40 caractères de tous les nœuds <HEAD>. Parcourt chaque élément enfant en collectant les noms de balises, les attributs et le contenu textuel, sérialisé et passé dans un hachage djb2.
Chaque condensé est introduit dans un BitHash(240 bits, 7 rounds, max 25 nodes) qui définit des bits sur une grille de 40×6, puis encode chaque segment de 6 bits en un caractère base64. Les balises de script de reCAPTCHA lui-même sont exclues via une expression régulière dynamique sur src.
Idx 17 (integer)
2falseIdx 18 (string)
a2hyNTFuNDI4NnA3falseIdx 27 (string)
location.originfalseIdx 28 (boolean)
falsefalsewindow.parent != window ? true : window.frameElement != null ? true : falseIdx 29 (string)
e2a3cd70truegrecaptcha.execute haché avec SHA-256, ExempleIdx 30 (integer)
0falsehttps://www.gstatic.com/recaptcha/releases/<version>/, ExempleIdx 31 (string)
AAgkAQI0SBtruedocument.cookie hachées, ExempleIdx 32 (string)
""falsedocument.referrer.Idx 33 (integer)
Valeur : 2
Haché : false
Description : Élément de la boîte reCAPTCHA où le rendu est effectué.
// comme grecaptcha.execute('.grecaptcha') -> ce div
// En gros, cela retourne un nombre ou la profondeur/niveau de ce div dans le document.
// Exemple : html > body > main > form > fieldset > div.recaptcha.form-field
// 5 4 3 2 1 0
// out = 5
nM = document.getElementsByClassName('grecaptcha-badge')[0] // ou document.getElementsByClassName('g-recaptcha')[0] v2
let count = 0;
for (; nM = nM.parentElement || null;) {
count++;
}
const result = count;
Idx 34 (string)
AAAAAAAAAAtrue<INPUT> hachés, ExempleIdx 35 (string)
0,DIV,f0c7414etruedocument.activeElement pour déterminer s'il s'agit d'un élément d'achat en utilisant une expression régulière /buy|pay|place|order|donate|purchase/i avec textContent, classNames et id, ExempleIdx 36 (string)
h3, h2 ou http/1.1falsenextHopProtocol de la performance de navigation performance.getEntriesByType("navigation")[0].nextHopProtocolIdx 37 (integer)
-1falseperformance.timing.unloadEventStartIdx 38 (integer)
96falseperformance.timing.domainLookupStart - performance.timing.domainLookupEndIdx 39 (integer)
0falseperformance.navigation.typeIdx 40 (integer)
0falsewindow.scrollY ou document.defaultView.pageYOffsetIdx 41 (string)
DIV,a08cd360true:hover où la souris était positionnée à ce même moment, obtient le dernier élément et hache tagName, classNames et id, ExempleIdx 42 (string)
9,e3b0c442trueIdx 44 (string)
2v591z63wqtrue:hover, document.activeElement, tous les éléments <INPUT> hachés, location.url, window.scrollY, Exemple :const element = document.querySelectorAll(":hover");
const current = element[element.length - 1];
const result = [
deriveSignalCode(hash(current)),
deriveSignalCode(hash(document.activeElement)),
deriveSignalCode(hash(document.querySelectorAll('input'))),
deriveSignalCode(location.url),
deriveSignalCode(String(window.scrollY))
].join('')
Idx 45 (integer)
4falsewindow.history.Idx 46 (string)
https://static.kogstatic.com/0000/34c...falseline:column où cela s'est produit.Idx 47 (integer)
0falsewindow.getSelection().toString().length || 0Idx 49 (string)
h2-0falsenextHopProtocol, et détermine si sa duration est zéro. Retourne le résultat au format protocol-isZero, ExempleIdx 50 (array)
Valeur : [1,0,null,1,1,["805b1z63wq"],"MWc2MzZwMnR0ZHNkMw==",0,null,[[1]]]
Haché : true
Description : Contient les comportements de l'utilisateur, les éléments :hover hachés et un timeout qui s'incrémente à chaque fois qu'un autre élément :hover est collecté. Il analyse les activations de l'utilisateur pour accumuler le nombre de fois que l'utilisateur a interagi ou est toujours présent sur la page en calculant (10 * IsActive + IsBeenActive)
Format de base :
[
count,
timeout,
null,
timeoutMultiplier,
transitionCount,
[ elementsHoverHashed ],
sessionId,
userActivationScore,
null,
[ [ flags ] ]
]
Idx 51 (array)
[0]falsedocument.body.innerText des correspondances avec les mots "try again", "incorrect", "invalid" ou "declined" et retourne le nombre total de correspondances trouvées. Sinon retourne 0, ExempleIdx 52 (integer)
11false10 * navigator.userActivation.isActive + navigator.userActivation.hasBeenActiveIdx 53 (integer)
Valeur : 4
Haché : false
Description : Prend l'URL actuelle du site web, la convertit en un tableau de caractères, la tronque à 100 caractères, puis la reconvertit en chaîne, Exemple
Ensuite, c'est 5 si la longueur de cette chaîne tronquée est paire, ou 4 si elle est impaire.
Idx 54 (boolean)
falsefalsewindow.document.hiddenIdx 55 (array)
[[[1,"2v"],[1,"9z"],[1,"z8"],[1,"us"],[1,..falseIdx 56 (string)
-1,-1falsewindow.opener, retourne "-1,-1" si null, ExempleIdx 57 (string)
Valeur : www.gstatic.com,_,static.kogstatic.com,www.google.com,www.googletagmanager.,...
Haché : false
Description : Collecte les hôtes provenant de l'attribut src de tous les éléments <SCRIPT> présents dans document.scripts, Exemple
Pour chaque <SCRIPT> :
Si l'élément a l'attribut src (hasAttribute("src")), la valeur de src est prise et analysée avec une regex de type URI (nd), en extrayant uniquement le composant hôte (groupe de capture 3 de la regex).
Si le <SCRIPT> n'a pas d'attribut src (c'est un script en ligne), le caractère de remplacement "_" est utilisé.
Idx 58 (string)
mapslitepromosdismissed1falsewindow.localStorage échantillonnée.Idx 59 (string)
""falsewindow.nameIdx 60 (string)
https://www.kogama.com,https://www.google.com,https://js.stripe.comfalseIdx 61 (array)
[0,"AAAAAAAAAA","AAAAAAAAAA"]trueMutationObserver, ExempleIdx 62 (string)
document.titlefalseIdx 63 (array)
Valeur : []
Haché : false
Description : Extrait les horodatages (nombres de 12-13 chiffres commençant par 1[2-9], typiques des millisecondes epoch ~2008-2099) des valeurs de cookies, Exemple
Accumule les valeurs suivantes sur tous les IDs trouvés : count, min, max et average.
Le résultat final est un tableau [count, min, max, avg]
Idx 64 (integer)
55557falseIdx 65 (string)
sha384-3qctruehttps://www.google.com/recaptcha/api.js?render= (tronqué à 10 caractères)Idx 67 (array)
[screen.width, screen.height, screen.availHeight, window.innerWidth, window.innerHeight, window.outerHeight]falseIdx 68 (array)
[300,null,1777685870223]false[new Date().getTimezoneOffset(), null, Date.now()]Idx 69 (string)
09AKhCRwjrDFiHsrRA--o23-HB-eWqOtk_XQ-rLE1...falseIdx 70 (array)
Valeur : [null,null,"","",null,"1l0fl"]
Haché : false
Description : Extrait les montants monétaires totaux (comme Total: $50.00 ou Total 150.00 EUR), Exemple
à partir de document.innerText en recherchant le symbole ou le code de la devise avec le montant numérique en utilisant l'expression régulière :
total[\S\s]{0,20}?(((R\$|\$|€|£|USD|EUR)\s*[\d\.,]+)|[\d\.,]+\s*(€|USD|EUR))`
total → Recherche le mot exact "total"
[\S\s]{0,20}? → Recherche entre 0 et 20 caractères intermédiaires (espaces, deux-points, sauts de ligne) avant de trouver le nombre
(((R\$|\$|€|£|USD|EUR)\s*[\d\.,]+)|[\d\.,]+\s*(€|USD|EUR)) → C'est le bloc principal qui capture l'argent et est divisé en deux options distinctes
Idx 71 (array)
[jsHeapSizeLimit,usedJSHeapSize,totalJSHeapSize]falseIdx 72 (array)
Valeur : [[["Google Chrome","147"],["Not.A/Brand",...
Haché : false
Description : Tableau de données userAgent navigator.userAgentData, un indicateur indiquant s'il est mobile et la plateforme, Exemple :
[
navigator.userAgentData.brands.map(b => [b.brand, b.version]),
navigator.userAgentData.mobile ? 1 : 0,
navigator.userAgentData.platform
]
Idx 73 (array)
[[null,2,0,"Mozilla/5.0 (Windows NT 10.0;...falseIdx 77 (integer)
nullfalseIdx 78 (string)
""falseCertains signaux reCAPTCHA peuvent être indisponibles ou ne pas être collectés (en raison de délais d'attente liés au débogage). Dans ce cas, le signal manquant est rempli avec une valeur encodée en base64 générée aléatoirement. Par exemple :``` ["CMXZyamk3ZHNjdGsyaQ==", 3249, 591]
Une fois la construction de l'empreinte terminée, elle est sérialisée en une chaîne et chiffrée à l'aide de la clé de chiffrement chargée depuis le champ d'ancrage (index 18). Le résultat chiffré est ensuite stocké dans le champ d'index `16` de la charge utile `/reload`.
## Signaux de la VM
Voici les valeurs du navigateur obtenues par la VM interne de reCAPTCHA, ainsi que la clé de signal. Certaines valeurs ne sont pas affichées dans l'empreinte exemple car la VM n'extrait que certaines valeurs du bytecode de configuration. Par conséquent, d'autres valeurs seront affichées. Pour plus de détails, voir [ici](https://github.com/elyelysiox/recaptcha-vm)
Format de signal de base :```
[
null,
collectorElapsed,
encryptElapsed,
value
]
Où collectorElapsed signifie combien de temps (ms) il a fallu au collecteur pour obtenir la valeur
Et encryptElapsed signifie combien de temps (ms) il a fallu pour chiffrer la valeur.
Ces temps sont mesurés pendant l'exécution de la VM.
[ null, 7, 0, "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/144.0.0.0 Safari/537.36" ], null, null, [ null, 0, 0, "[]" ], null, null, [ null, 4, 0, "["Join And Get Free Trial!","","Captcha Solving Service","Products","Docs"]" ], [ null, 0, 0, "false" ], null, [ null, 0, 0, "0" ], [ null, 1, 0, "[null,null,null,null,"false"]" ], [ null, 3, 4, "[[[0,921],[1,2053]],[[235054,1,1,1],[164277,3,4,1]],2,39]" ], [ null, 1, 4, "[[[0,0,979]],1,0]" ], [ null, 1, 1, "[[[1,0]]]" ], [ null, 4, 1, "[886,2300,262.2099999997952,392.7600000009595,2050.2399999993577]" ], [ null, 1, 1, "[[[1123,0,0]]]" ], [ null, 1, 2, "[[[1123,0]]]" ], [ null, 1, 3, "[0,null,null,null,null,null,null]" ], [ null, 5, 9, "[1,[[2050,125,633,266,[609,266,105,44],3,1,50,1,1]],[3,3,318],[28,777,858,28],0,0,25]" ], [ null, 0, 0, "" ], [ null, 0, 0, "229.6" ], [ null, 0, 0, "false" ], [ null, 1, 0, "[1,0,0,Infinity]" ], [ null, 1, 1, "["0",-1,-1,999]" ], [ null, 1, 0, "["undefined",3]" ], [ null, 1, 6, "[2,4,53324367238]" ], [ null, 12, 0, "["Google Inc. (Intel Inc.)","ANGLE (Intel Inc., Intel(R) UHD Graphics 630, OpenGL 4.1)",30]" ], [ null, 28, 2, "[1542343452,0,105002991,3556653,96784904,-731949068,-902263026,-1914850612,571474391,-1588406278,3443508,1967713171,-1249474914,-1588406278,0,918504841,-807058197]" ]
- **Key 417** (`string`)
- Value: `Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36...`
- Description: User-Agent complet du navigateur
- **Key 727** (`array`)
- Value: `[]`
- Description: Blocs de prix de produits, contient les prix des produits commerciaux sur le site web, ceux-ci peuvent apparaître sur des sites de places de marché comme Amazon, eBay, AliExpress, etc, [Exemple](https://github.com/elyelysiox/recaptcha/blob/main/findProductPricesBlock.js)
- **Key 150** (`array`)
- Value: `[\"Join And Get Free Trial!\",\"\",\"Captcha Solving Service\",\"Prod...`
- Description: Liens extraits des éléments `<a>` du site web, la machine virtuelle extrait rarement ces valeurs
- **Key 545** (`boolean`)
- Value: `false`
- Description: `navigator.webdriver`
- **Key 370** (`integer`)
- Value: `0`
- Description: Nombre maximal de points de contact de l'appareil `navigator.maxTouchPoints`
- **Key 779** (`array`)
- Value: `[null,null,null,null,"false"]`
- Description: Extrait les données des champs d'expédition/facturation, [Exemple](https://github.com/elyelysiox/recaptcha/blob/main/formFieldScrape.js)
`postal|postalcode|postalcode|postal_code|zip` → recherche le champ de code postal
`ship|deliver` → section d'expédition
`billing|payment|credit` → section de facturation
`country` → sélecteur de pays
`addr.*(match|equal)|(match|equal).*address` → case à cocher "même adresse"
Lit le nom, l'id, l'autocomplete, le className de chaque élément
Trois phases : saisies de texte, instructions SELECT, cases à cocher
- **Key 659** (`array`)
- Value: `[[[0,921],[1,2053]],[[235054,1,1,1],[164277,3,4,1]],2,39]`
- Description: Contient les données des éléments pressés sur la page au format `[[[count,timestamp]],[[hashedElem,tagType,type,autoComplete]],pressedCount,behaviorChecksum]`
### Type d'élément
| Type d'élément | ID |
|-------------|----|
| `""` | 1 |
| `text` | 2 |
| `button` | 3 |
| `submit` | 4 |
| `checkbox` | 5 |
| `email` | 6 |
| `password` | 7 |
| `select-one` | 8 |
| `select-multiple` | 9 |
| `radio` | 10 |
| `textarea` | 11 |
| `file` | 12 |
| `tel` | 13 |
| `number` | 14 |
| `date` | 15 |
| `reset` | 16 |
| `range` | 17 |
### Algorithme de somme de contrôle comportementale
La behaviorChecksum est un score comportemental compact dérivé des données d'interactions utilisateur collectées.
Pour chaque interaction :
- L'indice d'interaction est incrémenté de un
- L'horodatage est combiné avec l'indice d'interaction ajusté
- Le hachage de l'élément est réduit à l'aide d'une opération modulo
- Le résultat est accumulé dans un score courant
---
```javascript
function computePressedElementsChecksum(pressedElementsArray) {
let accumulator = 0;
for (const [countTimestampPair, elementInfo] of zip(
pressedElementsArray[0],
pressedElementsArray[1],
)) {
const shiftedTimestamp = countTimestampPair[0] + 1;
const baseValue = countTimestampPair[1] + shiftedTimestamp;
const remainder = elementInfo[0] % baseValue;
accumulator = remainder + accumulator;
}
const logTerm = Math.log(accumulator + 1) * 4;
const modTerm = accumulator % 10;
const behaviorChecksum = Math.floor(logTerm + modTerm);
return JSON.stringify(pressedElementsArray);
}
function zip(a, b) {
const length = Math.min(a.length, b.length);
const result = [];
for (let i = 0; i < length; i++) {
result.push([a[i], b[i]]);
}
return result;
}
```
### Exemple
```javascript
const checksum = computePressedElementsChecksum([[[0,921],[1,2053]],[[235054,1,1,1],[164277,3,4,1]],2,39]);
console.log(checksum); // → 39
```
- **Key 959** (`array`)
- Value: `[[[0,0,979]],1,0]`
- Description: Contient les mouvements de défilement collectés pendant la session de la page
### Format de base
```
[ [ [ scrollX, scrollY, timestamp ] ], count, behaviorChecksum ]
```
- **Key 895** (`array`)
- Value: `[[[1,0]]]`
- Description:` VisibilityStateEntry` suit l'état de visibilité de la page, indiquant si l'utilisateur consulte actuellement la page ou a navigué ailleurs, au format `[[[isVisible, startTime]]]`
- **Key 1092** (`array`)
- Value: `[886,2300,262.2099999997952,392.7600000009595,2050.2399999993577]`
- Description: Performance reCAPTCHA, PerformanceNavigationTiming et PerformanceEventTiming, au format `[vmStart,vmEnd,domContentLoadedEventEnd,loadEventEnd,firstUserInteractionTime]`
- **Key 41** (`array`)
- Value: `[[[1123,0,0]]]`
- Description: ClearTimeout
- **Key 43** (`array`)
- Value: `[[[1123,0]]]`
- Description: ClearTimeout
- **Key 549** (`array`)
- Value: `[0,null,null,null,null,null,null]`
- Description: KeyboardEvent, touches calculées + horodatages
- **Key 352** (`array`)
- Value: `1,[[2050,125,633,266,[609,266,105,44],3,1,50,1,1]],[3,3,318],...`
- Description:
La structure contient :
- Temporisation des mouvements de souris
- Coordonnées du curseur
- Durées des clics
- Dimensions des éléments pressés
- Informations sur le type de pointeur
- Métriques de pression
- Distance parcourue par le curseur
- Statistiques temporelles comportementales
### Format de base :
```
[
eventsCount,
mouseEvents,
[
coalescedEventsCount,
averageInterEventDelay,
timingVarianceScore
],
[
totalPoints,
clickSequenceScore,
totalCursorDistance,
totalPoints
],
0,
0,
behaviorChecksum
]
```
### Structure des événements souris :
Chaque entrée dans mouseEvents suit cette structure :
```
[
timestamp,
clickDuration,
clientX,
clientY,
[
elementLeft,
elementTop,
elementWidth,
elementHeight
],
elementTagType,
trackedElementIndex,
pressure,
pointerTravelDistance,
pointerArea
]
```
| Nom | Description |
|---|---|
| `timestamp` | Horodatage de l'événement relatif au début de l'événement |
| `clickDuration` | Durée entre `pointerdown` et `pointerup` |
| `clientX` | ... |
| `clientY` | ... |
| `elementBounds` | Rectangle englobant de l'élément interactif |
| `elementTagType` | Identifiant de balise DOM encodé de l'élément interactif |
| `trackedElementIndex` | Identifiant interne de l'élément DOM interactif provenant de la carte d'interaction d'exécution |
| `pressure` | Valeur de pression de pointeur normalisée multipliée par `100` |
| `pointerTravelDistance` | Distance parcourue entre `pointerdown` et `pointerup` |
| `pointerArea` | Zone de contact calculée comme `largeur * hauteur` |
### Type de balise d'élément
Ceci est initialisé lors de l'exécution de la VM reCAPTCHA
| Type | ID |
|-----------|----|
| `BODY` | 1 |
| `INPUT` | 2 |
| `BUTTON` | 3 |
| `SELECT` | 4 |
| `TEXTAREA`| 5 |
| `FORM` | 6 |
| `A` | 7 |
| `AREA` | 8 |
| `AUDIO` | 9 |
| `VIDEO` | 10 |
| `IFRAME` | 11 |
| `DIV` | 12 |
| `SPAN` | 13 |
| `LABEL` | 14 |
### Exemple
```
[
18342,
91,
742,
381,
[700, 320, 120, 38],
3,
1,
50,
1.4,
16
]
```
Représente :
- événement à `18,342s`
- durée du clic de `91ms`
- clic à `(742, 381)`
- interaction avec un `BOUTON`
- pression du pointeur `0,5` (50)
- léger mouvement du curseur pendant le clic
- zone de contact du pointeur de `16`
### Algorithme de somme de contrôle comportementale
Pour chaque événement souris :
- La durée du clic est amplifiée
- Le résultat est combiné avec l'horodatage de l'événement
- Les coordonnées du curseur et les dimensions de l'élément sont mélangées
- Un ratio pondéré est calculé
- Le ratio est arrondi et accumulé
Après le traitement de tous les événements :
- Le score accumulé est combiné avec la distance totale du curseur
- Le résultat est normalisé en utilisant le nombre de points collectés
- Une transformation logarithmique est appliquée
- Des statistiques de mouvement supplémentaires sont incorporées
- La valeur finale est réduite à l'aide d'une opération modulo
- Le résultat est arrondi en un petit score entier
---
```javascript
function computeMouseBehaviorChecksum(mouseDataArray) {
let totalSum = 0;
for (let i = 0; i < mouseDataArray[1].length; i++) {
const event = mouseDataArray[1][i];
const scaledClickDuration = event[1] * 4;
const xPlusScaledDuration = event[0] + scaledClickDuration;
const combinedElementDims = event[2] + event[4][2] + event[4][1]; // clientY + elementWidth + elementTop
const product = xPlusScaledDuration * combinedElementDims;
const divisor = event[3] + event[4][3] + event[4][0] + 1; // elementTagType + elementHeight + elementLeft + 1
const roundedValue = Math.round(product / divisor);
console.log(roundedValue);
totalSum += roundedValue;
}
const summaryArray = mouseDataArray[3]; // [totalPoints, clickSequenceScore, totalCursorDistance, totalPoints]
const sumPlusCursorDistance = totalSum + summaryArray[2];
const totalPointsPlusOne = summaryArray[0] + 1;
const ratio = sumPlusCursorDistance / totalPointsPlusOne;
const ratioPlusOne = ratio + 1;
const logRatio = Math.log(ratioPlusOne);
const scaledLog = logRatio * 4;
const ratioPlusTotalPoints = ratio + summaryArray[0];
const ratioPlusTotalPointsPlusScore = ratioPlusTotalPoints + summaryArray[1];
const modResult = ratioPlusTotalPointsPlusScore % 11;
const behaviorChecksum = Math.round(scaledLog + modResult);
return JSON.stringify(mouseDataArray);
}
```
### Exemple
```javascript
const checksum = computeMouseChecksum([1,[[2050,125,633,266,[609,266,105,44],3,1,50,1,1]],[3,3,318],[28,777,858,28],0,0,25]);
console.log(checksum); // → 25
```
- **Key 360** (`string`)
- Value: `""`
- Description: Analyse des prix totaux collectés du site web $ avec une expression régulière ``(?i)total[\S\s]{0,20}?(?:(?:(?:USD|\$)\s*)?[\d\.,]+\s*(?:USD|\$)|(?:USD|\$)\s*[\d\.,]+)``, [Exemple](https://github.com/elyelysiox/recaptcha/blob/main/websitePricesTotal.js)
- **Key 1278** (`integer`)
- Value: `229.6`
- Description: Division par 10 du temps d'exécution actuel `Math.trunc(performance.now()) / 10`
- **Key 1422** (`boolean`)
- Value: `false`
- Description: Analyse s'il y a des champs OTP/2FA sur le site web, [Exemple](https://github.com/elyelysiox/recaptcha/blob/main/parseOTPFields.js)
- **Key 614** (`array`)
- Value: `[1,0,0,Infinity]`
- Description: Informations sur la batterie de l'appareil, au format `[level,isCharging,chargingTime,dischargingTime]`
- **Key 2033** (`array`)
- Value: `["0",-1,-1,999]`
- Description: Certaines valeurs constantes de la VM et le timing d'exécution de la VM
- **Key 1313** (`array`)
- Value: `["undefined",3]`
- Description: Vérifications Android, Chrome, `[typeof window.android, Object.keys(window.chrome).length]`
- **Key 1994** (`array`)
- Value: `[2,4,53324367238]`
- Description: `[navigator.hardwareConcurrency, navigator.deviceMemory, navigator.storage.estimate().qouta]`
- **Key 1310** (`array`)
- Value: `["Google Inc. (Intel Inc.)","ANGLE (Intel Inc.,...`
- Description: Rendu WebGL, Fournisseur et extensions totales, [Exemple](https://github.com/elyelysiox/recaptcha/blob/main/webglSignals.js)
- **Key 291** (`array`)
- Value: `[1542343452,0,105002991,3556653,96784904,-731949068,-902263026,...`
- Description: Clés de prototypes spécifiques hachées d'objets navigateur, comme `SpeechSynthesisEvent`, `NetworkInformation`, `HTMLElement`, etc, [Exemple](https://github.com/elyelysiox/recaptcha/blob/main/hashBrowserProtos.js)
## Charge utile de la requête de rechargement
Ces données sont sérialisées à l'aide de protobuf (Protocol Buffers), une méthode efficace et neutre pour sérialiser des données structurées, développée par Google.```javascript
[
"rreq",
"U5VsmTDhJM1iOJUyw4DEUTYv",
"03AFcWeA5Els3SIQviW2OYCHYD4uDhKlFPG6A48z2nk74BC2vrXSRvtpP9uVkWW96kUKm7sHIr...",
null,
null,
"-383788762",
"q",
"05APQrAobTCFGtbKVY3NwExv-K_G7oqmzGyUcTXi532CBKvSJUiT49tkRSgGD__OdfVRZtrI3i...",
"submit",
null,
null,
null,
null,
null,
"6LcAbwIqAAAAAJvVAhSSJ8qzYsujc7kn1knmSgQX",
null,
"0XA4cHhALxgMF9_Kt6uze2ZTR08XAe2x8o3BrU1hbTouNf3o1cnRmYRxZW01IA0BCNC_qJykb...",
null,
null,
null,
"tbMywxNTQsODQ0XSxbMSwyOTksMTA4Nl1dLFtbMiw2MSwxMzIuNzM5OTk5OTk5ODQ2MzNdLF...",
"0aAPQrAobqMEazjp9PGLYWTgxWGGcq9k5MJNI6LFBWfTh0UkDaVobdH-InwAYsTXtxs3rH_fI",
"BDAAYAIAGEcAAUoYI4lDCKAsCJlcgB6AAJtgggoIEEOALSoQRZxSZyAJBHANCkAJQYhFJES...",
null,
null,
"W1tbNTAwNiw0M10sWzY0NjA3LDFdLFs0NTQ2NCwxXSxbMzU4MzcsMV1dXQ==",
null,
null,
]
| Index | Valeur | Description |
|---|---|---|
0 | rreq | Identifiant du type de requête |
1 | U5VsmTDhJM1iO... | Version de reCAPTCHA |
2 | 03AFcWeA5Els3... | Jeton de validation obtenu depuis le point de terminaison /anchor |
5 | -383788762 | Hachage de la totalité de la charge utile d'empreinte sérialisée, Exemple |
6 | q | Indique le défi ou le mode de requête en cours d'exécution |
7 | 05APQrAobTCFG... | Modèles d'utilisation, contenant l'historique d'utilisation et les motifs de défis précédemment résolus. Peut augmenter significativement le score si valide |
8 | submit | Action spécifique du site que l'utilisateur effectue sur la page au moment de la vérification (ex. signin, register) |
14 | 6LcAbwIqAAAA... | Clé du site Web |
16 | 0XA4cHhALxgM... | Chiffrement de la totalité de la charge utile d'empreinte sérialisée avec la clé de chiffrement provenant de l'ancre (index 18) 1777669303203 |
20 | tbMywxNTQsOD... | Structure de télémétrie encodée en Base64 contenant les écarts de temporisation de la machine virtuelle, les temporisations des ressources, les statistiques des objets navigateur collectées et les URL des scripts du site |
21 | 0aAPQrAobqME... | Jeton associé à un CAPTCHA résolu précédemment. Utilisé pour la continuité de session et la corrélation de confiance |
22 | BDAAYAIAGEcA... |
| Valeur | Description |
|---|---|
q | Défi reCAPTCHA V3 |
fi | Défi reCAPTCHA V2/Invisible |
a | Défi audio |
qr | Réponse rapide (nouveau) |
Idx 20)Pour décoder :```javascript atob("W1tbMywwLDgyOV0sWzEs...") // → [[[3,0,829],[1,42,863]],[[2,727,1341.300000011921]...
Le payload contient :
- Informations de chronométrage des ressources
- Chronométrage des performances des tâches longues
- Deltas d'exécution VM de reCAPTCHA
- URLs des scripts du site web
- Statistiques de collecte d'objets du navigateur
### Structure de base```js
[
[recaptchaResources],
[tasksTiming],
[
null,
null,
null,
idleTimeoutDelta,
deltaTiming,
0,
0,
0,
],
websiteScriptsUrls,
[collectionElapsed, browserObjectsLength]
]
[ PerformanceLongTaskTiming.name == "self" ? 2 : 4, PerformanceLongTaskTiming.duration, PerformanceLongTaskTiming.startTime ]
### Format de Synchronisation des Ressources reCAPTCHA```js
[
resourceType,
duration,
startTime
]
| Resource | Type |
|---|---|
recaptcha/api.js | 3 |
recaptcha/releases/<version> | 1 |
https://www.google.com/recaptcha/api2/anchor | 2 |
https://www.google.com/recaptcha/api2/bframe | 4 |
Idx 25)Ce champ contient des compteurs encodés en Base64 pour les événements d'interaction navigateur collectés lors de l'exécution
Exemple décodé :```js [ [ [5006, 43], [64607, 1], [45464, 1], [35837, 1] ] ]
### Structure du compteur d'événements```js
[
[
[eventType, count]
]
]
| Hash | Type d'événement |
|---|---|
5006 | pointermove |
64607 | pointerdown |
45464 | pointerup |
31617 | keydown |
37178 | keyup |
35837 | focusin |
[ "rresp", "", null, 120, [ "pmeta", [ "/m/013xlm", null, 3, 3, 3, null, "Tractor" ], null, [1] ], "", null, null, "", "", null, null, "", null, "", "", 0 ]
## Descriptions des champs
| Index | Nom | Description |
|---|---|---|
| `0` | `rresp` | Identifiant du type de réponse retourné par le point de terminaison de rechargement |
| `1` | `responseToken` | Jeton de réponse reCAPTCHA |
| `3` | `tokenExpiration` | Durée de vie du jeton en secondes avant expiration |
| `4` | `pmeta` | Bloc de métadonnées du défi contenant des informations sur le type, la catégorie et la disposition de la grille du CAPTCHA |
| `5` | `challengeType` | Type de défi CAPTCHA |
| `8` | `usagePatternToken` | Modèles d'utilisation, stockés dans `window.sessionStorage` et réutilisés dans les futures requêtes `/reload` |
| `9` | `challengeImageToken` | Jeton utilisé pour récupérer la charge utile de l'image CAPTCHA depuis le point de terminaison `/payload` |
| `12` | `humanVerificationToken` | Jeton d'identification humaine associé à l'état Google reCAPTCHA et aux cookies du navigateur |
| `14` | `captchaSessionToken` | Jeton de session interne |
| `15` | `bftToken` | ... |
---
### Structure de `pmeta`
Exemple :```js
[
"pmeta",
[
"/m/013xlm",
null,
3,
3,
3,
null,
"Tractor"
],
null,
[1]
]
pmeta[1] Values| Position | Valeur | Description |
|---|---|---|
0 | /m/013xlm | Identifiant de catégorie interne pour la classe de l'objet cible |
2 | 3 | Nombre de colonnes de la grille |
3 | 3 | Nombre de lignes de la grille |
5 | null | Champ de métadonnées réservé ou optionnel |
6 | "Tractor" | Étiquette textuelle |
| Type | Description |
|---|---|
imageselect | CAPTCHA standard de sélection d'image |
tileselect | Défi dynamique d'image basé sur des tuiles |
dynamic | Défi dynamique où les tuiles se rechargent après la sélection |
multicaptcha | Défi CAPTCHA en plusieurs étapes |
audio | Défi d'accessibilité audio |
nocaptcha | Flux de vérification par simple case à cocher |
doscaptcha | Refuser le CAPTCHA en cas d'abus |
La valeur à l'Idx 9 (challengeImageToken) est utilisée pour récupérer l'image CAPTCHA :```txt
https://www.google.com/recaptcha//payload
?p=
&k=
### Identifiants de catégorie
| ID | Catégorie |
|---|---|
| `/m/0pg52` | Taxi |
| `/m/01bjv` | Bus |
| `/m/02yvhj` | Autobus scolaire |
| `/m/04_sv` | Moto |
| `/m/013xlm` | Tracteur |
| `/m/01jk_4` | Cheminée |
| `/m/014xcs` | Passage piéton |
| `/m/015qff` | Feu de circulation |
| `/m/0199g` | Vélo |
| `/m/015qbp` | Parcomètre |
| `/m/0k4j` | Voiture |
| `/m/015kr` | Pont |
| `/m/019jd` | Bateau |
| `/m/0cdl1` | Palmier |
| `/m/09d_r` | Montagne / colline |
| `/m/01pns0` | Bouche d'incendie |
| `/m/01lynh` | Escaliers |
## Payload de la requête UserVerify
Le payload `/userverify` est soumis après que l'utilisateur ait terminé un défi d'image reCAPTCHA v2
# Structure du payload```
v: hsFBb1u5wWWWkWP4in1ua2cQ
c: 03AFcWeA5RIHz5mI2COQqTcLjdrC7FiPpdfkX-hB-B2iFenI...
response: eyJyZXNwb25zZSI6WzMsMSw0LDksMCwxMSwxMCwxMywxNF0....
t: 24521
ct: 24521
ch: -4007845
oc: 0ZpqzzPgYKEEaN0xpfo3v_voeVUySZK3Ktf_fLVRdhnu3yeTy4BhO...
pm: 51bGwsbnVsbCxbbnVsbCxudWxsLG51bGwsWzEwLDQuMjcwMDAwMD....
| Champ | Nom | Description |
|---|---|---|
v | recaptchaVersion | Version reCAPTCHA |
c | challengeToken | Jeton d'état du défi retourné par /replaceimage ou une requête précédente /userverify |
response | Charge utile encodée contenant les indices des tuiles sélectionnées et des métadonnées d'interaction supplémentaires | |
t | challengeElapsed | Temps écoulé depuis le chargement initial du défi |
ct | completionTime | Temps total pris par l'utilisateur pour résoudre et soumettre le CAPTCHA |
ch | ocHash | Haché la totalité de la charge utile sérialisée oc, Exemple |
oc | encryptedVmSignals | Signaux chiffrés collectés depuis la VM reCAPTCHA, voir ici. |
pm | telemetryPayload | Structure de télémétrie encodée en Base64 contenant les deltas de timing VM, les temporisations des ressources, les statistiques des objets du navigateur collectés et les métadonnées des scripts du site web |
response Charge utileReprésente les indices/positions des carrés sélectionnés par l'utilisateur dans la grille
La charge utile est ensuite encodée en JWT avant soumission
```txt
0 1 2
3 4 5
6 7 8
Exemple de charge utile :```json
{
"response": [2, 3, 6]
}
```txt
0 1 2 3
4 5 6 7
8 9 10 11
12 13 14 15
Exemple de payload:```json
{
"response": [2, 3, 6, 14]
}
eLa charge utile response peut également inclure un champ e :```json
{
"response": [2, 3, 6, 14],
"e": "bxCoiXVt49I7CTS_tNlVbGg"
}
Ce champ contient des métadonnées d'interaction chiffrées liées aux actions des boutons CAPTCHA telles que :
- `Vérifier`
- `Suivant`
- `Ignorer`
### Structure interne de `e`
Après déchiffrement, la structure ressemble à :```txt
[[["9ffd9672"]]]
Chaque valeur représente un identifiant haché pour la classe/action de bouton rc-button-default
Le hachage est :
Des séquences d'interaction plus longues peuvent ressembler à :```txt [ [ ["9ffd9672"], ["9ffd9672"], ["9ffd9672"], ["9ffd9672"] ] ]
### `oc` — Signaux VM chiffrés
Utilise le même algorithme de chiffrement que le champ `idx 16` de la charge utile de la requête `/reload`, avec la clé de chiffrement chargée dans l'ancre (index 31) `1777752103288`
### `pm` — Charge utile de télémétrie
Le champ `pm` contient une structure de télémétrie encodée en Base64, similaire à la charge utile de télémétrie utilisée dans `/reload`.
## Anti-débogage/Altération
- ***Étiquette d'intégrité basée sur les Symboles***
reCAPTCHA attribue une clé Symbol `Symbol(jas)` à chaque tableau et objet avec une valeur numérique utilisée comme vérification d'intégrité. Étant donné que les Symboles ne sont pas énumérables et invisibles pour les opérations de clonage standard (comme JSON.stringify), toute tentative de remplacement ou de clonage de ces structures perd l'étiquette, ce que reCAPTCHA détecte comme une altération.

- ***Capture de variables de fermeture***
Certaines valeurs sont intentionnellement définies dans la portée d'une fonction externe et consommées uniquement à l'intérieur de rappels ou de fermetures imbriqués ; la variable n'existe jamais dans la même portée que celle où elle est utilisée.
- ***Vérifications temporelles***
reCAPTCHA intègre des mécanismes de détection de débogage en cours d'exécution. S'il détecte que le code est analysé par un débogueur, il redémarre la session de débogage, ce qui entraîne la perte de l'état d'exécution et du contexte.
- ***Onglet source de plantage***
https://github.com/user-attachments/assets/4ae3bf68-5ae7-4adc-a0b9-36cc11b64998
Ce bug se produit dans les navigateurs utilisant Chromium. Il existe plusieurs bugs qui provoquent la fermeture inattendue du navigateur ou des outils de développement, comme celui montré ici :
https://blog.castle.io/detect-and-crash-chromium-bots-with-one-weird-trick-bots-hate-it/
https://issues.chromium.org/issues/340836884?ref=blog.castle.io
Par conséquent, l'un de ces bugs est très probablement déclenché lorsque le délai d'attente expire, car ce n'est probablement pas quelque chose qui se produirait dans le flux d'un véritable utilisateur.
## reCAPTCHA Botguard
Bien que BotGuard ait été supprimé par Google, je vais expliquer un peu comment il fonctionne en interne pour ceux qui s'intéressent à ce qu'il est et à quel point il diffère des autres types de botguard.
Premièrement, le processus de génération de token : la VM génère des tokens pour les erreurs persistantes lors de l'exécution. Ces erreurs peuvent être dues à des déviations de décalage de bytecode, des registres VM manquants, un état VM incorrect et une mauvaise intégrité du flux de contrôle. Ces erreurs sont capturées par le répartiteur, qui appelle une fonction qui écrit des octets pour le token.
La VM utilise ARX Cipher, un algorithme créé par la NSA en 2013, similaire à Speck pour chiffrer les données. Il est également utilisé dans tous les Google BotGuard. L'algorithme change à chaque version, tout comme la constante (3990), l'ordre des opérations et les tours. Il ressemble à ceci :```javascript
/*
D -> cipher state array
P -> seed
E -> byte index
*/
Vl = function(D, P, E, F, v) {
F = D[3] | 0;
D = D[2] | 0;
for (v = 0; v < 14; v++) {
E = E >>> 8 | E << 24;
E += P | 0;
E ^= D + 3990;
P = P << 3 | P >>> 29;
P ^= E;
F = F >>> 8 | F << 24;
F += D | 0;
F ^= v + 3990;
D = D << 3 | D >>> 29;
D ^= F;
}
return [P >>> 24 & 255, P >>> 16 & 255, P >>> 8 & 255, P >>> 0 & 255, E >>> 24 & 255, E >>> 16 & 255, E >>> 8 & 255, E >>> 0 & 255];
}
La fonction de chiffrement principale possède deux modes : chiffrement et non-chiffrement. Le mode chiffrement utilise ARX Cipher, Bitwise + XOR pour chiffrer les données et les placer dans un tampon. Il existe 4 types de tampons :
180353304243 (non utilisé dans ce type de botguard).Le mode non-chiffrement se contente de définir des octets déjà chiffrés dans le tampon.```javascript // (The sequence flatten messed up the statements a bit) /* Q -> cipher state array FQ(v, c) -> derive a seed c = (z << 3) - 4 -> index FQ(v, (c | 0) + 4) -> position */ t = function(D, P, E, F, v, Q) { if (E.O == E) { P = function(h, A, c, x, z) { A = v.length; // buffer length z = (A | 0) - 4 >> 3; if (v.Jw != z) { // vm.block_index != index v.Jw = z; // set new block index c = (z << 3) - 4; x = [0, 0, Q[1], Q[2]]; try { v.zB = Vl(x, FQ(v, c), FQ(v, (c | 0) + 4)); // call ARX Cipher to generate keyStream } catch (V) { throw V; } } v.push(v.zB[A & 7] ^ h); // sets A to bits 0-7, and obtains a byte from the keystream and applies xor with a byte of the data to be encrypted }; v = Z(P, E); // get buffer if (P == 180 || P == 243 || P == 353) { Q = Z(189, E); } else { // set encripted bytes to buffer P = function(h) { v.push(h); }; } F && P(F & 255); E = D.length; for (F = 0; F < E; F++) { P(D[F]); } } }
Et voici la fonction qui gère les erreurs et appelle le rédacteur d'octets.```javascript
// (The sequence flatten messed up the statements a bit)
p = function(D, P, E, F, v, Q, h, A) {
D = E[1];
A = E[2];
Q = Z(465, P) >> 3;
h.push(D, Q >> 8 & 255, Q & 255); // error code/header bytes
E.message && (D += E.message);
A = void 0;
E && E[0] === U && (E = void 0);
h = Z(244, P);
h.length == 0 && (A != void 0 && h.push(A & 255));
D = "";
E && (E.stack && (D += ":" + E.stack));
E = Z(0, P); // buffer size limit (2048)
if (!P.TB && E[0] > 3) {
D = D.slice(0, (E[0] | 0) - 3);
E[0] -= (D.length | 0) + 3; // reduce size limit
D = Pv(D);
E = P.O;
P.O = P;
try {
v = (v = Z(304, P)) && v[v.length - 1] || 95;
if (P.n3) {
// this path is never executed
(F = Z(123, P)) && F[F.length - 1] == v || t([v & 255], 123, P);
} else {
t([95], 304, P); // set byte to error buffer
}
t(O(2, D.length).concat(D), 180, P, 9); // set bytes to main buffer
} finally {
P.O = E;
}
}
}
À la fin de l'exécution, la VM appelle la fonction principale qui prépare la charge utile avec ces tampons et génère le jeton, qui est une signature. Contrairement aux autres VM BotGuard, cette VM ne dispose pas d'un système d'empreinte, ni de Google Botguard, et n'effectue pas ces opérations d'émission d'erreurs + écriture d'octets, et est beaucoup plus simple et plus facile à rétro-ingéniérer.
Ce dépôt est exclusivement destiné à des fins éducatives et de recherche. Le code, les techniques et les concepts présentés ici sont destinés à l'étude académique et à la compréhension des sujets abordés.
| Hachage de toutes les clés des objets navigateur |
25 | W1tbNTAwNiw... | Compteurs encodés en Base64 pour les événements d'interaction utilisateur tels que les mouvements de souris, les clics, le clavier et les changements de focus |