网络编程Day01:C++ 实现ARP数据包发送工具Linux

目录

0x00 基础知识回顾

0x01 Wireshark 使用方法回顾

0x02 以太网帧回顾

0x03 ARP协议的本质

0x04 ARP工具的基本命令

0x05 ARP欺骗

0x06 ARP协议编程基础

#字节序转化函数

#IO通道管理函数

#点分十进制 转unsigned int

#发送数据包的函数

#C++实现发送ARP数据包的程序


0x00 基础知识回顾

数据链路层:

mac地址理论上是唯一的,在网卡出厂的时候被烧写到网卡的ROM中,但是mac地址其实是可以通过软件修改

网络层:IP地址

为什么既需要IP地址也需要MAC地址呢?

因为在传输的过程中,不是一蹴而就的,而是经过多个路由器、交换机的接力,目的MAC地址就是这个过程一站又一站的小目标,是不断变换的。目的IP地址则是最终的目标。

传输层:

TCP:适合文件传输、网页浏览,等对数据完整有序要求比较高的场合

UDP:适合DNS查询、视频聊天等对数据完整有序要求比较低,但对数据及时性要求比较高的场合

0x01 Wireshark 使用方法回顾

过滤数据包:

限制源ip地址: ip.src ==192.168.1.1

限制目的IP地址:ip.dst == xxx

限制端口:tcp.port == 80

限制源端口:tcp.srcport ==80

限制目标端口:tcp.dstport == 80

限制udp端口:udp.port ==53

查看全部过滤表达式:

比如筛选所有的http get请求:

选择所有的http和arp:http or arp 或者使用 http || arp

ip.src==192.168.1.1 and ip.dst ==192.168.1.2  或者使用ip.src==192.168.1.1 && ip.dst ==192.168.1.2 

也可以使用wireshark的右键菜单设置过滤器

0x02 以太网帧回顾

以太网帧的格式多达5种,这是历史原因造成的,现在TCP/IP应用多半使用Ethernet V2帧格式

Ethernet V2格式:

前导码:因为收到一连串低电平或者一连串高电平这种数据,所以需要一个前导码(脉冲信号)用于同步。

目的地址:MAC地址

源地址:MAC地址

类型:指明上层协议 例如IP协议 0x0800 、ARP协议 0x0806 RARP协议 0x0835

CRC:效验和

Ethernet V2 工作过程:

接收方的网卡会收到各种帧,只有帧的MAC地址是自己的MAC地址的包,才会交给网卡驱动等去逐步拆包

如果接受方是路由器就不一样,如果路由器收到一个MAC地址和自己MAC地址不一样的包,就会查询自己的所有端口,看有没有MAC地址匹配的端口。如果没有就会将这个包丢弃,如果有就会转发这个帧到对应端口

0x03 ARP协议的本质

ARP协议本质上是链路层 和 网络层之间的 接口

为什么这么说呢?

MAC地址在数据链路层

IP地址在网络层

为了保证各层的稳定,各层之间都是相互独立的,所以跨层信息只能通过接口获取

ARP协议就是这样一个接口 :MAC地址 = ARP(IP地址)  即传入一个IP地址给该接口,就能返回一个对应的MAC地址。

类似的DNS协议也是一个接口:IP地址 = DNS(域名)

ARP报文格式:

硬件长度:MAC地址的长度 6字节

协议长度:IP地址的长度 4字节

操作类型:

0x1 ARP请求

0x2 ARP应答

0x3 RARP请求

0x4 RARP回复

0x04 ARP工具的基本命令

arp -a 查看arp缓存表

arp -d 192.168.x.x 删除ARP缓存表中的项目

0x05 ARP欺骗

ARP协议是建立在网络中各个主机相互信任的基础上的

如果攻击者发送一个伪造的ARP响应数据包给目标主机,目标主机就会根据响应数据包更新自己的ARP缓存

利用ARP欺骗的软件:

P2P终结者

网路执法官: 网关软件

网络剪刀手:一个专门断别人网的软件

ettercap :一个中间人工具

如何防范ARP欺骗?

利用ARP防火墙

0x06 ARP协议编程基础

socket 本质上是传输层提供给应用层的接口

帧 = socket(应用层数据)

套接字(SOCK_STREAM):提供TCP协议服务,允许用户发送或者接受应用层数据

数据包套接字(SOCK_DGRAM):提供UDP协议服务,允许用户发送或者接受应用层数据

原始套接字(SOCK_RAW):允许对较低层次的协议直接访问,可以读写内核没有处理的数据包

可以发送或者接受从应用层到链路层所有的数据(只能在root下)

创建原始套接字:

int socket(int domain,int type,int protocal);

返回值:sockfd 即socket描述符

domain: 协议域

常见参数:

AF_INET(IPV4)

AF_INET6(IPV6)

以上两个参数都使套接字工作在网络层

原始套接

PF_PACKET

可以让套接字工作在数据链路层,可以接受和发送所有网络分层的数据

 

type:套接字类型

常见参数:

SOCK_STREAM (TCP)

SOCK_DGRAM(UDP)

原始套接字:

SOCK_RAW

 

protocal :协议类型

常见类型

IPPROTO_TCP

IPPROTO_UDP

工作在传输层

原始套接字:

ETH_P_IP

ETH_P_ARP

ETH_P_ALL //支持所有协议

可以工作在网络层、数据链路层

#字节序转化函数

htons() 输入一个unsigned short 将其从主机字节序变为网络字节序。

主机字节序:绝大多数是小端模式(数据低位保存在内存高位),少部分是大端模式

网络字节序:大端模式,数据高位保存在内存低位。 如果主机字节序是大端模式,网络字节序也是大端模式,htons函数就无序转化。

因为交换机和路由器是大端系统,我们的主机是小端系统。所以不同的机器之间无法直接通信

所以就强行规定,网络中传输的数据一律是大端模式,并将这种顺序称为“网络字节序”

数据接收方如果是大端系统,直接接收即可,如果是小端系统,则需要将大端模式的数据转化为小端模式的顺序。

#IO通道管理函数

第三个参数 接受返回结果的指针

对设备进行控制(告诉驱动程序要干写什么,具体由驱动程序来实现) 或者获取设备的信息(比如获取接口的信息)

#点分十进制 转unsigned int

 

#发送数据包的函数

int len 数据长度 msg 的长度

flags  标志位 控制函数的执行方式

int tolen  地址数据结构

#C++实现发送ARP数据包的程序

效果

代码

arp.cpp

#include <sys/socket.h>
#include <sys/ioctl.h> //io通道头文件
#include <netinet/if_ether.h> //以太头头文件
#include <linux/if_ether.h>
#include <arpa/inet.h> //网络字节序转化的头文件
#include <netpacket/packet.h> //网络地址头文件
#include <net/if.h> //网络接口头文件
#include <stdlib.h>
#include <stdio.h>
#include <QDebug>
#include <string.h>
#include <unistd.h>



struct arpbuf{
    struct ether_header eth; // 以太头
    struct ether_arp arp; //arp报文
};

void sendarp(char*eth_src_mac,char*eth_dst_mac,char*arp_src_mac,char*
             arp_dst_mac,char* arp_src_ip,char* arp_dst_ip,
             char* interface_name,int op){
    int buflen = sizeof(arpbuf); //获取我们定义的arp包结构体大大小
    char buf[buflen];//开内存
    struct arpbuf*  pArpBuffer = (struct arpbuf*)buf; //强制类型转化为结构体
    struct sockaddr_ll toaddr;
    struct in_addr targetIP,srcIP; //in_addr is unsigned int 32bit
    struct ifreq intaceRequest; //接口请求结构体
    int socketfd;// 用于保存socket 描述符


    //define PF_PACKET        0x0011
    //#define ETH_P_IP        0x0800          /* Internet Protocol packet     */
    //#define ETH_P_ALL       0x0003          /* Every packet (be careful!!!) */
    //#define ETH_P_ARP        0x0806
    socketfd = socket(PF_PACKET,SOCK_RAW,htons(ETH_P_ARP));
    // htons() 输入一个unsigned short 16bit 将其从主机字节序变为网络字节序
    // 因为交换机和路由器是大端系统,我们的主机是小端系统
    if(socketfd < 0){
        qDebug()<< "创建原始套接字失败";
        qDebug()<< socketfd;
        qDebug()<<eth_src_mac;
        qDebug()<<eth_dst_mac;
        qDebug()<<arp_src_mac;
        qDebug()<<arp_dst_mac;
        exit(1); //创建原始套接字失败
    }
    qDebug()<<"创建原始套接字成功";

    //create toaddr
    bzero(&toaddr,sizeof(toaddr)); //将内存块清0 相当于memset()
    bzero(&interfaceRequest,sizeof(interfaceRequest));
    memcpy(&interfaceRequest,(const void*)interface_name,strlen(interface_name));
    //SIocgIFINDEX 获取接口索引的控制命令
    //define SIocgIFINDEX  0x8933
    ioctl(socketfd,0x8933,&interfaceRequest);
    toaddr.sll_ifindex = interfaceRequest.ifr_ifindex;

    //create arp package
    //step 1.create thernet header
    //define ETH_ALEN 6  Ethernet address 6 bytes
    memcpy(pArpBuffer->eth.ether_dhost,eth_dst_mac,ETH_ALEN);
    memcpy(pArpBuffer->eth.ether_shost,eth_src_mac,ETH_ALEN);
    pArpBuffer->eth.ether_type = htons(0x0806); //define ETHERTYPE_ARP 0x0806
    //step 2.create the part of ARP
    pArpBuffer->arp.arp_hrd =htons(0x0001); //hardware type:define ARPHRD_ETHER
    pArpBuffer->arp.arp_pro = htons(ETHERTYPE_IP); //protocal type :0x0800
    pArpBuffer->arp.arp_hln = ETH_ALEN; //hardware address size
    pArpBuffer->arp.arp_pln = 4; //protocal adress size,we don't use htons because size of 4 is 1 byte
    pArpBuffer->arp.arp_op = htons(op==1?ARPOP_REQUEST:ARPOP_REPLY); //Opcode :1 for request,2 for reply
    memcpy(pArpBuffer->arp.arp_sha,arp_src_mac,ETH_ALEN); //arp_sha is arp_sender_hardware address
    inet_pton(AF_INET,arp_src_ip,&srcIP); // string to unsigned int 32bit
    memcpy(pArpBuffer->arp.arp_spa,&srcIP,4); //arp_spa is arp_sender_ip_address
    memcpy(pArpBuffer->arp.arp_tha,arp_dst_mac,ETH_ALEN);
    inet_pton(AF_INET,arp_dst_ip,&targetIP);
    memcpy(pArpBuffer->arp.arp_tpa,&targetIP,4);

    toaddr.sll_family = PF_PACKET; //protocal domain PF_PACKET is equal AF_PACKET
    //step3 send it!
    sendto(socketfd,pArpBuffer,buflen,0,(const sockaddr*)&toaddr,sizeof(toaddr));
    close(socketfd);


}

widget.cpp

#include "widget.h"
#include "ui_widget.h"
#include "arp.cpp"

Widget::Widget(QWidget *parent)
    : QWidget(parent)
    , ui(new Ui::Widget)
{
    ui->setupUi(this);
    connect(ui->sendBtn,SIGNAL(clicked(bool)),this,SLOT(onSend()));
}

Widget::~Widget()
{
    delete ui;
}

void Widget::onSend()
{
    qDebug()<<"Send!";
    char eth_src_mac[6];
    char eth_dst_mac[6];
    char arp_src_mac[6];
    char arp_dst_mac[6];
    this->QTSTRtoMAC(ui->eth_src_mac->text(),eth_src_mac);
    this->QTSTRtoMAC(ui->eth_dst_mac->text(),eth_dst_mac);
    this->QTSTRtoMAC(ui->arp_src_mac->text(),arp_src_mac);
    this->QTSTRtoMAC(ui->arp_dst_mac->text(),arp_dst_mac);

    QByteArray arp_src_ip = ui->arp_src_ip->text().tolatin1();
    QByteArray arp_dst_ip = ui->arp_dst_ip->text().tolatin1();
    QByteArray interface_name = ui->interface_name->text().tolatin1();

    char* src_ip =arp_src_ip.data();
    char* dst_ip =arp_dst_ip.data();
    char* if_name = interface_name.data();

    int opcode = ui->opcode->currentText() == "request"?1:2;
    sendarp(eth_src_mac,eth_dst_mac,arp_src_mac,arp_dst_mac,src_ip,dst_ip,if_name,opcode);

}

void Widget::QTSTRtoMAC(QString str, char *mac)
{
  QStringList list = str.split(":");
  bool ok;
  for(int i=0;i<6;i++){
      QString temp = list.at(i);
      *(mac+i) = temp.toInt(&ok,16); // 16 for Hex
  }
}

 

相关文章

方法: 打开一个新的可跨域的chrome窗口实现方法: 1. 打开终...
MacBook Pro 14价格和配置对比 尺寸 SOC&#x9;内存&#...
现在给大家介绍一下如何查询MAC的生产日期,希望对你查询MAC...
1、点击【编辑虚拟机设置】,转到【CD/DVD (SATA)】,选中【...
注意:用哪个版本的 Python 运行安装脚本,pip 就被关联到哪...
Mac 安装nvm