We write a sniffer on c. We are writing a simple sniffer for Windows. Description of the IP packet structure

In this article we will look at creating a simple sniffer for Windows OS.
Anyone interested, welcome to cat.

Introduction

Target: write a program that will capture network traffic (Ethernet, WiFi) transmitted over the IP protocol.
Facilities: Visual Studio 2005 or higher.
The approach described here does not belong to the author personally and is successfully used in many commercial, as well as completely free programs (hello, GPL).
This work is intended primarily for beginners in network programming, who, however, have at least basic knowledge in the field of sockets in general, and Windows sockets in particular. Here I will often write well-known things, because the subject area is specific, if I miss something, my head will be a mess.

I hope you find it interesting.

Theory (reading is not required, but recommended)

At the moment, the vast majority of modern information networks are based on the foundation of the TCP/IP protocol stack. The TCP/IP protocol stack (Transmission Control Protocol/Internet Protocol) is a collective name for network protocols of different levels used in networks. In this article, we will be mainly interested in the IP protocol - a routed network protocol used for the non-guaranteed delivery of data divided into so-called packets (a more correct term is a datagram) from one network node to another.
Of particular interest to us are IP packets designed to transmit information. This is a fairly high level of the OSI network data model, when you can isolate yourself from the device and data transmission medium, operating only with a logical representation.
It is completely logical that sooner or later tools for intercepting, monitoring, recording and analyzing network traffic should have appeared. Such tools are usually called traffic analyzers, packet analyzers or sniffers (from English to sniff - sniff). This is a network traffic analyzer, a program or hardware-software device designed to intercept and subsequently analyze, or only analyze, network traffic intended for other nodes.

Practice (substantive conversation)

At the moment, quite a lot of software has been created to listen to traffic. The most famous of them: Wireshark. Naturally, the goal is not to reap his laurels - we are interested in the task of intercepting traffic by simply “listening” to a network interface. It is important to understand that we are not going to hack and intercept stranger traffic. We just need to view and analyze the traffic that passes through our host.

Why this may be needed:

  1. View the current traffic flow through the network connection (incoming/outgoing/total).
  2. Redirect traffic for subsequent analysis to another host.
  3. Theoretically, you can try to use it to hack a WiFi network (we're not going to do that, are we?).
Unlike Wireshark, which is based on the libpcap/WinPcap library, our analyzer will not use this driver. What’s more, we won’t have a driver at all, and we’re not going to write our own NDIS (oh the horror!). You can read about this in. He will simply be a passive observer, using only WinSock library. Using a driver in this case is redundant.

How so? Very simple.
The key step in turning a simple network application into a network analyzer is to switch the network interface to promiscuous mode, which will allow it to receive packets addressed to other interfaces on the network. This mode forces the network card to accept all frames, regardless of who they are addressed to on the network.

Starting with Windows 2000 (NT 5.0), it became very easy to create a program to listen to a network segment, because its network driver allows you to set the socket to receive all packets.

Enabling Promiscuous Mode
long flag = 1; SOCKET socket; #define SIO_RCVALL 0x98000001 ioctlsocket(socket, SIO_RCVALL, &RS_Flag);
Our program operates on IP packets and uses the Windows Sockets library version 2.2 and raw sockets. In order to gain direct access to an IP packet, the socket must be created as follows:
Creating a raw socket
s = socket(AF_INET, SOCK_RAW, IPPROTO_IP);
Here instead of a constant SOCK_STREAM(TCP protocol) or SOCK_DGRAM(UDP protocol), we use the value SOCK_RAW. Generally speaking, working with raw sockets is interesting not only from the point of view of traffic capture. In fact, we get complete control over the formation of the package. Or rather, we form it manually, which allows, for example, to send a specific ICMP packet...

Go ahead. It is known that an IP packet consists of a header, service information and, in fact, data. I advise you to look here to refresh your knowledge. Let's describe the IP header in the form of a structure (thanks to the excellent article on RSDN):

Description of the IP packet structure
typedef struct _IPHeader ( unsigned char ver_len; // header version and length unsigned char tos; // service type unsigned short length; // length of the entire packet unsigned short id; // Id unsigned short flgs_offset; // flags and offset unsigned char ttl ; // lifetime unsigned char protocol; // protocol unsigned long src; // sender IP address unsigned long dest; // destination IP address unsigned short *params; 320 bits) unsigned char *data; // data (up to 65535 octets) )IPHeader;
The main function of the listening algorithm will look like this:
Single packet capture function
IPHeader* RS_Sniff() ( IPHeader *hdr; int count = 0; count = recv(RS_SSocket, (char*)&RS_Buffer, sizeof(RS_Buffer), 0); if (count >= sizeof(IPHeader)) ( hdr = (LPIPHeader )malloc(MAX_PACKET_SIZE); memcpy(hdr, RS_Buffer, MAX_PACKET_SIZE); RS_UpdateNetStat(count, hdr) else return 0;
Everything is simple here: we receive a piece of data using the standard socket function recv, and then copy them into a structure like IPHeader.
And finally, we start an endless packet capture loop:
Let's capture all packets that reach our network interface
while (true) ( ​​IPHeader* hdr = RS_Sniff(); // processing the IP packet if (hdr) ( // print the header in the console) )
A bit offtopic
Here and below, the author made the RS_ (from Raw Sockets) prefix for some important functions and variables. I did the project 3-4 years ago, and I had a crazy idea to write a full-fledged library for working with raw sockets. As often happens, after obtaining some significant (for the author) results, the enthusiasm faded, and the matter did not go further than a training example.

In principle, you can go further and describe the headers of all subsequent protocols located above. To do this, you need to analyze the field protocol in the structure IPHeader. Look at the example code (yes, there should be a switch, damn it!), where the header is colored depending on what protocol the packet has encapsulated in IP:

/* * Highlighting a package with color */ void ColorPacket(const IPHeader *h, const u_long haddr, const u_long whost = 0) ( if (h->xsum) SetConsoleTextColor(0x17); // if the package is not empty else SetConsoleTextColor(0x07) ; // empty package if (haddr == h->src) ( SetConsoleTextColor(BACKGROUND_BLUE | /*BACKGROUND_INTENSITY |*/ FOREGROUND_RED | FOREGROUND_INTENSITY); // "native" package for return ) else if (haddr == h->dest ) ( SetConsoleTextColor(BACKGROUND_BLUE | /*BACKGROUND_INTENSITY |*/ FOREGROUND_GREEN | FOREGROUND_INTENSITY); // "native" receive packet ) if (h->protocol == PROT_ICMP || h->protocol == PROT_IGMP) ( SetConsoleTextColor(0x70) ; // ICMP packet ) else if(h->protocol == PROT_IP || h->protocol == 115) ( SetConsoleTextColor(0x4F); // IP-in-IP packet, L2TP ) else if(h- >protocol == 53 || h->protocol == 56) ( SetConsoleTextColor(0x4C); // TLS, IP with Encryption ) if(whost == h->dest || whost == h->src) ( SetConsoleTextColor (0x0A);

However, this is significantly beyond the scope of this article. For our training example, it will be enough to look at the IP addresses of the hosts from which and to which traffic is coming, and calculate its amount per unit of time (the finished program is in the archive at the end of the article).

In order to display IP header data, you must implement a function to convert the header (but not the data) of the datagram to a string. As an example of implementation, we can offer the following option:

Converting an IP header to a string
inline char* iph2str(IPHeader *iph) ( const int BUF_SIZE = 1024; char *r = (char*)malloc(BUF_SIZE); memset((void*)r, 0, BUF_SIZE); sprintf(r, "ver=% d hlen=%d tos=%d len=%d id=%d flags=0x%X offset=%d ttl=%dms prot=%d crc=0x%X src=%s dest=%s", BYTE_H (iph->ver_len), BYTE_L(iph->ver_len)*4, iph->tos, ntohs(iph->length), ntohs(iph->id), IP_FLAGS(ntohs(iph->flgs_offset)), IP_OFFSET (ntohs(iph->flgs_offset)), iph->ttl, iph->protocol, ntohs(iph->xsum), nethost2str(iph->src), nethost2str(iph->dest));
Based on the basic information given above, we get this small program (creepy name ss, short for simple sniffer), which implements local listening to IP traffic. Its interface is shown below in the figure.

I provide the source and binary code as is, as it was several years ago. Now I'm scared to look at it, and yet, it's quite readable (of course, you can't be so self-confident). Even Visual Studio Express 2005 will be sufficient for compilation.

What we ended up with:

  • The sniffer operates in user mode, but requires administrator privileges.
  • Packets are not filtered and are displayed as is (you can add custom filters - I suggest looking at this topic in detail in the next article if you are interested).
  • WiFi traffic is also captured (it all depends on the specific chip model, it may not work for you, like it did for me several years ago), although there is AirPcap, which can do this wonderfully, but costs money.
  • The entire datagram stream is logged to a file (see the archive attached at the end of the article).
  • The program operates as a server on port 2000. You can connect to the host using the telnet utility and monitor traffic flows. The number of connections is limited to twenty (the code is not mine, I found it on the Internet and used it for experiments; I didn’t delete it - it’s a pity)
Thank you for your attention, I congratulate the residents of Khabrovsk and Khabrovka residents and everyone, Merry Christmas!

In this article we will look at creating a simple sniffer for Windows OS.
Anyone interested, welcome to cat.

Introduction

Target: write a program that will capture network traffic (Ethernet, WiFi) transmitted over the IP protocol.
Facilities: Visual Studio 2005 or higher.
The approach described here does not belong to the author personally and is successfully used in many commercial, as well as completely free programs (hello, GPL).
This work is intended primarily for beginners in network programming, who, however, have at least basic knowledge in the field of sockets in general, and Windows sockets in particular. Here I will often write well-known things, because the subject area is specific, if I miss something, my head will be a mess.

I hope you find it interesting.

Theory (reading is not required, but recommended)

At the moment, the vast majority of modern information networks are based on the foundation of the TCP/IP protocol stack. The TCP/IP protocol stack (Transmission Control Protocol/Internet Protocol) is a collective name for network protocols of different levels used in networks. In this article, we will be mainly interested in the IP protocol - a routed network protocol used for the non-guaranteed delivery of data divided into so-called packets (a more correct term is a datagram) from one network node to another.
Of particular interest to us are IP packets designed to transmit information. This is a fairly high level of the OSI network data model, when you can isolate yourself from the device and data transmission medium, operating only with a logical representation.
It is completely logical that sooner or later tools for intercepting, monitoring, recording and analyzing network traffic should have appeared. Such tools are usually called traffic analyzers, packet analyzers or sniffers (from English to sniff - sniff). This is a network traffic analyzer, a program or hardware-software device designed to intercept and subsequently analyze, or only analyze, network traffic intended for other nodes.

Practice (substantive conversation)

At the moment, quite a lot of software has been created to listen to traffic. The most famous of them: Wireshark. Naturally, the goal is not to reap his laurels - we are interested in the task of intercepting traffic by simply “listening” to a network interface. It is important to understand that we are not going to hack and intercept stranger traffic. We just need to view and analyze the traffic that passes through our host.

Why this may be needed:

  1. View the current traffic flow through the network connection (incoming/outgoing/total).
  2. Redirect traffic for subsequent analysis to another host.
  3. Theoretically, you can try to use it to hack a WiFi network (we're not going to do that, are we?).

Unlike Wireshark, which is based on the libpcap/WinPcap library, our analyzer will not use this driver. What’s more, we won’t have a driver at all, and we’re not going to write our own NDIS (oh the horror!). You can read about this in this topic. He will simply be a passive observer, using only WinSock library. Using a driver in this case is redundant.

How so? Very simple.
The key step in turning a simple network application into a network analyzer is to switch the network interface to promiscuous mode, which will allow it to receive packets addressed to other interfaces on the network. This mode forces the network card to accept all frames, regardless of who they are addressed to on the network.

Starting with Windows 2000 (NT 5.0), it became very easy to create a program to listen to a network segment, because its network driver allows you to set the socket to receive all packets.

Enabling Promiscuous Mode
long flag = 1; SOCKET socket; #define SIO_RCVALL 0x98000001 ioctlsocket(socket, SIO_RCVALL, &RS_Flag);

Our program operates on IP packets and uses the Windows Sockets library version 2.2 and raw sockets. In order to gain direct access to an IP packet, the socket must be created as follows:

Creating a raw socket
s = socket(AF_INET, SOCK_RAW, IPPROTO_IP);

Here instead of a constant SOCK_STREAM(TCP protocol) or SOCK_DGRAM(UDP protocol), we use the value SOCK_RAW. Generally speaking, working with raw sockets is interesting not only from the point of view of traffic capture. In fact, we get complete control over the formation of the package. Or rather, we form it manually, which allows, for example, to send a specific ICMP packet...

Go ahead. It is known that an IP packet consists of a header, service information and, in fact, data. I advise you to look here to refresh your knowledge. Let's describe the IP header in the form of a structure (thanks to the excellent article on RSDN):

Description of the IP packet structure
typedef struct _IPHeader ( unsigned char ver_len; // header version and length unsigned char tos; // service type unsigned short length; // length of the entire packet unsigned short id; // Id unsigned short flgs_offset; // flags and offset unsigned char ttl ; // lifetime unsigned char protocol; // protocol unsigned long src; // sender IP address unsigned long dest; // destination IP address unsigned short *params; 320 bits) unsigned char *data; // data (up to 65535 octets) )IPHeader;

The main function of the listening algorithm will look like this:

Single packet capture function
IPHeader* RS_Sniff() ( IPHeader *hdr; int count = 0; count = recv(RS_SSocket, (char*)&RS_Buffer, sizeof(RS_Buffer), 0); if (count >= sizeof(IPHeader)) ( hdr = (LPIPHeader )malloc(MAX_PACKET_SIZE); memcpy(hdr, RS_Buffer, MAX_PACKET_SIZE); RS_UpdateNetStat(count, hdr) else return 0;

Everything is simple here: we receive a piece of data using the standard socket function recv, and then copy them into a structure like IPHeader.
And finally, we start an endless packet capture loop:

Let's capture all packets that reach our network interface
while (true) ( ​​IPHeader* hdr = RS_Sniff(); // processing the IP packet if (hdr) ( // print the header in the console) )
A bit offtopic

Here and below, the author made the RS_ (from Raw Sockets) prefix for some important functions and variables. I did the project 3-4 years ago, and I had a crazy idea to write a full-fledged library for working with raw sockets. As often happens, after obtaining some significant (for the author) results, the enthusiasm faded, and the matter did not go further than a training example.

In principle, you can go further and describe the headers of all subsequent protocols located above. To do this, you need to analyze the field protocol in the structure IPHeader. Look at the example code (yes, there should be a switch, damn it!), where the header is colored depending on what protocol the packet has encapsulated in IP:

/* * Highlighting a package with color */ void ColorPacket(const IPHeader *h, const u_long haddr, const u_long whost = 0) ( if (h->xsum) SetConsoleTextColor(0x17); // if the package is not empty else SetConsoleTextColor(0x07) ; // empty package if (haddr == h->src) ( SetConsoleTextColor(BACKGROUND_BLUE | /*BACKGROUND_INTENSITY |*/ FOREGROUND_RED | FOREGROUND_INTENSITY); // "native" package for return ) else if (haddr == h->dest ) ( SetConsoleTextColor(BACKGROUND_BLUE | /*BACKGROUND_INTENSITY |*/ FOREGROUND_GREEN | FOREGROUND_INTENSITY); // "native" receive packet ) if (h->protocol == PROT_ICMP || h->protocol == PROT_IGMP) ( SetConsoleTextColor(0x70) ; // ICMP packet ) else if(h->protocol == PROT_IP || h->protocol == 115) ( SetConsoleTextColor(0x4F); // IP-in-IP packet, L2TP ) else if(h- >protocol == 53 || h->protocol == 56) ( SetConsoleTextColor(0x4C); // TLS, IP with Encryption ) if(whost == h->dest || whost == h->src) ( SetConsoleTextColor (0x0A);

However, this is significantly beyond the scope of this article. For our training example, it will be enough to look at the IP addresses of the hosts from which and to which traffic is coming, and calculate its amount per unit of time (the finished program is in the archive at the end of the article).

In order to display IP header data, you must implement a function to convert the header (but not the data) of the datagram to a string. As an example of implementation, we can offer the following option:

Converting an IP header to a string
inline char* iph2str(IPHeader *iph) ( const int BUF_SIZE = 1024; char *r = (char*)malloc(BUF_SIZE); memset((void*)r, 0, BUF_SIZE); sprintf(r, "ver=% d hlen=%d tos=%d len=%d id=%d flags=0x%X offset=%d ttl=%dms prot=%d crc=0x%X src=%s dest=%s", BYTE_H (iph->ver_len), BYTE_L(iph->ver_len)*4, iph->tos, ntohs(iph->length), ntohs(iph->id), IP_FLAGS(ntohs(iph->flgs_offset)), IP_OFFSET (ntohs(iph->flgs_offset)), iph->ttl, iph->protocol, ntohs(iph->xsum), nethost2str(iph->src), nethost2str(iph->dest));

Based on the basic information given above, we get this small program (creepy name ss, short for simple sniffer), which implements local listening to IP traffic. Its interface is shown below in the figure.

I provide the source and binary code as is, as it was several years ago. Now I'm scared to look at it, and yet, it's quite readable (of course, you can't be so self-confident). Even Visual Studio Express 2005 will be sufficient for compilation.

What we ended up with:

  • The sniffer operates in user mode, but requires administrator privileges.
  • Packets are not filtered and are displayed as is (you can add custom filters - I suggest looking at this topic in detail in the next article if you are interested).
  • WiFi traffic is also captured (it all depends on the specific chip model, it may not work for you, like it did for me several years ago), although there is AirPcap, which can do this wonderfully, but costs money.
  • The entire datagram stream is logged to a file (see the archive attached at the end of the article).
  • The program operates as a server on port 2000. You can connect to the host using the telnet utility and monitor traffic flows. The number of connections is limited to twenty (the code is not mine, I found it on the Internet and used it for experiments; I didn’t delete it - it’s a pity)

Thank you for your attention, I wish you and everyone a Merry Christmas!

Good afternoon Somehow a problem arose at work - there was a device that worked via I2C and the protocol of which needed to be understood. Therefore, we need a sniffer for the I2C interface, which would output everything that comes and goes via I2C to the UART port and then through the converter to the COM port of the computer.

Start

I only had a couple of Atmeg8s lying around and I decided why not use them. Next, the question of the sniffer circuit arose.

There were 2 options - turn on the sniffer in parallel, or in an open circuit. Obviously, the first option looks much simpler, which in reality turned out to be completely wrong. But first things first.

Briefly, about the interface itself. I2C (TWI in Atmel) uses two wires - SCL and SDA. The first one is responsible for clocking the signal, the second one is responsible for transmitting information directly. The interface also has START and STOP states.

So, my first thought was to take a probe and, on the one hand, connect it to the external interrupt leg on atmega8, on the other, to the SDA line and catch the leading edge, and determine 0 or 1 by the elapsed time. Obviously, this should have worked very poorly , since the STOP signal was not processed correctly.

The second idea was to do the same thing, but catch the interruption on the SCL line, and read the SDA line connected to a regular digital leg using the interruption. Here everything looked more viable, except for the same STOP state, but I decided to try to assemble it on a breadboard and see what happens.

I apologize in advance if you find obvious mistakes in the code below, since I am reconstructing the rejected code versions from memory on my knees.

The interrupt handler code looked like this:

ISR(INT0_vect) ( cli(); if (bitIsHigh(PINB, 0)) uart_send_char("1"); else uart_send_char("0"); sei(); )
Zeros and ones flowed into the port, but it immediately became obvious that the data was incorrect - there was much less of it than expected and it changed when the same request was repeated. In the process of finding out the reasons, it all came down to the fact that data was lost due to access to the uart interface, which, by the way, worked at a maximum stable speed of 38 kbit/s, while I2C itself worked at 100 kbit/s. It was not possible to increase the speed of the UART due to the lack of a crystal of the required frequency to bring the UART to an acceptable speed. Therefore, it was necessary to remove work with the uart from the interrupt. Got something like this:

Static uint8_t data = 0; static uint8_t idx = 7; ISR(INT0_vect) ( cli(); data |= bitIsHigh(PINB, 0)<< (idx--); if (!idx) { uart_send_char(data); data = 0; idx = 7; } sei(); }
Everything became more stable, but the data still didn’t make any sense. After several hours of working through the algorithm, turning on STOP processing, etc., it was decided to go a different route.

On the right track

No matter how much I tried to implement the sniffer using a parallel circuit, nothing came of it. Based on this, there was only one option left - it was necessary to include the microcontroller in the gap, that is, it must be both a master for the response device and a slave for the original master. It probably sounds confusing, but in reality it's not like that.

Since atmega8 has only one hardware I2C on board, it is obvious that in order to work, you need to write software support for the protocol.

The result was the following code:

ISR (TWI_VECT) (CLI (); UINT8_T StATUS = TWSR; UINT8_T B; Char S; S = 0; _DLAY_MS (1); STATUS & I2C_STATUS_MASK) (CASE I2C_STATUS_SR_RX_ADR_AC:/* CASE I2C_ST Atus_sr_rx_adr_nack:*/ uart_send_str ("- AW:"); uart_send_int(TWDR); i2csoft_start(); i2csoft_open_write(I2C_ADDRESS); break; case I2C_STATUS_SR_RX_DATA_ACK:/* case I2C_STATUS_SR_RX_DATA_NACK:*/ b = TWDR; sprintf(s, " %.2X", b); uart_send_str( s); i2csoft_write_byte(b); break; case I2C_STATUS_SR_RX_STOP_RESTART: uart_send_str("E\n"); case I2C_STATUS_BUS_ERROR: uart_send_str("B\n"); break; case TW_ST_SLA_ACK: uart_send_str("-AR:"); b = i2csoft_read_byte(); uart_send_str(s); break; case TW_ST_DATA_ACK: b = i2csoft_read_byte(); b = i2csoft_read_byte(false);<The master device is connected to the hardware I2C of the atmega, the execution device is connected to any 2 digital legs, one of which operates in SCL mode, the other in SDA. All the above code does is receive an interrupt via I2C and cause a similar state on the software interface, while service information is written to the uart to help understand what is happening. The set delays were selected for a specific device and may differ slightly for others. As a result, we get a very good sniffer.

If anyone is interested, the sources can be taken from