Прочитав статью "Авторизуйся
картинкой
" я подумал, что многим будет
интереснее обратная статья о том, как эту 
напасть обойти. 

Intro 

На многих сайтах при регистрации, отправке SMS и
т.п. ты наверняка встречал форму с картинкой для
авторизации, типа защита от роботов. Хитрые и
злые дядьки веб-мастеры стали использовать их в
целях защиты от нас с вами. Ничто не мешает
написать программу, а еще лучше скрипт, который
будет, к примеру, создавать почтовые ящики, и
запустить его на серваке с хорошим каналом. В
результате таких действий атакуемый сервер может
просто накрыться. Дело упирается в эту самую
картинку. Но и ]i[ack-прогресс на месте не
стоит. Пишутся программы для распознавания таких
картинок, усложняются и картинки, увеличивается
количество мусора в них. Из этой статьи ты
узнаешь, как написать свою несложную программу
для анализа таких изображений. 

Теория 

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

  • Очень маленький размер картинки. Тот же
    FineReader на таком клочке просто не может
    ничего увидеть.
  • Нестандартные шрифты.
  • "Пляска" символов, в то время как
    предполагается, что они должны находиться на
    одном уровне.
  • И много чего другого.

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

Алгоритм работы 

  • Скачиваем картинку.
  • Отделяем символы от фона.
  • Очищаем от мусора.
  • Загружаем шаблоны символов (маски).
  • "Прикладываем" маски и перемещаем их по
    картинке, запоминая максимальный процент
    наложения для каждого символа на картинке.
  • Анализируем варианты совпадений для всех
    масок и выбираем наилучшие, запоминая номер
    маски.
  • Получаем результат — последовательность
    из номеров шаблонов, которая соответствует
    символам, нарисованным на картинке. 

Сразу хочу предупредить — этот алгоритм не
идеален. У него есть и существенные недостатки,
а именно: 

  • В нем не учитывается вращение символов,
    так как при этом алгоритм бы заметно
    усложнился. Но и это можно реализовать.
    Достаточно во время каждого "прикладывания"
    к картинке вращать маску в заданном
    интервале. 
  • Предполагается, что цвет символов или
    фона не меняется. Это тоже устранимо.
    Например, можно попробовать перевести
    картинку в черно-белый цвет (2 бита на
    пиксель) и получить четкую границу между
    символами и фоном. 
  • Количество символов, а также примерные
    места их размещения должны быть заранее
    известны, в противном случае алгоритм
    заметно усложняется. Пришлось бы находить
    положение первого символа, отступать от него
    определенное расстояние (средняя ширина
    символа), находить второй и т.д. 
  • Размер картинки в нем постоянен, что,
    практически всегда, так и есть. 
  • Требуются маски — не зашумленные и не
    искаженные образцы символов. Этот недостаток
    неустраним, поскольку мы не можем найти и
    распознать символ, не зная как он выглядит. 

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

Практика

Писать прогу будем на Delphi. В качестве
примера, ниже представлена простая программа.
Форма состоит из  двух кнопок, двух картинок и
одной метки. По нажатию на кнопку Button1
происходит открытие картинки. Button2 запускает
алгоритм распознавания и выводит результат.
Маски цифр загружаются из черно-белых
bmp-файлов. 

procedure
TForm1.Button1Click(Sender: TObject); 
begin 
{
Запускаем диалог и
открываем картинку 


If OpenPicture.Execute then 
Image_IN.Picture.LoadFromFile(OpenPicture.FileName); 
{—} 
end; 
procedure TForm1.Button2Click(Sender: TObject); 
const mask_height=10; {
Высота
маски

mask_width=8; {
Ширина
маски

pic_height=20; {
Высота
картинки

pic_width=48; {
Ширина
картинки

fig_width=round(pic_width/3); {
Третья
часть картинки (для трех цифр)

type mask=array[0..mask_width-1,0..mask_height-1]
of byte; {
Тип
массива для маски

var masks:array[0..9] of mask; {
Массив
масок

masks_pix:array[0..9] of integer; {
Массив
для хранения кол-ва белых пикселей для каждой
маски

pic:array[0..pic_width-1,0..pic_height-1] of
byte; {
Массив для
картинки

i,x,y,x_,y_,dx,max,prob:integer; 
dig,s,pos_x,pos_y:array[0..9] of byte; {
вспомогательные
массивы

{
Функция
определения номера максимально совпавшей маски
 

function Getmax(dig:array of byte):byte; 
{
dig — число
совпавших точек для каждой маски
 } 
var i,i_:byte; 
a,b:real; 
begin 
a:=0; 
i:=0; 
for i_:=0 to 9 do 
begin 
{
Вычисляем
вероятность совпадения 

b:=dig[i_]/masks_pix[i_]; 
if a<b then 
begin 
a:=b; 
i:=i_; 
end; 
end; 
result:=i; 
end; 
{—} 
begin 
{Загружаем
маски в массивы 

for i:=0 to 9 do 
begin 
masks_pix[i]:=0; 

Temp.Picture.LoadFromFile(ExtractFilePath(Application.Exename)+’MASKS\’+InttoStr(i)+’.bmp’); 
for x:=0 to mask_width-1 do 
for y:=0 to mask_height-1 do 
if Temp.Canvas.Pixels[x,y]=clwhite then 
begin 
masks[i][x,y]:=1; 
masks_pix[i]:=masks_pix[i]+1; 
end 
else masks[i][x,y]:=0; 
end; 
{—} 
{Загружаем картинку
в массив 

for x:=0 to pic_width-1 do 
for y:=0 to pic_height-1 do 
begin 
if Image_IN.Canvas.Pixels[x,y]=clwhite then pic[x,y]:=1
else 
pic[x,y]:=0; 
end; 
{—} 
{Подготовка
Image_OUT

Image_OUT.Picture.Bitmap.Width:=pic_width; 
Image_OUT.Picture.Bitmap.Height:=pic_height; 
{—} 
{
Очищаем картинку
от мусора 


{
Мусор в данном
случае — точки того же цвета, что и цифры 


for x:=0 to pic_width-1 do 
for y:=0 to pic_height-1 do 
if pic[x,y]=1 then 
begin 
x_:=x; 
y_:=y; 
if x<=-1 then x_:=1; 
if y<=-1 then y_:=1; 
{
Если нет соседних
точек или точка лежит на краях картинки то
убираем ее

{
Здесь может быть и
какой-нибудь другой признак: различие по цвету,
например

if (pic[x_+1,y_]<>1) and (pic[x_-1,y_]<>1) and 
(pic[x_,y_+1]<>1) and (pic[x_,y_-1]<>1) and 
(pic[x_+1,y_+1]<>1) and (pic[x_-1,y_-1]<>1) and 
(pic[x_-1,y_+1]<>1) and (pic[x_+1,y_-1]<>1) then 
pic[x_,y_]:=0; 
end; 
{—} 
dx:=0; {
Смещение по
горизонтали

prob:=100; {
Начальное
значение вероятности успешного распознавания(в
%)

label1.Caption:=»; 
{
В этой части и
происходит распознавание

repeat 
{
Обнуляем все
массивы

for i:=0 to 9 do 
begin 
dig[i]:=0; 
s[i]:=0; 
pos_x[i]:=0; 
pos_y[i]:=0; 
end; 
for x:=dx to dx+mask_width-1 do 
for y:=0 to 9 do {
Определенная
область на картинке

begin 
for i:=0 to 9 do s[i]:=0; 
for x_:=0 to mask_width-1 do 
for y_:=0 to mask_height-1 do {
Берем
по очереди все точки из маски

begin 
for i:=0 to 9 do begin {
Подставляем
все маски по очереди

if (pic[x_+x,y_+y]=masks[i][x_,y_]) 
and (masks[i][x_,y_]=1) then s[i]:=s[i]+1; {
При
совпадении увеличиваем элемент массива на 1

{
Запоминаем
максимальное число совпавших точек для
конкретной маски 


if s[i]>dig[i] then begin dig[i]:=s[i];pos_x[i]:=x;pos_y[i]:=y; 
end; 
end; 
end; 
end; 
max:=getmax(dig); {
А
где же у нас совпало лучше всех? 


{
Следующие строки
до комментария "!" можно выкинуть 


{
Здесь для понта
рисуются цифры по маскам, в тех позициях, где
они были на картинках


for x:=dx to dx+fig_width do 
for y:=0 to pic_height-1 do 
image_out.Picture.Bitmap.Canvas.Pixels[x,y]:=clblack; 
for x:=0 to mask_width-1 do 
for y:=0 to mask_height-1 do 
if masks[max][x,y]=1 then 

image_out.Picture.Bitmap.Canvas.Pixels[x+pos_x[max],y+pos_y[max]]:=$00AAFFAA; 
{!} 
{
Запоминаем самую
меньшую вероятность совпадения 


if round(100*dig[max]/masks_pix[max])<PROB then 

prob:=round(100*dig[max]/masks_pix[max]); 
label1.Caption:=Label1.Caption+inttostr(max); 
dx:=dx+fig_width; {
Сдвигаемся
вправо для следующей цифры

until dx>=pic_width; 
{—} 
label1.Caption:=Label1.Caption+’ (‘+inttostr(prob)+’%)’; 
end; 

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

Заключение 

Рассмотренный алгоритм является примерным.
Возможно, что он тебе не подойдет, но поможет
написать свой анализатор. 


Исходники с 50 картинками

Картинки в программе взяты с реального сайта

sms.orensot.ru
(раздел "Отправка СМС")

Оставить мнение

Check Also

Безопасность смарт-контрактов. Топ-10 уязвимостей децентрализованных приложений на примере спецификации DASP

Количество смарт-контрактов в блокчейне Ethereum только за первую половину 2018 года вырос…