Что такое Geetest CAPTCHA?

В сегодняшнюю цифровую эпоху около 40% сетевого трафика осуществляется ботами. Обычно нам нужен только человеческий доступ к нашим приложениям. Например, мы хотим запретить использование билетных ботов на популярные концерты. Вот почему в игру вступает CAPTCHA (полностью автоматизированный общедоступный тест Тьюринга для различения компьютеров и людей), мы используем его, чтобы отличать человека от ботов и, таким образом, блокировать ботов.

Традиционные CAPTCHA обычно просят вас распознать текст на изображении напечатанного текста при некоторых деформациях формы.

С развитием технологий больше ботов смогут решать эти CAPTCHA. Следовательно, некоторые компании разрабатывают более сложные CAPTCHA, чтобы бросить вызов ботам, Geetest - одна из них. CAPTCHA Geetest определяет, являетесь ли вы человеком, по вашему ответу и по тому, как вы отвечаете на CAPTCHA (например, человек не может ответить на тест менее чем за 0,01 секунды).

CAPTCHA Фон

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

В CAPTCHA есть несколько значков. Они могут выглядеть так, как просит вас выбрать CAPTCHA, или нет. Эти значки нужно выбирать в порядке, указанном CAPTCHA. Кроме того, процесс решения должен быть похож на человеческое, решающее CAPTCHA, например если вы можете выполнить CAPTCHA менее чем за 0,1 секунды, вы не сможете выполнить CAPTCHA.

Вот CAPTCHA Geetest, которую я могу взломать с помощью технологий обработки изображений и автоматизации. В следующих разделах я расскажу, как я пытаюсь взломать CAPTCHA.

Примечание: English CAPTCHA была доступна ранее, но сейчас недоступна. Однако китайская версия этого вида CAPTCHA все еще доступна, поэтому я буду использовать китайскую CAPTCHA в демонстрационном разделе ниже.

Стек технологий

  1. Селен (заставить нашего бота вести себя как человек, нажимая значки в CAPTCHA)
  2. OpenCV (наш бот использует его для решения CAPTCHA посредством обработки изображений и обнаружения объектов)
  3. Другие библиотеки (matplotlib, urllib, numpy, scipy, skimage, time, Pillow)

Терминология

Чтобы объяснить приведенные ниже шаги решения, мы будем использовать несколько терминов для описания различных частей CAPTCHA.

  1. Основная панель - область, содержащая несколько значков и фон.
  2. Цели - что мы найдем на главной панели
  3. Панель целей - область, содержащая все цели.

Шаги решения CAPTCHA

Мы возьмем эту капчу в качестве примера.

1. Загрузите CAPTCHA.

Чтобы загрузить CAPTCHA, нам просто нужно загрузить основную панель, так как она также содержит цели внизу. Основная панель относится к классу geetest_item_img, вы можете найти ее в консоли отладки браузера.

2. Удалите фон на главной панели.

Удаление фона может помочь нам повысить точность, поскольку бот может распознавать некоторые части фона как значок, которые являются ложными срабатываниями.

Для удаления фона есть 2 шага.

Преобразуйте CAPTCHA в изображение в градациях серого. Это может снизить сложность вычислений в 3 раза, так как общее количество пикселей в изображении шкалы серого составляет одну треть от количества пикселей в исходной CAPTCHA, состоящей из 3 каналов (красный, зеленый и синий). Кроме того, это упрощает удаление фона, нам просто нужно определить порог для изображения в градациях серого вместо всего 3 пороговых значений для красного, зеленого и синего каналов.

img_grey = cv2.imread(image_path,0) # image_path is the path where the CAPTCHA stored

Установите порог для удаления пикселей в main_pane, значение которых превышает это значение, поскольку эти пиксели составляют фон

# crop image
main_pane = img_grey[:350,:] # Extract main_pane from CAPTCHA
color_threshold = 180 # threshold is hard-coded
main_pane = cv2.blur(main_pane,(3,3)) # Blurring can reduce noise
main_pane[main_pane<color_threshold] = 0
main_pane[main_pane>=color_threshold] = 255

Примечание: при размытии на каждый пиксель влияют окружающие его пиксели. В этом случае некоторый шум (или какой-то пиксель имеет чрезвычайно высокое / низкое значение) будет «усреднен» по ближайшим значениям, поэтому его можно будет удалить.

С точки зрения математики, размытие - это свертка ядра с изображением, что является той же техникой, которая используется в сверточной нейронной сети (своего рода современный уровень глубокого обучения / искусственного интеллекта). Вы можете обратиться к материалам ниже для получения дополнительной информации.

Использованная литература:

  1. Https://en.wikipedia.org/wiki/Gaussian_blur
  2. Https://opencv-python-tutroals.readthedocs.io/en/latest/py_tutorials/py_imgproc/py_filtering/py_filtering.html

3. Найдите значки на главной панели.

Основная идея состоит в том, чтобы выделить внешнюю границу каждого значка, после чего мы можем получить некоторые «критические» точки этой границы (т.е. точки могут образовывать границу, соединяя их различными прямыми линиями). После этого мы можем нарисовать ограничивающий прямоугольник (он же прямоугольник) из этих точек, сравнив координаты x и y этих точек, например левый верхний угол рамки - это левая верхняя «критическая» точка

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

#Calculate the contours. Boundary is the layman term of contour
_, contours, hierarchy = cv2.findContours(pane, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
#Obtain the coordinates of top left corner (x,y), width and height of a bounding box
(x, y, w, h) = cv2.boundingRect(contour) 

4. Найдите цели внизу

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

5. Рассчитайте сходство для каждой пары значков и целей.

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

# Rotate the target by d degree each time and calculate the similarity
def calculate_max_matching(target,icon,d):
    largest_val = 0
    for degree in range(0,360,d):
# rotate target by d degrees
    rotated = ndimage.rotate(target, degree, reshape=False) 
# Calculate the similarity for each rotation
        res = cv2.matchTemplate(icon,rotated,cv2.TM_CCOEFF_NORMED) 
        min_val, max_val, min_loc, max_loc = cv2.minMaxLoc(res) 
# Only keep the highest similarity
        if max_val > largest_val:
            largest_val = max_val
    return largest_val

Примечание: cv2.matchTemplate, использованный выше, предназначен для обнаружения объектов. Теоретически мы можем использовать это, чтобы найти соответствующие значки, если мы уже знаем, каковы цели.

Однако у нас будет низкая точность, если мы применим ее напрямую. Во-первых, цели и значки в CAPTCHA не совпадают, у нас будет низкая точность, если мы не проведем предварительную очистку (например, удаление фона и установление порога). Во-вторых, цели и значки имеют разные размеры, cv2.matchTemplate не справляется с этим сценарием. (Мы действительно немного изменяем размер, но это довольно тривиально, поэтому мы не обсуждаем это здесь)

6. Пусть наш бот щелкает выбранные значки.

Мы используем селен для имитации человека, который щелкает значки один за другим со случайной паузой между двумя последовательными щелчками.

ele=driver.find_element_by_xpath("(.//*[normalize-space(text()) and normalize-space(.)='Loading'])[1]/following::div[1]")
action = webdriver.common.action_chains.ActionChains(driver)
action.move_to_element_with_offset(ele, centre_x, centre_y) #  centre_x, centre_y are the x,y coordinates of center of selected icon
time.sleep(randint(100,700)/1000) # random pause
action.click()
action.perform()

Результат

Geetest CAPTCHA довольно сложный, мой бот может решить только 5% из них.

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

Области улучшения

Мой бот не может решить эти CAPTCHA по нескольким причинам.

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

Один из подходов к решению, который заключается в выборке, мы можем собрать много CAPTCHA, протестировать различные виды пороговых значений (Percentage Threshold), которые сохраняют только несколько верхних процентов самых ярких пикселей, а затем применить этот новый «Percentage Threshold» к новым CAPTCHA. Однако у этого подхода есть два недостатка. Это потребует многих человеческих усилий, так как не существует метрики для измерения того, насколько хорош этот процентный порог, поэтому вам нужно определить все возможности процентного порога самостоятельно. Кроме того, этот подход не может охватить все CAPTCHA, поскольку процентный порог больше не будет применяться, если новая CAPTCHA имеет полностью другое распределение значений пикселей.

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

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

Исходный код: https://github.com/JoeHO888/Geetest-Icon-CAPTCHA-Solving

Эта статья также опубликована в моем блоге https://joeho888.github.io/projects/Attempt-to-solve-Geetest-CAPTCHA/

LinkedIn: https://www.linkedin.com/in/ho-cho-tai-0260758a/