
Dokumentation und Reverse Engineering von reCAPTCHA
Dieses Repository enthält eine technische Analyse von Googles Antibot-System (reCAPTCHA), mit Schwerpunkt auf:
@g_recaptcha@lyxlobyxreCAPTCHA ist eines der Antibot-Systeme mit den ausgefeiltesten Verschleierungstechniken. Es verwendet eine Reihe von Transformationen, die den Code weniger lesbar machen und das Reverse Engineering erschweren. Die meisten Verschleierungen lassen sich mithilfe des Abstract Syntax Tree (AST) einfach manipulieren, aber einige werden zur Laufzeit verarbeitet, wodurch der AST in diesem Fall nutzlos wird. Auf den Code wird außerdem Polymorphismus angewendet, um seine Struktur in jeder Version des Skripts zu verändern. Beispielsweise führt der Code die Aktion nicht direkt aus. Stattdessen verwendet er Objekte oder Funktionen, die ihre Form ändern.
Sequenzausdrücke
Der Code wird abgeflacht, indem jede Blockanweisung in einen fortlaufenden, durch Kommas getrennten Ausdruck umgewandelt wird. Dies kann in if-Anweisungen, Funktionsargumenten und sogar innerhalb von Objekten vorkommen.

Gemischte Boolesche Arithmetik
Verwebt arithmetische Operationen (Addition, Subtraktion, Multiplikation) mit bitweisen Operationen (AND, OR, XOR, NOT), um die ursprüngliche Logik zu verbergen, zum Beispiel -2 * ~(h & H) + -2 + (h ^ H)
Indirekte Funktionstabelle
Jede Funktion wird in einer Tabelle abgelegt und über ihren Index aufgerufen, wie functions[index](https://github.com/elyelysiox/recaptcha/blob/HEAD/args)
Inline-Konstanten-Array Ein lokales Array-Literal wird inline mitten im Ausdruck zugewiesen. Das Array bündelt Konstanten (Zahlen, Strings), die über den gesamten Funktionskörper hinweg per Indexzugriff wiederverwendet werden.
// b = [14, 1, "call"] assigned inline inside a sequence expression
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
}
Funktions-Multiplexing Mehrere logisch unterschiedliche Funktionen werden zu einer einzigen zusammengeführt, wobei ein numerischer Parameter als Blockauswahl dient. Der aktive Block wird bestimmt, indem der Parameter gegen bitweise Bedingungen ausgewertet wird. Aufrufer übergeben einen numerischen Literalwert als Selektor.
function(N, y, U, Y, h, H, m, C, u) {
C = [26, 47, 6];
// block 1
if ((N - 2 ^ 14) < N && (N - C[2] | 28) >= N) {
// convert value to string logic
}
// block 2
if ((N + 4 & 40) >= N && (N + 5 & C[0]) < N) {
Y = bB();
throw Error(Y === void 0 ? "unexpected value " + U + y : Y);
}
return u;
}
Verzweigung über logische Operatoren Ersetzt if-Anweisungen und if/else-Blöcke durch Kurzschlussauswertung logischer Operatoren und wandelt den Kontrollfluss in Ausdrücke um. In Kombination mit Sequenzausdrücken erscheinen mehrere Verzweigungen als ein einziger fortlaufender, durch Kommas getrennter Ausdruck.
// if (a) { block }
a && (block)
// if (!a) { block }
a || (block)
// if (a) { x } else { y }
a ? x : y
// combined with CFF and sequence expressions:
(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), ...)),
Gebundene native Methodenkonstanten Bindet native Browser-Methoden an ihre ursprünglichen Empfänger und speichert sie als Konstanten, um Manipulationen zu verhindern.
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)
Toter Code Unzugängliche oder ungenutzte Codeblöcke werden in der gesamten Datei eingefügt, wodurch deren Größe auf über 60.000 Zeilen anwächst. Dies erschwert statische Analyse und LLM-basiertes Reverse Engineering.
Kontrollfluss-Abflachung Transformiert jeden Teil des Codes (Deklarationen und Schleifen) in eine flache Zustandsmaschine. Es verbirgt die ursprüngliche Ausführungslogik, indem alle Codeblöcke über einen zentralen „Dispatcher“-Block geleitet werden.
Dispatcher können ihre Form ändern; einige haben 2–3 Zustandsvariablen, und der Schleifen-/Bedingungstyp ändert sich. Sie sehen wie folgt aus.

Dies ist ein CFF mit 2 Zustandsvariablen, eine verwaltet den catch-Block und die andere den try-Block.
Verschlüsselter String-Pool Alle String-Literale (DOM-APIs, Browser-Eigenschaften, CSS-Werte, Fehlermeldungen usw.) werden in einem einzigen massiven String-Pool verschlüsselt. Eine Entschlüsselungsfunktion verwendet einen Seed und eine auf LCG basierende XOR-Chiffre, um jeden String zur Laufzeit zu extrahieren.
Über 1990 Aufrufstellen sind über den Code verteilt. Die Entschlüsselungsfunktion verwendet einen laufenden Schlüssel, der dekodierte Codepunkte akkumuliert, sodass jedes Zeichen von allen vorherigen abhängt.
X = function(J, b, P, F, U) {
U = ["codePointAt", 127, "char encrypted pool"];
for (F = (P = 0, b = "", l); P < Q; P++)
J = (U[2][U[0]](c + P) ^ F) & U[1], // XOR with running key
b += String.fromCodePoint(J),
F += J; // accumulate key
return G = b;
}
// call sites pass a seed to locate and decrypt each string
Z[23](64, 4, 54961, 103)() // → "lang"
Z[23](66, 4, 54961, 103)() // → "addEventListener"
Z[23](32, 12, 20287, 852)() // → "inline-block"

Zustandsbehafteter Wert-Iterator reCAPTCHA verwendet eine zustandsbehaftete Funktion, die eine Sequenz von Laufzeitobjekten und -werten (wie window, document.body, numerische Konstanten) in fester Reihenfolge zurückgibt. Jeder Aufruf bewegt einen internen Cursor weiter. Ein Aufruf außerhalb der Reihenfolge oder zu häufig korrumpiert alle nachfolgenden Lesezugriffe. Ein Timeout-Mechanismus invalidiert den Zustand nach einem festen Intervall und gibt für verspätete Lesezugriffe null zurück.
// sequential calls return different values:
c() // → window
c() // → document.body
c() // → 123
c() // → null (timeout expired)
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
Berechnete Funktionstabelle
Dies ähnelt der Indirect Function Table, aber hier wird der Index der zu erhaltenden Funktion zur Laufzeit mit einem Seed mithilfe von XOR und Modulo berechnet.
c = ((Q ^ no | U[1]) >> 5) + no
A = mN[(c % U[2] + U[2]) % U[2]] // mN is the function table (50+ functions)
q[29](5, 6977) // seed=6977 → index resolves to function at mN[X]
q[29](53, 6187) // seed=6187 → different index, different function

Laufzeit-Verschlüsselung von Werten
Einige Werte (Captcha-Konfigurationsparameter, Anchor-Parameter usw.) werden niemals im Klartext gespeichert; sie werden unmittelbar nach der Erfassung verschlüsselt und erst zum Zeitpunkt der Verwendung entschlüsselt. Sie haben am Anfang ein Präfix B.

Verschleierung des asynchronen Kontrollflusses Synchrone Logik wird in generatorbasierte Zustandsmaschinen umgewandelt, die in rekursive Promise-Ketten eingebettet sind. Das Verfolgen eines beliebigen Werts im Debugger erzwingt das Durchschreiten mehrerer asynchroner Handler, wobei an jeder .then()-Grenze der ursprüngliche Ausführungskontext verloren geht.
Komponenten zur Initialisierung von reCAPTCHA``` ar: <WidgetInit?> k: co: hl: v: size: sa: anchor-ms: execute-ms: cb:
### Antwort
Die Antwort enthält:
- CAPTCHA-Iframe-Fensterdesign
- Anchor-Token zur Payload-Validierung (/reload)
- Die Hauptkonfiguration für die Initialisierung, die in der Methode `recaptcha.anchor.Main.init` ausgeführt wird, um sie in recaptcha_en.js zu empfangen
### Struktur:
Hinweis: Recaptcha BotGuard wurde am `04/01/2026` entfernt. Einige Beispiele findest du [hier](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
]
Die Werte basieren auf den entschlüsselten Werten aus dem ersten Beispiel-Fingerprint, siehe hier
Jedes Unterfeld der Fingerprint-Werte hat ein Grundformat:
[value, key, elapsed]
Wobei key der Verschlüsselungsschlüssel ist und elapsed die Zeit, die der Collector benötigt hat, um den Wert zu erhalten und zu verschlüsseln. Dadurch kann reCAPTCHA Folgendes erkennen:
Das System verwendet einen internen Scheduler:``` [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]
Dies stellt dar:
- Ausführungsreihenfolge
- Collector Pipeline
- Signalerzeugungssequenz
### Fingerabdruck-Signalcodes & Schlüsselableitung
Wandelt Fingerabdruck-Signale durch eine mehrstufige Ableitungspipeline in deterministische Verschlüsselungsschlüssel um.
Jeder Fingerabdruckwert wird:
- Normalisiert
- In einen kompakten Signalcodes kodiert
- In einen numerischen Verschlüsselungsschlüssel umgewandelt
- Mit dem abgeleiteten Schlüssel verschlüsselt
Der Prozess ist deterministisch, d. h. derselbe Eingabewert erzeugt immer denselben Signalcodes und denselben Verschlüsselungsschlüssel.```
Raw Value
↓
Signal Code Derivation
↓
Compact Signal Code
↓
Numeric Key Derivation
↓
Encryption Key
↓
Value Encryption
Eingangssignal``` "BUTTON,195a81c9"
***Schritt 1 - Signalcode ableiten***```javascript
deriveSignalCode("BUTTON,195a81c9")
// → "wg"
Der generierte Signalcode ist eine kompakte deterministische Kennung für den ursprünglichen Wert.
Schritt 2 - Numerischen Schlüssel ableiten```javascript deriveKey("wg") // → 3792
Der Signalcode wird in einen numerischen Verschlüsselungsschlüssel umgewandelt.
***Schritt 3 — Wert verschlüsseln***```javascript
encryptValueWithKey(3792, "wgia1z9pwq")
// → "bYVbh6BUsE_5pLA"
Der nächste Wert wird mit dem abgeleiteten Schlüssel verschlüsselt, der Collector-Reihenfolge folgend.
Alle generierten Signalcodes werden in einer kompakten serialisierten Struktur aggregiert.
Prozess``` 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)
Value: 3ccb
Hashed: true
Description: Generiert einen HMAC-Schlüssel aus window.localStorage.getItem("rc::a ") + "6d" konvertiert in Bytes, berechnet HMAC-SHA256(siteKey) mit diesem Schlüssel und gibt die ersten 4 Hexadezimalzeichen des Ergebnisses zurück.
Der Wert von rc::a ist ein zufällig generiertes Base64.
Idx 5 (integer)
window.localStorage.length * 2falsewindow.localStorage multipliziert mit 2Idx 16 (string)
Value:
Bestimmte reCAPTCHA-Signale sind möglicherweise nicht verfügbar oder können nicht erfasst werden (aufgrund debug-bedingter Zeitüberschreitungen). In solchen Fällen wird das fehlende Signal mit einem zufällig generierten, base64-kodierten Wert gefüllt. Zum Beispiel:``` ["CMXZyamk3ZHNjdGsyaQ==", 3249, 591]
Once the fingerprint construction is complete, it is serialized into a string and encrypted using the encryption key loaded from the anchor field (index 18). The encrypted result is then stored in the index `16` field of the `/reload` payload.
## VM-Signale
Im Folgenden sind die Browserwerte aufgeführt, die von der internen reCAPTCHA-VM zusammen mit dem Signalschlüssel ermittelt werden. Einige Werte werden im Beispiel-Fingerprint nicht angezeigt, da die VM nur bestimmte Werte aus dem Konfigurationsbytecode extrahiert. Daher werden andere Werte angezeigt. Weitere Einzelheiten finden Sie [hier](https://github.com/elyelysiox/recaptcha-vm)
Basis-Signalformat:```
[
null,
collectorElapsed,
encryptElapsed,
value
]
Wobei collectorElapsed angibt, wie lange (ms) der Collector benötigte, um den Wert zu erhalten,
und encryptElapsed, wie lange (ms) zum Verschlüsseln des Werts benötigt wurde.
Diese Zeiten werden während der Ausführung der VM gemessen.
[ 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`)
- Wert: `Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36...`
- Beschreibung: Vollständiger Browser-User-Agent
- **Key 727** (`array`)
- Wert: `[]`
- Beschreibung: Produktpreis-Blöcke, enthält Preise für kommerzielle Produkte auf der Website; diese können auf Marktplatz-Websites wie Amazon, eBay, AliExpress usw. erscheinen, [Beispiel](https://github.com/elyelysiox/recaptcha/blob/main/findProductPricesBlock.js)
- **Key 150** (`array`)
- Wert: `[\"Join And Get Free Trial!\",\"\",\"Captcha Solving Service\",\"Prod...`
- Beschreibung: Links, die aus den `<a>`-Elementen der Website extrahiert wurden; die VM extrahiert diese Werte selten
- **Key 545** (`boolean`)
- Wert: `false`
- Beschreibung: `navigator.webdriver`
- **Key 370** (`integer`)
- Wert: `0`
- Beschreibung: Maximale Geräte-Touchpoints `navigator.maxTouchPoints`
- **Key 779** (`array`)
- Wert: `[null,null,null,null,"false"]`
- Beschreibung: Extrahiert Daten aus Versand-/Abrechnungsfeldern, [Beispiel](https://github.com/elyelysiox/recaptcha/blob/main/formFieldScrape.js)
`postal|postalcode|postalcode|postal_code|zip` → sucht nach Postleitzahlenfeld
`ship|deliver` → Versandbereich
`billing|payment|credit` → Abrechnungsbereich
`country` → Länderauswahl
`addr.*(match|equal)|(match|equal).*address` → Kontrollkästchen „gleiche Adresse"
Liest Name, ID, Autocomplete, className jedes Elements
Drei Phasen: Texteingaben, SELECT-Anweisungen, Kontrollkästchen
- **Key 659** (`array`)
- Wert: `[[[0,921],[1,2053]],[[235054,1,1,1],[164277,3,4,1]],2,39]`
- Beschreibung: Enthält Daten der gedrückten Elemente auf der Seite im Format `[[[count,timestamp]],[[hashedElem,tagType,type,autoComplete]],pressedCount,behaviorChecksum]`
### Elementtyp
| Elementtyp | 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 |
### Verhaltens-Prüfsummen-Algorithmus
Die behaviorChecksum ist eine kompakte Verhaltensbewertung, die aus den gesammelten Daten der Benutzerinteraktionen abgeleitet wird.
Für jede Interaktion:
- Der Interaktionsindex wird um eins erhöht
- Der Zeitstempel wird mit dem angepassten Interaktionsindex kombiniert
- Der Element-Hash wird mithilfe einer Modulo-Operation reduziert
- Das Ergebnis wird zu einer laufenden Bewertung akkumuliert
---
```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;
}
```
### Beispiel
```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`)
- Wert: `[[[0,0,979]],1,0]`
- Beschreibung: Enthält während der Seitensitzung gesammelte Scrollbewegungen
### Basisformat
```
[ [ [ scrollX, scrollY, timestamp ] ], count, behaviorChecksum ]
```
- **Key 895** (`array`)
- Wert: `[[[1,0]]]`
- Beschreibung: `VisibilityStateEntry` verfolgt den Sichtbarkeitsstatus der Seite und zeigt an, ob der Benutzer die Seite derzeit ansieht oder sie verlassen hat, im Format `[[[isVisible, startTime]]]`
- **Key 1092** (`array`)
- Wert: `[886,2300,262.2099999997952,392.7600000009595,2050.2399999993577]`
- Beschreibung: reCAPTCHA-Leistung, PerformanceNavigationTiming und PerformanceEventTiming, im Format `[vmStart,vmEnd,domContentLoadedEventEnd,loadEventEnd,firstUserInteractionTime]`
- **Key 41** (`array`)
- Wert: `[[[1123,0,0]]]`
- Beschreibung: ClearTimeout
- **Key 43** (`array`)
- Wert: `[[[1123,0]]]`
- Beschreibung: ClearTimeout
- **Key 549** (`array`)
- Wert: `[0,null,null,null,null,null,null]`
- Beschreibung: KeyboardEvent, berechnete Tasten + Zeitstempel
- **Key 352** (`array`)
- Wert: `1,[[2050,125,633,266,[609,266,105,44],3,1,50,1,1]],[3,3,318],...`
- Beschreibung:
Die Struktur enthält:
- Zeitsteuerung der Mausbewegungen
- Cursor-Koordinaten
- Klickdauern
- Abmessungen der gedrückten Elemente
- Zeigertyp-Informationen
- Druckmetriken
- Zurückgelegte Cursor-Distanz
- Statistik zur Verhaltenszeitsteuerung
### Basisformat:
```
[
eventsCount,
mouseEvents,
[
coalescedEventsCount,
averageInterEventDelay,
timingVarianceScore
],
[
totalPoints,
clickSequenceScore,
totalCursorDistance,
totalPoints
],
0,
0,
behaviorChecksum
]
```
### Mausereignis-Struktur:
Jeder Eintrag in mouseEvents folgt dieser Struktur:
```
[
timestamp,
clickDuration,
clientX,
clientY,
[
elementLeft,
elementTop,
elementWidth,
elementHeight
],
elementTagType,
trackedElementIndex,
pressure,
pointerTravelDistance,
pointerArea
]
```
| Name | Beschreibung |
|---|---|
| `timestamp` | Ereigniszeitstempel relativ zum Ereignisstart |
| `clickDuration` | Dauer zwischen `pointerdown` und `pointerup` |
| `clientX` | ... |
| `clientY` | ... |
| `elementBounds` | Begrenzungsrechteck des interagierten Elements |
| `elementTagType` | Kodierte DOM-Tag-Kennung des interagierten Elements |
| `trackedElementIndex` | Interne Kennung des interagierten DOM-Elements aus der Laufzeit-Interaktionskarte |
| `pressure` | Normalisierter Zeigerdruckwert multipliziert mit `100` |
| `pointerTravelDistance` | Zurückgelegte Distanz zwischen `pointerdown` und `pointerup` |
| `pointerArea` | Kontaktfläche, berechnet als `width * height` |
### Element-Tag-Typ
Dies wird während der Ausführung der reCAPTCHA-VM initialisiert
| Typ | 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 |
### Beispiel
```
[
18342,
91,
742,
381,
[700, 320, 120, 38],
3,
1,
50,
1.4,
16
]
```
Stellt dar:
- Ereignis bei `18.342s`
- Klickdauer von `91ms`
- Klick bei `(742, 381)`
- Interaktion mit einem `BUTTON`
- Zeigerdruck `0.5` (50)
- leichte Cursor-Bewegung während des Klicks
- Zeiger-Kontaktfläche von `16`
### Verhaltens-Prüfsummen-Algorithmus
Für jedes Mausereignis:
- Die Klickdauer wird verstärkt
- Das Ergebnis wird mit dem Ereigniszeitstempel kombiniert
- Cursor-Koordinaten und Elementabmessungen werden miteinander vermischt
- Ein gewichtetes Verhältnis wird berechnet
- Das Verhältnis wird gerundet und akkumuliert
Nachdem alle Ereignisse verarbeitet wurden:
- Die akkumulierte Bewertung wird mit der gesamten Cursor-Distanz kombiniert
- Das Ergebnis wird mithilfe der Anzahl der gesammelten Punkte normalisiert
- Eine logarithmische Transformation wird angewendet
- Zusätzliche Bewegungsstatistiken werden einbezogen
- Der Endwert wird mithilfe einer Modulo-Operation reduziert
- Das Ergebnis wird zu einem kleinen ganzzahligen Wert gerundet
---
```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);
}
```
### Beispiel
```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`)
- Wert: `""`
- Beschreibung: Parsen der gesammelten Gesamtpreise der Website in $ mit einem regulären Ausdruck `(?i)total[\S\s]{0,20}?(?:(?:(?:USD|\$)\s*)?[\d\.,]+\s*(?:USD|\$)|(?:USD|\$)\s*[\d\.,]+)`, [Beispiel](https://github.com/elyelysiox/recaptcha/blob/main/websitePricesTotal.js)
- **Key 1278** (`integer`)
- Wert: `229.6`
- Beschreibung: Division der aktuellen Laufzeitmessung durch 10 `Math.trunc(performance.now()) / 10`
- **Key 1422** (`boolean`)
- Wert: `false`
- Beschreibung: Analysiert, ob es OTP/2FA-Felder auf der Website gibt, [Beispiel](https://github.com/elyelysiox/recaptcha/blob/main/parseOTPFields.js)
- **Key 614** (`array`)
- Wert: `[1,0,0,Infinity]`
- Beschreibung: Gerätebatterieinformationen im Format `[level,isCharging,chargingTime,dischargingTime]`
- **Key 2033** (`array`)
- Wert: `["0",-1,-1,999]`
- Beschreibung: Einige VM-Konstantenwerte und VM-Laufzeitmessung
- **Key 1313** (`array`)
- Wert: `["undefined",3]`
- Beschreibung: Android-, Chrome-Prüfungen, `[typeof window.android, Object.keys(window.chrome).length]`
- **Key 1994** (`array`)
- Wert: `[2,4,53324367238]`
- Beschreibung: `[navigator.hardwareConcurrency, navigator.deviceMemory, navigator.storage.estimate().qouta]`
- **Key 1310** (`array`)
- Wert: `["Google Inc. (Intel Inc.)","ANGLE (Intel Inc.,...`
- Beschreibung: WebGL-Renderer, Hersteller und Gesamtzahl der Erweiterungen, [Beispiel](https://github.com/elyelysiox/recaptcha/blob/main/webglSignals.js)
- **Key 291** (`array`)
- Wert: `[1542343452,0,105002991,3556653,96784904,-731949068,-902263026,...`
- Beschreibung: Gehashte spezifische Prototyp-Schlüssel von Browserobjekten, wie `SpeechSynthesisEvent`, `NetworkInformation`, `HTMLElement` usw., [Beispiel](https://github.com/elyelysiox/recaptcha/blob/main/hashBrowserProtos.js)
## Reload-Request-Payload
Diese Daten werden mithilfe von protobuf (Protocol Buffers) serialisiert, einer effizienten, neutralen Methode zur Serialisierung strukturierter Daten, die von Google entwickelt wurde.```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,
]
| Wert | Beschreibung |
|---|---|
q | reCAPTCHA V3-Challenge |
fi | reCAPTCHA V2/Invisible-Challenge |
a | Audio-Challenge |
Idx 20)Zum Dekodieren:```javascript atob("W1tbMywwLDgyOV0sWzEs...") // → [[[3,0,829],[1,42,863]],[[2,727,1341.300000011921]...
Das Payload enthält:
- Informationen zum Ressourcen-Timing
- Performance-Timing langer Aufgaben
- Ausführungsdeltas der reCAPTCHA-VM
- Skript-URLs der Website
- Statistiken zur Sammlung von Browser-Objekten
### Basisstruktur```js
[
[recaptchaResources],
[tasksTiming],
[
null,
null,
null,
idleTimeoutDelta,
deltaTiming,
0,
0,
0,
],
websiteScriptsUrls,
[collectionElapsed, browserObjectsLength]
]
[ PerformanceLongTaskTiming.name == "self" ? 2 : 4, PerformanceLongTaskTiming.duration, PerformanceLongTaskTiming.startTime ]
### reCAPTCHA Resource-Timing-Format```js
[
resourceType,
duration,
startTime
]
| Ressource |
|---|
Idx 25)Dieses Feld enthält Base64-kodierte Zähler für Browser-Interaktionsereignisse, die während der Ausführung gesammelt wurden.
Dekodiertes Beispiel:```js [ [ [5006, 43], [64607, 1], [45464, 1], [35837, 1] ] ]
### Ereigniszählerstruktur```js
[
[
[eventType, count]
]
]
[ "rresp", "", null, 120, [ "pmeta", [ "/m/013xlm", null, 3, 3, 3, null, "Tractor" ], null, [1] ], "", null, null, "", "", null, null, "", null, "", "", 0 ]
## Feldbeschreibungen
| Index | Name | Beschreibung |
|---|---|---|
| `0` | `rresp` | Antworttyp-Kennung, die vom Reload-Endpunkt zurückgegeben wird |
| `1` | `responseToken` | reCAPTCHA-Antwort-Token |
| `3` | `tokenExpiration` | Token-Lebensdauer in Sekunden bis zum Ablauf |
| `4` | `pmeta` | Challenge-Metadatenblock, der Informationen über den CAPTCHA-Typ, die Kategorie und das Rasterlayout enthält |
| `5` | `challengeType` | Typ der CAPTCHA-Challenge |
| `8` | `usagePatternToken` | Nutzungsmuster, gespeichert in `window.sessionStorage` und bei zukünftigen `/reload`-Anfragen wiederverwendet |
| `9` | `challengeImageToken` | Token zum Abrufen der CAPTCHA-Bild-Payload vom `/payload`-Endpunkt |
| `12` | `humanVerificationToken` | Token zur menschlichen Identifikation, verknüpft mit dem Google-reCAPTCHA-Zustand und Browser-Cookies |
| `14` | `captchaSessionToken` | Internes Session-Token |
| `15` | `bftToken` | ... |
---
### `pmeta`-Struktur
Beispiel:```js
[
"pmeta",
[
"/m/013xlm",
null,
3,
3,
3,
null,
"Tractor"
],
null,
[1]
]
pmeta[1]-WerteDer Wert bei Idx 9 (challengeImageToken) wird verwendet, um das CAPTCHA-Bild abzurufen:```txt
https://www.google.com/recaptcha//payload
?p=
&k=
### Kategorie-Kennungen
| ID | Kategorie |
|---|---|
| `/m/0pg52` | Taxi |
| `/m/01bjv` | Bus |
| `/m/02yvhj` | Schulbus |
| `/m/04_sv` | Motorrad |
| `/m/013xlm` | Traktor |
| `/m/01jk_4` | Schornstein |
| `/m/014xcs` | Zebrastreifen |
| `/m/015qff` | Ampel |
| `/m/0199g` | Fahrrad |
| `/m/015qbp` | Parkscheinautomat |
| `/m/0k4j` | Auto |
| `/m/015kr` | Brücke |
| `/m/019jd` | Boot |
| `/m/0cdl1` | Palme |
| `/m/09d_r` | Berg / Hügel |
| `/m/01pns0` | Hydrant |
| `/m/01lynh` | Treppe |
## UserVerify-Anfrage-Payload
Das `/userverify`-Payload wird übermittelt, nachdem der Nutzer eine reCAPTCHA-v2-Bild-Herausforderung abgeschlossen hat
# Payload-Struktur```
v: hsFBb1u5wWWWkWP4in1ua2cQ
c: 03AFcWeA5RIHz5mI2COQqTcLjdrC7FiPpdfkX-hB-B2iFenI...
response: eyJyZXNwb25zZSI6WzMsMSw0LDksMCwxMSwxMCwxMywxNF0....
t: 24521
ct: 24521
ch: -4007845
oc: 0ZpqzzPgYKEEaN0xpfo3v_voeVUySZK3Ktf_fLVRdhnu3yeTy4BhO...
pm: 51bGwsbnVsbCxbbnVsbCxudWxsLG51bGwsWzEwLDQuMjcwMDAwMD....
response-NutzlastStellt die Indizes/Positionen der Quadrate dar, die der Benutzer im Raster ausgewählt hat
Die Nutzlast wird später vor der Übermittlung als JWT kodiert
```txt
0 1 2
3 4 5
6 7 8
Beispiel-Payload:```json
{
"response": [2, 3, 6]
}
```txt
0 1 2 3
4 5 6 7
8 9 10 11
12 13 14 15
Beispiel-Payload:```json
{
"response": [2, 3, 6, 14]
}
e FeldDas response-Payload kann außerdem ein e-Feld enthalten:```json
{
"response": [2, 3, 6, 14],
"e": "bxCoiXVt49I7CTS_tNlVbGg"
}
Dieses Feld enthält verschlüsselte Interaktionsmetadaten zu CAPTCHA-Button-Aktionen wie:
- `Verify`
- `Next`
- `Skip`
### Interne Struktur von `e`
Nach der Entschlüsselung sieht die Struktur wie folgt aus:```txt
[[["9ffd9672"]]]
Jeder Wert stellt einen gehashten Bezeichner für die Button-Klasse/-Aktion rc-button-default dar
Der Hash ist:
Längere Interaktionssequenzen können wie folgt aussehen:```txt [ [ ["9ffd9672"], ["9ffd9672"], ["9ffd9672"], ["9ffd9672"] ] ]
### `oc` — Verschlüsselte VM-Signale
Verwendet denselben Verschlüsselungsalgorithmus wie das Feld `idx 16` der `/reload`-Anfragenutzlast, wobei der Verschlüsselungsschlüssel im Anker (Index 31) `1777752103288` geladen wird.
### `pm` — Telemetrie-Nutzlast
Das Feld `pm` enthält eine Base64-kodierte Telemetriestruktur, die der in `/reload` verwendeten Telemetrie-Nutzlast ähnelt.
## Anti-Debugging/Manipulationsschutz
- ***Symbolbasierte Integritätsmarkierung***
reCAPTCHA setzt einen Symbol-Schlüssel `Symbol(jas)` auf jedes Array und Objekt mit einem numerischen Wert, der als Integritätsprüfung dient. Da Symbole nicht aufzählbar und für standardmäßige Klonoperationen (wie JSON.stringify) unsichtbar sind, verliert jeder Versuch, diese Strukturen zu ersetzen oder zu klonen, die Markierung, was reCAPTCHA als Manipulation erkennt

- ***Erfassung von Closure-Variablen***
Einige Werte werden absichtlich in einer äußeren Funktion definiert und nur innerhalb verschachtelter Callbacks oder Closures konsumiert; die Variable existiert nie in demselben Gültigkeitsbereich, in dem sie verwendet wird
- ***Timing-Prüfungen***
reCAPTCHA enthält Mechanismen zur Erkennung von Debugging zur Laufzeit. Wenn es erkennt, dass der Code von einem Debugger analysiert wird, startet es die Debugging-Sitzung neu, wodurch der Ausführungszustand und der Kontext verloren gehen
- ***Absturz des Quell-Tabs***
https://github.com/user-attachments/assets/4ae3bf68-5ae7-4adc-a0b9-36cc11b64998
Dieser Fehler tritt in Browsern auf, die Chromium verwenden. Es gibt mehrere Fehler, die dazu führen, dass der Browser oder die Entwicklertools unerwartet geschlossen werden, wie zum Beispiel der hier gezeigte:
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
Daher wird einer dieser Fehler höchstwahrscheinlich ausgelöst, wenn das Timeout eintritt, da es vermutlich nichts ist, was im Ablauf eines echten Benutzers passieren würde
## reCAPTCHA Botguard
Obwohl BotGuard von Google entfernt wurde, erkläre ich für alle, die sich dafür interessieren, wie es funktioniert und wie sehr es sich von anderen Arten von Botguard unterscheidet, ein wenig über seine interne Funktionsweise
Zunächst der Prozess der Token-Erzeugung: Die VM erzeugt Token für anhaltende Fehler während der Ausführung. Diese Fehler können auf Abweichungen im Bytecode-Offset, fehlende VM-Register, falschen VM-Zustand und schlechte Kontrollfluss-Integrität zurückzuführen sein. Diese Fehler werden vom Dispatcher erfasst, der eine Funktion aufruft, die Bytes für das Token schreibt.
Die VM verwendet ARX Cipher, einen Algorithmus, der 2013 von der NSA entwickelt wurde und Speck ähnelt, zur Verschlüsselung von Daten. Er wird auch in allen Google-BotGuard-Versionen verwendet. Der Algorithmus ändert sich mit jeder Version, ebenso wie die Konstante (3990), die Reihenfolge der Operationen und die Runden. Er sieht so aus:```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];
}
Die Hauptverschlüsselungsfunktion hat zwei Modi: Verschlüsselung und keine Verschlüsselung. Der Verschlüsselungsmodus verwendet ARX Cipher, Bitwise + XOR, um die Daten zu verschlüsseln und in einem Puffer abzulegen. Es gibt 4 Arten von Puffern:
180353304243 (wird bei dieser Art von Botguard nicht verwendet).Der Modus ohne Verschlüsselung setzt lediglich bereits verschlüsselte Bytes in den Puffer.```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]); } } }
Und dies ist die Funktion, die Fehler behandelt und den Byte-Writer aufruft.```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;
}
}
}
Am Ende der Ausführung ruft die VM die Hauptfunktion auf, die den Payload mit diesen Puffern vorbereitet und das Token erzeugt, das eine Signatur ist. Im Gegensatz zu anderen BotGuard-VMs verfügt diese VM über kein Fingerprinting-System, kein Google Botguard, und sie führt auch keine fehlerauslösenden + Bytes schreibenden Vorgänge aus. Sie ist wesentlich einfacher und leichter zu reverse engineeren.
Dieses Repository dient ausschließlich Bildungs- und Forschungszwecken. Der Code, die Techniken und die hier vorgestellten Konzepte sind für das akademische Studium und das Verständnis der behandelten Themen bestimmt.
yM5Us/j/fn6EDgtmPlP4Pxj605nMJN9dRYHyy5MnHashed: true
Description: 40-Zeichen-Base64-Bloom-Filter-Fingerabdruck aller <HEAD>-Knoten. Geht jedes untergeordnete Element durch und sammelt Tag-Namen, Attribute und Textinhalte, serialisiert diese und führt sie durch einen djb2-Hash.
Jeder Digest wird in einen BitHash(240 bits, 7 rounds, max 25 nodes) eingespeist, der Bits über ein 40×6-Raster setzt und dann jedes 6-Bit-Segment als Base64-Zeichen kodiert. reCAPTCHAs eigene Script-Tags werden über einen dynamischen Regex auf src ausgeschlossen.
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, SampleIdx 30 (integer)
0falsehttps://www.gstatic.com/recaptcha/releases/<version>/ enthält, SampleIdx 31 (string)
AAgkAQI0SBtruedocument.cookie-Schlüssel, SampleIdx 32 (string)
""falsedocument.referrer.Idx 33 (integer)
Value: 2
Hashed: false
Description: Element der reCAPTCHA-Box, in der das Rendering durchgeführt wird.
// like grecaptcha.execute('.grecaptcha') -> this div
// Basically, it returns a number or the depth or level of that div in the document.
// Example: html > body > main > form > fieldset > div.recaptcha.form-field
// 5 4 3 2 1 0
// out = 5
nM = document.getElementsByClassName('grecaptcha-badge')[0] // or document.getElementsByClassName('g-recaptcha')[0] v2
let count = 0;
for (; nM = nM.parentElement || null;) {
count++;
}
const result = count;
Idx 34 (string)
AAAAAAAAAAtrue<INPUT>-Elemente, SampleIdx 35 (string)
0,DIV,f0c7414etruedocument.activeElement. Analysiert, ob es sich um ein Kaufelement handelt, mithilfe eines Regex /buy|pay|place|order|donate|purchase/i mit textContent, classNames und id, SampleIdx 36 (string)
h3, h2 oder http/1.1falsenextHopProtocol der Navigations-Performance 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 oder document.defaultView.pageYOffsetIdx 41 (string)
DIV,a08cd360true:hover-Element, auf dem sich die Maus in genau diesem Moment befand. Nimmt das letzte Element und hasht tagName, classNames und id, SampleIdx 42 (string)
9,e3b0c442trueIdx 44 (string)
2v591z63wqtrue:hover-Element, document.activeElement, alle <INPUT>-Elemente gehasht, location.url, window.scrollY, Beispiel: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, an der er passiert ist.Idx 47 (integer)
0falsewindow.getSelection().toString().length || 0Idx 49 (string)
h2-0falsenextHopProtocol und bestimmt, ob dessen duration Null ist. Gibt das Ergebnis im Format protocol-isZero zurück, SampleIdx 50 (array)
Value: [1,0,null,1,1,["805b1z63wq"],"MWc2MzZwMnR0ZHNkMw==",0,null,[[1]]]
Hashed: true
Description: Enthält Benutzerverhalten, gehashte :hover-Elemente und einen Timeout, der jedes Mal erhöht wird, wenn ein weiteres :hover-Element gesammelt wird. Es analysiert Benutzeraktivierungen, um die Anzahl der Interaktionen des Benutzers mit der Seite oder seiner Präsenz auf der Seite zu akkumulieren, indem (10 * IsActive + IsBeenActive) berechnet wird.
Basisformat:
[
count,
timeout,
null,
timeoutMultiplier,
transitionCount,
[ elementsHoverHashed ],
sessionId,
userActivationScore,
null,
[ [ flags ] ]
]
Idx 51 (array)
[0]falsedocument.body.innerText nach Übereinstimmungen mit den Wörtern „try again“, „incorrect“, „invalid“ oder „declined“ und gibt die Gesamtzahl der gefundenen Übereinstimmungen zurück. Andernfalls wird 0 zurückgegeben, SampleIdx 52 (integer)
11false10 * navigator.userActivation.isActive + navigator.userActivation.hasBeenActiveIdx 53 (integer)
Value: 4
Hashed: false
Description: Es nimmt die aktuelle Website-URL, wandelt sie in ein Zeichenarray um, kürzt sie auf 100 Zeichen und wandelt sie dann wieder in eine Zeichenkette zurück, Sample
Dann ist es 5, wenn die Länge dieser gekürzten Zeichenkette gerade ist, oder 4, wenn sie ungerade ist.
Idx 54 (boolean)
falsefalsewindow.document.hiddenIdx 55 (array)
[[[1,"2v"],[1,"9z"],[1,"z8"],[1,"us"],[1,..falseIdx 56 (string)
-1,-1falsewindow.opener, gibt „-1,-1“ zurück, wenn null, SampleIdx 57 (string)
Value: www.gstatic.com,_,static.kogstatic.com,www.google.com,www.googletagmanager.,...
Hashed: false
Description: Sammelt die Hosts aus dem src aller <SCRIPT>-Elemente in document.scripts, Sample
Für jedes <SCRIPT>
Wenn das Element das src-Attribut besitzt (hasAttribute("src")), wird der Wert von src genommen und mit einem URI-artigen Regex (nd) geparst, wobei nur die Host-Komponente extrahiert wird (Erfassungsgruppe 3 des Regex).
Wenn das <SCRIPT> kein src-Attribut besitzt (es ist ein Inline-Script), wird der Platzhalter "_" verwendet.
Idx 58 (string)
mapslitepromosdismissed1falsewindow.localStorage-Schlüssel.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 aufgezeichnet wurden, SampleIdx 62 (string)
document.titlefalseIdx 63 (array)
Value: []
Hashed: false
Description: Extrahiert Zeitstempel (12-13-stellige Zahlen, die mit 1[2-9] beginnen, typisch für Epochen-Millisekunden ~2008-2099) aus den Cookie-Werten, Sample
Sammle die folgenden Werte über alle gefundenen IDs: Anzahl, Min, Max und Durchschnitt.
Das Endergebnis ist ein Array [count, min, max, avg]
Idx 64 (integer)
55557falseIdx 65 (string)
sha384-3qctruehttps://www.google.com/recaptcha/api.js?render= konfiguriert ist (auf 10 Zeichen gekürzt)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)
Value: [null,null,"","",null,"1l0fl"]
Hashed: false
Description: Extrahiert Gesamtgeldbeträge (wie Total: $50.00 oder Total 150.00 EUR), Sample
aus document.innerText, indem nach dem Währungssymbol oder -code zusammen mit dem numerischen Betrag gesucht wird, unter Verwendung des regulären Ausdrucks:
total[\S\s]{0,20}?(((R\$|\$|€|£|USD|EUR)\s*[\d\.,]+)|[\d\.,]+\s*(€|USD|EUR))`
total → Sucht nach dem exakten Wort „total“
[\S\s]{0,20}? → Sucht zwischen (0) und (20) nach dazwischenliegenden Zeichen (wie Leerzeichen, Doppelpunkten oder Zeilenumbrüchen), bevor die Zahl gefunden wird
(((R\$|\$|€|£|USD|EUR)\s*[\d\.,]+)|[\d\.,]+\s*(€|USD|EUR)) → Dies ist der Hauptblock, der das Geld erfasst und in zwei separate Optionen unterteilt ist
Idx 71 (array)
[jsHeapSizeLimit,usedJSHeapSize,totalJSHeapSize]falseIdx 72 (array)
Value: [[["Google Chrome","147"],["Not.A/Brand",...
Hashed: false
Description: Array von userAgent-Daten navigator.userAgentData, ein Flag, das angibt, ob es mobil ist, sowie die Plattform, Beispiel:
[
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)
""false| Index | Wert | Beschreibung |
|---|
0 | rreq | Request-Typ-Kennung |
1 | U5VsmTDhJM1iO... | reCAPTCHA-Version |
2 | 03AFcWeA5Els3... | Validierungstoken, das vom /anchor-Endpunkt abgerufen wurde |
5 | -383788762 | Hash des gesamten serialisierten Fingerprint-Payloads, Beispiel |
6 | q | Gibt den Challenge- bzw. Request-Modus an, der ausgeführt wird |
7 | 05APQrAobTCFG... | Nutzungsmuster, die Nutzungsverlauf und zuvor gelöste Challenge-Muster enthalten. Kann die Punktzahl erheblich erhöhen, wenn gültig |
8 | submit | Spezifische Site-Aktion, die der Benutzer zum Zeitpunkt der Verifizierung auf der Seite ausführt (z. B. signin, register) |
14 | 6LcAbwIqAAAA... | Website-Schlüssel |
16 | 0XA4cHhALxgM... | Verschlüsselt das gesamte serialisierte Fingerprint-Payload mit dem Verschlüsselungsschlüssel vom Anchor (Index 18) 1777669303203 |
20 | tbMywxNTQsOD... | Base64-codierte Telemetriestruktur, die VM-Timing-Deltas, Ressourcen-Timings, gesammelte Browserobjekt-Statistiken und Website-Skript-URLs enthält |
21 | 0aAPQrAobqME... | Token, das mit einem zuvor gelösten CAPTCHA verknüpft ist. Wird für Sitzungskontinuität und Vertrauenskorrelation verwendet |
22 | BDAAYAIAGEcA... | Hash aller Browser-Objekt-Schlüssel |
25 | W1tbNTAwNiw... | Base64-codierte Zähler für Benutzerinteraktionsereignisse wie Mausbewegung, Klicks, Tastatur- und Fokusänderungen |
qr | Quick-Response (neu) |
| Typ |
|---|
recaptcha/api.js | 3 |
recaptcha/releases/<version> | 1 |
https://www.google.com/recaptcha/api2/anchor | 2 |
https://www.google.com/recaptcha/api2/bframe | 4 |
| Hash | Ereignistyp |
|---|
5006 | pointermove |
64607 | pointerdown |
45464 | pointerup |
31617 | keydown |
37178 | keyup |
35837 | focusin |
| Position | Wert | Beschreibung |
|---|
0 | /m/013xlm | Interner Kategoriebezeichner für die Zielobjektklasse |
2 | 3 | Anzahl der Rasterspalten |
3 | 3 | Anzahl der Rasterzeilen |
5 | null | Reserviertes oder optionales Metadatenfeld |
6 | "Tractor" | Textbeschriftung |
| Typ | Beschreibung |
|---|
imageselect | Standard-CAPTCHA zur Bildauswahl |
tileselect | Kachelbasierte dynamische Bild-Challenge |
dynamic | Dynamische Challenge, bei der die Kacheln nach der Auswahl neu geladen werden |
multicaptcha | Mehrstufige CAPTCHA-Challenge |
audio | Audio-basierte Barrierefreiheits-Challenge |
nocaptcha | Reiner Checkbox-Verifizierungsablauf |
doscaptcha | CAPTCHA bei Missbrauch verweigern |
| Feld | Name | Beschreibung |
|---|
v | recaptchaVersion | reCAPTCHA-Version |
c | challengeToken | Challenge-Status-Token, das von /replaceimage oder einer vorherigen /userverify-Anfrage zurückgegeben wird |
response | Kodierte Nutzlast, die die ausgewählten Kachelindizes und zusätzliche Interaktionsmetadaten enthält | |
t | challengeElapsed | Verstrichene Zeit seit dem initialen Laden der Challenge |
ct | completionTime | Gesamtzeit, die der Benutzer zum Lösen und Absenden des CAPTCHA benötigt hat |
ch | ocHash | Hash der gesamten serialisierten oc-Nutzlast, Beispiel |
oc | encryptedVmSignals | Verschlüsselte Signale, die von der reCAPTCHA-VM gesammelt wurden, siehe hier. |
pm | telemetryPayload | Base64-kodierte Telemetriestruktur, die VM-Zeitdeltas, Ressourcen-Timings, gesammelte Browserobjekt-Statistiken und Website-Skriptmetadaten enthält |