Introduction
To completely understand the article I expect the reader to know basic concept of network, different terms used in it and some C programming.
This article explains how a small program can be used to sniff packets from the network. I worked on this about 21/2 years back. I didn't get a chance to share this back then. Content of this article is for Ubuntu and its derivatives. (It can be achieved on Microsoft Windows as well with some modifications to the code presented in this article and related changes to commands )
Content
Before I start keep in mind that whenever I say network its
LAN unless specified to be something else.
The idea presented here is simple, with just few lines of code and some very good libraries(thanks to open source community), you can write a very strong tool which can be used to sniff packets out of the network, not just the packets that are meant for your machine, but the ones that are addressed to any machine on same network.
This is how packets reach the destination computer on LAN
Whenever a packet reaches gateway, it verifies that if the destination is one of the computers in the LAN for which it is gateway. If yes then
IP addresses are converted to corresponding
MAC addresses using
ARP protocol and then frames are forwarded to LAN, then the repeaters(if any) flood the LAN with these frames.
So all the computers in LAN read these frames to match the destination MAC address against theirs. If it doesn't match then the frames are discarded else it is passed along the network stack.
This is what makes it possible to sniff frames
It is possible to tell the computer to pass each frame up the network stack for any MAC address i.e its own MAC address and others'. If you are able to achieve this, then you are sniffing! This is also called as Promiscuous mode.
So how do we achieve this?
Here is the code to do it.
#include <pcap.h>
#include <stdio.h>
#include <arpa/inet.h>
#include <sys/time.h>
#define SIZE_ETHERNET 14
#define SNAP_LEN 1518
/* IP header */
struct sniff_ip {
u_char ip_vhl; /* version << 4 | header length >> 4 */
u_char 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 /* don't fragment flag */
#define IP_MF 0x2000 /* more fragments flag */
#define IP_OFFMASK 0x1fff /* mask for fragmenting bits */
u_char ip_ttl; /* time to live */
u_char ip_p; /* protocol */
u_short ip_sum; /* checksum */
struct in_addr ip_src,ip_dst; /* source and destination address */
};
#define IP_HL(ip) (((ip)->ip_vhl) & 0x0f)
#define IP_V(ip) (((ip)->ip_vhl) >> 4)
/* TCP header */
typedef u_int tcp_seq;
struct sniff_tcp {
u_short th_sport; /* source port */
u_short th_dport; /* destination port */
tcp_seq th_seq; /* sequence number */
tcp_seq th_ack; /* acknowledgement number */
u_char th_offx2; /* data offset, rsvd */
#define TH_OFF(th) (((th)->th_offx2 & 0xf0) >> 4)
u_char 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
#define TH_ECE 0x40
#define TH_CWR 0x80
#define TH_FLAGS (TH_FIN|TH_SYN|TH_RST|TH_ACK|TH_URG|TH_ECE|TH_CWR)
u_short th_win; /* window */
u_short th_sum; /* checksum */
u_short th_urp; /* urgent pointer */
};
//
//Prints the payload to stdout or to fd 1.
//
void print_hex_ascii_line(const u_char *payload, int len, int offset)
{
const u_char *ch;
ch = payload;
int i;
int gap;
for(i=0; i<len; i++)
{
printf("%02x ",*ch);
ch++;
if(i==7)
printf(" ");
}
if(len < 8)
printf(" ");
if (len < 16)
{
gap = 16 - len;
for (i = 0; i < gap; i++)
{
printf(" ");
}
}
printf(" ");
ch = payload;
for(i=0; i<len; i++)
{
if(isprint(*ch))
printf("%c",*ch);
else
printf(".");
ch++;
}
printf("\n");
return;
}
//
//Does book-keeping work required to print the payload.
//
void print_payload(const u_char *payload, int len)
{
int len_rem = len;
int line_width = 16;
int line_len;
int offset = 0;
const u_char *ch = payload;
if (len <= 0)
return;
if (len <= line_width)
{
print_hex_ascii_line(ch, len, offset);
return;
}
for ( ;; )
{
line_len = line_width % len_rem;
print_hex_ascii_line(ch, line_len, offset);
len_rem = len_rem - line_len;
ch = ch + line_len;
if (len_rem <= line_width)
{
print_hex_ascii_line(ch, len_rem, offset);
break;
}
}
return;
}
int cnt=1;
//
// Prints the details of the packet captured
// Packet number.
// Protocol, Of course this program will always print tcp packets because of the filter used. :)
// Source IP address.
// Destination IP address.
//
void got_packet(u_char *args, const struct pcap_pkthdr *header, const u_char *packet)
{
const struct sniff_ip *ip; /* IP header */
const struct sniff_tcp *tcp; /* The TCP header */
char *payload;
int size_ip;
int size_tcp;
int size_payload;
ip = (struct sniff_ip*)(packet + SIZE_ETHERNET);
size_ip = IP_HL(ip)*4;
printf("packet number %d\n",cnt); // Prints the packet number.
cnt++;
switch(ip->ip_p) { // Prints the protocol.
case IPPROTO_TCP:
printf(" Protocol: TCP\n");
break;
case IPPROTO_UDP:
printf(" Protocol: UDP\n");
return;
case IPPROTO_ICMP:
printf(" Protocol: ICMP\n");
return;
case IPPROTO_IP:
printf(" Protocol: IP\n");
return;
default:
printf(" Protocol: unknown\n");
return;
}
printf(" From: %s\n", inet_ntoa(ip->ip_src)); //Prints source IP address.
printf(" To: %s\n", inet_ntoa(ip->ip_dst)); //Prints destination IP address.
tcp = (struct sniff_tcp*)(packet + SIZE_ETHERNET + size_ip);
size_tcp = TH_OFF(tcp)*4;
payload = (u_char*)(packet + SIZE_ETHERNET + size_ip + size_tcp);
size_payload = ntohs(ip->ip_len) - (size_ip + size_tcp);
if (size_payload > 0) {
printf(" Payload (%d bytes):\n", size_payload);
print_payload(payload, size_payload);
}
}
int main(int c,char *v[])
{
char *dev;
char errbuf[PCAP_ERRBUF_SIZE];
char filter_exp[]="tcp"; // Filter expression is set to tcp, so it captures only TCP packets.
pcap_t *handle;
struct bpf_program fp;
bpf_u_int32 net;
bpf_u_int32 mask;
if(c==2)
{
dev=v[1];
}
else if(c>2)
{
printf("unidentified options\n");
return 1;
}
else
{
dev=pcap_lookupdev(errbuf); // Checks for the default device.
if(dev==NULL)
{
fprintf(stderr,"couldn't find the default device %s\n",errbuf);
return 1;
}
}
printf("Probing device %s\n",v[1]);
pcap_lookupnet(dev,&mask,&net,errbuf); // Gets the details of the device.
handle=pcap_open_live(dev,SNAP_LEN,1,1000,errbuf); // Opens the device for capturing the packets.
if(pcap_compile(handle,&fp,filter_exp,0,net) == -1) // Compilation with filter_exp.
{
fprintf(stderr,"no valid filter expression");
return 1;
}
pcap_setfilter(handle,&fp); // Sets the filter as tcp.
pcap_loop(handle,200,got_packet,NULL); // Sets the call-back method which would be called every time a packet is captured.
return 0; // Returns 0 if everything goes well.
}
How to compile the code?
First you have install the libraries that I was talking about, use the following command on Ubuntu or any other derivatives (for fedora use yum).
$ sudo apt-get install libpcap-dev
Us the following command to compile using gcc.
$ gcc -o sniffer sniffer.c -lpcap
You can always crate makefile and make to build to save you from trouble of compiling every time.
How to run it?
If you are connected to network via wifi then,
$ sudo ./sniffer wlan0
(Here wlan0 identifies your device to be used to sniff packets)
If you are using wired connection then following command should suffice.
$ sudo ./sniffer
Its because eth0 is usually the default device for wired connections,
which libraries will pick up directly, you don't have to explicitly set
the device. But you can always explicitly specify the device.
You can find out which device to use by running
$ ifconfig
it will list all the network interfaces, choose active one, usually its the one for which inet address, broadcast, mask and other details are assigned to, unless you are connected via two network interfaces simultaneously.
Here is the sample output
packet number 21
Protocol: TCP
From: 74.125.135.84
To: 192.168.1.2
Payload (133 bytes):
16 03 01 00 51 02 00 00 4d 03 01 50 36 37 97 dd ....Q...M..P67..
2e 7c 49 6c 4d 7e a8 f c e5 2f c3 5c 2c a0 12 32 .|IlM~.../.\,..2
e2 b2 43 9c 56 77 57 6f 2f a6 57 20 8b 0e 89 44 ..C.VwWo/.W ...D
ef 9a 31 b2 01 8a cc f3 ea 02 cd 59 01 e8 78 39 ..1........Y..x9
81 07 e0 9c 90 7b 2c f8 f9 40 6b 94 c0 11 00 00 .....{,..@k.....
05 ff 01 00 01 00 14 03 01 00 01 01 16 03 01 00 ................
24 9b c8 38 11 cb ea 21 1c 3a c3 8d 12 aa 4a 3d $..8...!.:....J=
9e a1 03 14 57 9e 9b dd ba 46 dc ba 18 ee f7 f8 ....W....F......
95 fd 84 6e 71 ...nq
Do leave your feedback!
Q&A
1. The packet number in the above output is 21. What does it mean?
First let me explain why multiple packets are used for communication
For every communication that happens between computers via network (LAN, WLAN or any type of network), information is interchanged between them through series of packets that are sent and received by both computers. If information to be sent is too big to be sent in single packet, its divided into smaller chunks and sent as series of packets.
From the perspective of above program what number 21 mean?
Packet number 21 means that from the time that this Sniffer has started executing, it has caught 20 packets already, and the above one is 21st.
Every time Sniffer is started,
Count is set to 1,
/*The below section is looped every time a packet is captured by sniffer */
For every packet captured,
it prints the Count as number of packet captured.
then Count is incremented by one and
prints all other details.
/*Looped till here */
This includes all the packets, including the ones that are sent and received by the machine that is running this Sniffer.