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:
- Create a raw socket.
- Bind the socket to the local IP over which the traffic is to be sniffed.
WSAIoctl()
the socket with SIO_RCVALL
to give it sniffing powers.
- Put the socket in an infinite loop of
recvfrom
.
- 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);
}
To get the local IPs associated with the machine, all that needs to be done is:
gethostname(hostname, sizeof(hostname);
HOSTENT *local = gethostbyname(hostname);
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;
unsigned char ip_version :4;
unsigned char ip_tos;
unsigned short ip_total_length;
unsigned short ip_id;
unsigned char ip_frag_offset :5;
unsigned char ip_more_fragment :1;
unsigned char ip_dont_fragment :1;
unsigned char ip_reserved_zero :1;
unsigned char ip_frag_offset1;
unsigned char ip_ttl;
unsigned char ip_protocol;
unsigned short ip_checksum;
unsigned int ip_srcaddr;
unsigned int ip_destaddr;
} IPV4_HDR;
typedef struct udp_hdr
{
unsigned short source_port;
unsigned short dest_port;
unsigned short udp_length;
unsigned short udp_checksum;
} UDP_HDR;
typedef struct tcp_header
{
unsigned short source_port;
unsigned short dest_port;
unsigned int sequence;
unsigned int acknowledge;
unsigned char ns :1;
unsigned char reserved_part1:3;
unsigned char data_offset:4;
unsigned char fin :1;
unsigned char syn :1;
unsigned char rst :1;
unsigned char psh :1;
unsigned char ack :1;
unsigned char urg :1;
unsigned char ecn :1;
unsigned char cwr :1;
unsigned short window;
unsigned short checksum;
unsigned short urgent_pointer;
} TCP_HDR;
typedef struct icmp_hdr
{
BYTE type;
BYTE 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