它在攔截了一些封包之後,可以透過另存新檔的方式,將攔截下來的內容儲存成一個檔案(*.pcap)
接著還想用程式化的方式自動分析封包的話,在 Java 上可以使用 jNetPcap 函式庫 [1] 來分析攔截下來的封包內容了。
從 jNetPcap 官方網站 [1] 上下載了二進位檔之後,解開會看到裡面除了 jar 以外還有其他檔案
其中在 Windows 平台上必須將 jnetpcap.dll 複製到系統路徑裡,例如複製到 C:\Windows\System32 資料夾內
此外,電腦上還必須有安裝 Wireshark(不過...或許實際需要安裝的是 WinPcap 而不是整個 Wireshark 吧?)
否則執行時可能會出現類似以下的錯誤訊息:
java.lang.UnsatisfiedLinkError: C:\Windows\System32\jnetpcap.dll: Can't find dependent libraries at java.lang.ClassLoader$NativeLibrary.load(Native Method) at java.lang.ClassLoader.loadLibrary1(ClassLoader.java:1965) at java.lang.ClassLoader.loadLibrary0(ClassLoader.java:1890) at java.lang.ClassLoader.loadLibrary(ClassLoader.java:1880) at java.lang.Runtime.loadLibrary0(Runtime.java:849) at java.lang.System.loadLibrary(System.java:1088) at org.jnetpcap.nio.JMemory.<clinit>(Unknown Source) at PcapTest.sniffer(PcapTest.java:97) at Sniffer.main(Sniffer.java:10)
接著,先手動產生一個 *.pcap 的檔案,內容需要攔截到想透過程式處理的封包
在本篇的範例中,目標是攔截 SMTP 協定的封包,並且把從本機透過任意郵件軟體寄出去的信的附件全部另存新檔。
以下是程式碼。程式碼主要的概念是在掃 pcap 檔的過程中,如果遇到 TCP 協定的封包、且封包的目的地是 25 port,就會對該封包進行內容複製
把內容暫存到一個 Map 上,Map 的 key 是封包的 sequence number。
同時因為封包的 sequence number 會變,因此每次處理完一個封包,都會將該內容對應的 sequence number 更新為下一個封包的 sequence number
以確保內容在眾多封包夾雜的狀態下,仍然可以正確地把一連串的封包內容都串連起來,組回完整且正確的內容。(封包的串接原理可以參考 [2])
import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.nio.file.Path; import java.nio.file.Paths; import java.util.HashMap; import java.util.Map; import javax.mail.MessagingException; import javax.mail.Multipart; import javax.mail.internet.MimeBodyPart; import javax.mail.internet.MimeMessage; import javax.mail.internet.MimeUtility; import org.apache.commons.lang3.StringUtils; import org.apache.log4j.LogManager; import org.apache.log4j.Logger; import org.jnetpcap.Pcap; import org.jnetpcap.packet.PcapPacket; import org.jnetpcap.packet.PcapPacketHandler; import org.jnetpcap.protocol.tcpip.Tcp; public class PcapTest { private static Logger log = LogManager.getLogger("PcapTest"); public static void main(String[] args) { try { Path pcapFilePath = Paths.get("D:", "test", "manyFile.pcap"); log.debug("Source file: " + pcapFilePath.toString()); Path outputPath = Paths.get("D:", "test", "output"); log.debug("Output path: " + outputPath.toString()); PcapTest test = new PcapTest(); test.capture(pcapFilePath, outputPath); } catch (Exception | Error e) { e.printStackTrace(); } } /** * Capture the bytes from a specified pcap file, and stores the attachments to the specified output folder. * @param pcapPath Path of the pcap file. * @param outputFolder Folder for storing the attachments. */ public void capture (Path pcapPath, Path outputFolder) { /*************************************************************************** * First we setup error buffer and name for our file **************************************************************************/ // For any error final StringBuilder errbuf = new StringBuilder(); // Path of the off line sniffered file (*.pcap). final String filePath = pcapPath.toString(); // Parser for parsing packet using TCP protocol. final Tcp tcp = new Tcp(); // Map for temporary storing the un-complete bytes. final Map<Long, ByteArrayOutputStream> packetMap = new HashMap<Long, ByteArrayOutputStream>(); /*************************************************************************** * Second we open up the selected file using openOffline call **************************************************************************/ Pcap pcap = Pcap.openOffline(filePath, 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. **************************************************************************/ 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 using TCP protocol and the destination port is 25 (which is SMTP protocol). if (packet.hasHeader(tcp) && tcp.destination() == 25) { // Prepare the byte array for caching the captured bytes. ByteArrayOutputStream outputStream = (packetMap.containsKey(tcp.seq())) ? packetMap.remove(tcp.seq()) : new ByteArrayOutputStream(); try { // Write the captured payload and append it to the end of the cached byte array. outputStream.write(tcp.getPayload()); // Predict the sequence number of the next packet. long ack = tcp.seq() + tcp.getPayloadLength(); // If this packet is the end of a TCP connection, try to output the content through outputPacket(). // Otherwise, put the stream back to the map with updating its sequence number. if(tcp.getPayloadLength() > 0) packetMap.put(ack, outputStream); else outputPacket(outputStream, outputPath); } catch (IOException | MessagingException e) { e.printStackTrace(); } } } }; try { pcap.loop(-1, jpacketHandler, outputFolder); } catch (Exception | Error e) { e.printStackTrace(); } finally { pcap.close(); } } /** * Output the captured packets of SMTP protocol to the storage. * @param outputStream Cached stream which stores the content of captured bytes. * @param outputFolderPath Path of the folder for outputting the content. * @throws MessagingException * @throws IOException */ public void outputPacket (ByteArrayOutputStream outputStream, Path outputFolderPath) throws MessagingException, IOException { MimeMessage msg = new MimeMessage(null, new ByteArrayInputStream(outputStream.toByteArray())); if (msg.getContentType().contains("multipart")) { // Get the body part from the multipart content. Multipart multipart = (Multipart) msg.getContent(); for (int i = 0; i < multipart.getCount(); i++) { try { MimeBodyPart bodyPart = (MimeBodyPart) multipart.getBodyPart(i); // Stores only the attachments from the multipart content to files. if (StringUtils.isNotEmpty(bodyPart.getDisposition()) && bodyPart.getDisposition().equalsIgnoreCase(MimeBodyPart.ATTACHMENT)) { String fileName = MimeUtility.decodeText(bodyPart.getFileName()); log.debug("Store attachment '" + fileName + "'."); // Save the attachment to a file. bodyPart.saveFile(Paths.get(outputFolderPath.toString(), fileName).toString()); } } catch (Exception | Error e) { e.printStackTrace(); } } } } }
上述程式的執行過程中,最後會將找到的附件檔名印出,並且將那些附件輸出到指定的輸出資料夾。
以下是在我的本機讀取某個 *.pcap 檔案後,印出的內容:
2014-10-17 13:53:29.425 | DEBUG | PcapTest > Source file: D:\test\manyFile.pcap 2014-10-17 13:53:29.426 | DEBUG | PcapTest > Output path: D:\test\output 2014-10-17 13:53:30.146 | DEBUG | PcapTest > Store attachment 'abc.jpg'. 2014-10-17 13:53:30.167 | DEBUG | PcapTest > Store attachment 'Chrysanthemum.jpg'. 2014-10-17 13:53:30.176 | DEBUG | PcapTest > Store attachment 'Desert.jpg'. 2014-10-17 13:53:30.184 | DEBUG | PcapTest > Store attachment 'Hydrangeas.jpg'. 2014-10-17 13:53:30.190 | DEBUG | PcapTest > Store attachment 'Jellyfish.jpg'. 2014-10-17 13:53:30.198 | DEBUG | PcapTest > Store attachment 'Koala.jpg'. 2014-10-17 13:53:30.205 | DEBUG | PcapTest > Store attachment 'Lighthouse.jpg'. 2014-10-17 13:53:30.211 | DEBUG | PcapTest > Store attachment 'Penguins.jpg'. 2014-10-17 13:53:30.219 | DEBUG | PcapTest > Store attachment 'Tulips.jpg'. 2014-10-17 13:53:30.229 | DEBUG | PcapTest > Store attachment '未命名.png'.
參考資料:
1、jNetPcap
2、TCP 與 UDP
沒有留言:
張貼留言