
reCAPTCHA의 문서화 및 리버스 엔지니어링
이 저장소는 Google의 안티봇(reCAPTCHA)에 대한 기술적 분석을 담고 있으며, 다음을 중점적으로 다룹니다:
@g_recaptcha@lyxlobyxreCAPTCHA는 가장 정교한 난독화 기법을 사용하는 안티봇 시스템 중 하나로, 코드를 덜 읽기 쉽게 만들고 리버스 엔지니어링을 더욱 어렵게 만드는 일련의 변환을 적용합니다. 대부분의 난독화는 AST(Abstract Syntax Tree)를 사용해 쉽게 조작할 수 있지만, 일부는 런타임에 처리되므로 이 경우 AST는 무용지물이 됩니다. 또한 코드에는 다형성(Polymorphism)이 적용되어 스크립트 버전마다 구조가 변경됩니다. 예를 들어, 코드는 작업을 직접 수행하지 않습니다. 대신 형태가 바뀌는 객체나 함수를 사용합니다
시퀀스 표현식(Sequence Expressions)
코드는 각 블록 문(block statement)을 연속적인 쉼표로 구분된 표현식으로 변환하여 평탄화됩니다. 이는 if 문, 함수 인수, 심지어 객체 내부에서도 나타날 수 있습니다.

혼합 부울 산술(Mixed Boolean Arithmetic)
산술 연산(덧셈, 뺄셈, 곱셈)과 비트 연산(AND, OR, XOR, NOT)을 서로 얽어 원래 로직을 숨깁니다. 예: -2 * ~(h & H) + -2 + (h ^ H)
간접 함수 테이블(Indirect Function Table)
각 함수는 테이블 내부에 구축되며 functions[index](https://github.com/elyelysiox/recaptcha/blob/main/args)처럼 인덱스를 사용하여 호출됩니다.
인라인 상수 배열(Inline Constant Array) 로컬 배열 리터럴이 표현식 중간에 인라인으로 할당됩니다. 이 배열은 함수 본문 전체에서 인덱스 접근을 통해 재사용되는 상수(숫자, 문자열)를 그룹화합니다.
// 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
}
함수 멀티플렉싱(Function Multiplexing) 논리적으로 구별되는 여러 함수가 하나로 병합되며, 숫자 매개변수를 블록 선택자로 사용합니다. 활성 블록은 매개변수를 비트 조건으로 평가하여 결정됩니다. 호출자는 선택자로 숫자 리터럴을 전달합니다.
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;
}
논리 연산자 분기(Logical Operator Branching) if 문과 if/else 블록을 논리 연산자 단락 평가(short-circuit evaluation)로 대체하여 제어 흐름을 표현식으로 변환합니다. 시퀀스 표현식과 결합하면 여러 분기가 단일한 연속 쉼표 구분 표현식으로 나타납니다.
// 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), ...)),
네이티브 메서드 상수 바인딩(Bind Native Methods Constants) 네이티브 브라우저 메서드를 원래 수신자에 바인딩하여 상수로 저장함으로써 변조를 방지합니다.
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)
데드 코드(Dead Code) 접근할 수 없거나 사용되지 않는 코드 블록이 파일 전체에 주입되어 파일 크기가 60,000줄 이상으로 늘어납니다. 이로 인해 정적 분석과 LLM 기반 리버스 엔지니어링이 어려워집니다.
제어 흐름 평탄화(Control Flow Flattening) 코드의 각 부분(선언 및 루프)을 평평한 상태 머신(flat state machine)으로 변환합니다. 모든 코드 블록을 중앙 "디스패처(dispatcher)" 블록을 통해 라우팅함으로써 원래 실행 로직을 숨깁니다.
디스패처는 형태가 바뀔 수 있습니다. 일부는 2~3개의 상태 변수를 가지며 루프/조건 유형도 변경됩니다. 다음과 같은 형태입니다.

이것은 2개의 상태 변수를 가진 CFF로, 하나는 catch 블록을 처리하고 다른 하나는 try 블록을 처리합니다.
암호화된 문자열 풀(Encrypted String Pool) 모든 문자열 리터럴(DOM API, 브라우저 속성, CSS 값, 오류 메시지 등)은 단일 대규모 문자열 풀로 암호화됩니다. 복호화 함수는 시드(seed)와 LCG 기반 XOR 암호를 사용하여 런타임에 각 문자열을 추출합니다.
코드 전체에 1990개 이상의 호출 지점이 분산되어 있으며, 복호화 함수는 디코딩된 코드포인트를 누적하는 실행 키(running key)를 사용하므로 각 문자가 이전의 모든 문자에 의존하게 됩니다.
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"

상태 저장 값 반복자(Stateful Value Iterator) reCAPTCHA는 window, document.body, 숫자 상수와 같은 런타임 객체와 값을 고정된 순서로 반환하는 상태 저장 함수를 사용합니다. 각 호출은 내부 커서를 전진시키며, 순서를 벗어나거나 너무 많이 호출하면 이후의 모든 읽기가 손상됩니다. 타임아웃 메커니즘은 일정 간격이 지나면 상태를 무효화하고 늦은 읽기에 대해 null을 반환합니다.
// 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
계산된 함수 테이블(Computed Function Table)
이것은 Indirect Function Table과 유사하지만, 여기서는 가져올 함수의 인덱스가 XOR과 Modulus를 사용하여 시드(seed)로 런타임에 계산됩니다.
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

런타임 값 암호화(Runtime Value Encryption)
일부 값 (캡차 구성 매개변수, 앵커 매개변수 등)은 평문으로 저장되지 않습니다. 수집 직후 암호화되어 사용 시점에만 복호화되며, 시작 부분에 B 접두사가 붙습니다.

비동기 제어 흐름 난독화(Async Control Flow Obfuscation) 동기 로직은 재귀적 Promise 체인으로 감싼 제너레이터 기반 상태 머신으로 변환됩니다. 디버거에서 값을 추적하면 여러 비동기 핸들러를 단계별로 거치게 되어 각 .then() 경계에서 원래 실행 컨텍스트가 손실됩니다.
reCAPTCHA를 초기화하는 구성 요소``` ar: <WidgetInit?> k: co: hl: v: size: sa: anchor-ms: execute-ms: cb:
### 응답
응답에는 다음이 포함됩니다:
- CAPTCHA iframe 창 디자인
- 페이로드 검증에 사용되는 앵커 토큰 (/reload)
- 초기화를 위한 주요 구성이며, `recaptcha.anchor.Main.init` 메서드에서 실행되어 recaptcha_en.js에서 이를 수신합니다.
### 구조:
참고: Recaptcha BotGuard는 `04/01/2026`에 제거되었습니다. 일부 샘플은 [여기](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
]
값 은 첫 번째 샘플 지문에서 복호화된 값 을 기반으로 하며, 여기를 참조하세요.
지문 값 의 각 하위 필드에는 기본 형식이 있습니다:
[value, key, elapsed]
여기서 key는 암호화 키이고, elapsed는 수집기가 값을 가져와 암호화하는 데 걸린 시간입니다. 이를 통해 reCAPTCHA는 다음을 감지할 수 있습니다:
시스템은 내부 스케줄러를 사용합니다:``` [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]
이는 다음을 나타냅니다:
- 실행 순서
- 수집기 파이프라인
- 신호 생성 시퀀스
### 지문 신호 코드 및 키 파생
다단계 파생 파이프라인을 통해 지문 신호를 결정적 암호화 키로 변환합니다.
각 지문 값은 다음과 같습니다:
- 정규화됩니다
- 컴팩트 신호 코드로 인코딩됩니다
- 숫자 암호화 키로 변환됩니다
- 파생된 키를 사용하여 암호화됩니다
이 프로세스는 결정적이므로 동일한 입력 값은 항상 동일한 신호 코드와 암호화 키를 생성합니다.```
Raw Value
↓
Signal Code Derivation
↓
Compact Signal Code
↓
Numeric Key Derivation
↓
Encryption Key
↓
Value Encryption
입력 신호``` "BUTTON,195a81c9"
***1단계 - 신호 코드 도출***```javascript
deriveSignalCode("BUTTON,195a81c9")
// → "wg"
생성된 신호 코드는 원래 값에 대한 간결한 결정적 식별자입니다.
2단계 - 숫자 키 도출```javascript deriveKey("wg") // → 3792
신호 코드는 숫자 암호화 키로 변환됩니다.
***3단계 — 값 암호화***```javascript
encryptValueWithKey(3792, "wgia1z9pwq")
// → "bYVbh6BUsE_5pLA"
다음 값은 수집기 순서에 따라 파생된 키로 암호화됩니다.
생성된 모든 신호 코드는 컴팩트한 직렬화 구조로 집계됩니다
프로세스``` 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
**최종**```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: window.localStorage.getItem("rc::a") + "6d"를 바이트로 변환하여 HMAC 키를 생성하고, 그 키로 HMAC-SHA256(siteKey)를 계산한 다음 결과의 처음 4개 16진수 문자를 반환합니다.
rc::a의 값은 무작위로 생성된 base64입니다.
Idx 5 (integer)
window.localStorage.length * 2falsewindow.localStorage 길이에 2를 곱한 값Idx 16 (string)
Value: yM5Us/j/fn6EDgtmPlP4Pxj605nMJN9dRYHyy5Mn
특정 reCAPTCHA 시그널은 사용할 수 없거나 수집에 실패할 수 있습니다(디버깅 관련 타임아웃으로 인해). 이러한 경우 누락된 시그널은 무작위로 생성된 base64 인코딩 값으로 채워집니다. 예를 들어:``` ["CMXZyamk3ZHNjdGsyaQ==", 3249, 591]
핑거프린트 구성이 완료되면 문자열로 직렬화된 후 앵커 필드(인덱스 18)에서 로드된 암호화 키를 사용하여 암호화됩니다. 그런 다음 암호화된 결과는 `/reload` 페이로드의 인덱스 `16` 필드에 저장됩니다.
## VM 신호
다음은 내부 reCAPTCHA VM이 획득한 브라우저 값과 신호 키입니다. VM은 구성 바이트코드에서 특정 값만 추출하므로 일부 값은 샘플 핑거프린트에 표시되지 않습니다. 따라서 다른 값들이 표시됩니다. 자세한 내용은 [여기](https://github.com/elyelysiox/recaptcha-vm)를 참조하세요.
기본 신호 형식:```
[
null,
collectorElapsed,
encryptElapsed,
value
]
여기서 collectorElapsed는 컬렉터가 값을 가져오는 데 걸린 시간(ms)을 의미합니다.
그리고 encryptElapsed는 값을 암호화하는 데 걸린 시간(ms)을 의미합니다.
이 시간들은 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
- **Key 727** (`array`)
- Value: `[]`
- Description: 상품 가격 블록, 웹사이트의 상업용 제품 가격을 포함하며, Amazon, eBay, AliExpress 등 마켓플레이스 웹사이트에 나타날 수 있습니다. [샘플](https://github.com/elyelysiox/recaptcha/blob/main/findProductPricesBlock.js)
- **Key 150** (`array`)
- Value: `[\"Join And Get Free Trial!\",\"\",\"Captcha Solving Service\",\"Prod...`
- Description: 웹사이트의 `<a>` 요소에서 추출된 링크, VM이 이러한 값을 추출하는 경우는 드뭅니다
- **Key 545** (`boolean`)
- Value: `false`
- Description: `navigator.webdriver`
- **Key 370** (`integer`)
- Value: `0`
- Description: 최대 디바이스 터치 포인트 `navigator.maxTouchPoints`
- **Key 779** (`array`)
- Value: `[null,null,null,null,"false"]`
- Description: 배송/결제 필드에서 데이터를 추출합니다. [샘플](https://github.com/elyelysiox/recaptcha/blob/main/formFieldScrape.js)
`postal|postalcode|postalcode|postal_code|zip` → 우편번호 필드 검색
`ship|deliver` → 배송 섹션
`billing|payment|credit` → 결제 섹션
`country` → 국가 선택기
`addr.*(match|equal)|(match|equal).*address` → "동일 주소" 체크박스
각 요소의 name, id, autocomplete, className을 읽습니다
세 단계: 텍스트 입력, SELECT 문, 체크박스
- **Key 659** (`array`)
- Value: `[[[0,921],[1,2053]],[[235054,1,1,1],[164277,3,4,1]],2,39]`
- Description: 페이지에서 눌린 요소의 데이터를 `[[[count,timestamp]],[[hashedElem,tagType,type,autoComplete]],pressedCount,behaviorChecksum]` 형식으로 포함합니다
### 요소 유형
| 요소 유형 | 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 |
### 동작 체크섬 알고리즘
behaviorChecksum은 수집된 사용자 상호작용 데이터에서 파생된 간결한 동작 점수입니다.
각 상호작용에 대해:
- 상호작용 인덱스가 1씩 증가합니다
- 타임스탬프가 조정된 상호작용 인덱스와 결합됩니다
- 요소 해시가 모듈로 연산을 통해 축소됩니다
- 결과가 누적 점수에 합산됩니다
---
```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;
}
```
### 예제
```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: 페이지 세션 중 수집된 스크롤 움직임을 포함합니다
### 기본 형식
```
[ [ [ scrollX, scrollY, timestamp ] ], count, behaviorChecksum ]
```
- **Key 895** (`array`)
- Value: `[[[1,0]]]`
- Description: `VisibilityStateEntry`는 페이지 가시성 상태를 추적하여 사용자가 현재 페이지를 보고 있는지 또는 다른 곳으로 이동했는지를 나타내며, 형식은 `[[[isVisible, startTime]]]`입니다
- **Key 1092** (`array`)
- Value: `[886,2300,262.2099999997952,392.7600000009595,2050.2399999993577]`
- Description: reCAPTCHA 성능, PerformanceNavigationTiming 및 PerformanceEventTiming, 형식은 `[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, 계산된 키 + 타임스탬프
- **Key 352** (`array`)
- Value: `1,[[2050,125,633,266,[609,266,105,44],3,1,50,1,1]],[3,3,318],...`
- Description:
구조는 다음을 포함합니다:
- 마우스 이동 타이밍
- 커서 좌표
- 클릭 지속 시간
- 눌린 요소 크기
- 포인터 유형 정보
- 압력 지표
- 커서 이동 거리
- 동작 타이밍 통계
### 기본 형식:
```
[
eventsCount,
mouseEvents,
[
coalescedEventsCount,
averageInterEventDelay,
timingVarianceScore
],
[
totalPoints,
clickSequenceScore,
totalCursorDistance,
totalPoints
],
0,
0,
behaviorChecksum
]
```
### 마우스 이벤트 구조:
mouseEvents 내부의 각 항목은 다음 구조를 따릅니다:
```
[
timestamp,
clickDuration,
clientX,
clientY,
[
elementLeft,
elementTop,
elementWidth,
elementHeight
],
elementTagType,
trackedElementIndex,
pressure,
pointerTravelDistance,
pointerArea
]
```
| 이름 | 설명 |
|---|---|
| `timestamp` | 이벤트 시작을 기준으로 한 이벤트 타임스탬프 |
| `clickDuration` | `pointerdown`과 `pointerup` 사이의 지속 시간 |
| `clientX` | ... |
| `clientY` | ... |
| `elementBounds` | 상호작용한 요소의 경계 사각형 |
| `elementTagType` | 상호작용한 요소의 인코딩된 DOM 태그 식별자 |
| `trackedElementIndex` | 런타임 상호작용 맵에서 상호작용한 DOM 요소의 내부 식별자 |
| `pressure` | `100`을 곱한 정규화된 포인터 압력 값 |
| `pointerTravelDistance` | `pointerdown`과 `pointerup` 사이에 이동한 거리 |
| `pointerArea` | `width * height`로 계산된 접촉 면적 |
### 요소 태그 유형
이는 reCAPTCHA VM 실행 중에 초기화됩니다
| 유형 | 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 |
### 예제
```
[
18342,
91,
742,
381,
[700, 320, 120, 38],
3,
1,
50,
1.4,
16
]
```
다음을 나타냅니다:
- `18.342s` 시점의 이벤트
- `91ms`의 클릭 지속 시간
- `(742, 381)` 위치의 클릭
- `BUTTON`과의 상호작용
- 포인터 압력 `0.5` (50)
- 클릭 중 약간의 커서 움직임
- 포인터 접촉 면적 `16`
### 동작 체크섬 알고리즘
각 마우스 이벤트에 대해:
- 클릭 지속 시간이 증폭됩니다
- 결과가 이벤트 타임스탬프와 결합됩니다
- 커서 좌표와 요소 크기가 함께 혼합됩니다
- 가중 비율이 계산됩니다
- 비율이 반올림되어 누적됩니다
모든 이벤트가 처리된 후:
- 누적 점수가 총 커서 거리와 결합됩니다
- 결과가 수집된 포인트 수를 사용하여 정규화됩니다
- 로그 변환이 적용됩니다
- 추가 이동 통계가 통합됩니다
- 최종 값이 모듈로 연산을 통해 축소됩니다
- 결과가 작은 정수 점수로 반올림됩니다
---
```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);
}
```
### 예제
```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: 수집된 웹사이트 가격 합계 $ 파싱, 정규식 ``(?i)total[\S\s]{0,20}?(?:(?:(?:USD|\$)\s*)?[\d\.,]+\s*(?:USD|\$)|(?:USD|\$)\s*[\d\.,]+)`` 사용. [샘플](https://github.com/elyelysiox/recaptcha/blob/main/websitePricesTotal.js)
- **Key 1278** (`integer`)
- Value: `229.6`
- Description: 현재 런타임 타이밍을 10으로 나눈 값 `Math.trunc(performance.now()) / 10`
- **Key 1422** (`boolean`)
- Value: `false`
- Description: 웹사이트에 OTP/2FA 필드가 있는지 파싱합니다. [샘플](https://github.com/elyelysiox/recaptcha/blob/main/parseOTPFields.js)
- **Key 614** (`array`)
- Value: `[1,0,0,Infinity]`
- Description: 기기 배터리 정보, 형식은 `[level,isCharging,chargingTime,dischargingTime]`입니다
- **Key 2033** (`array`)
- Value: `["0",-1,-1,999]`
- Description: 일부 VM 상수 값 및 VM 런타임 타이밍
- **Key 1313** (`array`)
- Value: `["undefined",3]`
- Description: 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: WebGL 렌더러, 공급업체 및 총 확장 수. [샘플](https://github.com/elyelysiox/recaptcha/blob/main/webglSignals.js)
- **Key 291** (`array`)
- Value: `[1542343452,0,105002991,3556653,96784904,-731949068,-902263026,...`
- Description: 브라우저 객체의 특정 프로토타입 키를 해시한 값, 예: `SpeechSynthesisEvent`, `NetworkInformation`, `HTMLElement` 등. [샘플](https://github.com/elyelysiox/recaptcha/blob/main/hashBrowserProtos.js)
## 리로드 요청 페이로드
이 데이터는 Google이 개발한 구조화된 데이터 직렬화를 위한 효율적이고 중립적인 방법인 protobuf(Protocol Buffers)를 사용하여 직렬화됩니다.```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,
]
| 값 | 설명 |
|---|---|
q | reCAPTCHA V3 챌린지 |
fi | reCAPTCHA V2/인비저블 챌린지 |
a | 오디오 챌린지 |
qr |
Idx 20)디코딩 방법:```javascript atob("W1tbMywwLDgyOV0sWzEs...") // → [[[3,0,829],[1,42,863]],[[2,727,1341.300000011921]...
페이로드에는 다음이 포함됩니다:
- 리소스 타이밍 정보
- Long-task 성능 타이밍
- reCAPTCHA VM 실행 델타
- 웹사이트 스크립트 URL
- 브라우저 객체 수집 통계
### 기본 구조```js
[
[recaptchaResources],
[tasksTiming],
[
null,
null,
null,
idleTimeoutDelta,
deltaTiming,
0,
0,
0,
],
websiteScriptsUrls,
[collectionElapsed, browserObjectsLength]
]
[ PerformanceLongTaskTiming.name == "self" ? 2 : 4, PerformanceLongTaskTiming.duration, PerformanceLongTaskTiming.startTime ]
### reCAPTCHA 리소스 타이밍 형식```js
[
resourceType,
duration,
startTime
]
| 리소스 |
|---|
Idx 25)이 필드에는 실행 중 수집된 브라우저 상호작용 이벤트의 Base64 인코딩 카운터가 포함되어 있습니다.
디코딩된 예시:```js [ [ [5006, 43], [64607, 1], [45464, 1], [35837, 1] ] ]
### 이벤트 카운터 구조```js
[
[
[eventType, count]
]
]
[ "rresp", "", null, 120, [ "pmeta", [ "/m/013xlm", null, 3, 3, 3, null, "Tractor" ], null, [1] ], "", null, null, "", "", null, null, "", null, "", "", 0 ]
## 필드 설명
| Index | Name | Description |
|---|---|---|
| `0` | `rresp` | reload 엔드포인트에서 반환되는 응답 유형 식별자 |
| `1` | `responseToken` | reCAPTCHA 응답 토큰 |
| `3` | `tokenExpiration` | 만료 전 토큰 수명(초 단위) |
| `4` | `pmeta` | CAPTCHA 유형, 범주, 그리드 레이아웃에 대한 정보를 포함하는 챌린지 메타데이터 블록 |
| `5` | `challengeType` | CAPTCHA 챌린지 유형 |
| `8` | `usagePatternToken` | 사용 패턴. `window.sessionStorage`에 저장되며 이후 `/reload` 요청에서 재사용됩니다 |
| `9` | `challengeImageToken` | `/payload` 엔드포인트에서 CAPTCHA 이미지 페이로드를 검색하는 데 사용되는 토큰 |
| `12` | `humanVerificationToken` | Google reCAPTCHA 상태 및 브라우저 쿠키와 연결된 인간 식별 토큰 |
| `14` | `captchaSessionToken` | 내부 세션 토큰 |
| `15` | `bftToken` | ... |
---
### `pmeta` 구조
예시:```js
[
"pmeta",
[
"/m/013xlm",
null,
3,
3,
3,
null,
"Tractor"
],
null,
[1]
]
pmeta[1] 값Idx 9(challengeImageToken)의 값은 CAPTCHA 이미지를 검색하는 데 사용됩니다:```txt
https://www.google.com/recaptcha//payload
?p=
&k=
### 카테고리 식별자
| ID | 카테고리 |
|---|---|
| `/m/0pg52` | 택시 |
| `/m/01bjv` | 버스 |
| `/m/02yvhj` | 스쿨버스 |
| `/m/04_sv` | 오토바이 |
| `/m/013xlm` | 트랙터 |
| `/m/01jk_4` | 굴뚝 |
| `/m/014xcs` | 횡단보도 |
| `/m/015qff` | 신호등 |
| `/m/0199g` | 자전거 |
| `/m/015qbp` | 주차 미터기 |
| `/m/0k4j` | 자동차 |
| `/m/015kr` | 다리 |
| `/m/019jd` | 보트 |
| `/m/0cdl1` | 야자수 |
| `/m/09d_r` | 산 / 언덕 |
| `/m/01pns0` | 소화전 |
| `/m/01lynh` | 계단 |
## UserVerify 요청 페이로드
`/userverify` 페이로드는 사용자가 reCAPTCHA v2 이미지 챌린지를 완료한 후 제출됩니다
# 페이로드 구조```
v: hsFBb1u5wWWWkWP4in1ua2cQ
c: 03AFcWeA5RIHz5mI2COQqTcLjdrC7FiPpdfkX-hB-B2iFenI...
response: eyJyZXNwb25zZSI6WzMsMSw0LDksMCwxMSwxMCwxMywxNF0....
t: 24521
ct: 24521
ch: -4007845
oc: 0ZpqzzPgYKEEaN0xpfo3v_voeVUySZK3Ktf_fLVRdhnu3yeTy4BhO...
pm: 51bGwsbnVsbCxbbnVsbCxudWxsLG51bGwsWzEwLDQuMjcwMDAwMD....
response 페이로드사용자가 그리드에서 선택한 사각형의 인덱스/위치를 나타냅니다.
페이로드는 제출 전에 JWT로 인코딩됩니다.
```txt
0 1 2
3 4 5
6 7 8
예제 페이로드:```json
{
"response": [2, 3, 6]
}
```txt
0 1 2 3
4 5 6 7
8 9 10 11
12 13 14 15
예제 페이로드:```json
{
"response": [2, 3, 6, 14]
}
e 필드response 페이로드에는 e 필드도 포함될 수 있습니다:```json
{
"response": [2, 3, 6, 14],
"e": "bxCoiXVt49I7CTS_tNlVbGg"
}
이 필드는 다음과 같은 CAPTCHA 버튼 작업과 관련된 암호화된 상호작용 메타데이터를 포함합니다:
- `Verify`
- `Next`
- `Skip`
### `e`의 내부 구조
복호화 후 구조는 다음과 같습니다:```txt
[[["9ffd9672"]]]
각 값은 rc-button-default 버튼 클래스/동작에 대한 해시 식별자를 나타냅니다.
해시는 다음과 같습니다:
더 긴 상호작용 시퀀스는 다음과 같이 보일 수 있습니다:```txt [ [ ["9ffd9672"], ["9ffd9672"], ["9ffd9672"], ["9ffd9672"] ] ]
### `oc` — 암호화된 VM 신호
`/reload` 요청 페이로드의 `idx 16` 필드와 동일한 암호화 알고리즘을 사용하며, 암호화 키는 앵커(인덱스 31) `1777752103288`에 로드됩니다.
### `pm` — 텔레메트리 페이로드
`pm` 필드에는 `/reload`에서 사용되는 텔레메트리 페이로드와 유사한 Base64로 인코딩된 텔레메트리 구조가 포함되어 있습니다.
## 안티 디버깅/변조
- ***Symbol 기반 무결성 태그***
reCAPTCHA는 무결성 검사에 사용되는 숫자 값을 가진 모든 배열과 객체에 Symbol 키 `Symbol(jas)`를 설정합니다. Symbol은 비열거형이며 표준 복제 작업(예: JSON.stringify)에는 보이지 않기 때문에 이러한 구조를 교체하거나 복제하려는 모든 시도는 태그를 잃게 되며, reCAPTCHA는 이를 변조로 감지합니다.

- ***클로저 변수 캡처***
일부 값은 의도적으로 외부 함수에 범위가 지정되고 중첩된 콜백이나 클로저 내부에서만 소비되며, 변수는 사용되는 동일한 범위에 존재하지 않습니다.
- ***타이밍 검사***
reCAPTCHA는 런타임 디버깅 탐지 메커니즘을 통합합니다. 코드가 디버거에 의해 분석되고 있음을 감지하면 디버깅 세션을 다시 시작하여 실행 상태와 컨텍스트가 손실되게 만듭니다.
- ***소스 탭 충돌***
https://github.com/user-attachments/assets/4ae3bf68-5ae7-4adc-a0b9-36cc11b64998
이 버그는 Chromium을 사용하는 브라우저에서 발생합니다. 여기에 표시된 것처럼 브라우저나 개발자 도구가 예기치 않게 닫히게 만드는 여러 버그가 있습니다:
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
따라서 실제 사용자 흐름에서는 발생하지 않을 것 같기 때문에, 이 버그 중 하나가 타임아웃 발생 시 트리거될 가능성이 높습니다.
## reCAPTCHA Botguard
BotGuard가 Google에 의해 제거되었지만, 그것이 어떻게 생겼는지와 다른 유형의 botguard와 얼마나 다른지에 관심이 있는 사람들을 위해 내부적으로 어떻게 작동하는지 조금 설명하겠습니다.
먼저 토큰 생성 과정: VM은 실행 중 지속적인 오류에 대한 토큰을 생성합니다. 이러한 오류는 바이트코드 오프셋 편차, 누락된 VM 레지스터, 잘못된 VM 상태, 제어 흐름 무결성 부족으로 인해 발생할 수 있습니다. 이러한 오류는 디스패처가 캡처하며, 디스패처는 토큰에 대한 바이트를 쓰는 함수를 호출합니다.
VM은 데이터 암호화를 위해 Speck과 유사한 2013년 NSA가 만든 알고리즘인 ARX Cipher를 사용합니다. 또한 모든 Google BotGuard에서 사용됩니다. 알고리즘은 상수(3990), 연산 순서 및 라운드와 마찬가지로 버전마다 변경됩니다. 다음과 같습니다:```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];
}
주요 암호화 기능에는 암호화 모드와 비암호화 모드의 두 가지 모드가 있습니다. 암호화 모드는 ARX Cipher, Bitwise + XOR을 사용하여 데이터를 암호화하고 버퍼에 설정합니다. 버퍼에는 4가지 유형이 있습니다:
180353304243 (이 유형의 botguard에서는 사용되지 않음).비암호화 모드는 이미 암호화된 바이트를 버퍼에 설정하기만 합니다.```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]); } } }
그리고 이것은 오류를 처리하고 byte writer를 호출하는 함수입니다.```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;
}
}
}
실행이 끝나면 VM은 이러한 버퍼로 페이로드를 준비하고 서명인 토큰을 생성하는 main 함수를 호출합니다. 다른 BotGuard VM과 달리 이 VM에는 핑거프린팅 시스템도 Google Botguard도 없으며, 이러한 오류 발생 + 바이트 쓰기 작업을 수행하지도 않아 훨씬 더 단순하고 리버스 엔지니어링하기 쉽습니다.
이 저장소는 교육 및 연구 목적으로만 제공됩니다. 여기에 제시된 코드, 기법, 개념은 다루는 주제에 대한 학문적 연구와 이해를 위한 것입니다.
Hashed: true
Description: 모든 <HEAD> 노드의 40자 base64 블룸 필터 지문입니다. 각 자식 요소를 탐색하여 태그 이름, 속성 및 텍스트 내용을 수집하고 직렬화한 다음 djb2 해시를 실행합니다.
각 다이제스트는 BitHash(240 bits, 7 rounds, max 25 nodes)에 입력되어 40×6 그리드에 비트를 설정한 다음, 각 6비트 세그먼트를 base64 문자로 인코딩합니다. reCAPTCHA 자체의 스크립트 태그는 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 함수 본문을 SHA-256으로 해시한 값, SampleIdx 30 (integer)
0falsehttps://www.gstatic.com/recaptcha/releases/<version>/가 포함된 스크립트의 인덱스, SampleIdx 31 (string)
AAgkAQI0SBtruedocument.cookie 키를 해시한 값, SampleIdx 32 (string)
""falsedocument.referrer.Idx 33 (integer)
Value: 2
Hashed: false
Description: 렌더링이 수행되는 reCAPTCHA 박스의 요소.
// 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> 속성 이름을 해시한 값, SampleIdx 35 (string)
0,DIV,f0c7414etruedocument.activeElement를 textContent, classNames 및 id와 함께 정규식 /buy|pay|place|order|donate|purchase/i를 사용하여 구매 요소인지 파싱합니다. SampleIdx 36 (string)
h3, h2 or http/1.1falsenextHopProtocol 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 또는 document.defaultView.pageYOffsetIdx 41 (string)
DIV,a08cd360true:hover 요소입니다. 마지막 요소를 가져와 tagName, classNames 및 id를 해시합니다. SampleIdx 42 (string)
9,e3b0c442trueIdx 44 (string)
2v591z63wqtrue:hover 요소, document.activeElement, 해시된 모든 <INPUT> 요소, location.url, window.scrollY, 예시: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 위치입니다.Idx 47 (integer)
0falsewindow.getSelection().toString().length || 0Idx 49 (string)
h2-0falsenextHopProtocol을 가져오고 duration이 0인지 확인합니다. 결과를 protocol-isZero 형식으로 반환합니다. SampleIdx 50 (array)
Value: [1,0,null,1,1,["805b1z63wq"],"MWc2MzZwMnR0ZHNkMw==",0,null,[[1]]]
Hashed: true
Description: 사용자 행동, 해시된 :hover 요소, 그리고 다른 :hover 요소가 수집될 때마다 증가하는 타임아웃을 포함합니다. (10 * IsActive + IsBeenActive) 계산을 통해 사용자가 페이지와 상호작용했거나 아직 페이지에 머물러 있는 횟수를 누적하도록 사용자 활성화를 분석합니다.
기본 형식:
[
count,
timeout,
null,
timeoutMultiplier,
transitionCount,
[ elementsHoverHashed ],
sessionId,
userActivationScore,
null,
[ [ flags ] ]
]
Idx 51 (array)
[0]falsedocument.body.innerText에서 "try again", "incorrect", "invalid" 또는 "declined" 단어를 검색하여 찾은 총 일치 횟수를 반환합니다. 그렇지 않으면 0을 반환합니다. SampleIdx 52 (integer)
11false10 * navigator.userActivation.isActive + navigator.userActivation.hasBeenActiveIdx 53 (integer)
Value: 4
Hashed: false
Description: 현재 웹사이트 URL을 가져와 문자 배열로 변환하고 100자로 자른 다음 다시 문자열로 변환합니다. Sample
그런 다음 잘린 문자열의 길이가 짝수이면 5이고, 홀수이면 4입니다.
Idx 54 (boolean)
falsefalsewindow.document.hiddenIdx 55 (array)
[[[1,"2v"],[1,"9z"],[1,"z8"],[1,"us"],[1,..falseIdx 56 (string)
-1,-1falsewindow.opener 확인. null이면 "-1,-1"을 반환합니다. SampleIdx 57 (string)
Value: www.gstatic.com,_,static.kogstatic.com,www.google.com,www.googletagmanager.,...
Hashed: false
Description: document.scripts에 있는 모든 <SCRIPT> 요소의 src에서 호스트를 수집합니다. Sample
각 <SCRIPT>에 대해
요소에 src 속성이 있는 경우(hasAttribute("src")), src 값을 가져와 URI 유형 정규식(nd)으로 파싱하여 호스트 구성 요소만 추출합니다(정규식의 캡처 그룹 3).
<SCRIPT>에 src 속성이 없는 경우(인라인 스크립트), 자리 표시자 "_"가 사용됩니다.
Idx 58 (string)
mapslitepromosdismissed1falsewindow.localStorage 키.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에 의해 기록된 속성 이름 및 대상 요소 유형의 해시, SampleIdx 62 (string)
document.titlefalseIdx 63 (array)
Value: []
Hashed: false
Description: 쿠키 값에서 타임스탬프(1[2-9]로 시작하는 1213자리 숫자, 대략 20082099년의 epoch 밀리초)를 추출합니다. Sample
발견된 모든 ID에 대해 count, min, max 및 average 값을 누적합니다. 최종 값 결과는 배열 [count, min, max, avg]입니다.
Idx 64 (integer)
55557falseIdx 65 (string)
sha384-3qctruehttps://www.google.com/recaptcha/api.js?render=에 구성된 reCAPTHCA 스크립트의 무결성(10자로 잘림)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: 총 금액(예: Total: $50.00 또는 Total 150.00 EUR)을 추출합니다. Sample
정규 표현식을 사용하여 통화 기호 또는 코드와 숫자 금액을 검색해 document.innerText에서 추출합니다:
total[\S\s]{0,20}?(((R\$|\$|€|£|USD|EUR)\s*[\d\.,]+)|[\d\.,]+\s*(€|USD|EUR))`
total → 정확한 단어 "total"을 검색합니다.
[\S\s]{0,20}? → 숫자를 찾기 전에 (0)에서 (20) 사이의 중간 문자(공백, 콜론, 줄 바꿈 등)를 검색합니다.
(((R\$|\$|€|£|USD|EUR)\s*[\d\.,]+)|[\d\.,]+\s*(€|USD|EUR)) → 금액을 캡처하는 주요 블록이며 두 가지 별도 옵션으로 나뉩니다.
Idx 71 (array)
[jsHeapSizeLimit,usedJSHeapSize,totalJSHeapSize]falseIdx 72 (array)
Value: [[["Google Chrome","147"],["Not.A/Brand",...
Hashed: false
Description: userAgent 데이터 배열 navigator.userAgentData, 모바일 여부를 나타내는 플래그 및 플랫폼입니다. 샘플:
[
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| 인덱스 | 값 | 설명 |
|---|
0 | rreq | 요청 유형 식별자 |
1 | U5VsmTDhJM1iO... | reCAPTCHA 버전 |
2 | 03AFcWeA5Els3... | /anchor 엔드포인트에서 얻은 검증 토큰 |
5 | -383788762 | 직렬화된 전체 핑거프린트 페이로드를 해시함, 샘플 |
6 | q | 실행 중인 챌린지 또는 요청 모드를 나타냄 |
7 | 05APQrAobTCFG... | 사용 패턴. 사용 기록과 이전에 해결된 챌린지 패턴을 포함. 유효한 경우 점수를 크게 높일 수 있음 |
8 | submit | 검증 시점에 사용자가 페이지에서 수행 중인 특정 사이트 작업 (예: signin, register) |
14 | 6LcAbwIqAAAA... | 웹사이트 키 |
16 | 0XA4cHhALxgM... | 앵커(인덱스 18)의 암호화 키로 직렬화된 전체 핑거프린트 페이로드를 암호화함 1777669303203 |
20 | tbMywxNTQsOD... | VM 타이밍 델타, 리소스 타이밍, 수집된 브라우저 객체 통계 및 웹사이트 스크립트 URL을 포함하는 Base64 인코딩 원격 측정 구조 |
21 | 0aAPQrAobqME... | 이전에 해결된 CAPTCHA와 연결된 토큰. 세션 연속성 및 신뢰 상관관계에 사용됨 |
22 | BDAAYAIAGEcA... | 모든 브라우저 객체 키를 해시함 |
25 | W1tbNTAwNiw... | 마우스 이동, 클릭, 키보드 및 포커스 변경과 같은 사용자 상호작용 이벤트에 대한 Base64 인코딩 카운터 |
| 빠른 응답 (신규) |
| 유형 |
|---|
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 | 이벤트 유형 |
|---|
5006 | pointermove |
64607 | pointerdown |
45464 | pointerup |
31617 | keydown |
37178 | keyup |
35837 | focusin |
| 위치 | 값 | 설명 |
|---|
0 | /m/013xlm | 대상 객체 클래스의 내부 카테고리 식별자 |
2 | 3 | 그리드 열 수 |
3 | 3 | 그리드 행 수 |
5 | null | 예약되었거나 선택적인 메타데이터 필드 |
6 | "Tractor" | 텍스트 라벨 |
| 유형 | 설명 |
|---|
imageselect | 표준 이미지 선택 CAPTCHA |
tileselect | 타일 기반 동적 이미지 챌린지 |
dynamic | 선택 후 타일이 다시 로드되는 동적 챌린지 |
multicaptcha | 다단계 CAPTCHA 챌린지 |
audio | 오디오 기반 접근성 챌린지 |
nocaptcha | 체크박스 전용 검증 흐름 |
doscaptcha | 남용이 있는 경우 CAPTCHA 거부 |
| Field | Name | Description |
|---|
v | recaptchaVersion | reCAPTCHA 버전 |
c | challengeToken | /replaceimage 또는 이전 /userverify 요청에서 반환된 챌린지 상태 토큰 |
response | 선택된 타일 인덱스와 추가 상호작용 메타데이터를 포함하는 인코딩된 페이로드 | |
t | challengeElapsed | 챌린지가 처음 로드된 이후 경과된 시간 |
ct | completionTime | 사용자가 CAPTCHA를 풀고 제출하는 데 걸린 총 시간 |
ch | ocHash | 직렬화된 전체 oc 페이로드를 해시한 값, 샘플 |
oc | encryptedVmSignals | reCAPTCHA VM에서 수집된 암호화된 신호, 여기 참조. |
pm | telemetryPayload | VM 타이밍 델타, 리소스 타이밍, 수집된 브라우저 객체 통계, 웹사이트 스크립트 메타데이터를 포함하는 Base64 인코딩된 텔레메트리 구조 |