
Documentación e ingeniería inversa de reCAPTCHA
Este repositorio contiene un análisis técnico del antibot de Google (reCAPTCHA) centrado en:
@g_recaptcha@lyxlobyxreCAPTCHA es uno de los sistemas antibot con las técnicas de ofuscación más sofisticadas, ya que emplea una serie de transformaciones que hacen que el código sea menos legible y más difícil de aplicar ingeniería inversa. La mayoría de las ofuscaciones pueden manipularse fácilmente utilizando el Árbol de Sintaxis Abstracta (AST), pero algunas se procesan en tiempo de ejecución, lo que hace que el AST sea inútil en ese caso. También se aplica polimorfismo al código para cambiar su estructura en cada versión del script. Por ejemplo, el código no realiza la acción directamente. En su lugar, utiliza objetos o funciones que cambian de forma
Expresiones de secuencia
El código se aplana convirtiendo cada sentencia de bloque en una expresión continua separada por comas; puede aparecer en sentencias if, argumentos de funciones e incluso dentro de objetos

Aritmética mixta booleana
Entrelaza operaciones aritméticas (suma, resta, multiplicación) con operaciones bit a bit (AND, OR, XOR, NOT) para ocultar la lógica original, por ejemplo -2 * ~(h & H) + -2 + (h ^ H)
Tabla indirecta de funciones
Cada función se construye dentro de una tabla y se llama mediante su índice, como functions[index](https://github.com/elyelysiox/recaptcha/blob/main/args)
Array constante en línea Se asigna un literal de array local en línea, en mitad de una expresión; el array agrupa constantes (números, cadenas) que se reutilizan en todo el cuerpo de la función mediante acceso por índice
// 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
}
Multiplexación de funciones Múltiples funciones lógicamente distintas se fusionan en una sola, utilizando un parámetro numérico como selector de bloque. El bloque activo se determina evaluando el parámetro con condiciones bit a bit. Los llamadores pasan un literal numérico como selector
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;
}
Ramificación con operadores lógicos Reemplaza las sentencias if y los bloques if/else con la evaluación de cortocircuito de operadores lógicos, convirtiendo el flujo de control en expresiones. Combinado con expresiones de secuencia, múltiples ramas aparecen como una única expresión continua separada por comas
// 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), ...)),
Constantes de métodos nativos vinculados Vincula métodos nativos del navegador a sus receptores originales, almacenándolos como constantes para evitar su manipulación
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)
Código muerto Se inyectan bloques de código inaccesibles o sin uso a lo largo del archivo, aumentando su tamaño a más de 60 000 líneas; esto dificulta el análisis estático y la ingeniería inversa basada en LLM
Aplanamiento del flujo de control Transforma cada parte del código (declaraciones y bucles) en una máquina de estados plana. Oculta la lógica de ejecución original enrutando todos los bloques de código a través de un bloque central «dispatcher»
Los dispatchers pueden cambiar de forma; algunos tienen 2 o 3 variables de estado y el tipo de bucle/condición cambia. Tienen este aspecto

Este es un CFF con 2 variables de estado: una gestiona el bloque catch y la otra el bloque try.
Grupo de cadenas cifradas Todos los literales de cadena (APIs DOM, propiedades del navegador, valores CSS, mensajes de error, etc.) se cifran en un único grupo de cadenas masivo. Una función de descifrado utiliza una semilla y un cifrado XOR basado en LCG para extraer cada cadena en tiempo de ejecución
Hay más de 1990 puntos de llamada repartidos por el código; la función de descifrado utiliza una clave continua que acumula los puntos de código descifrados, haciendo que cada carácter dependa de todos los anteriores
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"

Iterador de valores con estado reCAPTCHA utiliza una función con estado que devuelve una secuencia de objetos y valores de tiempo de ejecución (como window, document.body, constantes numéricas) en un orden fijo; cada llamada avanza un cursor interno, y llamarla fuera de secuencia o demasiadas veces corrompe todas las lecturas posteriores. Un mecanismo de tiempo de espera invalida el estado tras un intervalo fijo, devolviendo null para cualquier lectura tardía
// 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
Tabla de funciones calculadas
Esto es similar a Indirect Function Table, pero aquí el índice de la función a obtener se calcula en tiempo de ejecución con una semilla, usando XOR y módulo
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
