它在攔截了一些封包之後,可以透過另存新檔的方式,將攔截下來的內容儲存成一個檔案(*.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
沒有留言:
張貼留言