fbpx
  • Пример задачи взлома капчи за 15 минут с помощью машинного обучения

    really simple captcha

    А именно самого используемого в мире плагина для WordPress Really Simple Captcha.

    Капча (CAPTCHA) — надоедливая картинка с текстом, который надо ввести, чтобы попасть на сайт. Капчу придумали чтобы роботы не могли автоматически заполнять формы, и чтобы владельцы сайта были уверены, что пользователь — человек. С развитием машинного обучения и машинного зрения, ее зачастую стало довольно просто взломать.

    Я прочел отличную книгу: Deep Learning for Computer Vision with Python, которую написал Adrian Rosebrock. В этой книге Адриан описывает способ, которым он взломал капчу на сайте E-ZPass New York используя машинное обучение:

    account login form

    У Адриана не было доступа к исходному коду, генерирующему капчу. Чтобы обучить модель, ему пришлось скачать тысячи картинок и вручную разметить все надписи.

    Но предположим, что мы хотим взламывать капчу, к исходному коду которой у нас есть доступ.

    Я зашел на WordPress.org Plugin Registry и ввел в поиске “captcha”. Первый результат назывался “Really Simple CAPTCHA” и установлен более миллиона раз.

    really simple captcha

    И самое крутое — он с открытыми исходниками! Раз у нас есть алгоритм, генерирующий картинку, его, должно быть, не сложно взломать. Чтобы было сложнее, ограничим себе время. Можно ли уложиться в 15 минут? Давайте попробуем!

    Важное замечание: Это ни в коем случае не критика плагина “Really Simple CAPTCHA” или его автора. Сам автор указал, что плагин более не является надежным и рекомендует использовать что-нибудь другое. Это просто небольшой интересный челлендж. Если вы один из того миллиона пользователей, установивших его, то возможно вам стоит сменить данный плагин на что-нибудь другое 🙂

    Челлендж

    Для начала посмотрим на изображения, создаваемые Really Simple CAPTCHA. На демке видим это:

    demo

    Итак, капча состоит из четырех букв. Убедимся в этом, посмотрев исходники:

    	public function __construct() {
    		/* Characters available in images */
    		$this->chars = 'ABCDEFGHJKLMNPQRSTUVWXYZ23456789';
    
    		/* Length of a word in an image */
    		$this->char_length = 4;
    
    		/* Array of fonts. Randomly picked up per character */
    		$this->fonts = array(
    			dirname( __FILE__ ) . '/gentium/GenBkBasR.ttf',
    			dirname( __FILE__ ) . '/gentium/GenBkBasI.ttf',
    			dirname( __FILE__ ) . '/gentium/GenBkBasBI.ttf',
    			dirname( __FILE__ ) . '/gentium/GenBkBasB.ttf',
    		);

    Да, действительно капча создается случайным образом из четырех букв или цифр с разными шрифтами. Символы “O”, “0”, “I”, “1” не используются, чтобы пользователь не путался. Остается 32 символа, которые нужно распознать. Не проблема!

    Прошло: 2 минуты

    Наш набор инструментов

    Перед тем, как решать задачу, перечислим инструменты, которыми мы будем пользоваться:

    Python 3

    Python — простой и мощный язык программирования с отличными библиотеками для машинного обучения и машинного зрения.

    OpenCV

    Популярная библиотека для обработки изображений с поддержкой алгоритмов машинного зрения. Мы будем использовать ее для предварительной обработки капчи. OpenCV написана на C++, но все ее функции вызываются из питона, чем мы и будем пользоваться.

    Keras

    Фреймворк для машинного обучения, написанный на питоне. С его помощью легко строить, обучать и использовать нейронные сети. Требует минимального количества кода.

    TensorFlow

    Гугловская библиотека для машинного обучения. Мы будем пользоваться только Keras, но сам Keras в качестве бэкенда использует TensorFlow, поэтому его тоже придется установить.

    Итак, обратно к задаче!

    Создаем датасет

    Чтобы собрать любую модель машинного обучения, требуются обучающие данные. Нам понадобятся данные, выглядящие следующим образом:

    input-output

    Так как у нас есть исходный код плагина, создающего капчу, мы можем его использовать, чтобы нагенерировать 10,000 изображений, и для каждого будет известна расшифровка.

    Пару минут повозившись с кодом и добавив в него простой цикл ‘for’, я получил папку с обучающими данными — 10,000 PNG файлов, в названии которых указан правильный ответ:

    CAPTCHA

    В этом и только в этом месте я не дам вам рабочий пример кода. Мы делаем это в образовательных целях, и мне не хочется, чтобы вы заспамили реальные сайты, работающие на WordPress. Вместо этого, я дам ссылку на 10,000 изображений, сделанных мной, чтобы вы могли повторить мой результат.

    Прошло: 5 минут

    Упрощаем задачу

    Теперь, когда есть обучающие данные, мы могли бы их использовать напрямую, обучив по ним нейросеть:

    Deep CNN

    С достаточным количеством данных, этот подход мог бы даже сработать. Но можно сделать задачу гораздо проще. Чем проще задача, тем меньше нам понадобится обучающих данных и вычислительных ресурсов, чтобы ее решить. У нас ведь всего лишь 15 минут!

    К счастью, капча состоит всего из четырех символов. Если как-нибудь разделить ее так, чтобы каждая буква была отдельным изображением, то можно будет обучить нейросеть распознавать по одному символу за раз:

    Training CNN

    У меня нет времени разрезать каждую из 10,000 картинок в фотошопе. Это бы заняло несколько дней, а у меня осталось всего 10 минут. И нельзя просто автоматически разделить все картинки на 4 одинаковых куска. Потому что алгоритм задает символам случайное горизонтальное положение:

    random captcha

    Символы сдвинуты случайным образом, чтобы изображение было сложнее разделить.

    К счастью, это всё же можно автоматизировать. При обработке изображений часто приходится находить связные области из пикселей одного цвета. Границы таких областей называют контурами. В OpenCV есть встроенная функция findContours(), которую мы используем, чтобы найти связные области.

    Итак, начнем с изображения капчи:

    captcha image

    Преобразуем изображение в бинарное (это называется thresholding), чтобы было легче найти связные области:

    thresholding

    Далее, используем функцию findContours(), чтобы выделить связные группы пикселей, состоящие из одного цвета:

    findContours

    Теперь, нужно просто сохранить каждый прямоугольник как отдельное изображение. И, так как мы знаем последовательность символов в каждом изображении, можно подписать каждый прямоугольник своим символом при сохранении.

    Погодите-ка! Тут есть проблемка! Буквы в капче иногда накладываются друг на друга:

    problem
    И наш алгоритм отмечает их как одну:
    problem 2

    Если проблему не решить, то у нас будут плохие обучающие данные. Это нужно исправить, потому что с такими данными мы обучим модель распознавать эти две слившиеся буквы как одну.

    Простой выход из этой ситуации сказать, что если какой-то прямоугольник в ширину сильно больше, чем в высоту, то это нужно считать двумя буквами. В этом случае, прямоугольник можно просто разрезать посередине и считать, что это два прямоугольника:

    problem solving

    Мы разделим пополам все прямоугольники, ширина которых сильно превышает высоту, и будем считать их за две буквы. Способ слегка “химический”, но с этими капчами работает.

    Теперь, когда у нас есть способ извлекать из капчи отдельные буквы, давайте прогоним наш алгоритм через весь датасет. Цель — получить много вариантов написания для каждой буквы. Каждую букву можно сохранять в отдельную папку.

    Вот так выглядит моя папка с буквой “W” после того, как я запустил свой алгоритм:

    captcha W

    Некоторые буквы “W”, вытащенные из наших 10,000 изображений. У меня всего получилось 1,147 разных “W”.

    Прошло: 10 минут…

    Создаем и обучаем нейросеть

    Так как нам нужно всего лишь распознавать одну букву или цифру, нейросеть со сложной архитектурой не потребуется. Распознавание символов — это гораздо более простая задача, чем распознавание сложных изображений, к примеру, кошек и собак.

    Будем использовать простую сверточную нейросеть с двумя сверточными (convolutional) слоями и двумя полносвязными (dense):

    CNN Layers
    Задать архитектуру нейросети используя Keras можно всего в несколько строк:
    # Build the neural network!
    model = Sequential()
    
    # First convolutional layer with max pooling
    model.add(Conv2D(20, (5, 5), padding="same", input_shape=(20, 20, 1), activation="relu"))
    model.add(MaxPooling2D(pool_size=(2, 2), strides=(2, 2)))
    
    # Second convolutional layer with max pooling
    model.add(Conv2D(50, (5, 5), padding="same", activation="relu"))
    model.add(MaxPooling2D(pool_size=(2, 2), strides=(2, 2)))
    
    # Hidden layer with 500 nodes
    model.add(Flatten())
    model.add(Dense(500, activation="relu"))
    
    # Output layer with 32 nodes (one for each possible letter/number we predict)
    model.add(Dense(32, activation="softmax"))
    
    # Ask Keras to build the TensorFlow model behind the scenes
    model.compile(loss="categorical_crossentropy", optimizer="adam", metrics=["accuracy"])

    Теперь можно обучать нейросеть!

    # Train the neural network
    model.fit(X_train, Y_train, validation_data=(X_test, Y_test), batch_size=32, epochs=10, verbose=1)
    
    

    После 10 прогонов по всему обучающему набору, мы достигаем точности почти в 100%. В начале мы ставили задачу автоматически распознавать любую капчу. И мы это сделали!

    Прошло: 15 минут (фух!)

    Используем обученную модель для расшифровки капчи

    Теперь, когда нейросеть обучена, ее можно использовать для взлома капчи:

    1. Взять изображение капчи с сайта, который использует этот плагин.
    2. Разбить картинку на четыре части, на каждой из которых по одному символу.
    3. Прогнать каждую часть через нейросеть.
    4. Использовать выданные нейросетью буквы, чтобы решить капчу.
    5. PROFIT!

    Вот так выглядит расшифровка реальной капчи:

    real captcha

    А в командной строке вот так:

    command

    Попробовать самому!

    Если хотите попробовать сами, то возьмите код здесь. Он включает 10,000 картинок и код для каждого шага в этой статье. Инструкция к запуску в файле README.md.

    Но если хочется вникнуть и понять, что делает каждая строчка кода, советую купить книгу Deep Learning for Computer Vision with Python. Она содержит более детальное объяснение и кучу разобранных примеров. Это единственная книга из тех что я видел, в которой объясняется и как это работает, и как это использовать, чтобы решать реальные и сложные задачи. Рекомендую!

    Перевел Руслан Байназаров, оригинал — Adam Geitgey