Packet Capture With libpcap
Для кого эта статья:
Для понимания этой статьи необходим минимум знаний по сетям.
Например , что такое пакет , как он отсылается ,network layers и т.д.
Также должны иметь место быть базовые знания по си.
Если вы c/c++ master, то это просто супер.
man 3 pcap
Вам нужен компилятор и библиотека libpcap.
Мы сконцентрируемя на одном слое - Ethernet datalink layer.
Если вы используете несколько сетевых интерфейсов , например token ring,
вы должны будете использовать другие хидеры - datalink headers.
Все примеры этой статьи были оттестированы на линуксе с ядром 2.2.14.
Будет ли это компилироваться и работать на других версиях - автор ничего не гарантирует.
Intro:
Сразу возникает несколько вопросов: "Что мы подразумеваем под захватом пакетов ?!"
и "Что такое libpcap!?"
- Packet Capture, означает просто "grab packets" или "захват пакетов".
Нам нужно понять , как мы можем использовать возможности операционной системы для этого.
Рассмотрим сетевую карту , которая принимает пакеты из сети.
Ось должна определить , какого типа этот пакет, поставить ему в соответствие Ethernet header.
Пусть это UDP packet.Ему ставится в соответствие UDP-хидер ,
и пакет загружается приложением, которому он был послан.
Т.о. мы можем захватить любой пакет и глянуть на его хидер.
Мы можем узнать порт, на который он пришел, хост , с которого он пришел, и т.д.
- libpcap "обеспечивает независимый от реализации
захват пакетов на основе свойств операционной системы (Stevens, UNP page. 707).
libpcap - это библиотека , которую мы собираемся использовать для захвата пакетов
с сетевой карты напрямую.
Тут есть несколько подходов: BPF (Berkeley Packet Filter),
DLPI (Data Link Provider Interface) и SOCKET_PACKET type sockets (Linux only).
Getting Started
Итак , у нас есть сетевая карта.
Рассмотрим первую программу:
%>gcc ldev.c -lpcap
/* ldev.c
Martin Casado
To compile:
>gcc ldev.c -lpcap
Looks for an interface, and lists the network ip
and mask associated with that interface.
*/
#include <stdio.h>
#include <stdlib.h>
#include <pcap.h> /* GIMME a libpcap plz! */
#include <errno.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
int main(int argc, char **argv)
{
char *dev; /* name of the device to use */
char *net; /* dot notation of the network address */
char *mask;/* dot notation of the network mask */
int ret; /* return code */
char errbuf[PCAP_ERRBUF_SIZE];
bpf_u_int32 netp; /* ip */
bpf_u_int32 maskp;/* subnet mask */
struct in_addr addr;
/* ask pcap to find a valid device for use to sniff on */
dev = pcap_lookupdev(errbuf);
/* error checking */
if(dev == NULL)
{
printf("%s\n",errbuf);
exit(1);
}
/* print out device name */
printf("DEV: %s\n",dev);
/* ask pcap for the network address and mask of the device */
ret = pcap_lookupnet(dev,&netp,&maskp,errbuf);
if(ret == -1)
{
printf("%s\n",errbuf);
exit(1);
}
/* get the network address in a human readable form */
addr.s_addr = netp;
net = inet_ntoa(addr);
if(net == NULL)/* thanks Scott :-P */
{
perror("inet_ntoa");
exit(1);
}
printf("NET: %s\n",net);
/* do the same as above for the device's mask */
addr.s_addr = maskp;
mask = inet_ntoa(addr);
if(mask == NULL)
{
perror("inet_ntoa");
exit(1);
}
printf("MASK: %s\n",mask);
return 0;
}
Если запустить эту программу , то она может распечатать примерно следующее:
DEV: eth0
NET: 192.168.12.0
MASK: 255.255.255.0
Если ваше DEV не eth0, не eth1 и т.д. , то вас ждут проблемы.
В этой программе мы попросили libpcap распечатать некоторые спецификации сетевого интерфейса.
Так , в Unix eth0 - сетевая карта номер 1.
И мы будем грабить именно это устройство.
Capturing Our First Packet
Итак , мы определили интерфейс и готовы к захвату пакетов.
Рассмотрим пример - testpcap1.c:
/***************************************************
* file: testpcap1.c
* Date: Thu Mar 08 17:14:36 MST 2001
* Author: Martin Casado
* Location: LAX Airport (hehe)
*
* Simple single packet capture program
*****************************************************/
#include <stdio.h>
#include <stdlib.h>
#include <pcap.h> /* if this gives you an error try pcap/pcap.h */
#include <errno.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netinet/if_ether.h> /* includes net/ethernet.h */
int main(int argc, char **argv)
{
int i;
char *dev;
char errbuf[PCAP_ERRBUF_SIZE];
pcap_t* descr;
const u_char *packet;
struct pcap_pkthdr hdr; /* pcap.h */
struct ether_header *eptr; /* net/ethernet.h */
u_char *ptr; /* printing out hardware header info */
/* grab a device to peak into... */
dev = pcap_lookupdev(errbuf);
if(dev == NULL)
{
printf("%s\n",errbuf);
exit(1);
}
printf("DEV: %s\n",dev);
/* open the device for sniffing.
pcap_t *pcap_open_live(char *device,int snaplen, int prmisc,int to_ms,
char *ebuf)
snaplen - maximum size of packets to capture in bytes
promisc - set card in promiscuous mode?
to_ms - time to wait for packets in miliseconds before read
times out
errbuf - if something happens, place error string here
Note if you change "prmisc" param to anything other than zero, you will
get all packets your device sees, whether they are intendeed for you or
not!! Be sure you know the rules of the network you are running on
before you set your card in promiscuous mode!! */
descr = pcap_open_live(dev,BUFSIZ,0,-1,errbuf);
if(descr == NULL)
{
printf("pcap_open_live(): %s\n",errbuf);
exit(1);
}
/*
grab a packet from descr (yay!)
u_char *pcap_next(pcap_t *p,struct pcap_pkthdr *h)
so just pass in the descriptor we got from
our call to pcap_open_live and an allocated
struct pcap_pkthdr */
packet = pcap_next(descr,&hdr);
if(packet == NULL)
{/* dinna work *sob* */
printf("Didn't grab packet\n");
exit(1);
}
/* struct pcap_pkthdr {
struct timeval ts; time stamp
bpf_u_int32 caplen; length of portion present
bpf_u_int32; lebgth this packet (off wire)
}
*/
printf("Grabbed packet of length %d\n",hdr.len);
printf("Recieved at ..... %s\n",ctime((const time_t*)&hdr.ts.tv_sec));
printf("Ethernet address length is %d\n",ETHER_HDR_LEN);
/* lets start with the ether header... */
eptr = (struct ether_header *) packet;
/* Do a couple of checks to see what packet type we have..*/
if (ntohs (eptr->ether_type) == ETHERTYPE_IP)
{
printf("Ethernet type hex:%x dec:%d is an IP packet\n",
ntohs(eptr->ether_type),
ntohs(eptr->ether_type));
}else if (ntohs (eptr->ether_type) == ETHERTYPE_ARP)
{
printf("Ethernet type hex:%x dec:%d is an ARP packet\n",
ntohs(eptr->ether_type),
ntohs(eptr->ether_type));
}else {
printf("Ethernet type %x not IP", ntohs(eptr->ether_type));
exit(1);
}
/* THANK YOU RICHARD STEVENS!!! RIP*/
ptr = eptr->ether_dhost;
i = ETHER_ADDR_LEN;
printf(" Destination Address: ");
do{
printf("%s%x",(i == ETHER_ADDR_LEN) ? " " : ":",*ptr++);
}while(--i>0);
printf("\n");
ptr = eptr->ether_shost;
i = ETHER_ADDR_LEN;
printf(" Source Address: ");
do{
printf("%s%x",(i == ETHER_ADDR_LEN) ? " " : ":",*ptr++);
}while(--i>0);
printf("\n");
return 0;
}
Если вы соблюдаете все правила , то вывод должен быть типа:
[root@pepe libpcap]# ./testpcap1
DEV: eth0
Grabbed packet of length 76
Recieved at time..... Mon Mar 12 22:23:29 2001
Ethernet address length is 14
Ethernet type hex:800 dec:2048 is an IP packet
Destination Address: 0:20:78:d1:e8:1
Source Address: 0:a0:cc:56:c2:91
[root@pepe libpcap]#
destination address - в данном случае должен быть гейтом.
Проверим роутинг:
[root@pepe libpcap]# /sbin/route
Kernel IP routing table
Destination Gateway Genmask Flags Metric Ref Use Iface
192.168.1.0 * 255.255.255.0 U 0 0 0 eth0
127.0.0.0 * 255.0.0.0 U 0 0 0 lo
default 192.168.1.1 0.0.0.0 UG 0 0 0 eth0
теперь наберем команду arp для получения адреса ethernet:
[root@pepe libpcap]# /sbin/arp
Address HWtype HWaddress Flags Mask Iface
192.168.1.1 ether 00:20:78:D1:E8:01 C eth0
Обратите внимание на то , что адрес гейта - 192.168.1.1 -соответствует destination address пакета.
Все пакеты , посылаемые с вашей машины , если они не прдназначены для конкретной машины,
посылаются через гейт.А откуда наш компьютер знает адрес гейта? Хороший вопрос.
Компьютер должен знать адрес гейта по определению.
Это же нам показала команда arp,
которая берет его из специальной внутренней таблицы-кеша, в которой каждый ip-адрес
поставлен в соответствие hardware-адресу.
Этот Hardware-адрес получается с использованием Address Resolution Protocol или ARP.
ARP описан в RFC826.
Когда вы посылаете пакет, ядро проверяет arp cache , если там destination address.
Давайте удалим destination address из кеша:
[root@pepe libpcap]# /sbin/arp -n # look at arp cache
Address HWtype HWaddress Flags Mask Iface
192.168.1.1 ether 00:20:78:D1:E8:01 C eth0
[root@pepe libpcap]# /sbin/arp -n -d 192.168.1.1 #delete gateqay entrance
[root@pepe libpcap]# /sbin/arp -n #make sure gateway hardware addy is empty
Address HWtype HWaddress Flags Mask Iface
192.168.1.1 (incomplete) eth0
[root@pepe libpcap]# ./a.out
DEV: eth0
Grabbed packet of length 42
Recieved at time..... Tue Mar 13 00:36:49 2001
Ethernet address length is 14
Ethernet type hex:806 dec:2054 is an ARP packet
Destination Address: ff:ff:ff:ff:ff:ff
Source Address: 0:a0:cc:56:c2:91
[root@pepe libpcap]#echo YAY
После чего компьютер пытается сделать широковещательный запрос - broadcast (ff:ff:ff:ff:ff:ff),
пытаясь отыскать IP 192.168.1.1.
Допустим наша сетка имеет пропускную способность в 10Mb/s...
/* 10Mb/s ethernet header */
struct ether_header
{
u_int8_t ether_dhost[ETH_ALEN]; /* destination eth addr */
u_int8_t ether_shost[ETH_ALEN]; /* source ether addr */
u_int16_t ether_type; /* packet type ID field */
} __attribute__ ((__packed__));
Первое поле ETH_ALEN есть destination ethernet address пакета.
Второе поле ETH_ALEN - источник.
Третье поле - тип пакета.
id-шники протоколов из net/ethernet.h
/* Ethernet protocol ID's */
#define ETHERTYPE_PUP 0x0200 /* Xerox PUP */
#define ETHERTYPE_IP 0x0800 /* IP */
#define ETHERTYPE_ARP 0x0806 /* Address resolution */
#define ETHERTYPE_REVARP 0x8035 /* Reverse ARP */
Нам итересен прежде всего IP и немного ARP.
Writing a Basic Packet Capture Engine
В этом разделе обсуждается такая тема,как написание енжина по захвату пакетов.
Нам нужно научиться анализировать и фильтровать пакеты.
Рассмотрим следующий библиотечный метод pcap :
- int pcap_loop(pcap_t *p, int cnt, pcap_handler callback, u_char *user)
Он будет использован нами как один из базовых.
Во время цикла pcap_loop(..) будет происходить захват пакетов,
которые будут передаваться в callback function типа pcap_handler.
typedef void (*pcap_handler)(u_char *, const struct pcap_pkthdr *, const u_char *);
Здесь представляют интерес аргументы 2 и 3, хидер для pcap packet и константа u_char.
В качестве примера напишем программу , которая захватывает в цикле n пакетов.
/**********************************************************************
* file: testpcap2.c
* date: 2001-Mar-14 12:14:19 AM
* Author: Martin Casado
* Last Modified:2001-Mar-14 12:14:11 AM
*
* Description: Q&D proggy to demonstrate the use of pcap_loop
*
**********************************************************************/
#include <pcap.h>
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netinet/if_ether.h>
/* callback function that is passed to pcap_loop(..) and called each time
* a packet is recieved */
void my_callback(u_char *useless,const struct pcap_pkthdr* pkthdr,const u_char*
packet)
{
static int count = 1;
fprintf(stdout,"%d, ",count);
if(count == 4)
fprintf(stdout,"Come on baby sayyy you love me!!! ");
if(count == 7)
fprintf(stdout,"Tiiimmmeesss!! ");
fflush(stdout);
count++;
}
int main(int argc,char **argv)
{
int i;
char *dev;
char errbuf[PCAP_ERRBUF_SIZE];
pcap_t* descr;
const u_char *packet;
struct pcap_pkthdr hdr; /* pcap.h */
struct ether_header *eptr; /* net/ethernet.h */
if(argc != 2){ fprintf(stdout,"Usage: %s numpackets\n",argv[0]);return 0;}
/* grab a device to peak into... */
dev = pcap_lookupdev(errbuf);
if(dev == NULL)
{ printf("%s\n",errbuf); exit(1); }
/* open device for reading */
descr = pcap_open_live(dev,BUFSIZ,0,-1,errbuf);
if(descr == NULL)
{ printf("pcap_open_live(): %s\n",errbuf); exit(1); }
/* allright here we call pcap_loop(..) and pass in our callback function */
/* int pcap_loop(pcap_t *p, int cnt, pcap_handler callback, u_char *user)*/
/* If you are wondering what the user argument is all about, so am I!! */
pcap_loop(descr,atoi(argv[1]),my_callback,NULL);
fprintf(stdout,"\nDone processing packets... wheew!\n");
return 0;
}
Allright then, lets give her a whirl!
[root@pepe libpcap]# gcc testpcap2.c -lpcap
[root@pepe libpcap]# ./a.out 7
1, 2, 3, 4, Come on baby sayyy you love me!!! 5, 6, 7, Tiiimmmeesss!!
Done processing packets... wheew!
[root@pepe libpcap]#
Как вы видите , my_callback(...) была вызвана 7 раз.
Весь анализ пакетов мы положили внутрь my_callback.
Но это не совсем правильное решение.
Первая проблема в том , что pcap_loop(..) может подвиснуть в случае отсутствия пакетов.
Было бы неплохо установить тайм-аут на чтение.
Одним из параметров функции pcap_open_live(..) является тайм-аут в милли-секундах.
Функция pcap_loop игнорирует этот аргумент, но pcap_dispatch(..) нет!
Смотрите описание pcap_dispatch() в man page
************
pcap_dispatch() используется для сбора пакетов.
cnt - определяет максимальное число пакетов.
cnt = -1 говорит о том , что все пакеты возвращаются в одном буфере.
cnt = 0 обрабатывает пакеты до тех пор , пока не произойдет ошибка.
callback - подпрограмма , вызываемая 3-м аргументом:u_char - указатель на нее,
туда входит структура pcap_pkthdr и данные.
pcap_dispatch() возвращает число прочитанных пакетов.
-1 означает ошибку, для ее вывода можно использовать pcap_perror() или pcap_geterr().
************
Нам неинтересны все пакеты,гуляющие по сети.
Что нужно для того , чтобы захватить пакеты , зная номер хоста и номер порта ?
Использовать pcap_compile(..) и pcap_setfilter(...).
Мы можем захватить все пакеты и отсортировать их.
Но трафик может быть нешуточным.
В библиотеке libpcap есть интерфейс,позволяющий определить точно , какие пакеты нас интересуют.
Для этого нужно передать фильтр в форме строки функции pcap_compile().
Есть волшебная программа tcpdump и ее man page.
Как правило,эта программа стоит по умолчанию везде.
Tcpdump - это фактически враппер для libpcap.
tcpdump man page явно описывает синтаксис и семантику языка фильтров.
Вот часть этого мана:
The expression consists of one or more primitives. Primitives usu
ally consist of an id (name or number) preceded by one or more
qualifiers. There are three different kinds of qualifier:
type qualifiers say what kind of thing the id name or number
refers to. Possible types are host, net and port. E.g.,
`host foo', `net 128.3', `port 20'. If there is no type
qualifier, host is assumed.
dir qualifiers specify a particular transfer direction to and/or
from id. Possible directions are src, dst, src or dst and
src and dst. E.g., `src foo', `dst net 128.3', `src or dst
port ftp-data'. If there is no dir qualifier, src or dst is
assumed. For `null' link layers (i.e. point to point proto
cols such as slip) the inbound and outbound qualifiers can
be used to specify a desired direction.
proto qualifiers restrict the match to a particular protocol.
Possible protos are: ether, fddi, ip, arp, rarp, decnet,
lat, sca, moprc, mopdl, tcp and udp. E.g., `ether src foo',
`arp net 128.3', `tcp port 21'. If there is no proto quali
fier, all protocols consistent with the type are assumed.
E.g., `src foo' means `(ip or arp or rarp) src foo' (except
the latter is not legal syntax), `net bar' means `(ip or arp
or rarp) net bar' and `port 53' means `(tcp or udp) port
53'.
In addition to the above, there are some special `primitive' key
words that don't follow the pattern: gateway, broadcast, less,
greater and arithmetic expressions. All of these are described
below.
More complex filter expressions are built up by using the words
and, or and not to combine primitives. E.g., `host foo and not
port ftp and not port ftp-data'. To save typing, identical quali
fier lists can be omitted. E.g., `tcp dst port ftp or ftp-data or
domain' is exactly the same as `tcp dst port ftp or tcp dst port
ftp-data or tcp dst port domain'.
Allowable primitives are:
dst host host
True if the IP destination field of the packet is host,
which may be either an address or a name.
src host host
True if the IP source field of the packet is host.
host host
True if either the IP source or destination of the packet is
host. Any of the above host expressions can be prepended
with the keywords, ip, arp, or rarp as in:
ip host host
which is equivalent to:
ether proto \ip and host host
If host is a name with multiple IP addresses, each address
will be checked for a match.
ether dst ehost
True if the ethernet destination address is ehost. Ehost
may be either a name from /etc/ethers or a number (see
ethers(3N) for numeric format).
ether src ehost
True if the ethernet source address is ehost.
ether host ehost
True if either the ethernet source or destination address is
ehost.
gateway host
True if the packet used host as a gateway. I.e., the ether
net source or destination address was host but neither the
IP source nor the IP destination was host. Host must be a
name and must be found in both /etc/hosts and /etc/ethers.
(An equivalent expression is
ether host ehost and not host host
which can be used with either names or numbers for host /
ehost.)
dst net net
True if the IP destination address of the packet has a net
work number of net. Net may be either a name from /etc/net
works or a network number (see networks(4) for details).
src net net
True if the IP source address of the packet has a network
number of net.
net net
True if either the IP source or destination address of the
packet has a network number of net.
net net mask mask
True if the IP address matches net with the specific net
mask. May be qualified with src or dst.
net net/len
True if the IP address matches net a netmask len bits wide.
May be qualified with src or dst.
dst port port
True if the packet is ip/tcp or ip/udp and has a destination
port value of port. The port can be a number or a name used
in /etc/services (see tcp(4P) and udp(4P)). If a name is
used, both the port number and protocol are checked. If a
number or ambiguous name is used, only the port number is
checked (e.g., dst port 513 will print both tcp/login traf
fic and udp/who traffic, and port domain will print both
tcp/domain and udp/domain traffic).
src port port
True if the packet has a source port value of port.
port port
True if either the source or destination port of the packet
is port. Any of the above port expressions can be prepended
with the keywords, tcp or udp, as in:
tcp src port port
which matches only tcp packets whose source port is port.
less length
True if the packet has a length less than or equal to
length. This is equivalent to:
len <= length.
greater length
True if the packet has a length greater than or equal to
length. This is equivalent to:
len >= length.
ip proto protocol
True if the packet is an ip packet (see ip(4P)) of protocol
type protocol. Protocol can be a number or one of the names
icmp, igrp, udp, nd, or tcp. Note that the identifiers tcp,
udp, and icmp are also keywords and must be escaped via
backslash (\), which is \\ in the C-shell.
ether broadcast
True if the packet is an ethernet broadcast packet. The
ether keyword is optional.
ip broadcast
True if the packet is an IP broadcast packet. It checks for
both the all-zeroes and all-ones broadcast conventions, and
looks up the local subnet mask.
ether multicast
True if the packet is an ethernet multicast packet. The
ether keyword is optional. This is shorthand for `ether[0]
& 1 != 0'.
ip multicast
True if the packet is an IP multicast packet.
ether proto protocol
True if the packet is of ether type protocol. Protocol can
be a number or a name like ip, arp, or rarp. Note these
identifiers are also keywords and must be escaped via back
slash (\). [In the case of FDDI (e.g., `fddi protocol
arp'), the protocol identification comes from the 802.2 Log
ical Link Control (LLC) header, which is usually layered on
top of the FDDI header. Tcpdump assumes, when filtering on
the protocol identifier, that all FDDI packets include an
LLC header, and that the LLC header is in so-called SNAP
format.]
ip, arp, rarp, decnet
Abbreviations for:
ether proto p where p is one of the above protocols.
tcp, udp, icmp
Abbreviations for:
ip proto p
where p is one of the above protocols.
expr relop expr
True if the relation holds, where relop is one of >,
<, >=, <=, =, !=, and expr is an arithmetic
expression composed of integer constants (expressed in
standard C syntax), the nor mal binary operators [+, -,
*, /, &, |], a length operator, and special packet
data accessors. To access data inside the packet, use the
following syntax: proto [ expr : size ] Proto is one of
ether, fddi, ip, arp, rarp, tcp, udp, or icmp, and
indicates the protocol layer for the index opera tion.
The byte offset, relative to the indicated protocol
layer, is given by expr. Size is optional and indicates
the number of bytes in the field of interest; it can be
either one, two, or four, and defaults to one. The
length opera tor, indicated by the keyword len, gives the
length of the packet.
For example, `ether[0] & 1 != 0' catches all multicast traf
fic. The expression `ip[0] & 0xf != 5' catches all IP pack
ets with options. The expression `ip[6:2] & 0x1fff = 0'
catches only unfragmented datagrams and frag zero of frag
mented datagrams. This check is implicitly applied to the
tcp and udp index operations. For instance, tcp[0] always
means the first byte of the TCP header, and never means the
first byte of an intervening fragment.
Primitives may be combined using:
A parenthesized group of primitives and operators (parenthe
ses are special to the Shell and must be escaped).
Negation (`!' or `not').
Concatenation (`&&' or `and').
Alternation (`||' or `or').
Negation has highest precedence. Alternation and concatenation
have equal precedence and associate left to right. Note that
explicit and tokens, not juxtaposition, are now required for con
catenation.
If an identifier is given without a keyword, the most recent key
word is assumed. For example,
not host vs and ace
is short for
not host vs and host ace
which should not be confused with
not ( host vs or ace )
Expression arguments can be passed to tcpdump as either a single
argument or as multiple arguments, whichever is more convenient.
Generally, if the expression contains Shell metacharacters, it is
easier to pass it as a single, quoted argument. Multiple arguments
are concatenated with spaces before being parsed.
EXAMPLES
To print all packets arriving at or departing from sundown:
tcpdump host sundown
To print traffic between helios and either hot or ace:
tcpdump host helios and \( hot or ace \)
To print all IP packets between ace and any host except helios:
tcpdump ip host ace and not helios
To print all traffic between local hosts and hosts at Berkeley:
tcpdump net ucb-ether
To print all ftp traffic through internet gateway snup: (note that the
expression is quoted to prevent the shell from (mis-)interpreting the
parentheses):
tcpdump 'gateway snup and (port ftp or ftp-data)'
To print traffic neither sourced from nor destined for local hosts (if you
gateway to one other net, this stuff should never make it onto your local
net).
tcpdump ip and not net localnet
To print the start and end packets (the SYN and FIN packets) of each TCP
conversation that involves a non-local host.
tcpdump 'tcp[13] & 3 != 0 and not src and dst net localnet'
To print IP packets longer than 576 bytes sent through gateway snup:
tcpdump 'gateway snup and ip[2:2] > 576'
To print IP broadcast or multicast packets that were not sent via ethernet
broadcast or multicast:
tcpdump 'ether[0] & 1 = 0 and ip[16] >= 224'
To print all ICMP packets that are not echo requests/replies (i.e., not
ping packets):
tcpdump 'icmp[0] != 8 and icmp[0] != 0"
Используем эту информацию в качестве отправной точки.
Допустим,в моей локальной сетке 2 машины - линуксовая и виндовая,
которые соединены через хаб.
Если я сетевую карту на линуксовой машине установлю в promiscuous mode,
я смогу увидеть весь трафик,идущий через виндовую машину.
Рассмотрим следующую программу :
/**********************************************************************
* file: testpcap3.c
* date: Sat Apr 07 23:23:02 PDT 2001
* Author: Martin Casado
* Last Modified:2001-Apr-07 11:23:05 PM
*
* Investigate using filter programs with pcap_compile() and
* pcap_setfilter()
*
**********************************************************************/
#include <pcap.h>
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netinet/if_ether.h>
/* just print a count every time we have a packet... */
void my_callback(u_char *useless,const struct pcap_pkthdr* pkthdr,const u_char*
packet)
{
static int count = 1;
fprintf(stdout,"%d, ",count);
fflush(stdout);
count++;
}
int main(int argc,char **argv)
{
int i;
char *dev;
char errbuf[PCAP_ERRBUF_SIZE];
pcap_t* descr;
const u_char *packet;
struct pcap_pkthdr hdr; /* pcap.h */
struct ether_header *eptr; /* net/ethernet.h */
struct bpf_program fp; /* hold compiled program */
bpf_u_int32 maskp; /* subnet mask */
bpf_u_int32 netp; /* ip */
if(argc != 2){ fprintf(stdout,"Usage: %s \"filter program\"\n"
,argv[0]);return 0;}
/* grab a device to peak into... */
dev = pcap_lookupdev(errbuf);
if(dev == NULL)
{ fprintf(stderr,"%s\n",errbuf); exit(1); }
/* ask pcap for the network address and mask of the device */
pcap_lookupnet(dev,&netp,&maskp,errbuf);
/* open device for reading this time lets set it in promiscuous
* mode so we can monitor traffic to another machine */
descr = pcap_open_live(dev,BUFSIZ,1,-1,errbuf);
if(descr == NULL)
{ printf("pcap_open_live(): %s\n",errbuf); exit(1); }
/* Lets try and compile the program.. non-optimized */
if(pcap_compile(descr,&fp,argv[1],0,netp) == -1)
{ fprintf(stderr,"Error calling pcap_compile\n"); exit(1); }
/* set the compiled program as the filter */
if(pcap_setfilter(descr,&fp) == -1)
{ fprintf(stderr,"Error setting filter\n"); exit(1); }
/* ... and loop */
pcap_loop(descr,-1,my_callback,NULL);
return 0;
}
Эта программа принимает строку от пользователя, компилирует ее и устанавливает ее в качестве фильтра-
при этом нужно сделать запрос на google.com:
[root@localhost libpcap]# gcc testpcap3.c -lpcap
[root@localhost libpcap]# ./a.out "host www.google.com"
(** try and ping www.slashdot.org ... nothing **)
(** try and ping www.google.com **)
1, 2, 3, 4, 5, 6,
(** hurray! **)
Все указывает на то , что фильтр работает.
Попробуем теперь захватить пакеты с виндовой машины, в то время как она
пытается сделать запрос на battle.net:
[root@localhost libpcap]# ./a.out "src 192.168.1.104"
1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23,
24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40,
Похоже, и тут мы попали в точку.
Теперь неплохо бы расшифровать полученную информацию.
В следующем разделе рассмотрим анализ пакетов.
Packet Analysis
В этом разделе мы затронем тему выделения информации.
Для этого неплохо бы знать соответственные RFC:
RFC 791 (IP),RFC 768 (UDP),RFC 826 (ARP),RFC 792 (ICMPv4) и конечно
RFC 793 (TCPv4).
Настоятельно рекомендую вам использовать другой сниффер для подтверждения всего того,
что я тут наговорил.Например,tcpdump или ethereal.
Оба этих снифера могут анализировать как пакеты, так и сами данные.
Следующая программа будет построена на основе предыдущих,
с небольшим изменением callback function, которая передается в pcap_loop()
или pcap_dispatch().
Рассмотрим следующий пример:
/**********************************************************************
* file: pcap_main.c
* date: Tue Jun 19 20:07:49 PDT 2001
* Author: Martin Casado
* Last Modified:2001-Jun-23 12:55:45 PM
*
* Description:
* main program to test different call back functions
* to pcap_loop();
*
* Compile with:
* gcc -Wall -pedantic pcap_main.c -lpcap (-o foo_err_something)
*
* Usage:
* a.out (# of packets) "filter string"
*
**********************************************************************/
#include <pcap.h>
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netinet/if_ether.h>
#include <net/ethernet.h>
#include <netinet/ether.h>
/*
* workhorse function, we will be modifying this function
*/
void my_callback(u_char *args,const struct pcap_pkthdr* pkthdr,c
u_char*
packet)
{
}
int main(int argc,char **argv)
{
char *dev;
char errbuf[PCAP_ERRBUF_SIZE];
pcap_t* de
struct bpf_program fp; /* hold compiled program */
bpf_u_int32 maskp; /* subnet mask */
bpf_u_int32 netp; /* ip */
u_char* args = NULL;
/* Options must be passed in as a string because I am lazy */
if(argc < 2){
fprintf(stdout,"Usage: %s numpackets \"options\"\n",argv[0]);
return 0;
}
/* grab a device to peak into... */
dev = pcap_lookupdev(errbuf);
if(dev == NULL)
{ printf("%s\n",errbuf); exit(1); }
/* ask pcap for the network address and mask of the device */
pcap_lookupnet(dev,&netp,&maskp,errbuf);
/* open device for reading. NOTE: defaulting to
* promiscuous mode*/
descr = pcap_open_live(dev,BUFSIZ,1,-1,errbuf);
if(descr == NULL)
{ printf("pcap_open_live(): %s\n",errbuf); exit(1); }
if(argc > 2)
{
/* Lets try and compile the program.. non-optimized */
if(pcap_compile(descr,&fp,argv[2],0,netp) ==
{ fprintf(stderr,"Error calling pcap_compile\n"); exit(1); }
/* set the compiled program as the filter */
if(pcap_setfilter(descr,&fp) == -1)
{ fprintf(stderr,"Error setting filter\n"); exit(1); }
}
/* ... and loop */
pcap_loop(descr,atoi(argv[1]),my_callback,args);
fprintf(stdout,"\nfinished\n");
return 0;
}
Эта программа будет использована в качестве заготовки для дальнейших программ.
Вы могли заметить, что я передаю u_char* ( NULL) в pcap_loop().
Это делается для передачи первого аргумента.
Итак, нам нужно прочитать тело TCP packets.
Важнейшим элементом любого хидера является тип пакета.
Структура struct ether_header из net/ethernet.h:
/* This is a name for the 48 bit ethernet address available on many
systems. */
struct ether_addr
{
u_int8_t ether_addr_octet[ETH_ALEN];
} __attribute__ ((__packed__));
/* 10Mb/s ethernet header */
struct ether_header
{
u_int8_t ether_dhost[ETH_ALEN]; /* destination eth addr */
u_int8_t ether_shost[ETH_ALEN]; /* source ether addr */
u_int16_t ether_type; /* packet type ID field */
} __attribute__ ((__packed__));
Это дает нам возможность конвертации ethernet headers в читаемый ascii.
/* Convert 48 bit Ethernet ADDRess to ASCII. */
extern char *ether_ntoa (__const struct ether_addr *__addr) __THROW;
extern char *ether_ntoa_r (__const struct ether_addr *__addr, char *__buf)
__THROW;
/* Convert ASCII string S to 48 bit Ethernet address. */
extern struct ether_addr *ether_aton (__const char *__asc) __THROW;
extern struct ether_addr *ether_aton_r (__const char *__asc,
struct ether_addr *__addr) __THROW;
конвертация ethernet address в HOSTNAME
/* Map HOSTNAME to 48 bit Ethernet address. */
extern int ether_hostton (__const char *__hostname, struct ether_addr *__addr)
__THROW;
Кое-что тут было позаимствовано из небезизвестного Steven's-а.
А вот и callback function для управления ethernet headers, распечатки
source и destination - адресов:
u_int16_t handle_ethernet
(u_char *args,const struct pcap_pkthdr* pkthdr,const u_char*
packet);
/* looking at ethernet headers */
void my_callback(u_char *args,const struct pcap_pkthdr* pkthdr,const u_char*
packet)
{
u_int16_t type = handle_ethernet(args,pkthdr,packet);
if(type == ETHERTYPE_IP)
{/* handle IP packet */
}else if(type == ETHERTYPE_ARP)
{/* handle arp packet */
}
else if(type == ETHERTYPE_REVARP)
{/* handle reverse arp packet */
}/* ignorw */
}
u_int16_t handle_ethernet
(u_char *args,const struct pcap_pkthdr* pkthdr,const u_char*
packet)
{
struct ether_header *eptr; /* net/ethernet.h */
/* lets start with the ether header... */
eptr = (struct ether_header *) packet;
fprintf(stdout,"ethernet header source: %s"
,ether_ntoa(eptr->ether_shost));
fprintf(stdout," destination: %s "
,ether_ntoa(eptr->ether_dhost));
/* check to see if we have an ip packet */
if (ntohs (eptr->ether_type) == ETHERTYPE_IP)
{
fprintf(stdout,"(IP)");
}else if (ntohs (eptr->ether_type) == ETHERTYPE_ARP)
{
fprintf(stdout,"(ARP)");
}else if (ntohs (eptr->ether_type) == ETHERTYPE_REVARP)
{
fprintf(stdout,"(RARP)");
}else {
fprintf(stdout,"(?)");
exit(1);
}
fprintf(stdout,"\n");
return eptr->ether_type;
}
В исходниках вы найдете пример disect1.c.
Давайте теперь подробнее посмотрим на IP-хидер:
IP: RFC 791 :
3.1 Internet Header Format
A summary of the contents of the internet header follows:
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 |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
Example Internet Datagram Header
Figure 4.
Note that each tick mark represents one bit position.
netinet/ip.h
struct ip
{
#if __BYTE_ORDER == __LITTLE_ENDIAN
unsigned int ip_hl:4; /* header length */
unsigned int ip_v:4; /* version */
#endif
#if __BYTE_ORDER == __BIG_ENDIAN
unsigned int ip_v:4; /* version */
unsigned int ip_hl:4; /* header length */
#endif
u_int8_t ip_tos; /* type of service */
u_short ip_len; /* total length */
u_short ip_id; /* identification */
u_short ip_off; /* fragment offset field */
#define IP_RF 0x8000 /* reserved fragment flag */
#define IP_DF 0x4000 /* dont fragment flag */
#define IP_MF 0x2000 /* more fragments flag */
#define IP_OFFMASK 0x1fff /* mask for fragmenting bits */
u_int8_t ip_ttl; /* time to live */
u_int8_t ip_p; /* protocol */
u_short ip_sum; /* checksum */
struct in_addr ip_src, ip_dst; /* source and dest address */
};
tcpdump-ская версия длины хидера:
struct my_ip
u_int8_t ip_vhl; /* header length, version */
#define IP_V(ip) (((ip)->ip_vhl & 0xf0) >> 4)
#define IP_HL(ip) ((ip)->ip_vhl & 0x0f)
u_int8_t ip_tos; /* type of service */
u_int16_t ip_len; /* total length */
u_int16_t ip_id; /* identification */
u_int16_t ip_off; /* fragment offset field */
#define IP_DF 0x4000 /* dont fragment flag */
#define IP_MF 0x2000 /* more fragments flag */
#define IP_OFFMASK 0x1fff /* mask for fragmenting bits */
u_int8_t ip_ttl; /* time to live */
u_int8_t ip_p; /* protocol */
u_int16_t ip_sum; /* checksum */
struct in_addr ip_src,ip_dst; /* source and dest address */
};
Теперь фрагмент из следующей программы - disect2.c:
u_char* handle_IP
(u_char *args,const struct pcap_pkthdr* pkthdr,const u_char*
packet)
{
const struct my_ip* ip;
u_int length = pkthdr-&len;
u_int hlen,off,version;
int i;
int len;
/* jump pass the ethernet header */
ip = (struct my_ip*)(packet + sizeof(struct ether_header));
length -= sizeof(struct ether_header);
/* check to see we have a packet of valid length */
if (length < sizeof(struct my_ip))
{
printf("truncated ip %d",length);
return NULL;
}
len = ntohs(ip->ip_len);
hlen = IP_HL(ip); /* header length */
version = IP_V(ip);/* ip version */
/* check version */
if(version != 4)
{
fprintf(stdout,"Unknown version %d\n",version);
return NULL;
}
/* check header length */
if(hlen < 5 )
{
fprintf(stdout,"bad-hlen %d \n",hlen);
}
/* see if we have as much packet as we should */
if(length < len)
printf("\ntruncated IP - %d bytes missing\n",len - length);
/* Check to see if we have the first fragment */
off = ntohs(ip->ip_off);
if((off &apm; 0x1fff) == 0 )/* aka no 1's in first 13 bits */
{/* print SOURCE DESTINATION hlen version len offset */
fprintf(stdout,"IP: ");
fprintf(stdout,"%s ",
inet_ntoa(ip->ip_src));
fprintf(stdout,"%s %d %d %d %d\n",
inet_ntoa(ip->ip_dst),
hlen,version,len,off);
}
return NULL;
}
Вывод программы - в это время я пытаюсь за-телнетиться на 134.114.90.1:
[root@localhost libpcap]# ./a.out 5
ETH: 0:10:a4:8b:d3:b4 ff:ff:ff:ff:ff:ff (ARP) 42
ETH: 0:20:78:d1:e8:1 0:10:a4:8b:d3:b4 (ARP) 60
ETH: 0:10:a4:8b:d3:b4 0:20:78:d1:e8:1 (IP) 74
IP: 192.168.1.100 134.114.90.1 5 4 60 16384
ETH: 0:20:78:d1:e8:1 0:10:a4:8b:d3:b4 (IP) 60
IP: 134.114.90.1 192.168.1.100 5 4 40 0
- my computer:gateways IP (192.168.1.100)?
ETH: 0:10:a4:8b:d3:b4 ff:ff:ff:ff:ff:ff (ARP) 42
- gateway: совпало!
ETH: 0:20:78:d1:e8:1 0:10:a4:8b:d3:b4 (ARP) 60
- my computer(through gateway): Hello Mr. 134.114.90.1 can we talk?
ETH: 0:10:a4:8b:d3:b4 0:20:78:d1:e8:1 (IP) 74
IP: 192.168.1.100 134.114.90.1 5 4 60 16384
- 134.114.90.1: No, piss off, I'm not listening
ETH: 0:20:78:d1:e8:1 0:10:a4:8b:d3:b4 (IP) 60
IP: 134.114.90.1 192.168.1.100 5 4 40 0
Теперь можно обобщить :
- Все пакеты посылаются через ethernet
- ethernet header определяет тип протокола
- IP - один из этих типов
Полезно взглянуть на исходники tcpdump и на следующие методы:
- ether_if_print (print-ether.c)
- ip_print (print-ip.c)
- tcp_print (print-tcp.c)
- udp_print (print-udp.c)
Тут лежат исходники к данной статье
Тут лежит пример реализации сниффера
Тут лежат исходники tcpdump
Тут лежит библиотека libpcap-1.0.0
|