0x443 Декодирование уровней
В наших перехваченных пакетах самым внешним уровнем, а также са- мым низким из видимых, является Ethernet. На этом уровне происхо- дит передача данных между устройствами Ethernet с помощью MAC- адресов. Заголовок этого уровня содержит MAC-адреса отправителя и получателя, а также 16-разрядное число, описывающее тип Ethernet- пакета. В Linux структура этого заголовка определена в /usr/include/
linux/if_ethernet.h, а структуры заголовков IP и TCP находятся в /usr/
include/netinet/ip.h и /usr/include/netinet/tcp.h соответственно. В исхо- дном коде tcpdump тоже есть структуры этих заголовков, и с таким же успехом мы могли создать их сами по RFC, где они описаны. Чтобы лучше разобраться в материале, полезно написать эти структуры само- стоятельно, поэтому, руководствуясь описаниями, создадим собствен- ные структуры заголовков, которые поместим в hacking-network.h.
0x440 Анализ сетевых пакетов (сниффинг)
259
Сначала посмотрим, какое описание заголовка Ethernet у нас уже есть.
Фрагмент /usr/include/if_ether.h
#define ETH_ALEN 6 /* Байтов в Ethernet-адресе */
#define ETH_HLEN 14 /* Всего байтов в заголовке */
/*
* Заголовок Ethernet-пакета.
*/
struct ethhdr {
unsigned char h_dest[ETH_ALEN]; /* Адрес получателя */
unsigned char h_ source[ETH_ALEN]; /* Адрес отправителя */
__be16 h_proto; /* Идентификатор типа пакета */
} __attribute__((packed));
В этой структуре есть три элемента заголовка Ethernet. Тип перемен- ной _
_be16 оказывается 16-разрядным unsigned short integer. Это вы- ясняется, если рекурсивно выполнить grep определения типа во вклю- чаемых файлах.
reader@hacking:/booksrc $
$ grep -R “typedef.*__be16” /usr/include
/usr/include/linux/types.h:typedef __u16 __bitwise __be16;
$ grep -R “typedef.*__u16” /usr/include | grep short
/usr/include/linux/i2o-dev.h:typedef unsigned short __u16;
/usr/include/linux/cramfs_fs.h:typedef unsigned short __u16;
/usr/include/asm/types.h:typedef unsigned short __u16;
$
Во включаемом файле также определен размер Ethernet-заголовка ETH_
HLEN
, равный 14 байт. Это совпадает с суммой размеров MAC-адресов от- правителя и получателя, каждый из которых имеет длину 6 байт, и поля типа пакета, имеющего тип 16-разрядного короткого целого, занима- ющего 2 байта. Однако многие компиляторы выравнивают структуры по 4-байтным границам путем дописки, вследствие чего sizeof(struct ethhdr)
возвращает неверный размер. По этой причине размер Ethernet- заголовка следует брать из ETH_HLEN или как фиксированное значение
14 байт.
В результате включения будут также включены дру- гие файлы, содержащие необходимое определение типа _
_be16. Так как мы хотим создать собственные структуры для hacking-network.h, ссыл- ки на неизвестные типы нужно удалить. Заодно дадим полям структу- ры более удачные имена.
Добавлено в hacking-network.h
#define ETHER_ADDR_LEN 6
#define ETHER_HDR_LEN 14
260
0x400Сетевое взаимодействие struct ether_hdr {
unsigned char ether_dest_addr[ETHER_ADDR_LEN]; // MAC-адрес получателя unsigned char ether_src_addr[ETHER_ADDR_LEN]; // MAC-адрес отправителя unsigned short ether_type; // Тип пакета Ethernet
};
То же самое проделаем для структур IP и TCP, основываясь на соответ- ствующих структурах и диаграммах в RFC.
Фрагмент /usr/include/netinet/ip.h
struct iphdr
{
#if __BYTE_ORDER == __LITTLE_ENDIAN
unsigned int ihl:4;
unsigned int version:4;
#elif __BYTE_ORDER == __BIG_ENDIAN
unsigned int version:4;
unsigned int ihl:4;
#else
# error “Please fix ”
#endif u_int8_t tos;
u_int16_t tot_len;
u_int16_t id;
u_int16_t frag_off;
u_int8_t ttl;
u_int8_t protocol;
u_int16_t check;
u_int32_t saddr;
u_int32_t daddr;
/* Здесь начинаются параметры. */
};
Фрагмент RFC 791
0 1 2 3 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|Version| IHL |Type of Service| Total Length |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Identification |Flags| Fragment Offset |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Time to Live | Protocol | Header Checksum |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Source Address |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Destination Address |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Options | Padding |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
Пример заголовка интернет-дейтаграммы
0x440 Анализ сетевых пакетов (сниффинг)
261Каждый элемент структуры соответствует полям, показанным на ди- аграмме заголовка в RFC. Так как размер первых двух полей Version и IHL (от Internet Header Length – размер интернет-заголовка) всего в 4 бита, а в языке С нет 4-разрядных
типов переменных, в определе- нии заголовка, взятом из Linux, байт делится по-разному – в зависи- мости от порядка байтов, принятого в архитектуре машины. Эти поля записаны в сетевом порядке байтов, поэтому в архитектуре «сначала младший байт» (little-endian) IHL расположен перед Version, посколь- ку порядок байтов инвертирован. В наших программах эти поля не ис- пользуются, поэтому даже делить байт пополам нам не нужно.
Добавлено в hacking-network.hstruct ip_hdr {
unsigned char ip_version_and_header_length; // Версия и размер заголовка unsigned char ip_tos; // Тип сервиса unsigned short ip_len; // Общая длина unsigned short ip_id; // Идентификатор unsigned short ip_frag_offset; // Смещение сегмента и флаги unsigned char ip_ttl; // Максимальный срок существования unsigned char ip_type; // Протокол unsigned short ip_checksum; // Контрольная сумма unsigned int ip_src_addr; // IP-адрес отправителя unsigned int ip_dest_addr; // IP-адрес получателя
};
Как уже говорилось, компилятор дополнит структуру, чтобы выров- нять ее по 4-байтной границе. Размер IP-заголовка всегда 20 байт.
Структуру TCP-заголовка мы возьмем из
/usr/include/netinet/tcp.h, а его диаграмму – из RFC 793.
Фрагмент /usr/include/netinet/tcp.htypedef u_int32_t tcp_seq;
/*
* TCP header.
* Per RFC 793, September, 1981.
*/
struct tcphdr
{
u_int16_t th_sport; /* Source Port */
u_int16_t th_dport; /* Destination Port */
tcp_seq th_seq; /* Sequence Number */
tcp_seq th_ack; /* Acknowledgment Number */
# if __BYTE_ORDER == __LITTLE_ENDIAN
u_int8_t th_x2:4; /* (резерв) */
u_int8_t th_off:4; /* Data Offset */
# endif
# if __BYTE_ORDER == __BIG_ENDIAN
u_int8_t th_off:4; /* Data Offset */
u_int8_t th_x2:4; /* (резерв) */
2620x400Сетевое взаимодействие
# endif u_int8_t th_flags;
# define TH_FIN 0x01
# define TH_SYN 0x02
# define TH_RST 0x04
# define TH_PUSH 0x08
# define TH_ACK 0x10
# define TH_URG 0x20
u_int16_t th_win; /* Window */
u_int16_t th_sum; /* Checksum */
u_int16_t th_urp; /* Urgent Pointer */
};
Фрагмент RFC 793Формат TCP-заголовка
0 1 2 3 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Source Port | Destination Port |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Sequence Number |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Acknowledgment Number |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Data | |U|A|P|R|S|F| |
| Offset| (резерв) |R|C|S|S|Y|I| Window |
| | |G|K|H|T|N|N| |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Checksum | Urgent Pointer |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Options | Padding |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| data |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
Data Offset: 4 бита
Количество 32-разрядных слов в TCP-заголовке. Указывает начало данных. Размер заголовка TCP (даже с опциями)
всегда кратен 32 битам.
Reserved: 6 бит
Зарезервировано на будущее. Должны быть нули.
Options: переменный размер
1
В структуре tcphdr из Linux также переключается порядок 4-разрядно- го поля смещения данных и 4-разрядного поля резерва в зависимости
1
Source Port – порт отправителя, Destination Port– порт получателя, Se- quence Number – порядковый номер, Acknowledgment Number –
номер подтверждения, Data Offset – смещение данных, Window – размер окна,
Checksum – контрольная сумма, Urgent Pointer – указатель срочности, Op- tions – параметры, Padding – дополнение пробелами. –
Прим. перев.
0x440 Анализ сетевых пакетов (сниффинг)
263
от архитектуры узла. Поле смещения данных (Data Offset) для нас су- щественно, потому что оно сообщает размер TCP-заголовка, который может меняться. Как вы могли заметить, в Linux в структуре tcphdr не оставлено места для опций TCP. Это связано с тем, что в RFC это поле определено как необязательное. Заголовок TCP всегда выравнивает- ся по 32 битам, и из смещения мы узнаем, сколько 32-разрядных слов в заголовке. Таким образом, размер TCP-заголовка в байтах равен зна- чению поля Data Offset, умноженному на 4. Так как поле смещения не- обходимо нам для вычисления размера заголовка, разобьем содержа- щий его байт, предположив, что байты в архитектуре узла располага- ются в порядке «сначала младший».
Поле th_flags в структуре tcphdr определено как unsigned character
(8 разрядов). Значения, определяемые ниже этого поля, представляют собой битовые маски, соответствующие шести возможным флагам.
Добавлено в hacking-network.h
struct tcp_hdr {
unsigned short tcp_src_port; // TCP: порт отправителя unsigned short tcp_dest_port; // TCP: порт получателя unsigned int tcp_seq; // TCP: порядковый номер unsigned int tcp_ack; // TCP: номер подтверждения unsigned char reserved:4; // 4 из 6 бита резервного поля unsigned char tcp_offset:4; // TCP: смещение данных для узла
“сначала младший”
unsigned char tcp_flags; // TCP: флаги (и 2 бита из резерва)
#define TCP_FIN 0x01
#define TCP_SYN 0x02
#define TCP_RST 0x04
#define TCP_PUSH 0x08
#define TCP_ACK 0x10
#define TCP_URG 0x20
unsigned short tcp_window; // TCP: размер окна unsigned short tcp_checksum; // TCP: контрольная сумма unsigned short tcp_urgent; // TCP: указатель срочности
};
Теперь, определив все заголовки в виде структур, мы можем написать программу, осуществляющую декодирование инкапсулированных за- головков каждого пакета. Но сначала задержимся немного на libpcap.
В этой библиотеке есть функция pcap_loop(), которая выполняет пе- рехват пакетов лучше, чем циклический вызов pcap_next(). Функция pcap_next()
очень редко используется в программах, поскольку ее при- менение некрасиво и неэффективно. Функция pcap_loop() использует функцию обратного вызова. Это значит, что функции pcap_loop() в ка- честве аргумента передается указатель на функцию, которая будет вы- зываться при каждом перехвате пакета. Прототип pcap_loop():
int pcap_loop(pcap_t *handle, int count, pcap_handler callback, u_char *args);
2640x400Сетевое взаимодействие
Первый аргумент – дескриптор pcap, затем задаются количество паке- тов, которые нужно перехватить, и указатель на функцию обратного вызова. Если задать количество пакетов равным –1, цикл будет про- должаться бесконечно. Последний аргумент –
необязательный указа- тель, который будет передан программе обратного вызова. Естествен- но, функции обратного вызова должен соответствовать некоторый прототип, поскольку pcap_loop() должна ее вызывать. Имя функции обратного вызова может быть любым, но аргументы должны быть та- кими:
void callback(u_char *args, const struct pcap_pkthdr *cap_header, const u_char *packet);
Первый аргумент – необязательный указатель, передаваемый pcap_
loop()
в качестве последнего аргумента. С его помощью можно пере- дать дополнительную информацию, но нам это не понадобится. Следу- ющие два аргумента знакомы нам по pcap_next() – это указатели на за- головок перехвата и на сам пакет.
В следующем примере функция pcap_loop() и функция обратного вызо- ва осуществляют перехват пакетов и заполнение наших структур заго- ловков для их последующего декодирования. Объяснение работы про- граммы приведено после листинга.
decode_sniff.c#include
#include “hacking.h”
#include “hacking-network.h”
void pcap_fatal(const char *, const char *);
void decode_ethernet(const u_char *);
void decode_ip(const u_char *);
u_int decode_tcp(const u_char *);
void caught_packet(u_char *, const struct pcap_pkthdr *, const u_char *);
int main() {
struct pcap_pkthdr cap_header;
const u_char *packet, *pkt_data;
char errbuf[PCAP_ERRBUF_SIZE];
char *device;
pcap_t *pcap_handle;
device = pcap_lookupdev(errbuf);
if(device == NULL)
pcap_fatal(“pcap_lookupdev”, errbuf);
printf(“Sniffing on device %s\n”, device);
0x440 Анализ сетевых пакетов (сниффинг)
265
pcap_handle = pcap_open_live(device, 4096, 1, 0, errbuf);
if(pcap_handle == NULL)
pcap_fatal(“pcap_open_live”, errbuf);
pcap_loop(pcap_handle, 3, caught_packet, NULL);
pcap_close(pcap_handle);
}
В начале программы объявлен прототип функции обратного вызо- ва с именем caught_packet(), а также несколько функций декодирова- ния. Все остальное в main() осталось практически прежним, только цикл for заменен одним вызовом pcap_loop(). Этой функции передают- ся pcap_handle, предписание перехватить 3 пакета и указатель на функ- цию обратного вызова caught_packet(). Последним аргументом являет- ся NULL, потому что дополнительных данных для передачи в caught_
packet()
нет. Заметим, что decode_tcp() возвращает u_int. Посколь- ку длина TCP-заголовка переменная, эта функция возвращает размер
TCP-заголовка.
void caught_packet(u_char *user_args, const struct pcap_pkthdr *cap_header, const u_char *packet) {
int tcp_header_length, total_header_size, pkt_data_len;
u_char *pkt_data;
printf(“==== Got a %d byte packet ====\n”, cap_header->len);
decode_ethernet(packet);
decode_ip(packet+ETHER_HDR_LEN);
tcp_header_length = decode_tcp(packet+ETHER_HDR_LEN+sizeof(struct ip_hdr));
total_header_size = ETHER_HDR_LEN+sizeof(struct ip_hdr)+tcp_header_length;
pkt_data = (u_char *)packet + total_header_size; // pkt_data указывает
// на данные.
pkt_data_len = cap_header->len - total_header_size;
if(pkt_data_len > 0) {
printf(“\t\t\t%u bytes of packet data\n”, pkt_data_len);
dump(pkt_data, pkt_data_len);
} else printf(“\t\t\tNo Packet Data\n”);
}
void pcap_fatal(const char *failed_in, const char *errbuf) {
printf(“Fatal Error in %s: %s\n”, failed_in, errbuf);
exit(1);
}
Функция caught_packet() вызывается, как только pcap_loop() перехва- тит пакет. Эта функция использует размеры заголовков, чтобы разде-
2660x400Сетевое
взаимодействие лить пакет по уровням, и функции декодирования, чтобы вывести де- тали заголовков каждого уровня.
void decode_ethernet(const u_char *header_start) {
int i;
const struct ether_hdr *ethernet_header;
ethernet_header = (const struct ether_hdr *)header_start;
printf(“[[ Layer 2 :: Ethernet Header ]]\n”);
printf(“[source: %02x”, ethernet_header->ether_src_addr[0]);
for(i=1; i < ETHER_ADDR_LEN; i++)
printf(“:%02x”, ethernet_header->ether_src_addr[i]);
printf(“\tDest: %02x”, ethernet_header->ether_dest_addr[0]);
for(i=1; i < ETHER_ADDR_LEN; i++)
printf(“:%02x”, ethernet_header->ether_dest_addr[i]);
printf(“\tType: %hu ]\n”, ethernet_header->ether_type);
}
void decode_ip(const u_char *header_start) {
const struct ip_hdr *ip_header;
ip_header = (const struct ip_hdr *)header_start;
printf(“\t(( Layer 3 ::: IP Header ))\n”);
printf(“\t(Source: %s\t”, inet_ntoa(ip_header->ip_src_addr));
printf(“Dest: %s )\n”, inet_ntoa(ip_header->ip_dest_addr));
printf(“\t( Type: %u\t”, (u_int) ip_header->ip_type);
printf(“ID: %hu\tLength: %hu )\n”, ntohs(ip_header->ip_id), ntohs(ip_header->ip_len));
}
u_int decode_tcp(const u_char *header_start) {
u_int header_size;
const struct tcp_hdr *tcp_header;
tcp_header = (const struct tcp_hdr *)header_start;
header_size = 4 * tcp_header->tcp_offset;
printf(“\t\t{{ Layer 4 :::: TCP Header }}\n”);
printf(“\t\t{ Src Port: %hu\t”, ntohs(tcp_header->tcp_src_port));
printf(“Dest Port: %hu }\n”, ntohs(tcp_header->tcp_dest_port));
printf(“\t\t{ Seq #: %u\t”, ntohl(tcp_header->tcp_seq));
printf(“Ack #: %u }\n”, ntohl(tcp_header->tcp_ack));
printf(“\t\t{ Header Size: %u\tFlags: “, header_size);
if(tcp_header->tcp_flags & TCP_FIN)
printf(“FIN “);
if(tcp_header->tcp_flags & TCP_SYN)
printf(“SYN “);
if(tcp_header->tcp_flags & TCP_RST)
printf(“RST “);
if(tcp_header->tcp_flags & TCP_PUSH)
0x440 Анализ сетевых пакетов (сниффинг)
267
printf(“PUSH “);
if(tcp_header->tcp_flags & TCP_ACK)
printf(“ACK “);
if(tcp_header->tcp_flags & TCP_URG)
printf(“URG “);
printf(“ }\n”);
return header_size;
}
Декодирующим функциям передается указатель на начало заголов- ка, который приводится к типу соответствующей структуры. Это дает доступ к разным полям заголовка, но нужно помнить, что все значе- ния представлены в сетевом порядке байтов. Это данные прямо из
Сети, поэтому их нужно преобразовать к порядку, принятому в архи- тектуре x86.
reader@hacking:/booksrc $ gcc -o decode_sniff decode_sniff.c -lpcap reader@hacking:/booksrc $ sudo ./decode_sniff
Sniffing on device eth0
==== Got a 75 byte packet ====
[[ Layer 2 :: Ethernet Header ]]
[ Source: 00:01:29:15:65:b6 Dest: 00:01:6c:eb:1d:50 Type: 8 ]
(( Layer 3 ::: IP Header ))
( Source: 192.168.42.1 Dest: 192.168.42.249 )
( Type: 6 ID: 7755 Length: 61 )
{{ Layer 4 :::: TCP Header }}
{ Src Port: 35602 Dest Port: 7890 }
{ Seq #: 2887045274 Ack #: 3843058889 }
{ Header Size: 32 Flags: PUSH ACK }
9 bytes of packet data
74 65 73 74 69 6e 67 0d 0a | testing..
==== Got a 66 byte packet ====
[[ Layer 2 :: Ethernet Header ]]
[ Source: 00:01:6c:eb:1d:50 Dest: 00:01:29:15:65:b6 Type: 8 ]
(( Layer 3 ::: IP Header ))
( Source: 192.168.42.249 Dest: 192.168.42.1 )
( Type: 6 ID: 15678 Length: 52 )
{{ Layer 4 :::: TCP Header }}
{ Src Port: 7890 Dest Port: 35602 }
{ Seq #: 3843058889 Ack #: 2887045283 }
{ Header Size: 32 Flags: ACK }
No Packet Data
==== Got a 82 byte packet ====
[[ Layer 2 :: Ethernet Header ]]
[Source: 00:01:29:15:65:b6 Dest: 00:01:6c:eb:1d:50 Type: 8 ]
(( Layer 3 ::: IP Header ))
( Source: 192.168.42.1 Dest: 192.168.42.249 )
( Type: 6 ID: 7756 Length: 68 )
{{ Layer 4 :::: TCP Header }}
{ Src Port: 35602 Dest Port: 7890 }