2014年10月23日 星期四

透過 Java 讀取、分析封包攔截記錄 (2):即時封包擷取

續上篇文章「透過 Java 讀取、分析封包攔截記錄 (1):使用 jNetPcap 函式庫讀取封包擷取記錄
上篇文章主要是讓 Wireshark 軟體事先擷取好封包,將擷取記錄儲存成 *.pcap 檔之後,再寫程式去分析 *.pcap 檔的記錄內容。
不過有些狀況會希望能夠即時擷取網卡上的資料,這時就可以利用 jNetPcap 函式庫提供的 openLive() 方法了。

以下是範例程式碼。在這個範例中,會先嘗試列舉出所有電腦可用的網卡(扣除 Lookback 以及部分虛擬網卡)
並且依序把網卡丟去做即時攔截。
攔截下來的封包會先被過濾,只留下由本機向外發出的封包
並嘗試用 HTTP 協定來解析,在 log 上印出本機發出的 HTTP 封包要發送的目的地。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
import java.io.ByteArrayOutputStream;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.Set;
 
import org.apache.commons.codec.binary.Base64;
import org.apache.log4j.LogManager;
import org.apache.log4j.Logger;
import org.jnetpcap.Pcap;
import org.jnetpcap.PcapAddr;
import org.jnetpcap.PcapIf;
import org.jnetpcap.packet.PcapPacket;
import org.jnetpcap.packet.PcapPacketHandler;
import org.jnetpcap.protocol.network.Ip4;
import org.jnetpcap.protocol.tcpip.Http;
 
public class WebMailPcap {
  private static Logger log = LogManager.getLogger("WebMailPcap");
  
  public static void main(String[] args) {
    try {
      Path outputPath = Paths.get("D:", "test", "output");
      log.debug("Output path: " + outputPath.toString());
      
      WebMailPcap test = new WebMailPcap();
      test.capture(outputPath);
    } catch (Exception | Error e) {
      e.printStackTrace();
    }
  }
  
  /**
   * Capture live data from network interfaces.
   * @param outputFolder
   */
  public void capture (Path outputFolder) {
    StringBuilder errbuf = new StringBuilder();
    LinkedList<PcapIf> infList = new LinkedList<>();
    if(Pcap.findAllDevs(infList, errbuf) < 0)
      log.error("Cannot find available interfaces.");
    else {
      Iterator<PcapIf> iter = infList.iterator();
      while(iter.hasNext()) {
        PcapIf netInf = iter.next();
        log.debug("Find network interface '" + netInf.getName() + "' (" + netInf.getDescription() + ").");
        
        String description = netInf.getDescription().toLowerCase();
        if(!description.contains("virtual") && description.split(" ").length > 1)
          capture(netInf, outputFolder);
      }
    }
  }
  
  /**
   * Capture the bytes from a specified network interface, and stores the attachments to the specified output folder.
   * @param netInf Network interface to be captured.
   * @param outputFolder Folder for storing the attachments.
   */
  public void capture (final PcapIf netInf, Path outputFolder) {
    log.debug("Try to capture packets from network interface interface '" + netInf.getName() + "' (" + netInf.toString() + ").");
    
    /***************************************************************************
     * First we setup error buffer and name for our file
     **************************************************************************/
    // For any error
    final StringBuilder errbuf = new StringBuilder();
    // Parser for parsing packet using HTTP protocol.
    final Http http = new Http();
    
    // Truncate packet at this size.
    int snaplen = 2048;
    // Set the interface in 'Promiscuous Mode' which capture all packets with or without destination address points to it.
    int promiscous = Pcap.MODE_PROMISCUOUS;
    // In milliseconds
    int timeout = 60000;
 
    /***************************************************************************
     * Second we open up the selected file using openOffline call
     **************************************************************************/
    Pcap pcap = Pcap.openLive(netInf.getName(), snaplen, promiscous, timeout, errbuf);
    
    // Error occurred while accessing the specified *.pcap file.
    if (pcap == null) {
      log.error("Error while opening device for capture: " + errbuf.toString());
      return;
    }
 
    /***************************************************************************
     * Third we create a packet handler which will receive packets from
     * the libpcap loop.
     **************************************************************************/
    
    // Collection for storing the Internet addresses of the specified interface.
    final Set<String> ifAddrs = new HashSet<String>(netInf.getAddresses().size());
    for(PcapAddr addr : netInf.getAddresses()) {
      try {
        // Parse the Internet address and cache it in the memory.
        String capturedAddr = InetAddress.getByAddress(addr.getAddr().getData()).getHostAddress();
        ifAddrs.add(capturedAddr);
      } catch (UnknownHostException e) {
        e.printStackTrace();
      }
    }
    
    PcapPacketHandler<Path> jpacketHandler = new PcapPacketHandler<Path>() {
      
      /**
       * Get the next packet from the captured file.
       * @param packet Captured packet.
       * @param outputPath Path for outputting the captured result.
       */
      public void nextPacket(PcapPacket packet, Path outputPath) {
 
        // --------------------------------------------------------- //
        //       Filter packets which is sent from local host.       //
        // --------------------------------------------------------- //
        Ip4 ip4 = new Ip4();
        if (packet.hasHeader(ip4)) {
          try {
            String srcAddr = InetAddress.getByAddress(ip4.source()).getHostAddress();
            if (!ifAddrs.contains(srcAddr))
              return;
          } catch (UnknownHostException e) {
            e.printStackTrace();
          }
        }
 
        // ----------------------------------------------------------------------------- //
        //       Filter packets which is using TCP protocol and is a HTTP request.       //
        // ----------------------------------------------------------------------------- //
 
        if (packet.hasHeader(http)) {
          if (http.hasField(Http.Request.Accept)) {
            String host = http.fieldValue(Http.Request.Host);
            log.debug("Capture a HTTP request to '" + host + "'.");
          }
        }
      }
    };
 
    try {
      pcap.loop(Pcap.LOOP_INFINITE, jpacketHandler, outputFolder);
    } catch (Exception | Error e) {
      e.printStackTrace();
    } finally {
      pcap.close();
    }
  }
}

上面的程式碼中,比較需要注意的地方是,44 ~ 56 行是列舉電腦上可用的網卡
這裡該用的列舉方法並不是 Java 內建的方法,而是 Pcap 提供的方法
如果使用 Java 內建的 NetworkInterface.getNetworkInterfaces() 方法去列舉的話,Pcap 函式庫會在初始化時出現錯誤而停止執行。

參考資料:
1、Opening a Network Interface for Capture
2、Capturing network packets using jNetPcap API

沒有留言: