目录
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
}
}