Салимóненко Дмитрий Александрович

Вычислительные Сети, Системы и Телекоммуникации

ЗАДАНИЕ 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), что называется, далеко не уедешь. Ведь необходим, собственно, транспорт, т.е. передача сообщения от одного интерфейса на другой. Поэтому и в первом случае (для IPPROTO_RAW) также будет использован какой-нибудь транспортный протокол. Какой же? UDP, разумеется. Так как он - более простой, по сравнению с ТСР. Условно говоря, задав IPPROTO_RAW, мы как бы конструируем заголовки не только транспортного протокола, но и сетевого. Хотя, конечно, на прикладном уровне (а именно на нем будет работать написанная Вами программа, даже если Вы ее запустите из-под администратора, т.е. из-под root) возможности по конфигурированию сетевого и транспортного протоколов (т.е. по изменению их заголовков) достаточно ограничены - даже в Linux. А в Windows, кстати. начиная где-то с Windows Vista, осуществляется постепенный и планомерный запрет на такое конфигурирование, т.е. на "ручную" работу с заголовками протоколов. Видимо, так делается с целью закрыть возможные пути появления уязвимостей в ОС.

А как быть, если все-таки необходимо более-менее существенное изменение заголовков, вплоть до создания своих версий сетевых и транспортных протоколов? Выход здесь, пожалуй, один: вносить изменения в состав ядра ОС и/или сетевого драйвера.

Программный код, задающий нестандартные заголовки протокола IP

Посмотрим, как можно задать (некоторые) нестандартные заголовки протокола IP:

  1. #include "sys/types.h" /*включаем заголовки*/
  2. #include "sys/socket.h"
  3. #include "arpa/inet.h"
  4. #if defined(__i386__) && defined (__linux__) /*если исходный код компилируем из под linux'a, то следует использовать заголовки BSD*/
  5. #define __FAVOR_BSD
  6. #include "netinet/ip.h"
  7. #include "netinet/ip_icmp.h"
  8. #endif

  1. #include "netinet/ip.h"
  2. #include "netinet/ip_icmp.h"
  3. #include "errno.h"
  4. #include "stdlib.h"
  5. #include "stdio.h"
  6. #include "string.h"
  7. #include "fcntl.h"

  1. #define ICMP IPPROTO_ICMP
  2. #define IPCPM_SZ sizeof(struct ip) + sizeof(struct icmp) /*размер ip+icmp протокола*/
  3. #define IP_SZ sizeof(struct ip) /*размер ip протокола*/

  1. int sock;
  2. int on = 1;
  3. unsigned short icmp_pack();
  4. char packet[IPCPM_SZ]; /*пакет, который мы будем посылать[размер протоколов]*/

  1. struct ip *ip_head = (struct ip*)packet; /*заталкиваем ip-header в наш пакет*/
  2. struct icmp *icmp_head = (struct icmp*)packet + IP_SZ; /*заталкиваем iсmp-header в наш пакет*/
  3. struct sockaddr_in sins; /*структура адресата */
  4. struct in_addr inp;

  1. int main(){

  1. setsockopt(sock, IPPROTO_IP, IP_HDRINCL, (char*)&on, sizeof(sock)); /*это мы сделали, для того, чтобы использовать
  2. нашу конфигурацию протокола IP, а не ядерную (т.е. не kernel'овскую) */
  3. sock = socket(AF_INET, SOCK_RAW, IPPROTO_RAW); /* создаем сокет-raw на нашем протоколе, похожем на IP */

  1. if(sock == 0) {
  2. perror("socket-icmp");
  3. }

  1. ip_head->ip_hl = 5; /*размер заголовка*/
  2. ip_head->ip_v = 4; /*версия протокола ip*/
  3. ip_head->ip_tos = 0; /*тип сервиса*/
  4. ip_head->ip_len = IP_SZ; /*размер ip*/
  5. ip_head->ip_id = htons(1234); /*идентификатор (можно установить любой)*/
  6. ip_head->ip_off = 0; /*фрагментация (если 0, то ядро само вычислит)*/
  7. ip_head->ip_ttl = 231; /*Time To Live - кол-во роутеров, через которое пройдет наш пакет. По умолчанию 255, но мы устанавливаем 231*/
  8. ip_head->ip_p = ICMP; /*используемый протокол*/
  9. ip_head->ip_sum = 0; /*контрольная сумма*/
  10. inet_aton("54.4.2.1", &inp);/*подделываем адрес отправителя (т.е. наш "адрес"). В общем-то, можно поставить любой другой, это без разницы */
  11. ip_head->ip_src = inp;
  12. inet_aton("127.0.0.1", &inp);/* Адрес назначения. Так как работаем на localhost, то, соответственно, указываем локальный адрес */
  13. ip_head->ip_dst = inp;

  1. sins.sin_family = AF_INET;
  2. sins.sin_addr.s_addr = htonl(INADDR_LOOPBACK);/* А здесь тоже задаем адрес назначения, как и раньше, для UDP/ТСР-сокетов */
  3. sins.sin_port = htons(3425);  

  1. icmp_pack(); /*функция построения пакета icmp*/
  2. printf("Begin.....\n");
  3. printf("%d \n", (int)sendto(sock, packet, IPCPM_SZ, 0,
  4. (struct sockaddr*)&sins, sizeof(sins))); // Отправляем заголовок на localhost и выводим в консоль количество отправленных байтов
  5. }

  1. unsigned short icmp_pack() {
  2. icmp_head->icmp_type = ICMP_ECHO; /*тип запроса, в нашем случае типа запроса утилиты ping*/
  3. icmp_head->icmp_cksum= 0; /*контрольная сумма для протокола icmp*/

  1. return 0;
  2. }

Сохраняем этот код в файле, например, 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-адрес.

В нашем случае сообщение программа отправали, по сути, в никуда. Ну, что значит, в никуда?... Понятно, что сообщение вначале в любом случае попало ядру ОС (попутно это сообщение перехватит утилита tcpdump). Оно начало поиск с целью выяснить - есть ли какая-либо загруженная и выполняющаяся программа, открывшая на прослушивание сокет на порту 3425 (см. строчку 49 программы)? Очевидно, что так как мы никакого сервера не запускали, то ядро такой программы найти, скорее всего, не сможет. Если только какая-то другая программа не открыла сокет на этом порту. Стало быть, ядро, прекратив поиски, отправит запрос, в самом деле, в никуда (точнее, на устройство null). И этим дело закончится.

А теперь представьте, что у нас все-таки был бы работающий сервер, который как раз открыл сокет на прослушивание на порту 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.


С уважением, Салимоненко Д.А.