Skip to content

Repository files navigation

reCAPTCHA VM

Runs Google's BotGuard engine in a jsdom sandbox (no Chrome, no browser) to mint real reCAPTCHA tokens. The engine ships as a Closure-compiled JS blob that runs a custom bytecode VM with ~2048 registers and 42 opcodes. Instead of reimplementing the VM, this runs Google's own code inside a fidelity shim: real Chrome fingerprint values, a same-realm MessageChannel polyfill, a WebGL stub, and a Worker bridge so the engine's postMessage handshake completes. It does real network calls to Google's /recaptcha/api2/clr endpoint with a protobuf body and gets back a valid token.

v3 works fully: tokens are Google-accepted (success: true, correct hostname/action). But they score ~0.2-0.3. A fresh incognito browser scores the same band. The gap to usable scores (0.5+) is i think profile reputation (aged cookies, browsing history), which can't be synthesized in code.

v2 is a proof-of-concept: proves the browserless anchor flow (render widget, click checkbox, fire /reload, set up the bframe iframe, capture the audio challenge URL) but does not solve the image/audio challenge itself.

Mint a v3 token

Fetch a fresh anchor for your sitekey:

$ SITEKEY=6L... ORIGIN=https://example.com OUT=live/v3-mine node fetch-anchor-generic.mjs

[api.js] 200 -> 1582B
  version: XOqlk8PL_yVx6IdpLbpXdiLy | origin: https://example.com
[anchor] 200 -> 54906B
  anchor valid=true error=false
[engine] 200 -> 823536B
[webworker] 200 -> 102B
saved -> live/v3-mine (valid=true)

Mint a token:

$ V3_DIR=live/v3-mine V3_ACTION=submit node live-v3-single.mjs

[load] parent execute= function | anchor set up= true

========== V3 SINGLE-JSDOM RESULT ==========
execute: RESOLVED len=2254 0cAFcWeA7Hcdbe3v9rqX9CaSgAX8...
log: 103
  P.pm recaptcha-setup
  ch1 p1.addEL
  ch1 p1.start
  Worker() created
  anchor set up; payload=35944B initErr=none
  ...
anchor token timeline:
  300ms len=1956 03AFcWeA51B_8PNwv4FazEQvzRFC...

NETWORK (anchor, real): 3
   POST https://www.google.com/recaptcha/api2/clr?k=6L... 2115B
     -> 200

errs: 0

FINAL anchor token: 1956 03AFcWeA51B_8PNwv4FazEQvzRFC...

Token is saved to dump/v3-token.txt.

Score a token

Score it via Google's siteverify (needs the key's secret):

$ SECRET=6L... node siteverify.mjs

==> SCORE = 0.2   action=submit   host=example.com

How the VM works

Two bytecodes are loaded from the /anchor response: the first is a literal init (strings, integers, the encrypted key table, 36KB), the second is the actual program (fingerprint collection, value generation, encryption). Both are base64-decoded then XOR-decrypted with a seed derived from two keys.

String decryption uses two integer keys and a custom character table: each codepoint is XOR'd with key1, then MOD'd with key2, then mapped to a character from the table. The encryption of the final payload uses an LCG (linear congruential generator) seeded with performance.now() so replays are time-locked.

For a deeper breakdown of the VM internals (opcode table, variant reader, LCG math, signal list), search GitHub for reCAPTCHA VM reversing.

Run

npm install

# 1. fetch a fresh anchor for your sitekey
SITEKEY=6L... ORIGIN=https://yourdomain.com OUT=live/v3-mine node fetch-anchor-generic.mjs

# 2. mint a v3 token
V3_DIR=live/v3-mine V3_ACTION=submit node live-v3-single.mjs

# 3. score it (needs the key's secret)
SECRET=6L... node siteverify.mjs

For v2 (proof-of-concept, captures the anchor flow + audio challenge URL):

SITEKEY=6L... ORIGIN=https://yourdomain.com OUT=live/v2-mine node fetch-anchor-generic.mjs
V2_DIR=live/v2-mine node live-v2-single.mjs

Options

Env var Default Description
SITEKEY required reCAPTCHA sitekey (fetch-anchor-generic)
ORIGIN http://localhost Target origin the sitekey is registered to
OUT live/v3-own Output directory for the fetched anchor
V3_DIR live/v3 Anchor directory to use for minting
V3_ACTION submit Action string for grecaptcha.execute
V3_TLS off Set 1 to route network through node-tls-client (Chrome JA3)
V3_TLS_PROXY off Set 1 to route through an external TLS proxy on localhost:407
V3_CHROMEHDRS off Set 1 to send Chrome client-hint headers on network requests
V3_COOKIE off Cookie string to send with network requests
V3_STEALTH off Set 1 to make overridden functions report [native code]
V3_APIS off Set 1 to fill missing browser APIs (AudioContext, RTC, etc.)
V3_REALCANVAS off Set 1 to use real canvas 2D instead of the stub
V3_BEHAVIOR off Set 1 to inject mouse/scroll/key events
V3_SETTLE off Wait N ms before execute (let the engine settle)
V3_NOFID off Set 1 for a deliberately bot-like profile (negative control)
SECRET required reCAPTCHA secret key (siteverify)

Files

File Description
live-v3-single.mjs v3 token minter (single jsdom realm)
live-v2-single.mjs v2 proof-of-concept (anchor + bframe + audio capture)
fetch-anchor-generic.mjs fetch a fresh anchor + engine + webworker for any sitekey
siteverify.mjs score a token via Google's siteverify
dump/real-fp.json harvested Chrome fingerprint fed into the sandbox shim

The ceiling

The v3 score is built from the real browser environment's collected signals (canvas/WebGL rendering, layout/paint timing, event streams, worker computation). jsdom can't generate those. Exhaustively tested: fingerprint fidelity, behavior injection, native-toString stealth, real 2D canvas, Chrome cookies, Chrome client-hint headers, TLS impersonation, missing-API fill. Only the worker handshake moved the score (0.1 to 0.2). None of the others helped.

Reference points: the VM scores ~0.2-0.3. A fresh incognito browser scores 0.1-0.3 (same band). An aged-profile browser scores 0.7-0.9 (reputation). The ceiling is reputation, not environment.

License

MIT. See LICENSE.


Telegram: @HK407.

For a working headless version with 0.6-0.9 scores, contact @B00H0.

About

Runs Google's BotGuard engine in a jsdom sandbox to mint real reCAPTCHA token browserless.

Topics

Resources

Stars

8 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages