2014年10月23日 星期四

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

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

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

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

沒有留言: