Своя система распределенных вычислений №2

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

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

Итак, что мы имеем? Сервер написан таким
образом, что при обращении к нему он выдает
всего одну строку - зашифрованный пароль (getpass.cgi).
Причем делает это рандомно и каждый раз
проверяет количество участников, которые
расшифровывают выданный пароль. Если число
превышает заданную в конфе переменную,
скрипт временно комментирует пароль (в
будущем планируется сделать суточную крон-проверку
по базе. Если пароль не расшифрован -
отменять комментарий). Успешно разгаданный
пароль клиент отдает серверу (putpass.cgi).
Скрипт проверяет его правильность и если
все ок - начисляет поинты участнику.

Наконец разобрались. Теперь поглядим
структуру базы паролей.

user:$2a$08$cpIrpG.tfe/LfMfHnvb42.FfouTtGNcJk/43M9K5UQOm5fEB1eA6:14:0

Что означают параметры? С первыми двумя все
ясно - это пользователь и пароль. Затем идет
идентификатор участника, который прислал
пароль в базу. Последний параметр - счетчик,
о котором говорилось выше.

После этой вводной части можно приступить к
дальнейшему кодингу клиента, который в
полной мере реализует вышеизложенный
алгоритм.

Я всегда использую отдельный конфиг для
крупных проектов, чтобы не заморачиваться
изменением переменных. Поэтому у клиента
будет также свой конф. Вот он (расположен в
папке etc/fclient.conf относительно скрипта client.pl):

## Config file for Fsystem

# Server of Fsystem
$server='www.remotesystem.org'; ## Адрес
сервера, где будут крутиться скрипты
системы.

# Path to getpass.cgi and putpass.cgi scripts
$getpath='/system/getpass.cgi';
$putpath='/system/putpass.cgi'; ## Пути
к getpass и putpass соответственно.

# Client id, which recieve you from FS
$cid=31337; ## Id клиента,
который будет передаваться серверу (обязательный
параметр).

# Default type for salt (1 - DES, 2 - MD5)
$stype=1; ## Тип
запрашиваемого пароля по умолчанию. Его
можно будет задать также в командной строке
клиента.

# Save file (Salt will dump in this file)
$savefile='salt.save'; ## Файл,
куда будет записан хэш после скачивания.

# External program for crack passwords
$progname="/usr/bin/john"; ## Путь
к программе John The Ripper.

# Wordlist (optional) for cracking passwords
$wordlist="etc/wl.fs"; ## Путь
к вордлисту (если он будет использован).

# Enable rules - enable option '-rules' when cracking by wordlist
$enablerules=1; ## Параметр -rules
Джоника может перебирать много вариантов
заданного словарного слова (актуален
только при использовании словаря).

Вот весь конф. Большинство переменных из
него могут быть изменены в самой командной
строке клиента (соответственно они и будут
иметь наивысший приоритет), который мы
сейчас и напишем. В принципе, клиент очень
простой и разобраться в коде может даже
начинающий Perl'овик, но я не поленился
указать комментарии в коде. Итак,
собственно, сам клиент.

#!/usr/bin/perl

## Client for Fsystem. Options

# -g get the password salt from remote server
# (if value 1 - crack process started automatically 0 - get only)
# -t type of the password salt (1 - DES, 2 - OpenBSD Blowfish). DES by default
# -i client id
# -c begin crack process (1 - new crack, 2 - restoring process)

## are avaliable in this client

## Небольшой хелп. Так
принято в крупных проектах ;). Как ты понял,
может быть изменен тип хеша и идентификатор
клиента. Параметр -g (get) запрашивает пароль у
сервера. Если его значение 1 - происходит
автоматическая расшифровка пароля, если 0 -
клиент лишь сохранит пароль в заданном (в
конфе) файле.

use Getopt::Std; ## Используем
модуль для парсинга (не путать с пирсингом
;)) командной строки.
use IO::Socket; ## А также
сокеты.
$|++; ## Выключаем
буферизацию.

$config = 'etc/fclient.conf'; ## Путь
к конфигу.

if (-e "$config") {
require "$config"
} else {
exit print "Config is missing. Maybe you create it?\n";
}

## Если конф не найден -
ругаемся ;). Иначе обрабатываем его как Perl-скрипт.

getopt("gtic"); ## Хватаем
из командной строки параметры -g -t -i и -c (они
будут автоматически преобразованы в
переменные $opt_параметр).

if (defined $opt_g && defined $opt_c) {
usage ("Options -g and -c are not compartible\n"); ## Если
указаны параметры -g и -c ругаемся на их
несовместимость.
}
getsalt($opt_g)
if (defined $opt_g); ## Отправляемся
делать запрос если указан параметр -g

cracksalt()
if (defined $opt_c); ## Отправляемся
расшифровывать пароль при опции -c

usage("No parameters given\n"); ## В
противном случае грязно ругаемся и выходим
(выход в usage()).

sub usage {
my $error=shift; ## Ошибка -
возможный параметр процедуры.
print $error
if ($error); ## Если она есть -
печатаем ее на экран.
print "Usage: $0 <options>\nOptions are:
-g get the password salt from remote server
\t(if value 1 - crack process started automatically 0 - get only)
-t type of the password salt (1 - DES, 2 - OpenBSD Blowfish). DES by default
-i client id
-c begin crack process (0 - new crack, 1 - restoring process)\n";
exit 0; ## Затем выводим
команды для использования и выходим.
}

sub getsalt { ## Процедура
запроса пароля у сервера.
my $flag=shift; ## Хватаем
значение параметра -g
my($socket,@answer,$answer); ## Локализуем
переменные процедуры
$socket=IO::Socket::INET->new("$server:80") || die print "Cant
connect to Fsystem.
Check your Internet Connection\n"; ## Создаем
сокет с сервером, либо ругаемся на
отсутствие соединения.
$socket->autoflush(1); ## Выключаем
буферизацию у сокета.
print $socket "GET $getpath?id=$cid&type=$stype HTTP/1.0\n";
print $socket "Server: $server\n\n"; ## Пишем
в сокет запрос на выдачу пароля (обязательные
переменные id и type
while(<$socket>) {
$socket->read($answer,1024);
@answer=split("\n",$answer);
} ## Считываем ответ,
который разбиваем построчно в массив для
удобного анализа.
$answer=$answer[scalar @answer-1]; ## Ответ
(пароль) - предпоследняя строка (последняя
будет символом перевода строки).
open(SF,">$savefile");
print SF "$answer";
close(SF); ## Дампим ответ
сервера в файл $savefile (ты уже забыл конфиг? ;)).
$flag == 1 ? cracksalt() : exit print "Salt was dumped\n"; ## Если
значение 0 - выходим. Иначе переходим к
перебору.
}

sub cracksalt { ## Процедура
перебора пароля.
my($rules,$pwd,$binstring,@res); ## Локализуем
переменные
if ($wordlist) { ## Если
используем вордлист
$rules == 0 ? $rules='' : $rules='-rules'; ## Проверяем
$rules
$binstring="$progname -w:$wordlist $rules $savefile 1>fs.tmp 2>/dev/null";
## Формируем запрос (без
вывода на экран, сохранение результата в
файл).
print "Starting process...\n"; ## Напоминаем
юзеру о том, что мы еще живы.
`$binstring`; ## Выполняем
программу john.
$pwd=checkres(); ## Когда
программа завершается - переходим на
проверку пароля.
putsalt("$pwd")
if (defined $pwd); ## Если
пароль обнаружен - засылаем его серваку!
}
if ($opt_c eq 1) {
$binstring="$progname -i:all $savefile 1>fs.tmp 2>/dev/null"; ##
Если параметр crack равен
1 - запускаем john в режиме all.
} else {
unless(-f "~/.john/restore") {
system("cp restore ~/.john/");
} ## Иначе ресторим
процесс. При отсутствии restore - копируем
бекап.
$binstring="$progname -restore $savefile 1>fs.tmp 2>/dev/null";
## И формируем запрос с -restore.
}
## Some signal stuff...
$SIG{INT}=\&sigterm; ## Ставим
обработчик на ctrl+c.
`$binstring`; ## Запускаем john.
$pwd=checkres();
putsalt("$pwd")
if (defined $pwd);
exit ## Повтор операции (что
и со словарем).
}

sub checkres { ## Процедура
проверки результата взлома
my(@res,$pwd); ## Локализация
переменных
open(TMP,"fs.tmp");
@res=<TMP>;
if(scalar @res eq 1) {
print "No resuilts... Switch to ALL mode.\n";
return; ## Если результатов
нет.. - переключаемся в режим ALL
} else {
($pwd,undef)=split(' ',$res[1]);
print "Done. Your pwd is $pwd\n"; ## Иначе
пишем пароль на экран.
return $pwd;
}
close(TMP);
unlink("fs.tmp"); ## Удалим
временный файл.
}

sub sigterm {
print "Exiting. You must use -c 2 for restoring session\n";
system("copy ~/.john/restore .");
unlink("fs.tmp"); ## Предупреждение
о выходе и бекап restore-файла.
exit; ## Выход :).
}

sub putsalt { ## Самое
интересное - отправка результата.
my $pwd = shift;
my($salt,$socket,@answer,$answer); ##
Локализуем переменные и параметры
open(SF,"salt.save");
chomp($salt=<SF>);
close(SF); ## Хватаем хэш+параметры
user и id.
$socket=IO::Socket::INET->new("$server:80"); ## Создаем
сокет с сервером.
$socket->autoflush(1);
print $socket "GET $putpath?pwd=$pwd&salt=$salt&id=$cid
HTTP/1.0\n";
print $socket "Server: $server\n\n"; ## Посылаем
запрос.
$socket->recv($answer,1024);
@answer=split("\n",$answer);
$answer=$answer[scalar @answer-1];
close($socket);
print "Salt was transferred to Fserver. Res is $answer\n"; ## И
оповещаем возврат сервера (1 все в порядке, 0
- пароль неправильно расшифрован).
exit; ## Выходим.
}

Вот и весь клиент. Модульный кодинг придает
ему неопределенную красоту.

Что же, проверим его в действии ;). Пароль
будет выдан 1111, чтобы Джоник быстро
перебрал его по словарю.

[root@forbik client]# perl client.pl -g 1 -t 1
Starting process...
Done. Your pwd is 1111
Salt was transferred to Fserver. Res is 1

Как видим - все работает. Сервер обслуживает
клиента на все 100%. Теперь попробуем так:

[root@forbik client]# perl client.pl -c 1
Starting process...
Done. Your pwd is 1111
Salt was transferred to Fserver. Res is 1

Опция -c также действует. Все идет по плану ;).

Таким образом, система готова. Остается
лишь намутить статистику и начисление
поинтов за результаты. Об этом мы поговорим
в следующей тематической статье. А пока -
ставь клиент-сервер и обкатывай систему на
себе ;). Удачи!