Yandex smart captcha на заросеках
Введение
Вбивал я тихонько дорки в яд, никого не трогал. И тут хуяк.. Капча!
Капчи средь бело дня это ужос.
И как же меня она уже заебала.. Я часто юзаю яд в инкогнито, а там она вылезает постоянно.
Я уже несколько раз разбирал Яндекс капчу на канале, но в последний не затащил. И вот почему:
Сначала у яда был просто фингер и xor по ключу, который прилетал с сервера.
Позднее они добавили обфускацию, ключевое слово уже генерилось каким-то гига массивом в кучей сдвигов. Тоесть хуй поймешь как оно кастует ключ. Только вот массив при каждой загрузке кода капчи был разный.
Довольно долго ручками пытался деобфусцировать то, что по факту, как оказалось, является динамической хуйней (примерно как у твиттера).
Тоесть шифрование (енкод) в каждом файле новое, новые цифры в циклах, как и их количество. Забегая вперед скажу, что итогу я порешал, его нужно целиком из файла пиздить, но об этом чуточку позже)
С появлением сурсов самой капчи (взлома яндекса), имея открытый список большей части проверок, выебать их теперь не составит большого труда. Похуярили!
Подготовка
Проведем поверхностный прочек, чтобы просто понимать чё куда.
В запросе много всяких параметров, но самый интересный, имхо, фингер. Поэтому разберемся с ним.
Как я уже сказал, сурсы яда были слиты.
Чего там есть интересного?
Ну например то, как устроен антифрод на этой капче. С всякими схемами и пояснениями, всей внутрянкой и документацией. В чатике мы это когда-то уже обсуждали.
Даже код генерации текстовой капчи был, со списком блек слов.
Сурсы капчи
Но меня больше интересовали сурсы Yandex SmartCaptcha, потому перейдем сразу к ним. И как же дохуя там всяких папок, версий, инструкций..
Берем просто какую-то демку и чекаем с небольшим допилом..
Инициатор сбора одна из этих функций. Делают они примерно одно и то же.
Судя по сурсам, у нас просто есть куча функций фингера. При вызове мы дергаем каждую и результаты сейвим в жсончег. Подход довольно базированный. i6 я докинул от себя, про это будет в конце.
А вот и сами функции под каждый параметр фингера. Зная что собирают, не трудно потом это подменить.
Без обфускаиции все так хорошо читается. Удобно иметь ориг сурсы)
На самом деле, как таковой необходимости в сурсах прям небыло, это чисто интерес почитать как оно там в проде. По факту, тут те же самые сурсы, что и раньше на дзене или яндексе, прост обфускации докинули и новый енкод, что меняет примерно ничего. Сурсы это просто приятный бонус.
Продакшн
Раз все примерно ясно, потыкаем её в проде, хоть и на демо страничке.
В проде они вызывают сбор фингера через window.PGreed.safeGet()
С капчей на поиске всё также. После получения значения мы его ксорим с миксованным массивом. В целом, с фингером все ясно, нужно только придумать че делать с этим массивом ебучим.
Picasso (canvas)
Хорошо, чё делать теперь примерно ясно. Будем собирать по чуть-чуть скрипт для решения. А начнем с чего-то простого, с хедера picasso.
Енкод у всех заголовков обычный base64, кроме фингера. И по названию параметров не сложно найти сами функции и докинуть брек. Единственное, входной параметр тут какой-то seed.
Оказалось, це простой рандомчег. Тоест 2 рандом числа от 1 до 1000.
А потом на оба массива вызывают асинхронную функцию для рисования канваса (как не трудно заметить). Функция на самом деле довольно небольшая и относительно простая. Удобно, что в этом файле 0 обфускации.
Мне показалось, что я увидел что-то знакомое, поэтому я просто вбил в поиск по гиту.
Да, это же мой любимый мур мур хэш) Тот самый хэш, который юзается в популярной либе сбора фингера fingerprintjs, а потому есть на куче сайтов и в множестве всяких защит (фанкапча, яху и тд).
Если внимательно сравнить, то коду буквально оно.
На гите была ровно та же хуйня с подписью рандом. Так шо еще раз Github +1.
Ещё были функции: So, xo, ko, Oo. Они рисуют на канвасе, но мне уже как-то похуй, просто копировать вставить и фсе, канвас же.
Акей, получаем вот такой код. Зачекаем его валид.
Хешик в моём коде. Ну и они совпадают, что не удивительно. Значит, эту штуку я разъебал.
Вообще, рисуют они вот такую ебень, если кому интересно.
Если представить, что уник канвасов в мире 156 078, отталкиваясь от canvas, получается, им нужно хранить 156 078 000 канвасов на 1 фильтр, а фильтра у них 2 (2 разных настройки рисовалки канваса). Вряд ли они столько хранят.
Закономерно предположить, что можно было бы пихать и рандом, но мне показалось удобным просто сделать по 1к канвасиков (на каждый фильтр) на свое железо и тянуть рандомно.
По итогу это выглядит вот так.
Proof Of Work
Перейдем к следующей хуйне, к pov pow.
Как обычно, имеем powPrefix и powComplexity. Тоесть ключ и сложность.
Они передаются в отдельную функцию.
Ну оки, дернем че за код и зачекаем его.
Применим убийственный примём из серии серьезных - серьезный поиск по гитхабу.
Это буквально, самый дефолтный пов. В текстовик я закинул отформатированный код воркера. Код совпадает с решалкой в сурсах с гита.
Ну типа ставим либу и фсе. Отличия в том, что яндекс к коду либы пару строк от себя наговнял для удобства.
В итоге я форматнул под вид яда. Ну и этот хедер тож выебан, получается.
Новая капча
В процессе тестов, на демо страничке столкнулся с новой капчей. Я её чуть потыкал, в целом разобрался как оно работает, но решил забить, тк кроме демки её ни где больше нету.
Ладно, пока я писал эту хуйню, она появилась на яндексе.
Rdata (fingerprint)
И так, а теперь всё-таки разберется с самым интересным, с фингером, в котором хуева туча мелких параметров.
У нас есть динамический кусок хуйни для енкода.
Тоесть идет сбор фингера и его енкод.
Если спиздить енкод, шифрануть самому, то будет такая же хуйня. Ладно, надо подумать как фул код енкода подрезать.
Также, в фингере помимо обычных есть еще парочку значений:
pgrdt - тайм штамп времени сбора фингера
pgrd всегда есть где-то в файле. Над будет придумать как его спиздить.
Я решил сравнить фингер собранный с демки и "самодельный".
Отличается только одна хуйня функция i6.
По имени зачекаем чего она делает.
Видим сам код. Else никогда не сработает, поэтому сразу чекаем наши 3 строчки.
Если потыкать чуть ручками код, становится очевидно, что он чекает Performance. memory. Видимо, просто проверяет наличие этих функций.
Штош, тогда обновим код, а потом сравним значения фингера с сайта и с сборщика.
Тк мне лень глазками все это чекать, добавил вывод тех что отличаются.
Подчеркнул, чтоб было проще ориентироваться.
Старый скрипт яда скипает d1 и c9, новый отсутствие значения сейвит как null.
a8 пихает в боди рекламный блок. Думаю, похуй на а8)
c7 чекает возможность установки куки. Тк у меня не сервер, ясен хуй оно не работает.
x,y чисто ошибки таймаута в асинхроне, скорее всего из за бреков, тк фингер с сайта стянул криво.
В фингере есть 2 хэша: z1 и z2.
z1 хэш от шрифтов, в проде совпадает с моим.
А вот z2 мой ему поему-то не нравился. Ну а это какбэ канвас.
Чуть потыкав код я понял, что наебался вот с этой строкой.
В норм виде тут смайлики должны быть.
Но в файле ведь у меня все правильно было.. А не работало потому, что я не указал кодировку у своей демо html странички. Кароч, фикс и все воркает как надо.
С фингером в целом разобрались, ебошим дальше.
Капча
Для капчи у нас целых 3 заголовка:
tdata - инфа о кликах по экрану относительно старт тайминга и позиции относительно фул экрана. Кароч, чисто для статки инфа. pointerPositions и clickPositions на мобилке одинаковые.
Не сложно заметить, что rep действительно инфа об ответах.
А тут видно, как формируется статка по кликам.
Сравним первый поинт и ответ. Разница 28. Шаг от края экрана до картинки у нас тоже 28.
Ну раз тут все ясно, можно накидать простой код для генерации всей даты. Я кастую от момента запуска генератора до минус сколько то секунд (на каждый клик), а потом разворачиваю тайминги типо я гадал в риалтайм.
Помимо очевидного, тут используется ахуительная функция, чтобы слить массивы в 1, упомянутая ранее в материале про перевочики. Array.prototype.slice.call(arguments)
Итог
Фингер
Сначала порешаем до конца с фингером. Я искал наиболее простой путь, поэтому мне не хотелось писать деобфускацию.
В процессе заметил, что по дефолту у скрипта все функции сбора фингера сейвятся в глобальную переменную.
Поэтому я решил просто взять и запихать туда фингер с функцией оберткой, которая превратит ключи в функции. И это сработало в браузере, а потом и в бас ноде.
В итоге получился вот такой код, который полностью работал.
Капча
Дальше я пытался сделать решалку под яд, по аналогии с гитестом, но выходило говнище, ебучие иконки хуй выдернешь.
Фильтр по белому помогал только в 10% случаев и в итоге валид решение получалось примерно 1 из 100.
Поэтому я сделал форматирование картинки и выбрал сервис с ручными индусами.
Не так конечно круто, как без сервисов, но зато решалось.
Соберем все вместе
Через eval сейвим данные капчи в переменную.
Так как у яда на самом деле просто при клике на иконку тоже шлет фул данные (установка чекбокса тоже типо капча), добавляю проверку нужно ли решать капчу. Тоесть pow в любом случае нам надо будет проходить 2 раза.
И так, если капча есть, решаем её и чекаем, что мы его точно получили.
Независимо от капчи шифруем фингер, кастуем pow и picasso. Если капчи небыло, то шлем пустой tdata, как это было и у яд в запросах.
Если капча была решена, шлем запрос с её хэдерами.
В случае верного решения также вертаем куку spravka.
Дополнение (Новая капча)
Лан, я решил её добить, тк получилось всё слишком легко, но капча не без сервисов. Напоминаю https://captcha-api.yandex.ru/demo
И так, не трудно заметить, что в качестве данных решения всегда передается положение.
Ну, это и так на самом деле видно. Крайнее правое положение 13, а начальное 0.
При каждом движении слайдера капча перерисовывается и надо бы найти как и почему она это делает. Заранее стоит пояснить, что капча прилетает вот в таком виде. Судя по всему, таск это порядок перемешивания картинок в каком-то уебанском виде.
Ну акей, я поставил брек на модификации поддерева, чуть потыкал и попал сюды.
Чуть ниже мы видим код перерисовки. Синее - сама перерисовка. А вот зеленое, то что нам нужно, это выполнение функции v, начало которой я подчеркнул красной линией. В неё мы передаем наш массив task (параметр t) и шаг на слайдере, чтобы получить порядок картинок в норм виде, нарезать их и перемиксовать. Параметр e в функции это просто массив размера 9 элементов (3 на 3), тоесть 0-8. Выглядит непривычно, но на деле код не страшный.
Для красоты я поставил логпоинт и в процессе двигания слайда было видно как оно просчитывает переменную v. Правильным решением было 4. Как видно по решенной капче, кусочки стоят в порядке, соответствующем цифрам кусочков с ориг капчи.
Я спиздил функцию, убрал чуть мусора, который нихуя не делал, и получил тот же результат, тот же порядок при указанной позиции слайдера.
Хорошо, раз с этим ясно, нужно разобраться с самим решением. Для начала просто нарежем капчу на кусочки.
Смешная нарезка яндыкса работаит.
Если разрезать картинку на 2 части, то пиксельная линия в месте разреза на обоих картинках будет иметь на 99% схожий рисунок. Как уже говорил в материале по тиктоку, я подсмотрел эту идею когда-то туть. Картинок у нас много, разрезов может быть 4, поэтому докинем к прошлому скрипту пару блоков. По итогу нарезки теперь у нас будет 9 массивов в каждом по 4 картинки (края стороны).
Ну а теперь их нужно как-то сравнить. Очевидно, что верх одной мы можем соединить только с низом другой и наоборот, также и со сторонами. Код я взял из материала по iconcaptcha, чуть перенес циклы и добавил выходы из цикла в соответствии с условиями выше.
Потом чуть допилил код, добавив кастом порог для приемлемой степени совпадения.
Акей, оно решает и получает ровно то что я ожидаю. Теперь у нас есть каждый кусочек пазла и мы знаем его соседей. Нужно понять как превратить это в собранный пазл и желательно без хардкода.
Я пришел к следующей мысли: берем и дергаем левый верхний угол и идем от него сначала влево получая каждый элемент, а потом от него же спускаемся вниз и опять собираем все левые, так повторяем до конца.
А потом закодил это. Вроде всё работает как надо, результат тот же.
Так это выглядит по итогу в бас
Остается только найти на какой позиции слайдера при входных данных будет давать такой порядок. Ну эт изи, грубый перебор, всего ведь 12 вариантов положения. Изе. Не забываем учесть проёб если оно какого-то хуя не нашло решения.
Для удобства я также сделал рисовалку. Делал на основе нарезки, докинул только перестановку по небольшому набору правил.
Сделал скрипт аналогичный тому что в прошлой капче. Тут разве что picasso отсутствует.
Ну и запрос типа text/plain;charset=UTF-8.
Поэтому енкод тела сделать пришлось лапками.
Решалку перенес из тестового скрипта, заодно пару мелочей пофиксил, чтобы ошибки давало если не решило.
На тестах всё ебошит по красоте.
Забавно, что у яда картинки повторяются. Зная этот факт можно сделать решалку сильно пизже по качеству. Тоесть ищем фон по базе, потом режем его и чекаем пару.
Да, звучит неплохо, мож когда-то сделаю(нет).
Есть основания полагать, что яд юзает нейронку. Нинаю, це байт на классы или чисто прикол, при том что фоны повторяются..
В целом, на 100 тестах качество решалки картинок вышло примерно 80%. Результат неплохой, но если есть база картинок, будет точно сильно лучше.
Эта капча была уже чуток интереснее. Но у неё даже обфускации небыло.