Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Sniffing using Winsock

0.00/5 (No votes)
23 Apr 2007 1  
Sniffing IP Packets using Winsock API

Introduction

Ever since Windows 2000/XP when IP_HDRINCL became a valid option for setsockopt(), WSAIoctl() had another option called SIO_RCVALL which enabled a raw socket to sniff all incoming traffic over the selected interface to whose IP the socket was bound. Hence to make a sniffer in Winsock, the simple steps are:

  1. Create a raw socket.
  2. Bind the socket to the local IP over which the traffic is to be sniffed.
  3. WSAIoctl() the socket with SIO_RCVALL to give it sniffing powers.
  4. Put the socket in an infinite loop of recvfrom.
  5. n' joy! the Buffer from recvfrom.

Now this feature of Winsock is available on all 2000/XP and higher Windows. But as usual, there are drawbacks. If you have used sniffers like Ethereal, then you would realise that ethernet header is not available in Winsock sniffing. Secondly you can only sniff incoming data on XP and XP+SP1 but both incoming and outgoing on XP+SP1+SP2 (as far as I could see - actual behavior may vary). I have not checked higher versions of Windows. Moreover non IP packets (e.g. arp) may not be captured at all. So if full fledged sniffing is required, then packet drivers like winpcap should help.

In the following source code, all packets are assumed to be IP packets. VC++ 6.0 on Windows XP is used here.

Code

SOCKET sniffer = socket(AF_INET, SOCK_RAW, IPPROTO_IP);

bind(sniffer,(struct sockaddr *)&dest,sizeof(dest))

Dest must have details as follows...

memcpy(&dest.sin_addr.s_addr,local->h_addr_list[in],sizeof(dest.sin_addr.s_addr));
dest.sin_family = AF_INET;
dest.sin_port = 0;
dest.sin_zero = 0

... where HOSTENT *local should have the local IPs:

WSAIoctl(sniffer, SIO_RCVALL, &j, sizeof(j), 0, 0, &in,0, 0)

... where j must be 1 and in can be any integer:

while(1)
{
 recvfrom(sniffer,Buffer,65536,0,0,0); //ring-a-ring-a roses
}

To get the local IPs associated with the machine, all that needs to be done is:

gethostname(hostname, sizeof(hostname);   //it's a char hostname[100] for local hostname
HOSTENT *local = gethostbyname(hostname); //now local will have all local IPs

Now socket sniffer will receive all incoming packets along with their headers and all that will be stored in the buffer. After that, some typecasting with structures representing the headers will help.

typedef struct ip_hdr
{
    unsigned char  ip_header_len:4;  // 4-bit header length (in 32-bit words)
    unsigned char  ip_version   :4;  // 4-bit IPv4 version
    unsigned char  ip_tos;           // IP type of service
    unsigned short ip_total_length;  // Total length
    unsigned short ip_id;            // Unique identifier 
    
    unsigned char  ip_frag_offset   :5; // Fragment offset field
    
    unsigned char  ip_more_fragment :1;
    unsigned char  ip_dont_fragment :1;
    unsigned char  ip_reserved_zero :1;
    
    unsigned char  ip_frag_offset1;    //fragment offset
    
    unsigned char  ip_ttl;           // Time to live
    unsigned char  ip_protocol;      // Protocol(TCP,UDP etc)
    unsigned short ip_checksum;      // IP checksum
    unsigned int   ip_srcaddr;       // Source address
    unsigned int   ip_destaddr;      // Source address
}   IPV4_HDR;

typedef struct udp_hdr
{
    unsigned short source_port;     // Source port no.
    unsigned short dest_port;       // Dest. port no.
    unsigned short udp_length;      // UDP packet length
    unsigned short udp_checksum;    // UDP checksum (optional)
}   UDP_HDR;

typedef struct tcp_header 
{
    unsigned short source_port;  // source port 
    unsigned short dest_port;    // destination port 
    unsigned int   sequence;     // sequence number - 32 bits 
    unsigned int   acknowledge;  // acknowledgement number - 32 bits 
        
    unsigned char  ns   :1;          //Nonce Sum Flag Added in RFC 3540.
    unsigned char  reserved_part1:3; //according to rfc
    unsigned char  data_offset:4;    //number of dwords in the TCP header. 
    
    unsigned char  fin  :1;      //Finish Flag
    unsigned char  syn  :1;      //Synchronise Flag
    unsigned char  rst  :1;      //Reset Flag
    unsigned char  psh  :1;      //Push Flag 
    unsigned char  ack  :1;      //Acknowledgement Flag 
    unsigned char  urg  :1;      //Urgent Flag
    
    unsigned char  ecn  :1;      //ECN-Echo Flag
    unsigned char  cwr  :1;      //Congestion Window Reduced Flag
        
    unsigned short window;          // window 
    unsigned short checksum;        // checksum 
    unsigned short urgent_pointer;  // urgent pointer 
}   TCP_HDR;

typedef struct icmp_hdr    
{
    BYTE type;          // ICMP Error type
    BYTE code;          // Type sub code
    USHORT checksum;
    USHORT id;
    USHORT seq;
}   ICMP_HDR;

The ip_protocol field of the IP header determines the type of the packet; for example, 1 means an ICMP packet and 2-IGMP 6-TCP 17-UDP and so on. RFC 1340 should help more. Another function in the source code is PrintData() which prints the data dumps in a hex view fashion as done by other sniffers and looks like this:

IP Header
    45 00 03 19 D8 17 00 00 FC 06 85 C0 42 F9 59 63         E...........B.Yc
    C0 A8 01 02                                             ....
TCP Header
    00 50 0C E4 D6 2B 77 70 9F 0D B1 8D 50 18 1A A8         .P...+wp....P...
    71 C1 00 00                                             q...
Data Payload
    48 54 54 50 2F 31 2E 31 20 32 30 30 20 4F 4B 0D         HTTP/1.1 200 OK.
    0A 53 65 74 2D 43 6F 6F 6B 69 65 3A 20 47 6F 6F         .Set-Cookie: Goo
    67 6C 65 41 63 63 6F 75 6E 74 73 4C 6F 63 61 6C         gleAccountsLocal
    65 5F 73 65 73 73 69 6F 6E 3D 65 6E 0D 0A 53 65         e_session=en..Se
    74 2D 43 6F 6F 6B 69 65 3A 20 53 49 44 3D 45 58         t-Cookie: SID=EX
    50 49 52 45 44 3B 44 6F 6D 61 69 6E 3D 2E 67 6F         PIRED;Domain=.go
    6F 67 6C 65 2E 63 6F 2E 69 6E 3B 50 61 74 68 3D         ogle.co.in;Path=
    2F 3B 45 78 70 69 72 65 73 3D 4D 6F 6E 2C 20 30         /;Expires=Mon, 0
    31 2D 4A 61 6E 2D 31 39 39 30 20 30 30 3A 30 30         1-Jan-1990 00:00
    3A 30 30 20 47 4D 54 0D 0A 43 6F 6E 74 65 6E 74         :00 GMT..Content
    2D 54 79 70 65 3A 20 74 65 78 74 2F 68 74 6D 6C         -Type: text/html
    3B 20 63 68 61 72 73 65 74 3D 55 54 46 2D 38 0D         ; charset=UTF-8.
    0A 43 61 63 68 65 2D 63 6F 6E 74 72 6F 6C 3A 20         .Cache-control: 
    70 72 69 76 61 74 65 0D 0A 43 6F 6E 74 65 6E 74         private..Content

... and so on. To the right we only print the characters which are either an alphabet or a number. Everything is saved in a log file named log.txt.

Conclusion

The source code demonstrates simple concepts which can be used to develop a protocol analyzer like Ethereal. In the source code, only TCP, UDP and ICMP packets have been broken into fields and that too according to normal situations. Variations in packet structures (e.g. in case of ICMP) are there to which the program might give inaccurate results. So you have to develop the program in the relevant way and implement all necessary error checking algorithms etc. Minor changes in the source code will adapt it to the winpcap environment so that everything on the wire can be sniffed!

Happy Sniffing!

For any questions, comments or feedback, email me at prasshhant.p@gmail.com

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here