
Документация и обратная разработка reCAPTCHA
Этот репозиторий содержит технический анализ антибота Google (reCAPTCHA), сфокусированный на:
@g_recaptcha@lyxlobyxreCAPTCHA — одна из антибот-систем с наиболее изощрёнными техниками обфускации, применяющая ряд преобразований, которые делают код менее читаемым и усложняют обратную разработку. Большинство обфускаций легко обрабатываются с помощью абстрактного синтаксического дерева (AST), но некоторые выполняются во время выполнения, делая AST в этом случае бесполезным. К коду также применяется полиморфизм, изменяющий его структуру в каждой версии скрипта. Например, код не выполняет действие напрямую. Вместо этого используются объекты или функции, меняющие форму.
Последовательные выражения (Sequence Expressions)
Код уплощается путём преобразования каждого блока инструкций в непрерывное выражение, разделённое запятыми; может встречаться в операторах 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"] присвоено внутри последовательного выражения
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];
// блок 1
if ((N - 2 ^ 14) < N && (N - C[2] | 28) >= N) {
// логика преобразования значения в строку
}
// блок 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 на короткое замыкание логических операторов, превращая поток управления в выражения. В сочетании с последовательными выражениями множество ветвей выглядят как одно непрерывное выражение, разделённое запятыми.
// if (a) { block }
a && (block)
// if (!a) { block }
a || (block)
// if (a) { x } else { y }
a ? x : y
// в сочетании с CFF и последовательными выражениями:
(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) Преобразует каждую часть кода (объявления и циклы) в плоский конечный автомат. Скрывает исходную логику выполнения, направляя все блоки кода через центральный блок-диспетчер.
Диспетчеры могут менять форму; некоторые имеют 2-3 переменных состояния, а тип цикла/условия меняется. Выглядят они так:

Это CFF с двумя переменными состояния: одна управляет блоком catch, другая — блоком try.
Зашифрованный пул строк (Encrypted String Pool) Все строковые литералы (DOM API, свойства браузера, CSS-значения, сообщения об ошибках и т.д.) зашифрованы в одном огромном пуле строк. Функция дешифрования использует seed и XOR-шифр на основе LCG для извлечения каждой строки во время выполнения.
По всему коду разбросано более 1990 мест вызова; функция дешифрования использует нарастающий ключ, накапливающий декодированные кодовые точки, что делает каждый символ зависимым от всех предыдущих.
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 с нарастающим ключом
b += String.fromCodePoint(J),
F += J; // накопление ключа
return G = b;
}
// места вызова передают seed для поиска и дешифровки каждой строки
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 для любых поздних чтений.
// последовательные вызовы возвращают разные значения:
c() // → window
c() // → document.body
c() // → 123
c() // → null (тайм-аут истёк)
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, но здесь индекс получаемой функции вычисляется во время выполнения на основе seed с использованием XOR и Modulus.
c = ((Q ^ no | U[1]) >> 5) + no
A = mN[(c % U[2] + U[2]) % U[2]] // mN — таблица функций (50+ функций)
q[29](5, 6977) // seed=6977 → индекс разрешается в функцию mN[X]
q[29](53, 6187) // seed=6187 → другой индекс, другая функция
