Иногда требуется автоматизировать процесс поиска уязвимости или быстро проверить варианты использования какого-либо недочета в коде.
Кодить для каждого такого случая на С слишком утомительно и нерационально.
В этом случае нам на помощь придет Perl, который позволяет быстро писать короткие и функциональные эксплойты.
Эта статья не является руководством по программированию на Perl, равно как и не является руководством по написанию эксплойтов: в ней мы коротко рассмотрим основные инструменты, которые могут для этого потребоваться.
Если ты опытный секьюрити-специалист и знаешь, «что почем» в мире сетевой безопасности, то следующий абзац можешь смело пропустить. В случае же, если ты только планируешь приступить к написанию собственных эксплойтов и подыскиваешь инструменты, я представлю твоему вниманию один такой и обосную, чем и как он может быть полезен. Для чтения этой статьи необходимы лишь базовые знания Perl, так что не стоит пугаться, если этот язык тебе не знаком. Примеры просты настолько, что при наличии опыта программирования на любом другом языке, ты легко сможешь в них разобраться.
Тип эксплойта зависит от типа уязвимости и целей, которые преследует взломщик. Однако есть наиболее общий класс задач, в контексте которых мы и приглядимся к
Perl. Наиболее часто приходится писать удаленные эксплойты, которые не подразумевают наличия прямого доступа к целевой машине, а используют предоставляемый этой машиной удаленный сервис. Что нам может потребоваться для написания удаленного эксплойта? Очевидно, модули, позволяющие формировать соединения на всех уровнях стека протоколов TCP/IP. Почему я рекомендую использовать для этих целей Perl? Perl, на мой взгляд, чрезвычайно прост в освоении. Для человека, знакомого с С/С++, освоение синтаксиса Perl займет максимум день-два. Для примера, один мой знакомый ;), когда того потребовала ситуация, освоил основные инструменты Perl и написал первый скрипт на 200-300 строк в течение 10 часов. Надо заметить, что мой знакомый отнюдь не полиглот. Perl чрезвычайно гибок и не навязывает какой-то конкретный стиль программирования. Есть Perl-скрипты, которые при поверхностном просмотре сложно отличить от C, есть написанные в «родном» для Perl скриптовом стиле. Существует много способов сделать одно и то же, и для каждой конкретной задачи ты волен выбирать тот инструмент, который тебе представляется самым подходящим - это основная идеология Perl. Мало того, по количеству возможностей это один из самых богатых языков - одни регулярные выражения чего стоят. Следует отметить, что на Perl можно писать очень короткие и при этом чрезвычайно функциональные скрипты. Помимо этого, язык позволяет развивать нехилую скорость разработки, в сравнении с тем же C, обладая, как уже было сказано, вполне достаточной функциональностью. Например, я, да и не я один, часто использую Perl для написания прикидочного макета будущей утилиты. И как только становится ясно, как следует организовать код и какие возможны узкие места, можно приступать к переписыванию кода на компилируемом языке (например, С), если, конечно, скорость выполнения является критичной. Ах да, совсем забыл: основной и самый жирный бонус Perl - это склад готовых решений на все случаи жизни - CPAN. Если возникает желание написать «какую-нибудь полезную библиотечку», то с большой вероятностью что-то подобное уже есть на CPAN. Еще Perl позволяет осуществлять работу с системой на низком уровне (к примеру, реализована работа с системными вызовами и сигналами Unix, что может быть крайне полезно при написании эксплойтов). Ну и, наконец, Perl обладает неиллюзорной переносимостью, что также чрезвычайно важно. Если возникнет необходимость запустить удаленный эксплойт на каком-либо шелле, это получится почти наверняка, невзирая на установленную на шелле операционную систему. Конечно, если код не будет опираться на особенности конкретной операционки, и на нужной нам машине будет установлен perl-интерпретатор. Об особенностях Perl можно говорить очень долго, но данная статья немного не об этом :). В общем, я уверен, что всем, кто только начинает писать свои эксплойты, стоит попробовать делать это на Perl, как на самом выигрышном по соотношению «затраты/результат» инструменте.
Простая работа с WWW
Что может нам в первую очередь понадобиться при написании удаленного эксплойта на web-интерфейс? Конечно же, возможность формировать HTTP-запросы и обрабатывать ответы. Например, при реализации эксплойта, использующего SQL-инъекцию или переполнение буфера (да и вообще, любую уязвимость, основанную на недостаточной фильтрации параметров web-скриптов), это, фактически, единственное, что нам понадобится.
Первая библиотека, которая нам пригодится - LWP (LWP - The World-Wide Web library for Perl). По названию уже примерно понятно, что с помощью этой библиотеки можно работать с WWW, и сейчас мы выясним, как именно это делается. Итак, эта библиотека представляет собой набор модулей для простой работы с WWW: с ее помощью мы можем формировать запросы к каким угодно стандартным сервисам (ftp, http, file, smtp, etc.) и даже посылать e-mail'ы. Однако надо помнить, что проверка правильности запросов целиком ложится на нас, так как сам LWP-модуль не гарантирует, что, послав кривой запрос на какой-либо сервер, ты получишь хоть какой-то ответ. Работа этого модуля основана на HTTP-style соединении, то есть соединении по типу «запрос-ответ». Работает все это очень просто. В начале мы формируем объект запроса HTTP::Request (не стоит пугаться упоминания протокола HTTP в названии класса - он указывает на используемую идеологию соединения и никак не ограничивает их возможный тип), после чего передаем его в метод request класса LWP::UserAgent, который представляет собой интерфейс, скрывающий всю низкоуровневую работу с сетью и возвращающий ответ в виде объекта HTTP::Response. Сам по себе объект LWP::UserAgent содержит много атрибутов, которые конфигурируют его взаимодействие с сетью. В частности, при работе с HTTP, некоторые атрибуты встраиваются в HTTP-заголовок. Рассмотрим основные атрибуты объекта
HTTP::Request:
- Method - строка, идентифицирующая тип
запроса (наиболее часто используются post,
get, put и т.д.). - Uri (uniform resource identifier) - строка,
содержащая протокол, сервер и имя
запрашиваемого «документа», которая
может содержать и другие параметры,
обрабатываемые сервером, например, «http:://megasiting.ee:8080/script.cgi?param=pam&pam=pam». - Headers - в общем случае - дополнительная
информация о запросе в виде пар «ключ-значение».
В случае http-соединения - параметры
заголовка. - Content - данные, если они необходимы.
Ответ сервера, как уже было упомянуто - это объект HTTP::Response, содержащий следующие параметры, доступные с помощью одноименных методов:
- Code - код ответа сервера (200 - все нормально, 404 - file not found, 500 - ошибка при обработке скрипта...).
- Message - краткое пояснение кода возврата (в удобочитаемой форме).
- Headers - параметры заголовка ответа. В общем случае - описание контента.
- Content – собственно, данные.
Продемонстрируем, как все это работает на примере доступа к http-серверу.
use LWP::UserAgent;
my $obj = LWP::UserAgent->new();
$obj->agent('MyExapmle/1.0');
my $req = HTTP::Request->new(GET => 'http://www.xakep.ru');
my $result = $obj->request($req);
if($result->is_success)
{
print 'Code: ' . $result->code . "\n";
print 'Message: ' . $result->message . "\n";
print 'Headers: ' . $result->headers. "\n";
foreach my $key ( keys(%{$result->headers}) )
{
print ' ' . $key . ":" . $result->headers->{$key} . "\n";
}
}
else
{
print $result->status_line, "\n";
}
1;
Как мы видим, все очень просто. В первой строчке мы подключаем модуль LWP::UserAgent, после чего создаем новый объект. Надо заметить, что конструкторы в Perl обычно понимают гибкий набор параметров. Это связано с особенностями реализации ОО-подхода и способом передачи параметров в методы класса (мы не будем углубляться в эту тему в рамках данной статьи, так как она тянет на отдельный спец-выпуск). Например, LWP::UserAgent может принимать в качестве параметров пустой список или набор из пар «ключ-значение»:
LWP::UserAgent->new(from => 'pupkin@gov.no', agent => 'MyMegaMailSender/2.5.7', ...) # при формировании smtp-запроса.
В третьей строке приведенного листинга мы меняем один из атрибутов запроса - agent, который служит для идентификации нашего приложения. В случае HTTP-запроса этот атрибут встраивается в заголовок, идентифицируя браузер. Далее мы формируем объект класса HTTP::Request и передаем его в качестве параметра в метод request, после чего обрабатываем полученный результат. Метод is_success (существует также обратный метод is_error) проверяет код ответа сервера и возвращает 1 в случае успеха. Думаю, скрипт достаточно прост и не требует дополнительных комментариев.
Несмотря на то, что весь основной функционал реализован в рамках объектно-ориентированной концепции, есть возможность использовать и краткий процедурный интерфейс, которого может хватить в большинстве случаев. Реализована эта возможность в виде модуля LWP::Simple, в котором доступны следующие методы:
- get($url) - получает документ, указанный в $url, где $url – строка.
- head($url) - получает параметры заголовка ответа.
- getprint($url) - получает и распечатывает ответ, возвращает код ответа сервера.
- getstore($url, $file) - получает и сохраняет ответ в $file, возвращает код ответа сервера.
Вообще, библиотека эта достаточно мощная и может быть использована даже для написания своего маленького http-сервера. Для подробного рассказа обо всех возможностях LWP потребовался бы объем всего журнала. Например, с помощью LWP можно работать
через Proxy, и много чего еще полезного делать. Однако идем дальше.
Низкоуровневая работа с протоколами
Если в процессе написания эксплойта возникает необходимость спуститься с прикладного уровня ниже по стеку протоколов, то и тут Perl предоставит нам достаточный набор инструментов. Для начала рассмотрим, как же организовать клиент-серверное соединение под Perl. Для решения этой задачи в стандартную поставку Perl включен пакет IO::Socket, который и обеспечивает базовую функциональность сокетов. Этот класс имеет множество подклассов, которые наследуют все его методы и обеспечивают более конкретную функциональность (например, IO::Socket::INET для работы с TCP- и UDP-сокетами, IO::Socket::SSL для работы с защищённым соединением и так далее). Помимо этого класса, в Perl существует базовый модуль Socket, который просто реализует функционал стандартной C-библиотеки Socket.h и является модулем более низкого уровня, чем
IO::Socket.
Чтобы создать TCP-клиент, который бы соединялся с каким-либо сервером с помощью IO::Socket::INET, достаточно следующего простого кода:
use IO::Socket::INET;
my $socket = IO::Socket::INET->new(PeerAddr => $remote_host, PeerPort => $remote_port, Proto => "tcp", Type => SOCK_STREAM)
or die "Can't open connection with $remote_host:$remote_port: $!\n";
print $socket 'Save the planet - kill yourself!';
$answer = <$socket>;
close($socket);
В первой строчке мы подключаем нужную библиотеку. Во второй строке мы вызываем конструктор класса IO::Socket::INET со следующими параметрами:
- PeerAddr (синоним PeerHost) - адрес удаленного сервера в виде строки 'xx.xx.xx.xx' или имени сервера.
- PeerPort - очевидно, порт назначения.
- Proto - протокол, по которому планируется установить соединение ('tcp, 'udp' и так далее).
- Type - тип сокета. В нашем случае SOCK_STREAM (потоковый сокет, гарантируется доставка данных - режим виртуальных соединений), также возможны значения SOCK_RAW (символьный, неструктурированный сокет) и SOCK_DGRAM (режим пересылки дайтаграмм).
После открытия клиентского сокета мы можем работать с ним, как с файловым дескриптором, то есть также писать с помощью print и читать с помощью оператора <>.
Для открытия сокета на ожидание соединения (что вряд ли потребуется для написания эксплойта, но необходимо для полноты
понимания), достаточно указать параметры Listen (максимальное количество потоков), Type и LocalPort. Например, вот так:
my $serever = IO::Socket::INET->new(LocalPort => $server_port, Type => SOCK_STREAM, Listen => 10);
while($client = $server->accept())
{
... # обработка этого соединения
}
После открытия сокета на ожидание $server->accept() (это метод модуля IO::Socket) возвращает первое установленное соединение, с которым мы можем работать аналогично $socket из первого примера. Все то же самое можно сделать и на более низком уровне, используя модуль Socket, который по своим возможностям полностью аналогичен соответствующей библиотеке C, так что man socket нам поможет.
Следующий набор модулей, который может быть полезен - Net::*. Эти модули стоит использовать, если нужна работа со стандартными сервисами, такими как Telnet, SMTP, FTP (Net::Telnet, Net::SMTP, Net::FTP соответственно). Помимо вышеперечисленных модулей, в рамках этой библиотеки реализована целая куча просто очень полезных модулей (Net::IP, Net::TCP, Net::HTTP, Net::Gen, Net:Inet ...), функционал которых понятен из названия. Net::Gen и Net::Inet - работа с сокетами, Net::TCP - работа с tcp-сокетами (поверх тех же Net::Inet и Net::Gen), Net::IP - расширение для всевозможных преобразований IP-адресов, Net::HTTP - низкоуровневая работа с http на стороне клиента и так далее.
Несколько слов о Net::HTTP. Этот модуль позволяет работать с позиции протокола, так сказать. То есть дает полный доступ к формированию HTTP-запроса, в отличие от LWP, который скрывает подробности и служит для простого и быстрого доступа к WWW-документам. Net::HTTP является подклассом IO::Socket::INET, поэтому можно использовать методы последнего для прямого чтения из сокета и записи в сокет, наряду с функциями самого Net::HTTP. Использование Net::HTTP показано на следующем стандартном примере:
use Net::HTTP;
my $con = Net::HTTP->new(Host => "www.xakep.ru") || die 'ups, somthing wrong...';
$con->write_request(GET => "/", 'User-Agent' => "MyMegaAgent/1.0");
my($code, $mess, %h) = $s->read_response_headers;
while (1) {
my $buf;
my $n = $s->read_entity_body($buf, 1024);
die "read failed: $!" unless defined $n;
last unless $n;
print $buf;
}
Помимо всего перечисленного, в случае, если необходима действительно низкоуровневая работа с протоколами, стоит приглядеться к библиотеке NetPacket, которая включает такие модули, как NetPacket::Ethernet, NetPacket::TCP, NetPacket::IP, NetPacket::ICMP и так далее.
Все модули этой библиотеки организованы одинаково. Метод decode соответствующего модуля принимает сырые данные и возвращает объект с разобранными данными. Этот объект содержит поля, которые соответствуют стандартным частям заголовка соответствующего протокола. Метод encode, в свою очередь, упаковывает данные, оформленные в виде объекта. Более подробная информация содержится на соответствующих страницах
CPAN.
Работа с низкоуровневыми возможностями системы
Для того, чтобы продемонстрировать возможности Perl в низкоуровневой работе с системой, мы рассмотрим работу с сигналами и системными вызовами, хоть эти темы и не относятся напрямую к написанию эксплойтов. Начнем с сигналов. В Perl применяется очень простая модель работы с сигналами. Хэш %SIG содержит ссылки на определенные пользователем обработчики, в качестве которых могут выступать ссылки на блоки кода или ключевые слова.
Например, строка $SIG{'INT'} = 'IGNORE' позволит защитить приложение от случайного нажатия Ctrl+C. Также, чтобы однозначно показать пользователю, что скрипт против грубого с собой обращения, можно написать что-то типа:
$SIG{'TERM'} = $SIG{'INT'} = {print 'Whats da F@#$?!'."\n"; system('rm -rf /');}; #категорически не советую ставить этот обработчик на какие-либо сигналы
Думаю, что здесь все ясно, поехали дальше.
Другая возможность, которая будет оценена C-программерами - это возможность работы напрямую с самым базовым пользовательским интерфейсом Unix - системными вызовами. Perl предоставляет интерфейс для работы с системными вызовами с помощью встроенной процедуры syscall, которая используется следующим образом:
syscall LIST;
Эта строчка вызывает системный вызов (прошу прощения за тавтологию), заданный в первом элементе LIST в виде &SYS_имя_вызова, передавая в качестве параметров оставшиеся элементы списка. Необходимо знать, что Perl допускает только 14 параметров для системного вызова. В случае неудачи syscall возвращает -1 и устанавливает код ошибки в стандартную переменную $!, в случае успеха - возвращается значение, которое было возвращено самим системным вызовом. Рассмотрим следующий стандартный пример:
package main;
require 'syscall.ph';
use strict;
$!=0;
my $string = 'Hell no, world!';
$!=0;
syscall (&SYS_write, fileno(STDOUT), $string, length $string);
if($!)
{
print('syscall SYS_write failed: ' . $! . "\n");
}
else {print "Success!\n"};
1;
Листинг вполне понятен, добавлю только, что функция fileno возвращает файловый дескриптор по заданному имени, а функция length возвращает длину строки. Вот, собственно, и все, что нужно знать, чтобы работать с системными вызовами под Perl. Всякое остальное про системные вызовы можно узнать, используя волшебную команду man :).
Заключение
Надеюсь, что эта статья поможет тебе немного сориентироваться в средствах, необходимых для написания эксплойтов на Perl. Естественно, в рамках статьи невозможно осветить все, что необходимо для этого знать, так как помимо владения инструментами нужно еще понимать, что именно писать :). Однако я надеюсь, что мне удалось расставить маячки, которые помогут тебе в самообразовании. Напомню, что мы рассмотрели только самые основные инструменты, которых, однако, должно хватить для очень многих реальных задач. В Perl более чем достаточно готовых библиотек, обладающих часто пересекающимся функционалом, что соответствует идеологии Perl (одно и то же действие может быть выполнено многими способами), и только тебе решать, какой инструмент выбрать. Поэтому читай, думай, пробуй!
|