Салимóненко Дмитрий Александрович
ЗАДАНИЕ 10: Работаем с сырыми сокетами LINUX
СОДЕРЖАНИЕ
Введение
В этом задании Вы попробуете расширить свои умения по работе с сырыми сокетами Linux. А именно – создавать свои (пользовательские) заголовки для протокола IP и отправлять соответствующие сообщения.
Что такое сырой сокет
О сырых сокетах немного рассказано в методических указаниях. Создаются они примерно так:
sock = socket(AF_INET, SOCK_RAW, IPPROTO_RAW);
Здесь параметр SOCK_RAW как раз и задает сырой сокет. А вот параметр IPPROTO_RAW задает еще и сырой протокол. Конечно, он не совсем сырой, так как согласно положению в иерархии модели OSI это будет, в любом случае, сетевой протокол (т.е. аналог протокола IP). А вот заголовки у него придется задавать ВРУЧНУЮ. Этим он, кстати, отличается от сокета, созданного несколько иначе:
sock = socket(AF_INET, SOCK_RAW, IPPROTO_UDP);
В последнем случае, все-таки, используется протокол UDP. И заголовки нужно будет задавать (так как используется SOCK_RAW) именно для него (а не для IP).
А как быть, если все-таки необходимо более-менее существенное изменение заголовков, вплоть до создания своих версий сетевых и транспортных протоколов? Выход здесь, пожалуй, один: вносить изменения в состав ядра ОС и/или сетевого драйвера.
Программный код, задающий нестандартные заголовки протокола IP
Посмотрим, как можно задать (некоторые) нестандартные заголовки протокола IP:
- #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) + sizeof(struct icmp) /*размер ip+icmp протокола*/
- #define IP_SZ sizeof(struct ip) /*размер ip протокола*/
- int sock;
- int on = 1;
- unsigned short icmp_pack();
- char packet[IPCPM_SZ]; /*пакет, который мы будем посылать[размер протоколов]*/
- struct ip *ip_head = (struct ip*)packet; /*заталкиваем ip-header в наш пакет*/
- struct icmp *icmp_head = (struct icmp*)packet + IP_SZ; /*заталкиваем iсmp-header в наш пакет*/
- struct sockaddr_in sins; /*структура адресата */
- struct in_addr inp;
- int main(){
- setsockopt(sock, IPPROTO_IP, IP_HDRINCL, (char*)&on, sizeof(sock)); /*это мы сделали, для того, чтобы использовать
- нашу конфигурацию протокола IP, а не ядерную (т.е. не kernel'овскую) */
- sock = socket(AF_INET, SOCK_RAW, IPPROTO_RAW); /* создаем сокет-raw на нашем протоколе, похожем на IP */
- 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(1234); /*идентификатор (можно установить любой)*/
- ip_head->ip_off = 0; /*фрагментация (если 0, то ядро само вычислит)*/
- ip_head->ip_ttl = 231; /*Time To Live - кол-во роутеров, через которое пройдет наш пакет. По умолчанию 255, но мы устанавливаем 231*/
- ip_head->ip_p = ICMP; /*используемый протокол*/
- ip_head->ip_sum = 0; /*контрольная сумма*/
- inet_aton("54.4.2.1", &inp);/*подделываем адрес отправителя (т.е. наш "адрес"). В общем-то, можно поставить любой другой, это без разницы */
- ip_head->ip_src = inp;
- inet_aton("127.0.0.1", &inp);/* Адрес назначения. Так как работаем на localhost, то, соответственно, указываем локальный адрес */
- ip_head->ip_dst = inp;
- sins.sin_family = AF_INET;
- sins.sin_addr.s_addr = htonl(INADDR_LOOPBACK);/* А здесь тоже задаем адрес назначения, как и раньше, для UDP/ТСР-сокетов */
- sins.sin_port = htons(3425);
- icmp_pack(); /*функция построения пакета icmp*/
- printf("Begin.....\n");
- printf("%d \n", (int)sendto(sock, packet, IPCPM_SZ, 0,
- (struct sockaddr*)&sins, sizeof(sins))); // Отправляем заголовок на localhost и выводим в консоль количество отправленных байтов
- }
- unsigned short icmp_pack() {
- icmp_head->icmp_type = ICMP_ECHO; /*тип запроса, в нашем случае типа запроса утилиты ping*/
- icmp_head->icmp_cksum= 0; /*контрольная сумма для протокола icmp*/
- return 0;
- }
Сохраняем этот код в файле, например, client.c
и компилируем его:
gcc -Wall -o client client.c
Как проконтролировать работу этой программы?
Вообще-то, неплохо было бы написать для нее соответствующий сервер. Но, пока обойдемся более простым способом: используем утилиту tcpdump. Итак, открываем еще одну консоль, на ней запускаем sudo tcpdump -i any -vvv -X
. Вводим пароль, если еще не вводили раньше.
После этого возвращаемся к первой консоли (где только что компилировали нашу программу) и запускаем ее обычным образом:
./client
Программа выведет:
Begin.....
48
Т.е. программа отправила на локальный интерфейс (IP_адрес 127.0.0.1) 48 байтов. А вот во второй консоли tcpdump выведет несколько более обширную и интересную информацию, посмотрим ее:
tcpdump: listening on any, link-type LINUX_SLL (Linux cooked), capture size 262144 bytes
00:16:10.823831 IP (tos 0x0, ttl 231, id 1234, offset 0, flags [none], proto ICMP (1), length 48)
54.4.2.1 > localhost: ICMP echo reply, id 0, seq 0, length 28 (wrong icmp cksum 0 (->ffff)!)
0x0000: 4500 0030 04d2 0000 e701 17f5 3604 0201 E..0........6...
0x0010: 7f00 0001 0000 0000 0000 0000 0000 0000 ................
0x0020: 0000 0000 0000 0000 0000 0000 0000 0000 ................
00:16:10.824591 IP (tos 0x0, ttl 64, id 52509, offset 0, flags [DF], proto UDP (17), length 67)
localhost.52654 > qwer-Virt.domain: [bad udp cksum 0xff42 -> 0x1151!] 39544+ PTR? 1.2.4.54.in-addr.arpa. (39)
0x0000: 4500 0043 cd1d 4000 4011 6e8a 7f00 0001 E..C..@.@.n.....
0x0010: 7f00 0101 cdae 0035 002f ff42 9a78 0100 .......5./.B.x..
0x0020: 0001 0000 0000 0000 0131 0132 0134 0235 .........1.2.4.5
0x0030: 3407 696e 2d61 6464 7204 6172 7061 0000 4.in-addr.arpa..
0x0040: 0c00 01 ...
00:16:10.824669 IP (tos 0x0, ttl 64, id 16500, offset 0, flags [DF], proto UDP (17), length 67)
10.0.2.15.36783 > 10.0.0.1.domain: [bad udp cksum 0x1650 -> 0x2a82!] 43065+ PTR? 1.2.4.54.in-addr.arpa. (39)
0x0000: 4500 0043 4074 4000 4011 e426 0a00 020f E..C@t@.@..&....
0x0010: 0a00 0001 8faf 0035 002f 1650 a839 0100 .......5./.P.9..
0x0020: 0001 0000 0000 0000 0131 0132 0134 0235 .........1.2.4.5
0x0030: 3407 696e 2d61 6464 7204 6172 7061 0000 4.in-addr.arpa..
0x0040: 0c00 01 ...
00:16:11.044466 IP (tos 0x0, ttl 64, id 58080, offset 0, flags [none], proto UDP (17), length 136)
10.0.0.1.domain > 10.0.2.15.36783: [udp sum ok] 43065 NXDomain q: PTR? 1.2.4.54.in-addr.arpa. 0/1/0 ns: 54.in-addr.arpa. [1h] SOA z.arin.net. dns-ops.arin.net. 2017030346 1800 900 691200 10800 (108)
0x0000: 4500 0088 e2e0 0000 4011 8175 0a00 0001 E.......@..u....
0x0010: 0a00 020f 0035 8faf 0074 0c46 a839 8183 .....5...t.F.9..
0x0020: 0001 0000 0001 0000 0131 0132 0134 0235 .........1.2.4.5
0x0030: 3407 696e 2d61 6464 7204 6172 7061 0000 4.in-addr.arpa..
0x0040: 0c00 0102 3534 0769 6e2d 6164 6472 0461 ....54.in-addr.a
0x0050: 7270 6100 0006 0001 0000 0e10 002a 017a rpa..........*.z
0x0060: 0461 7269 6e03 6e65 7400 0764 6e73 2d6f .arin.net..dns-o
0x0070: 7073 c044 7839 70ca 0000 0708 0000 0384 ps.Dx9p.........
0x0080: 000a 8c00 0000 2a30 ......*0
00:16:11.044633 IP (tos 0x0, ttl 64, id 11338, offset 0, flags [DF], proto UDP (17), length 136)
qwer-Virt.domain > localhost.52654: [bad udp cksum 0xff87 -> 0xf314!] 39544 NXDomain q: PTR? 1.2.4.54.in-addr.arpa. 0/1/0 ns: 54.in-addr.arpa. [1h] SOA z.arin.net. dns-ops.arin.net. 2017030346 1800 900 691200 10800 (108)
0x0000: 4500 0088 2c4a 4000 4011 0f19 7f00 0101 E...,J@.@.......
0x0010: 7f00 0001 0035 cdae 0074 ff87 9a78 8183 .....5...t...x..
0x0020: 0001 0000 0001 0000 0131 0132 0134 0235 .........1.2.4.5
0x0030: 3407 696e 2d61 6464 7204 6172 7061 0000 4.in-addr.arpa..
0x0040: 0c00 0102 3534 0769 6e2d 6164 6472 0461 ....54.in-addr.a
0x0050: 7270 6100 0006 0001 0000 0e10 002a 017a rpa..........*.z
0x0060: 0461 7269 6e03 6e65 7400 0764 6e73 2d6f .arin.net..dns-o
0x0070: 7073 c044 7839 70ca 0000 0708 0000 0384 ps.Dx9p.........
0x0080: 000a 8c00 0000 2a30 ......*0
00:16:11.045083 IP (tos 0x0, ttl 64, id 52529, offset 0, flags [DF], proto UDP (17), length 67)
localhost.36442 > qwer-Virt.domain: [bad udp cksum 0xff42 -> 0x2c8f!] 49816+ PTR? 1.0.0.10.in-addr.arpa. (39)
0x0000: 4500 0043 cd31 4000 4011 6e76 7f00 0001 E..C.1@.@.nv....
0x0010: 7f00 0101 8e5a 0035 002f ff42 c298 0100 .....Z.5./.B....
0x0020: 0001 0000 0000 0000 0131 0130 0130 0231 .........1.0.0.1
0x0030: 3007 696e 2d61 6464 7204 6172 7061 0000 0.in-addr.arpa..
0x0040: 0c00 01 ...
00:16:11.045288 IP (tos 0x0, ttl 64, id 16520, offset 0, flags [DF], proto UDP (17), length 67)
10.0.2.15.36783 > 10.0.0.1.domain: [bad udp cksum 0x1650 -> 0xbfe7!] 5854+ PTR? 1.0.0.10.in-addr.arpa. (39)
0x0000: 4500 0043 4088 4000 4011 e412 0a00 020f E..C@.@.@.......
0x0010: 0a00 0001 8faf 0035 002f 1650 16de 0100 .......5./.P....
0x0020: 0001 0000 0000 0000 0131 0130 0130 0231 .........1.0.0.1
0x0030: 3007 696e 2d61 6464 7204 6172 7061 0000 0.in-addr.arpa..
0x0040: 0c00 01
Так, видим параметры ttl 231, id 1234
, которые мы установили в программе. Т.е. соответствующие параметры заголовка протокола IP приняли именно эти, нами установленные значения. Также видим запись length 48
- это количество байтов, которые отправила наша программа (заголовок + данные; в нашем случае - это данные протокола ICPM.
Также видим 54.4.2.1 > localhost
. Т.е., в самом деле, наш IP-адрес, с которого наша программа, якобы, отправляла сообщение, в самом деле подменился. И вместо 127.0.0.1
стал, как видим, равным 54.4.2.1
. Т.е. нам удалось подделать IP-адрес.
А теперь представьте, что у нас все-таки был бы работающий сервер, который как раз открыл сокет на прослушивание на порту 3425. Как Вы думаете, ЧТО произошло бы? При том, что сервер-то считал бы, что запрос пришел ему вовсе не от локального клиента (т.е. не с адреса
127.0.0.1
), а из сети интернет с IP-адреса 54.4.2.1
. Так вот, подумайте дальше, ЧТО произошло бы и распишите соответствующий сценарий.
Как видно, по мере того, как сообщение пересылается по разным адресатам ( 54.4.2.1 > localhost; 10.0.2.15 > 10.0.0.1 и затем - в обратном порядке), его размер вначале увеличивается, а затем - снижается. Также обратите внимание, на каких стадиях делаются ARP-запросы.
Выводы
Таким образом, у нас получилось изменить заголовки протокола IP и послать сообщение с этими заголовками. Это сообщение было перехвачено утилитой tcpdump. В процессе передачи сообщения оно переходило нескольким адресатам в пределах локальной сети с маской 10.0.0.0.
Практическое задание
Следует сделать для этой программы (являющейся, очевидно, клиентом) программу-сервер. Которая принимала бы сообщение и выводило его, как обычно, в свою консоль.
Примечание. Имейте в виду, что сырые сокеты домена интернет (т.е. AF_INET + SOCK_RAW
) способны только отправлять сообщения, но НЕ принимать их. Для приема следует использовать пакетные сокеты, относящиеся уже даже не к сетевому, а к канальному уровню модели OSI.
С уважением, Салимоненко Д.А.