JustPaste.it

Jak odczytać kody CAPTCHA?

Część I

Na nasze strony wchodzi coraz więcej botów internetowych. Zaśmiecają komentarze, księgi gości, czy formularze kontaktowe. Jednak wynaleziono dosyć łatwy sposób na zatrzymanie ich niepożądanej działalności - kody CAPTCHA. Metoda ta polega na przeprowadzeniu testu, który jest w stanie rozwiązać tylko człowiek - zazwyczaj musimy przepisać kilka literek z obrazka. Początkujący programiści tworzą proste kody, które w rzeczywistości nie spełniają należycie swojej funkcji. Pokażę dzisiaj jak za pomocą prostych metod odczytać tekst umieszczony na obrazku.

 

Przypuśćmy, że jesteśmy programistą serwisu. Mamy problem z robotami internetowymi, więc piszemy skrypt, który będzie generował nam losowy zestaw znaków. Żeby dodatkowo utrudnić robotowi pracę, dodajemy w tle różnokolorowe, losowe paski. Ponadto litery znajdują się na różnych wysokościach. Efekt wygląda tak:

Pozwoliłem sobie odpowiednio powiększyć grafikę, aby była ona dobrze widoczna.

 

slide0029_image036

I będąc na miejscu takiego programisty myślimy sobie, że żaden bot już nam nie wejdzie na stronę. I tu jest błąd - odczytanie tekstu z tak zaprojektowanego obrazka zajmie mniej niż sekundę…

A teraz krok po kroku pokażę, w jaki sposób działa taki bot. W ten sposób będziemy znali słabe punkty naszego skryptu i będziemy mogli wyeliminowac ewentualne błędy.

 

1. Separacja tła

Programista dodaje kolorowe linie w tle, aby utrudnić robotowi odczytanie liter. Czasami potrafi tak bardzo przesadzić, że nawet człowiek nie potrafi odczytać poprawnie liter. Jednak przy prostym zabezpieczeniu odseparowanie liter od tła nie stanowi większego problemu. Zobaczmy jak to się ma w praktyce.

Każdy kolor na ekranie składa się z trzech barw składowych: czerwonej (R), zielonej (G) i niebieskiej (B). Każda składowa jest z przedziału od 0 do 255. I tak dla koloru czarnego reprezentacją RGB jest (0, 0, 0), czerwony ma (255, 0, 0), niebieski (0, 0, 255), biały (255, 255, 255) itd. Jeżeli zsumujemy wszystkie trzy składowe, to możemy zauważyć, że im suma jest bliższa zeru, tym kolor jest ciemniejszy. I tak dla czarnego będzie to 0, a dla białego 765. Co nam to daje? To, że jeżeli bot przejedzie piksel po pikselu cały obrazek i ustawi próg np. na 300, to jest w stanie odseparować tło, od właściwego tekstu. Efekt wygląda następująco:
slide0024_image0291slide0025_image030slide0026_image031

Jak widać, dobierając odpowiednia wartość jesteśmy w stanie bez problemu oddzielić tło, od właściwego tekstu. Wystarczy tylko przyjąć odpowiedni próg.

2. Oddzielenie poszczególnych liter

Kolejnym krokiem jest oddzielenie od siebie liter. Nie jest to trudne zadanie, a wręcz banalne. Spójrzmy na przykład:
slide0027_image033

Skrypt jedzie piksel po pikselu od góry do dołu. Jeżeli w całej kolumnie nie znajdzie ani jednego czarnego piksela, to jest to dla niego informacja, że tutaj znajduje się odstęp między literami. W języku polskim nie ma takiej litery, która składała by się z odstępu, między poszczególnymi znakami. Więc kolejny problem jest już rozwiązany.

3. Rozpoznawanie liter

I teraz chyba najtrudniejsze zadanie - rozpoznanie liter. Wbrew pozorom jest to łatwiejsze niż to się wydaje. Spójrzmy:
slide0028_image034

Na czym to polega? Skrypt analizuje każdą kolumnę w obrazku i zlicza ilość czarnych pikseli - w rezultacie otrzymujemy liczbową reprezentację danej liczby. Dla litery “a” zawsze będzie taka sama, nieważne, czy będzie znajdować się nisko, czy wysoko. I tak dla litery “a” będzie to 25433376. W alfabecie nie ma dwóch takich samych liter, więc nie ma problemu z rozpoznawaniem liter.

Jeżeli wcześniej przeanalizujemy kilka takich “kodów”, to jesteśmy w stanie wygenerować bazę wzorców, z którymi będziemy porównywać poszczególne reprezentacje liczbowe.

Ot i mamy cały obrazek odczytany. Teraz tylko wystarczy napisac bota i zaspamować koledze serwis :).

I jeszcze taka uwaga: jeżeli i tak nasz bot będzie się od czasu do czasu mylił, to nie ma to większego znaczenia. Nie zapominajmy, że bot może chodzić po stronach 24 godziny na dobę. Przy kilku wywołaniach na minutę, po kilku godzinach nasz serwis jest zawalony niepotrzebnymi informacjami.

I na koniec przypomina mi się powiedzenie mojej mamy: “Zrób coś raz, a porządnie” :D

 

Część II

W poprzednim poście pokazałem w jaki sposób “oszukać” programistów laików i odczytać kody CAPTCHA zabezpieczające strony internetowe. Jednak sam opis nie wystarcza zabardzo, aby skutecznie skanować kody na obrazkach. W niniejszym poście zaprezentuję kod, za którego pomocą odszyfrujemy zabezpieczający tekst.

W poniższym tutorialu zajmiemy się takim oto obrazkiem:

token

Jak widać bez problemu jesteśmy w stanie odczytać jego zawartość: 011e11af. Jednak komputer widzi obrazek mniejwięcej tak: 1000111010010010100011000…, itd. Tak więc nic nam to specjalnie nie mówi. Jednak stosując krok po kroku zasadę, którą przedstawiłem w poprzednim poście jesteśmy w stanie bezbłędnie rozszyfrować obrazek.

 

<?php
$image = 'token.gif';

$info = getimagesize($image);
$width = $info[0];
$height = $info[1];
$img = imagecreatefromgif($image);

 

Kod zaczynamy od stworzenie zmiennej, która będzie przechowywała odres do pliku z obrazkiem. Następnie za pomoca funkcji getimagesize() pobieramy informacje o pliku. Nas będzie interesowały tylko szerokość i wysokość obrazka. Są one przechowywane odpowiednio w zerowym i pierwszym kluczu, zwracanym przez funkcję. Przypisujemy je do zmiennych $width i $height. Ostatnim krokiem jest otwarcie obrazka funkcją imagecreatefromgif() i zapisanie uchwytu do zmiennej $img.

Oddzielenie tła od liter

$map = array();
for($y=0; $y<$height; $y++)
{
for($x=0; $x<$width; $x++)
 {
$color = imagecolorsforindex($img, imagecolorat($img, $x, $y));
$map[$x][$y] = ($color['red'] + $color['blue'] + $color['green'] < 200) ? undefined : FALSE;
 }
}

 

Następnie tworzymy dwie pętle, które będą po kolei analizowały każdy piksel obrazka. Tutaj właśnie przydają się zmienne $width$height.

Fragment ten sprawdza, czy dany piksel jest literą (undefined), czy też nie (FALSE). W tym celu sumuje wszsytkie trzy składowe danego koloru, które są pobierane za pomocą dwóch funkcji: imagecolorat() oraz imagecolorscorindex(). Pierwsza z nich tworzy uchwyt do koloru o podanych współrzędnych, a druga zamienia go na tablicę ze składowymi kolorów.

Po dodaniu wszystkich kolorów sprawdzamy, czy dany piksel jest ciemny, czy też jasny. Ja przyjąłem próg równy 200. W rezultacie otrzymujemy dwuwymiarową tablicę z oddzielonym tekstem od tła.


$sum = '';
for($x=0; $x<$width; $x++)
{
$count = 0;
for($y=0; $y<$height; $y++)
{
if($map[$x][$y] == undefined) $count++;
}
$sum .= ($count == 0) ? 'X' : $count;
}
//echo $sum;

 

Kolejnym krokiem jest zsumowanie wszystkich pikseli leżących w każdej kolumnie. Do tego celu znowu tworzymy dwie pętle, oraz zmienną $str, która zapisze wszystkie wyniki w jeden ciąg.

Do kodu wprowadziłem małe udogodnienie: jeżeli w kolumnie nie ma ani jednego czarnego piksela (undefined), to zamiast cyfry zero zapisuje znak X. Pozwoli to uniknąć niedogodnień, przy rozbijaniu tego ciągu na poszczególne litery.

Po wyświetleniu zmiennej ujrzymy mniejwięcej taki ciąg znaków:

XXXXX46444464XXX23101011XXXX23101011XXX35533453XXX23101011XXXX23101011XXX25433376XX119102232XX

A teraz wytłumaczę po co te literki, zamiast zera. Nasz skrypt będzie dzielił na poszczególne litery tam, gdzie znajdzie litery X. Jeśli byśmy nie dali litery to dzielony byłby tam, gdzie jest 0. Jednak, gdyby w kolumnie było 10 liter, to skrypt błędnie podzieliłby ciąg.

Podział na poszczególne litery

 

$sum = preg_replace('#X+#', 'X', $sum);
$sum = trim($sum, 'X');
$letters = explode('X', $sum);

 

Teraz dzielimy ciąg na wzorce liter. Najpierw za pomocą wyrażenia regularnego usuwamy powtarzające się litery X. Następnie usuwamy niepotrzebne litery z końca i z poczatku funkcją trim(), a na końcu dzielimy ciąg za pomocą funkcji explode na poszczególne wzorce. Efektem tego będzie taka oto tablica:


Array (
[0] => 46444464
[1] => 23101011
[2] => 23101011
[3] => 35533453
[4] => 23101011
[5] => 23101011
[6] => 25433376
[7] => 119102232

)

 

Jak widzimy posiada ona dokładnie tyle elementów, ile liter posiada nasz obrazek. Zauważmy, że elementy o kluczach 1, 2, 4 i 5 mają takie same wartości. Patrząc na obrazek widzimy również, że na tych pozycjach znajdują się takie sameznaki - cyfry 1. Co z tego wynika? To, że tworząc “bazę” wzorców dla liter jesteśmy w stanie rozpoznać cały ciąg. Moja baza wygląda tak:

 

$patterns = array(
  '46444464' => '0',
  '23101011' => '1',
  '25433376' => 'a',
  '35533453' => 'e',
  '119102232' => 'f'
  //reszta liter
);

 

Rozpoznawanie liter

 

A teraz najlepsza część naszego skryptu, czyli rozpoznawanie:

 

$token = '';
for($i=0; $i<count($letters); $i++)
{
$token .= $patterns[$letters[$i]];
}
echo $token;

 

I oto na ekranie ukazuje się nam ciąg znaków “011e11af”, czyli rozwiązanie naszego tokenu.

Jak widać  odczytanie zawartości tokenu nie jest szczególnie trudną sprawą. Znajomośc podstawowych funkcji do obsługi grafiki z poziomu PHP pozwala nam bezbłędnie odczytywać tekst na obrazkach. Nawet jeśli nasz skrypt byłby omylny, to weźmy pod uwagę to, że bot może chodzić 24 godziny na dobę. I nawet przy 1% skuteczności, przy 1 zapytaniu na sekundę, po godzinie mamy zaspamowaną księgę gości.

Tak więc zachęcam koderów do rozsądnego tworzenia zabezpieczeń. Zwracajcie uwagę na to co robicie, bo czasami odrobina lenistwa może dać nieoczekiwane efekty.

Na koniec zamieszczam jeszcze link do całego kodu źródłowego.


Przedruk dokonany za zgodą autora.

 

Źródło: Paweł Kuna