preface

Some knowledge even if we do not use, but also know.


Like raw socket, actually he has his own field, often use him to develop safety related applications, such as nmap, network performance monitoring, detection, network attack tools, such as is commonly used in our ordinary application, so, but just like that sentence above, understanding the original socket is necessary.

We have two main types of sockets, streaming sockets and datagram sockets. Streaming sockets use TCP and datagram sockets use UDP, which follow the IP transport-level protocol. But in normal streaming socket and datagram socket applications, the operating system kernel adds headers, such as IP and TCP headers, to the data as it is transferred. Therefore, the application only needs to care about the data it is sending and the data it is sending back. Additionally, there is no way to modify TCP or IP header fields directly. The transport and network headers are taken care of by the protocol stack according to the parameters specified when the socket is created.

This is not the case with raw sockets, which allow IP packets to be sent or received directly without any transport layer format, bypassing normal TCP/IP processing and sending packets to other users’ applications.

Since network-level IP packets have no concept of “ports”, they can read all incoming packets from network devices. What does this mean? This means that an application using the raw socket can read all network packets coming into the system, which means that we can capture packets from other applications. To prevent this from happening, Linux requires all applications accessing the raw socket to be run as root.

If you want to write your own protocol, you need to use a raw socket, which does not automatically encode/decode TCP or UDP headers.

In addition, there are many limitations in Windows, because the original socket provides functions that ordinary sockets do not have, can control network packets, but also bring a lot of convenience to the attacker, so different Windows versions of the environment for the original socket is also different. As the following:

  1. Unable to send TCP data over the raw socket.

  2. It is not allowed to call bind using IPPROTO_TCP with raw sockets.

  3. UDP datagrams with invalid source addresses cannot be sent over the raw socket. The source IP address of any outgoing UDP packet must exist on the network interface; otherwise, the packet will be discarded. This change was made to limit the ability of malicious code to create distributed denial of service attacks and to send spoofed packets (TCP/IP packets with forged source IP addresses).

So this leads to the original function provided by the original socket can not be used, but we can lower the programming level in a layer, you can use WinPcap directly operate the frame, WinPcap is Windows platform access data link layer open source library, can be applied to the network data frame construction, capture, analysis.

Create the raw socket

The raw socket is created using the same socket function, but with different parameters. Type needs to be set to SOCK_RAW. The third protocol type needs to be selected based on requirements, such as IPPROTO_ICMP, IPPROTO_TCP, and IPPROTO_UDP.

int socket(int domain, int type, int protocol);
Copy the code

example

Capture all ICMP packets

Most people are most impressed by the ping command, which makes use of ICMP request echo and echo reply packets. ICMP stands for Internet Control Message Protocol. It is used to send control messages in the Internet protocol (IP) to return various problems that have occurred in the communication environment. With this information, we can diagnose the problems that have occurred.

The following program captures all ICMP packets entering the system. Therefore, when creating a socket, select IPPROTO_ICMP for protocol. After creating a socket, recvFROM is used to receive packets.

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>

#include <netinet/ip.h>
#include <netinet/ip_icmp.h>

#include <arpa/inet.h>  

int main(a){
     int sockfd,retval,n;
     socklen_t clilen;
     struct sockaddr_in cliaddr.servaddr;
     char buf[10000]; 
     int i;
     
     sockfd = socket(AF_INET, SOCK_RAW, IPPROTO_ICMP); 
     if (sockfd < 0) {exit(1);
     }
     clilen = sizeof(struct sockaddr_in);    
     while(1){
          n = recvfrom(sockfd, buf, 10000.0, (struct sockaddr *)&cliaddr, &clilen);
          struct ip *ip_hdr = (struct ip *)buf;
          printf("IP header size %d bytes.\n", ip_hdr->ip_hl*4);
          
          for (i = 0; i < n; i++) {
               printf("%02X%s", (uint8_t)buf[i], (i + 1) %16 ? "" : "\n");
          }
          printf("\n");
          
          struct icmp *icmp_hdr = (struct icmp *)((char *)ip_hdr + (4 * ip_hdr->ip_hl));

          printf("ICMP msgtype=%d, code=%d ", icmp_hdr->icmp_type, icmp_hdr->icmp_code);
          printf("%s -> ", inet_ntoa(ip_hdr->ip_src));  
          printf("%s\n", inet_ntoa(ip_hdr->ip_dst)); }}Copy the code

The ping end sets the packet type to 8, and the ping end sets the packet type to 0. After running the ping 127.0.0.1 command to send an ICMP packet, the program catches the packet and outputs the following information. You can see that the request is of type 8.

IP header size20 bytes.
45 00 00 3C 7E 8B 00 00 80 01 3A 14 C0 A8 00 67
C0 A8 00 6A 08 00 4C 21 00 01 01 3A 61 62 63 64
65 66 67 68 69 6A 6B 6C 6D 6E 6F 70 71 72 73 74
75 76 77 61 62 63 64 65 66 67 68 69 
ICMP msgtype=8, code=0 192.168. 0103. -> 192.168. 0106.IP header size20 bytes.
45 00 00 3C 7E 8D 00 00 80 01 3A 12 C0 A8 00 67
C0 A8 00 6A 08 00 4C 20 00 01 01 3B 61 62 63 64
65 66 67 68 69 6A 6B 6C 6D 6E 6F 70 71 72 73 74
75 76 77 61 62 63 64 65 66 67 68 69 
ICMP msgtype=8, code=0 192.168. 0103. -> 192.168. 0106.IP header size20 bytes.
45 00 00 3C 7E 8F 00 00 80 01 3A 10 C0 A8 00 67
C0 A8 00 6A 08 00 4C 1F 00 01 01 3C 61 62 63 64
65 66 67 68 69 6A 6B 6C 6D 6E 6F 70 71 72 73 74
75 76 77 61 62 63 64 65 66 67 68 69 
ICMP msgtype=8, code=0 192.168. 0103. -> 192.168. 0106.

Copy the code

Sending ICMP Packets

Sending ICMP also requires the creation of a raw socket. Sending ICMP requires us to build ICMP packets ourselves. One of the most difficult steps is to calculate the checksum, as shown in the following image from the network.

The following figure shows the ICMP packet sent by Windows to Linux. We know that the Ping program of Windows sends ICMP packets four times by default, and Linux also replies four times. Therefore, there are 8 packets in total, and the IP addresses of the two operating systems are respectively:

Windows: 192.168.0.103

Linux: 192.168.0.106

The following figure shows the ICMP packet of type 8, the same as ours above.

The ICMP checksum is calculated as follows:

  1. Group ICMP packets (including headers and data) in groups of 16 bits (2 bytes) and add all the groups (binary summation)
  2. If the high 16bit is not 0, the high 16bit and the low 16bit are added repeatedly until the high 16bit is 0, thus obtaining a value of only 16 bits
  3. Invert this 16bit value by bit.

Nothing else is too difficult.

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip_icmp.h>
#include <arpa/inet.h>
#include <netdb.h>

u_int16_t checksum(unsigned short *buf, int size)
{
	unsigned long sum = 0;
	while (size > 1) {
		sum += *buf;
		buf++;
		size -= 2;
	}
	if (size == 1)
		sum += *(unsigned char *)buf;
	sum = (sum & 0xffff) + (sum >> 16);
	sum = (sum & 0xffff) + (sum >> 16);
	return ~sum;
}


void setup_icmphdr(u_int8_t type, u_int8_t code, u_int16_t id, u_int16_t seq, struct icmphdr *icmphdr)
{
	memset(icmphdr, 0.sizeof(struct icmphdr));
	icmphdr->type = type;
	icmphdr->code = code;
	icmphdr->checksum = 0;
	icmphdr->un.echo.id = id;
	icmphdr->un.echo.sequence = seq;
	icmphdr->checksum = checksum((unsigned short *)icmphdr, sizeof(struct icmphdr));
}

int main(int argc, char **argv)
{
	int n, soc;
	char buf[1500];
	struct sockaddr_in addr;
	struct in_addr insaddr;
	struct icmphdr icmphdr;
	struct iphdr *recv_iphdr;
	struct icmphdr *recv_icmphdr;

	if (argc < 2) {
		printf("Please pass in the parameter: %s IP address \n", argv[0]);
		return 1;
	}

	addr.sin_family = AF_INET;
	addr.sin_addr.s_addr = inet_addr(argv[1]);
	soc = socket(AF_INET, SOCK_RAW, IPPROTO_ICMP);
	setup_icmphdr(ICMP_ECHO, 0.0.0, &icmphdr);

	n = sendto(soc, (char *)&icmphdr, sizeof(icmphdr), 0, (struct sockaddr *)&addr, sizeof(addr));
	if (n < 1) {

		return 1;
	}

	n = recv(soc, buf, sizeof(buf), 0);
	if (n < 1) {

		return 1;
	}

	recv_iphdr = (struct iphdr *)buf;

	recv_icmphdr = (struct icmphdr *)(buf + (recv_iphdr->ihl << 2));
    	printf("ICMP msgtype=%d, code=%d\n", recv_icmphdr->type, recv_icmphdr->code);
	insaddr.s_addr = recv_iphdr->saddr;
	close(soc);
	return 0;
}
Copy the code

Capture all packages

The following example will capture all the data packets entering the system and print some information about the data packets. However, it will print only if the source address is 192.168.0.101. This IP address is my mobile phone address. That’s when the terminal will print.

#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<netinet/ip.h>
#include<sys/socket.h>
#include<arpa/inet.h>

int main(a) {

    struct sockaddr_in source_socket_address.dest_socket_address;

    int packet_size;


    unsigned char *buffer = (unsigned char *)malloc(65536);

    int sock = socket (PF_INET, SOCK_RAW, IPPROTO_TCP);
    if(sock == - 1)
    {

        perror("Failed to create Socket");
        exit(1);
    }
    while(1) {

      packet_size = recvfrom(sock , buffer , 65536 , 0 , NULL.NULL);
      if (packet_size == - 1) {
        return 1;
      }

      struct iphdr *ip_packet = (struct iphdr *)buffer;

      memset(&source_socket_address, 0.sizeof(source_socket_address));
      source_socket_address.sin_addr.s_addr = ip_packet->saddr;
      memset(&dest_socket_address, 0.sizeof(dest_socket_address));
      dest_socket_address.sin_addr.s_addr = ip_packet->daddr;

      char *sourceAddress=inet_ntoa(source_socket_address.sin_addr);
      if(strcmp(sourceAddress ,"192.168.0.101") = =0) {printf("Packet size (bytes): %d\n",ntohs(ip_packet->tot_len));
          printf("Original address: %s\n",sourceAddress );
          printf("Destination address: %s\n", (char *)inet_ntoa(dest_socket_address.sin_addr));
          printf("Identification: %d\n\n", ntohs(ip_packet->id)); }}return 0;
}

Copy the code

The output is the same as the Wireshark capture.

Packet size (bytes):52The original address:192.168. 0101.Destination Address:192.168. 0106.
Identification: 846Packet size (bytes):52The original address:192.168. 0101.Destination Address:192.168. 0106.
Identification: 847Packet size (bytes):52The original address:192.168. 0101.Destination Address:192.168. 0106.
Identification: 848Packet size (bytes):52The original address:192.168. 0101.Destination Address:192.168. 0106.
Identification: 849.Copy the code

So much for the primitive socket example, and it really opened up a new world.