Что мы знаем о капче? Капча — автоматизированный тест тьюринга, помогающий отсеивать подозрительные действия недобросовестных роботов от реальных людей. Но, к сожалению (или к счастью, смотря для кого), текстовая капча сильно устарела. Если еще 10 лет назад она была более-менее эффективным методом защиты от роботов, то сейчас ее может взломать любой желающий человек, более-менее разбирающийся в компьютере.  В данной статье-мануале я покажу, как создать собственную нейросеть по распознанию текстовых капч, имея под рукой домашний компьютер, базовые знания в python и немножко примеров капч. Автор данной статьи ни коим образом не призывает вас совершать какие-либо незаконные действия, используя материалы из данной статьи (да и с другими тоже не стоит) и не несет ответственности за вред, причененный ею. Цель данной статьи — наглядно показать распространённую проблему текстовых капч. Было время, когда я писал один скрипт по спаму (альтернативному методу продвижения — рассылке) вк. Скрипт должен был уметь обходить капчу через сторониие сервисы (решают 1000 капч за 10-20 руб). И я подумал, почему бы не сохранять уже решенные капчи у себя на сервере? Таким образом я бесплатно накопил 150к примеров решенных капч (правда 5-10% из них были решены неправильно, но бОльшая часть датасета всё-таки валидна). Сразу скажу, что я — человек не сильно смыслящий в машинном обучении, поэтому подправьте меня в комментариях, если что) Несмотря на то, что на дворе 2023 год, ничего более-менее адекватно работающего сходу я найти не смог. Большинство примеров предлагали перед распознаванием убрать шумы, разделить капчу ПОБУКВЕННО и перевести её в черно-белый формат (типо для облегчения работы). Но в моём случае были некоторые проблемы: Как убрать шумы, если при переводе в black and white линии сливаются с буквам Как разделить капчу побуквенно, если заранее неизвестно, сколько букв она в себе содержит В теории можно заморочиться с 1 и 2 пунктом, но терять неделю свободного времени ради такого занятия я был не готов, поэтому пошёл искать другие варианты. Так я нашёл Tensorflow keras OCR model — CNN + RNN модель, реализующую фукнцию ошибки через CTC loss. В ней необязательно вообще что-то делать с картинками — достаточно минимально их обработать ( transpose(perm=[1, 0, 2]) ) и всё. Дальше модель разберётся сама. Для ворчунов-профессионалов, которые скажут, что без грамотной обработки данных результат получится хуже, отвечаю: да, он получится хуже. Но он получится. И заработает. А это главное. В распознавании капч не нужна 100% вероятность, так что зачем сильно морочиться с preprocessing, если можно не париться?) Для начала необходимо понять, какие вообще символы встречаются у нас в капче. Для этого я написал простенький скрипт Output:  Эти действия сразу помогают "почистить" примеры: видно, что буквы b, f, g, i, j, l, o, r, t  заведомо ошибочные и что капч, состоящих из 3 и 8 символов, не существует. Отлично, с символами определились  Перед дальнейшими действиями разобьем датасет с картинками на 2 папки: train и test. Папка test будет использоваться для тестирования готовой модели, train — для обучения (в процессе обучения датасет случайным образом разбивается на train и validate). Я разбил в пропорциях 1/15 ( у меня примеров много, поэтому 10к для  теста будет достаточно; если у Вас датасет меньше, чем 20к, то стоит  разбивать хотя бы 2/8 ) Теперь надо сделать преобработку картинки. В модели будет использоваться RGB float32 input: И запихать это всё в 2 датасета: один для обучения, другой — для проверки работоспособности модели (в процессе обучения)  Пришло время для самой модели. Она будет состоять из 2х блоков Conv2D, RNN, (output), CTC loss Layer (для ошибки)  Добавляем защиту от переобучения  Обучалась модель у меня на процессоре (AMD RYZEN 5 3600) ~ 10 часов, на видеокарте (GTX 1070 TI) ~ 3 часа. Если вдруг захотите обучать на видеокарте, то будьте готовы потратить 2-3 часа на уставку CUDA, tensorflow-GPU, и кучу другого гемороя (например, conda, — без неё на windows с обучением всё очень грустно) Для тестирования нужно прогнать нашу модель через датасет, заранее оставленный в папочке test.  Подгружаем нашу модель Загружаем тестовый датасет Визуализация (по желанию): И, соответственно, тесты:  У модель выдавала правильный ответ с вероятностью 94.05%. В целом, можно сказать, что модель готова. НО! Тут, как всегда, не без сюрпризов. А сюрпризы в данном случае: 2ГБ библиотеки Tensorflow, 10-15 секунд на её import, долгое время работы и т.п. В общем, после длительных поисков, было решено переходить на формат onnx. А что, стильно, модно, молодёжно, подумал я. Но, естественно, с 1-го раза ничего не заработало Сначала я попытался использовать keras2onnx, но он у меня не заработал. Погуглив ошибку, я пришёл к выводу, что во всём виновата windows (о великие линукса), перешел на виртуалку, попробовал на ней и.... Опять ничего не заработало (после 4х часов попыток заставить что-либо двигаться). Далее на очередь пришёл tf2onnx. Но заработал он у меня только с 3-го раза: в первый раз, после установки, оказалось, что python3.10 не поддерживается, затем, на 2й раз — что я обучал модель на слишком новой версии tensorflow, которую tf2onnx пока не поддерживает (вот зараза).  В результате у меня-таки получилось всё провернуть со следующими версиями: Ну и сам скрипт конвертации: UPDATE: вроде как сейчас уже есть поддержка python3.10, но проверять я это не хочу) Теперь, после конвертации в ONNX, возникла необходимость переписать препроцессинг картинок с tensorflow на великий numpy (весит меньше, работает шустрее). В случае препроцессинга это не составило особого труда: Тестируем и... Процент верных ответов снижается на 10%. Это стало очень неприятно, и, как оказалось, всё дело в методе преобрабоки картинки. Существует много методов декодирования JPEG картинок, и, естественно, tensorflow и numpy по умолчанию используют разные. Чтобы они были наиболее cхожи, стоит указать параметр dct_method='INTEGER_ACCURATE'.  img = tf.io.decode_jpeg(img, channels=3, dct_method='INTEGER_ACCURATE') Немного официальной документации: dct_method - String specifying a hint about the algorithm used for decompression.  Defaults to "" which maps to a system-specific default. Currently valid values are .  The hint may be ignored (e.g., the internal jpeg library changes to a version that does not have that specific option.)  Не повторяйте моих ошибок :) А вот в случае декодирования результа пришлось вникнуть в Beam Search Decoding for ctc alorithm. Если вкратце, то на выход мы получаем 2-мерный массив длиной в 32 элемента, где каждый элемент — массив вероятностей на этом месте каждой буквы. Код декодирования результата:  Настало время финальной проверки: необходимо удостовериться, что мы всё сделали правильно. То есть результат работы ONNX модели не должен сильно отличаться от оригинальной. Также необходимо прогнать тесты на "реальных" капчах (генерируемых сервером) и посмотреть, насколько наша модель с ними справляется (а то вдруг всё было зря😄) Загружаем модель: Переделываем функцию для решения капчи из файла А дальше есть 2 варианта.  Прогнать тесты на реальных капчах и вывести их через matplotlib.pyplotи вручную проанализировать результаты: Прогнать модель заново на тестовых данных и посмотреть, не "испортилась" ли она в процессе перевода в другой формат, а также проанализировать ошибки, которые она делает. Функция анализа ошибок: Ну и проход по тестовым данным: В моём случае я получил примерно такую картину: Из этих данных видно, что модель довольно часто путает "e" с "c", "h" с "n", "z" c "2", "v" c "y". Это стандартная ситуация для моделей, распознающих текст, — эти символы и люди часто не могут до конца распознать (а у нас, извините, нечетаемая капча).  Также стоит упомянуть один интересную фичу, которой можно воспользоваться при решении текстовых капч: если модель выдает плохую уверенность в ответе (например, ниже чем 0.2), то довольно часто можно "забить" на решение данной капчи и просто запросить следующую (в случае ВК и запрашивать не надо: просто заново скачиваем картинку по той же ссылке, и сервер для нас любезно сгенерировует новую "задачку").То есть "умная" функция будет решать капчу до тех пор (естественно, можно задать макс. количество раз), пока вероятность успешного ответа меньше, чем нужная нам. Почти. Осталось написать библиотеку-обёрту для получившейся модели.   Установка библиотеки: Ну и использование: С помощью библиотеки vk_api "Ручками" Ну и куда в текущих реалиях без всемилюбимого async  Если, прочитав эту статью, вы задались вопросами по типу: «Ради чего это всё?», «Ради 20 рублей за 1000 капч?», то хочу вас "обеспокоить", всё это было не зря. Мы будем взламывать вк. Дисклеймер №2: Все последующие действия являются чисто теоретическими. Автор статьи никого не взламывал и не совершал никаких других противозаконных действий; он всего лишь "придумал" теоретическую возможность по обходу защитного функционала VK.  Автор также не советует (и ни в коем случае не побуждает) вам совершать никаких противозаконных действий с кодом ниже: помните про уголовный кодекс. Наверное многих школьников, решивших взломать вк, посещала мысь: "а давайте просто переберем пароли, и взломаем одноклассни[цу]?". Но вся реализация упиралась в неё — капчу, выскакивающую после 3-5 запросов, которую VK добросовестно начинал показывать молодым "злоумышленникам". Но теперь обойти 1 экземпляр этой защиты стоит нам всего 2мс, так что почему бы не провести "исследование" о том, насколько реально взламывать странички брутфорсом в 21 веке. Кстати, VK до недавнего времени даже не чекал тайминги (да и вроде сейчас не всегда их проверяет): можно было решить капчу за 0.1 секунду, и, если она была решена правильно, нам честно давали ошибку, что пароль неправильный (или правильный). Итак, первая идея, которая пришла мне на ум: «давайте, для получения информации, воспользуемся ОФИЦИАЛЬНЫМ API VK». Cмысл примерно такой: ищем аккаунты с публично указанным номером телефона и используем его как логин. Далее на основе других публично-личных данных (имени, фамилии, даты рождения, тех же данных их родственников/2х половинок) будем генерировать базу паролей для каждого конкретного пользователя За основу взят этот скрипт Вот пример того, что генерирует скрипт (все совпадения случайны, это просто пример)  Таким образом были написаны несколько скриптов (все ссылки внизу статьи — она и так уже очень засорена), реализуещих эту идею, и вот их примерные результаты: Скорость перебора достигала 60 паролей в секунду за 12 часов ночной работы на моём домашнем компьютере (интернет=100мбит) с использованием 15 прокси были "намайнены" 130 валидных аккаунтов (и ещё в районе 300 со включенной двухфакторкой). Естественно потом я предупредил их владельцев о том, что их пароль слишком слабый, и удалил все незаконно полученные данные. Примерно у 5% пользователей ВКонтакте указан номер телефона и примерно у 3% из них пароль содержится в наборе из 2000 сгенерированных символов (наиболее популярна комбинация [Nn]ame[bdate][specsymbol] - например, «Пупкин2003», или «masha1!» И да, я думал насчёт того, чтобы написать в BugBounty, но... Во-первых, у меня уже был негативный опыт, а, во-вторых, о чём писать? О том, что у вас плохая капча? А брутфорс аккаунтов является невполнезаконным, так что я забросил эту идею (меня пару раз с похожим уже посылали). Текстовая капча не только не спасает от роботов, но и губит систему, если она является единственной защитой от недобросовестных роботов. Также неожиданностью стало то, что такой IT-гигант, как ВКонтакте, доверяет защиту от брутфорса исключительно своей «мегакапче» (хотя, ради честности, стоит сказать, что она одна из самых сложных, из ранее мне встречавшихся). Также, по моему мнению, стоит ввести более жесткие положения для паролей — хотя бы условие несодержания имени/фамилии и даты рождения (правда 50% паролей пойдут в мусорку, но им туда и дорога😁). Если у вас есть свой проект, в котором используется текстовая капча, пожалуйста, поймите, что это более НЕ ЯВЛЯЕТСЯ защитой КАК ТАКОВОЙ. Позаботьтесь о безопасности сейчас, а то потом может уже быть слишком поздно. Ну и, конечно, не используйте никакие публичные данные в своих паролях: сильному паролю — капча, не капча — всё нипочём! Ссылки на source code: https://github.com/imartemy1524/AITextCaptcha - Скрипты для обучения модели. https://github.com/imartemy1524/vk_captcha/tree/main/VkHacker - Скрипты для брута аккаутов. https://github.com/imartemy1524/vk_captcha - source код библиотеки vk_captcha https://mega.nz/folder/H4kHDA6b#s2ZHPAnKcfwdgnhSByRGow - архив с 150к капчами ВК. Если Вам понравилась эта статья, Вы можете отблагодарить меня за проделанный труд материально по криптоадресам ниже: BTC: bc1qc37ytj9ygycqhmt7dfeg0re5fekv0te7hk2hks BCH: bitcoincash:qzffexvr8gw886gnrcfw9xemrhqr8zyg4s3zjvplg3 TRX/USDT|USDC(TRC20): TS6xHd68gi6vP2KauV7g8uhs4yAhmJK6PY LTC: Li12vQRf2bRfwP1Cg4ZaxrPK9ae36g4ARo ETH/usdt/usdc(ERC20): 0xD43F9388a0E6607D6D1eB22Dda819e1D40F1949c