PcapMinerV2
PcapMIner V2 allows to parse PCAP files by displaying the all of the relevant data within including ip addresses, ports, flows, specific protocol breakdown, searching by regex, decrypting encrypted traffic and more. This automation takes about a minute to process 20,000 packets (which is approximately 10MB). If you want to mine large files you can either: a) Use the `pcap_filter` parameter to filter your PCAP file and thus make is smaller. b) Copy the automation and change the `default timeout` parameter to match your needs.
python · PCAP Analysis
Source
import re import traceback from typing import Any import demistomock as demisto import pyshark from CommonServerPython import * """GLOBAL VARS""" BAD_CHARS = ["[", "]", ">", "<", "'", " Layer", " ", "{", "}"] EMAIL_REGEX = r"\b[A-Za-z0-9._%=+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}\b" IP_REGEX = r"\b(?:(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])\.){3}(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])\b" URL_REGEX = r"http[s]?://(?:[a-zA-Z]|[0-9]|[$-_@.&+]|[!*\(\),]|(?:%[0-9a-fA-F][0-9a-fA-F]))+" PRAGMA_REGEX = r"Pragma: ([^\\]+)" TYPE_REGEX = r"Type: (.+)" CLASS_REGEX = r"Class: (.+)" COMMAND_REGEX = r"Command: (.+)" SNAME_REGEX = r"SNameString: (.+)" MESSAGE_CODE = r"Message Code: (.+)" RESPONSE_CODE = r"Response code: (.+)" ALL_SUPPORTED_PROTOCOLS = [ "HTTP", "DNS", "LLMNR", "SYSLOG", "SMTP", "NETBIOS", "ICMP", "KERBEROS", "TELNET", "SSH", "IRC", "FTP", "SMB2", ] class PCAP: @logger def __init__(self, is_reg_extract: bool, extracted_protocols: list, homemade_regex: str, unique_ips: bool, entry_id: str): """ PCAP class. This class will contain all data and functions in order to mine the pcap. Args: is_reg_extract: Whether to extract regular expressions from the PCAP. URL, IP, Email extracted_protocols: A list of protocols to extract homemade_regex: A regex to extract from the PCAP unique_ips: Whether to export to context the unique source and dest IPs from the PCAP. """ # setup data structures self.hierarchy: dict[str, int] = {} self.num_of_packets = 0 self.tcp_streams = 0 self.udp_streams = 0 self.last_packet = 0 self.bytes_transmitted = 0 self.min_time = float("inf") self.max_time = -float("inf") self.conversations: dict[tuple, Any] = {} self.flows: dict[tuple, Any] = {} self.unique_source_ip: set = set() self.unique_dest_ip: set = set() self.ips_extracted: set = set() self.urls_extracted: set = set() self.emails_extracted: set = set() self.homemade_extracted: set = set() self.last_layer: set = set() self.irc_data: list = [] self.protocol_data: dict[str, Any] = {} self.entry_id = entry_id self.extracted_protocols = extracted_protocols self.homemade_regex = homemade_regex self.unique_ips = unique_ips for protocol in extracted_protocols: self.protocol_data[protocol] = {} # Regex compilation if "LLMNR" in extracted_protocols: self.llmnr_type = re.compile("Type: (.*)\n") self.llmnr_class = re.compile("Class: (.*)\n") self.llmnr_dict: dict = {} if is_reg_extract: self.reg_ip = re.compile(IP_REGEX) self.reg_email = re.compile(EMAIL_REGEX) self.reg_url = re.compile(URL_REGEX) if "HTTP" in extracted_protocols: self.reg_pragma = re.compile(PRAGMA_REGEX) if "ICMP" in extracted_protocols: self.icmp_data: set = set() if "DNS" in extracted_protocols or "NETBIOS" in extracted_protocols or "ICMP" in extracted_protocols: self.reg_type = re.compile(TYPE_REGEX) if "NETBIOS" in extracted_protocols: self.reg_class = re.compile(CLASS_REGEX) if "SMB2" in extracted_protocols: self.reg_cmd = re.compile(COMMAND_REGEX) if "KERBEROS" in extracted_protocols: self.reg_sname = re.compile(SNAME_REGEX) self.kerb_data: list = [] if "SSH" in extracted_protocols: self.ssh_data = { "EntryID": entry_id, "ClientProtocols": set(), "ServerProtocols": set(), "KeyExchangeMessageCode": set(), } self.reg_message_code = re.compile(MESSAGE_CODE) if "FTP" in extracted_protocols: self.reg_res_code = re.compile(RESPONSE_CODE) if "TELNET" in extracted_protocols: self.telnet_data: set = set() self.telnet_commands: set = set() if homemade_regex: self.reg_homemade = re.compile(self.homemade_regex) @logger def extract_dns(self, packet): dns_layer = packet.dns temp_dns = { "EntryID": self.entry_id, "ID": dns_layer.get("id"), "Request": dns_layer.get("qry_name"), "Response": dns_layer.get("a"), "Type": self.reg_type.findall(str(dns_layer))[0] if self.reg_type.findall(str(dns_layer)) else None, } add_to_data(self.protocol_data["DNS"], temp_dns) @logger def extract_kerberos(self, packet): kerb_layer = packet.kerberos sname_results = self.reg_sname.findall(str(kerb_layer)) self.kerb_data.append( { "EntryID": self.entry_id, "Realm": kerb_layer.get("realm"), "CName": kerb_layer.get("CNameString"), "SName": sname_results if sname_results else None, } ) @logger def extract_telnet(self, packet): telnet_layer = packet.telnet for message in telnet_layer._get_all_field_lines(): if "Data:" in message: self.telnet_data.add(message.lstrip("Data: ")) # noqa: B005 continue if ":" not in message: self.telnet_commands.add(message.lstrip("\t").rstrip("\n")) @logger def extract_llmnr(self, packet): llmnr_layer = packet.llmnr llmnr_layer_string = str(llmnr_layer) query_type_results = self.llmnr_type.findall(llmnr_layer_string) query_class_results = self.llmnr_class.findall(llmnr_layer_string) llmnr_data = { "EntryID": self.entry_id, "ID": llmnr_layer.get("dns_id"), "QueryType": None if len(query_type_results) == 0 else query_type_results[0], "QueryClass": None if len(query_class_results) == 0 else query_class_results[0], "QueryName": str(llmnr_layer.get("dns_qry_name")), "Questions": int(llmnr_layer.get("dns_count_queries")), } add_to_data(self.protocol_data["LLMNR"], llmnr_data) @logger def extract_syslog(self, packet): syslog_layer = packet.syslog syslog_data = { "EntryID": self.entry_id, "ID": syslog_layer.get("msgid"), "Message": syslog_layer.get("msg"), "Hostname": syslog_layer.get("hostname"), "Timestamp": syslog_layer.get("timestamp"), } add_to_data(self.protocol_data["SYSLOG"], syslog_data) @logger def extract_imf(self, packet): imf_layer = packet.imf imf_data = { "EntryID": self.entry_id, "ID": imf_layer.get("Message-ID", -1), "To": strip(imf_layer.get("to"), ["<", ">"]), "From": strip(imf_layer.get("from"), ["<", ">"]), "Subject": imf_layer.get("subject"), "MimeVersion": imf_layer.get("mime-version"), } add_to_data(self.protocol_data["SMTP"], imf_data) @logger def extract_smtp(self, packet): smtp_layer = packet.smtp if smtp_layer.get_field("req_parameter") is None: parameters = [] else: parameters = smtp_layer.req_parameter.split(":") smtp_data = {"EntryID": self.entry_id, "ID": packet.tcp.seq} if len(parameters) == 2: smtp_data[parameters[0].title()] = strip(parameters[1], ["<", ">"]) add_to_data(self.protocol_data["SMTP"], smtp_data, packet.tcp.nxtseq) @logger def extract_smb(self, packet): smb_layer = packet.smb2 command_results = self.reg_cmd.findall(str(smb_layer)) smb_data = { "EntryID": self.entry_id, "ID": smb_layer.get("sesid", -1), "UserName": smb_layer.get("ntlmssp_auth_username"), "Domain": smb_layer.get("ntlmssp_auth_domain"), "HostName": smb_layer.get("ntlmssp_auth_hostname"), "Command": command_results[0] if command_results else None, "FileName": smb_layer.get("smb2.filename"), "Tree": smb_layer.get("tree"), } add_to_data(self.protocol_data["SMB2"], smb_data) @logger def extract_netbios(self, packet): netbios_layer = packet.nbns type_results = self.reg_type.findall(str(netbios_layer)) class_results = self.reg_class.findall(str(netbios_layer)) netbios_data = { "EntryID": self.entry_id, "ID": netbios_layer.get("id", -1), "Name": netbios_layer.get("name"), "Type": type_results if type_results else None, "Class": class_results if class_results else None, } add_to_data(self.protocol_data["NETBIOS"], netbios_data) @logger def extract_icmp(self, packet): icmp_layer = packet.icmp type_results = self.reg_type.findall(str(icmp_layer)) for result in type_results: self.icmp_data.add(result) @logger def extract_ssh(self, packet): ssh_layer = packet.ssh protocol = ssh_layer.get("protocol") message_code_results = self.reg_message_code.findall(str(ssh_layer)) if protocol and ssh_layer.get("direction") == 1: # direction is server to client self.ssh_data["ServerProtocols"].add(protocol) # type: ignore[attr-defined] if protocol and ssh_layer.get("direction") == 0: # direction is client to server self.ssh_data["ClientProtocols"].add(protocol) # type: ignore[attr-defined] if message_code_results and message_code_results: self.ssh_data["KeyExchangeMessageCode"].add(message_code_results[0]) # type: ignore[attr-defined] @logger def extract_irc(self, packet): irc_layer = packet.irc if irc_layer.get("request"): command = irc_layer.get("request_command") trailer = irc_layer.get("request_trailer", "") prefix = irc_layer.get("request_prefix", "") parameters = irc_layer.get("request").replace(command, "").replace(trailer, "").replace(prefix, "").split(" ") parameters.remove(" ") irc_data = { "EntryID": self.entry_id, "ID": packet.tcp.get("ack"), "RequestCommand": command, "RequestTrailer": trailer, "RequestPrefix": prefix, "RequestParameters": parameters, } else: command = irc_layer.get("response_command") trailer = irc_layer.get("response_trailer", "") prefix = irc_layer.get("response_prefix", "") parameters = irc_layer.get("response").replace(command, "").replace(trailer, "").replace(prefix, "").split(" ") parameters.remove(" ") irc_data = { "EntryID": self.entry_id, "ID": packet.tcp.get("seq"), "ResponseCommand": command, "ResponseTrailer": trailer, "ResponsePrefix": prefix, "ResponseParameters": parameters, } add_to_data(self.protocol_data["IRC"], irc_data, next_id=packet.tcp.nxtseq) @logger def extract_ftp(self, packet): ftp_layer = packet.ftp res_code_results = self.reg_res_code.findall(str(ftp_layer)) ftp_data = { "EntryID": self.entry_id, "ID": packet.tcp.get("seq"), "RequestCommand": ftp_layer.get("request_command"), "ResponseArgs": ftp_layer.get("response_arg"), "ResponseCode": res_code_results[0] if res_code_results else None, } if ftp_data["ResponseCode"]: # if packet is a response, don't update ID. This is because FTP may have a lot of requests and # responses in the same conversation add_to_data(self.protocol_data["FTP"], ftp_data) else: add_to_data(self.protocol_data["FTP"], ftp_data, next_id=packet.tcp.ack) @logger def extract_context_from_packet(self, packet, layers_str: str, is_reg_extract: bool = False) -> None: """ Get a packet and extract context from it for the specified protocols specified in `self.extracted_protocols` Args: packet: The packet itself. layers_str: A comma-seperated string of the layers in the packet. is_reg_extract: Whether to export to context the unique source and dest IPs from the PCAP. """ layers = layers_str.split(",") if is_reg_extract: self.ips_extracted.update(self.reg_ip.findall(str(packet))) self.emails_extracted.update(self.reg_email.findall(str(packet))) self.urls_extracted.update(self.reg_url.findall(str(packet))) if self.homemade_regex: self.homemade_extracted.update(self.reg_homemade.findall(str(packet))) if "DNS" in self.extracted_protocols and "DNS" in layers: return self.extract_dns(packet) if "KERBEROS" in self.extracted_protocols and "KERBEROS" in layers: return self.extract_kerberos(packet) if "TELNET" in self.extracted_protocols and "TELNET" in layers: return self.extract_telnet(packet) if "LLMNR" in self.extracted_protocols and "LLMNR" in layers: return self.extract_llmnr(packet) if "SYSLOG" in self.extracted_protocols and "SYSLOG" in layers: return self.extract_syslog(packet) if "SMB2" in self.extracted_protocols and "SMB2" in layers: return self.extract_smb(packet) if "NETBIOS" in self.extracted_protocols and "NETBIOS" in layers: return self.extract_netbios(packet) if "ICMP" in self.extracted_protocols and "ICMP" in layers: return self.extract_icmp(packet) if "SSH" in self.extracted_protocols and "SSH" in layers: return self.extract_ssh(packet) if "IRC" in self.extracted_protocols and "IRC" in layers: return self.extract_irc(packet) if "FTP" in self.extracted_protocols and "FTP" in layers: return self.extract_ftp(packet) if "SMTP" in self.extracted_protocols: # noqa: RET503 if "IMF" in layers: return self.extract_imf(packet) if "SMTP" in layers: # noqa: RET503 return self.extract_smtp(packet) @logger def get_outputs(self, conversation_number_to_display=15, is_flows=False, is_reg_extract=False): if self.num_of_packets == 0: return "## No packets found.\nTry changing the filter, if applied.", {}, {} md = ( f"## PCAP Info:\n" f"Between {formatEpochDate(self.min_time)} and {formatEpochDate(self.max_time)} " f"there were {self.num_of_packets} " f"packets transmitted in {self.tcp_streams + self.udp_streams} streams.\n" ) md += "#### Protocol Breakdown\n" md += hierarchy_to_md(self.hierarchy) md += f"#### Top {conversation_number_to_display} Conversations\n" md += conversations_to_md(self.conversations, conversation_number_to_display) if is_flows: md += f"#### Top {conversation_number_to_display} Flows\n" md += flows_to_md(self.flows, conversation_number_to_display) # Entry Context general_context = { "EntryID": self.entry_id, "Bytes": self.bytes_transmitted, "Packets": self.num_of_packets, "StreamCount": self.tcp_streams + self.udp_streams, "UniqueSourceIP": len(self.unique_source_ip), "UniqueDestIP": len(self.unique_dest_ip), "StartTime": formatEpochDate(self.min_time), "EndTime": formatEpochDate(self.max_time), "Protocols": list(self.last_layer), } ec = {"PCAPResults(val.EntryID == obj.EntryID)": general_context} for protocol in self.extracted_protocols: if self.protocol_data[protocol]: ec[f"PCAPResults{protocol}"] = list(self.protocol_data[protocol].values()) # type: ignore[assignment] if "ICMP" in self.extracted_protocols and self.icmp_data: ec["PcapResultsICMP"] = list(self.icmp_data) # type: ignore[assignment] if "KERBEROS" in self.extracted_protocols and self.kerb_data: ec["PCAPResultsKERBEROS"] = self.kerb_data # type: ignore[assignment] if "SSH" in self.extracted_protocols: temp = { "EntryID": self.entry_id, "ClientProtocols": list(self.ssh_data["ClientProtocols"]), "ServerProtocols": list(self.ssh_data["ServerProtocols"]), "KeyExchangeMessageCode": list(self.ssh_data["KeyExchangeMessageCode"]), } ec["PCAPResultsSSH"] = assign_params(**temp) if "TELNET" in self.extracted_protocols: ec["PCAPResultsTelnet"] = { "Commands": list(self.telnet_commands), "Data": list(self.telnet_data), "EntryID": self.entry_id, } if is_flows: ec["PCAPResultsFlow"] = flows_to_ec(self.flows) if is_reg_extract: general_context["IP"] = list(self.ips_extracted) general_context["URL"] = list(self.urls_extracted) general_context["Email"] = list(self.emails_extracted) if self.homemade_regex: general_context["Regex"] = list(self.homemade_extracted) if self.unique_ips: all_ips = self.unique_source_ip.copy() all_ips.update(self.unique_dest_ip) if general_context.get("IP"): general_context.get("IP").append(list(all_ips)) # type: ignore[union-attr] else: general_context["IP"] = list(all_ips) general_context["SourceIP"] = list(self.unique_source_ip) general_context["DestIP"] = list(self.unique_dest_ip) return md, ec, general_context @logger def mine( self, file_path: str, wpa_password: str, rsa_key_file_path: str, is_flows: bool, is_reg_extract: bool, pcap_filter: str, pcap_filter_new_file_path: str, ) -> None: """ The main function of the script. Mines the PCAP. Args: file_path: The PCAP's file path. wpa_password: The wpa password for the decryption is_flows: Whether to extract flows. is_reg_extract: Whether to extract regexes from the PCAP. pcap_filter: A filter to apply on the PCAP. Same filter syntax as in Wireshark pcap_filter_new_file_path: The new path to save the filtered PCAP in """ cap = None try: custom_parameters = None if rsa_key_file_path: custom_parameters = {"-o": f'uat:rsa_keys:"{rsa_key_file_path}",""'} cap = pyshark.FileCapture( file_path, display_filter=pcap_filter, output_file=pcap_filter_new_file_path, decryption_key=wpa_password, encryption_type="WPA-PWD", keep_packets=False, custom_parameters=custom_parameters, ) for packet in cap: self.last_packet = int(packet.number) self.last_layer.add(packet.layers[-1].layer_name.upper()) layers_string = str(packet.layers) # remove duplicate layer names such as [ETH,DATA,DATA] -> # [ETH, DATA] layers: list = list(dict.fromkeys(layers_string.split(","))) layers = strip(str(layers)) self.hierarchy[layers] = self.hierarchy.get(layers, 0) + 1 # type: ignore[index, call-overload] # update times packet_epoch_time = float(packet.frame_info.get("time_epoch")) self.max_time = max(self.max_time, packet_epoch_time) self.min_time = min(self.min_time, packet_epoch_time) # count packets self.num_of_packets += 1 # count bytes self.bytes_transmitted += int(packet.length) # count num of streams + get src/dest ports tcp = packet.get_multiple_layers("tcp") tcp_or_udp = "Not TCP and not UDP" if tcp: self.tcp_streams = max(int(tcp[0].get("stream", 0)) + 1, self.tcp_streams) src_port = int(tcp[0].get("srcport", 0)) dest_port = int(tcp[0].get("dstport", 0)) tcp_or_udp = "TCP" udp = packet.get_multiple_layers("udp") if udp: self.udp_streams = max(int(udp[0].get("stream", 0)) + 1, self.udp_streams) src_port = int(udp[0].get("srcport", 0)) dest_port = int(udp[0].get("dstport", 0)) tcp_or_udp = "UDP" # add conversations ip_layer = packet.get_multiple_layers("ip") if ip_layer: a = ip_layer[0].get("src_host", "") b = ip_layer[0].get("dst_host") self.unique_source_ip.add(a) self.unique_dest_ip.add(b) # generate flow data if is_flows: if "src_port" not in locals(): continue if (b, dest_port, a, src_port) in self.flows: b, a, src_port, dest_port = a, b, dest_port, src_port flow = (a, src_port, b, dest_port) flow_data = self.flows.get( flow, { "EntryID": self.entry_id, "Transport": tcp_or_udp, "min_time": float("inf"), "max_time": -float("inf"), "bytes": 0, "counter": 0, }, ) flow_data["min_time"] = min(flow_data["min_time"], packet_epoch_time) flow_data["max_time"] = max(flow_data["min_time"], packet_epoch_time) flow_data["bytes"] += int(packet.length) flow_data["counter"] += 1 self.flows[flow] = flow_data # gather http data if "HTTP" in self.extracted_protocols: http_layer = packet.get_multiple_layers("http") if http_layer: http_layer = http_layer[0] all_fields = http_layer._all_fields temp_http = { "EntryID": self.entry_id, "ID": http_layer.get("request_in", packet.number), "RequestAgent": all_fields.get("http.user_agent"), "RequestHost": all_fields.get("http.host"), "RequestSourceIP": a, "RequestURI": http_layer.get("request_full_uri"), "RequestMethod": http_layer.get("request_method"), "RequestVersion": http_layer.get("request_version"), "RequestAcceptEncoding": http_layer.get("accept_encoding"), "RequestPragma": self.reg_pragma.findall(str(http_layer))[0] if self.reg_pragma.findall(str(http_layer)) else None, "RequestAcceptLanguage": http_layer.get("accept_language"), "RequestCacheControl": http_layer.get("cache_control"), } # if the packet is a response if all_fields.get("http.response"): temp_http.update( { "EntryID": self.entry_id, "ResponseStatusCode": http_layer.get("response_code"), "ResponseVersion": all_fields.get("http.response.version"), "ResponseCodeDesc": http_layer.get("response_code_desc"), "ResponseContentLength": http_layer.get("content_length"), "ResponseContentType": http_layer.get("content_type"), "ResponseDate": formatEpochDate(packet_epoch_time), } ) add_to_data(self.protocol_data["HTTP"], temp_http) if (b, a) in self.conversations: a, b = b, a hosts = (a, b) self.conversations[hosts] = self.conversations.get(hosts, 0) + 1 self.extract_context_from_packet(packet, layers, is_reg_extract) except pyshark.capture.capture.TSharkCrashException: raise ValueError( "Could not find packets. Make sure that the file is a .cap/.pcap/.pcapng file, " "the filter is of the correct syntax and that the rsa key is added correctly." ) finally: if cap: cap.close() """HELPER FUNCTIONS""" @logger def strip(s: str, bad_chars=None): """ Args: s: string to strip bad_chars: all characters to remove from string. Returns: The input s without the bad_chars """ if bad_chars is None: bad_chars = BAD_CHARS temp = s for char in bad_chars: temp = temp.replace(char, "") return temp @logger def hierarchy_to_md(hierarchy: dict) -> str: """ Args: hierarchy: a dictionary of layer hierarchy for all packets Returns: A markdown string for displaying the hierarchy in a nice view. The script also counts the number of occurrences each hierarchy. """ final_dict: dict[str, Any] = {} num_of_all_packets = 0 for k in hierarchy: layer_heir = "" for layer in k.split(","): layer_heir += " -> " + layer final_dict[layer_heir] = final_dict.get(layer_heir, 0) + hierarchy[k] num_of_all_packets += hierarchy[k] md = "|Layer| # of Packets|% of Packets|\n|---|----|---|\n" for key in sorted(final_dict): md += f"|{key}|{final_dict[key]}|{round(final_dict[key] / num_of_all_packets, 3) * 100}%|\n" return md @logger def conversations_to_md(conversations: dict, disp_num: int) -> str: """ Args: conversations: a raw dictionary of conversations. disp_num: The limit of conversations to display. Returns: A mardkown of <=disp_num of conversations, ordered in descending order. """ md = "|A|B|# of Packets|\n|---|---|---|\n" ordered_conv_list = sorted(conversations.items(), key=lambda x: x[1], reverse=True) disp_num = min(disp_num, len(ordered_conv_list)) for conv in ordered_conv_list[:disp_num]: (ipA, ipB), data = conv md += f"|{ipA}|{ipB}|{data}|\n" return md @logger def flows_to_md(flows: dict, disp_num: int) -> str: """ Args: flows: a raw dictionary of flows. disp_num: The limit of flows to display. Returns: A mardkown of <=disp_num of flows, ordered in descending order. """ md = "|A|port|B|port|# of Packets|\n|---|---|---|---|---|\n" ordered_flow_list = sorted(flows.items(), key=lambda x: x[1].get("counter"), reverse=True) disp_num = min(disp_num, len(ordered_flow_list)) for flow in ordered_flow_list[:disp_num]: (ipA, portA, ipB, portB), data = flow md += f'|{ipA}|{portA}|{ipB}|{portB}|{data.get("counter", 0)}|\n' return md @logger def flows_to_ec(flows: dict) -> list: """ Args: flows: A dictionary that hold the flows data Returns: flows data in ec format. """ flows_ec = [] for flow in flows: flow_data = flows[flow] flow_ec = { "SourceIP": flow[0], "SourcePort": flow[1], "DestIP": flow[2], "DestPort": flow[3], "Duration": round(flow_data.get("max_time", 0) - flow_data.get("min_time", 0)), "StartTime": formatEpochDate(flow_data.get("min_time", 0)), "EndTime": formatEpochDate(flow_data.get("max_time", 0)), "Bytes": flow_data.get("bytes", 0), "EntryID": flow_data.get("EntryID"), "Transport": flow_data.get("Transport"), } flows_ec.append(flow_ec) return flows_ec @logger def add_to_data(d: dict, data: dict, next_id: int = None) -> None: """ updates dictionary d to include/update the data. Also removes None values. Args: d: a Dictionary of ID: data to which we want to update the data according to ID data: the data to update. data must have an "ID" field. prev_id: when there's no ID in a packet, we'd like to keep link the data by the next sequence number from TCP protocol. In order to keep this up to date we'd like to know what the next sequence ID and then update it to be the current acknowledge number. Returns: updates dictionary d to include/update the data. Also removes None values. """ data_id = data.get("ID") if not data_id: return else: if not d.get(data_id): if list(data.keys()) == ["ID"] or list(data.keys()) == ["ID", "EntryID"]: # The dictionary doesn't exist and is empty (except ID/EntryID) return if next_id: d[next_id] = assign_params(**data) else: d[data_id] = assign_params(**data) else: if next_id and next_id > data_id: # id exists but we want to keep its next seq as ID (and also next seq id is larger therefore # there's another packet in the future) temp = d.pop(data_id) temp.update(assign_params(**data)) d[next_id] = temp else: # ID exists, we just want to update it. d[data_id].update(assign_params(**data)) """MAIN""" def main(): args = demisto.args() entry_id = args.get("entry_id", "") file_path = demisto.getFilePath(entry_id).get("path") wpa_password = args.get("wpa_password", "") rsa_decrypt_key_entry_id = args.get("rsa_decrypt_key_entry_id", "") rsa_key_file_path = None if rsa_decrypt_key_entry_id: rsa_key_file_path = demisto.getFilePath(rsa_decrypt_key_entry_id).get("path") conversation_number_to_display = int(args.get("convs_to_display", "15")) extracted_protocols = argToList(args.get("protocol_output", "")) if "All" in extracted_protocols: extracted_protocols = ALL_SUPPORTED_PROTOCOLS is_flows = True is_reg_extract = args.get("extract_strings", "False") == "True" pcap_filter = args.get("pcap_filter", "") homemade_regex = args.get("custom_regex", "") # 'Layer (.+):' pcap_filter_new_file_path = "" pcap_filter_new_file_name = args.get("filtered_file_name", "") unique_ips = args.get("extract_ips", "False") == "True" if pcap_filter_new_file_name: temp = demisto.uniqueFile() pcap_filter_new_file_path = demisto.investigation()["id"] + "_" + temp try: pcap = PCAP(is_reg_extract, extracted_protocols, homemade_regex, unique_ips, entry_id) pcap.mine(file_path, wpa_password, rsa_key_file_path, is_flows, is_reg_extract, pcap_filter, pcap_filter_new_file_path) hr, ec, raw = pcap.get_outputs(conversation_number_to_display, is_flows, is_reg_extract) return_outputs(hr, ec, raw) except Exception as e: return_error(f"Unexpected error: {e!s}", error=traceback.format_exc()) if pcap_filter_new_file_name: demisto.results( {"Contents": "", "ContentsFormat": formats["text"], "Type": 3, "File": pcap_filter_new_file_name, "FileID": temp} ) if __name__ in ["__main__", "builtin", "builtins"]: main()
README
PcapMIner V2 allows to parse PCAP files by displaying the all of the relevant data within including ip addresses, ports, flows, specific protocol breakdown, searching by regex, decrypting encrypted traffic and more.
This automation takes about a minute to process 20,000 packets (which is approximately 10MB). If you want to mine large files you can either:
a) Use the pcap_filter parameter to filter your PCAP file and thus make is smaller.
b) Copy the automation and change the default timeout parameter to match your needs.
Script Data
| Name | Description |
|---|---|
| Script Type | python3 |
| Tags | pcap, mine, file, Utility |
| Cortex XSOAR Version | 5.0.0 |
Inputs
| Argument Name | Description |
|---|---|
| entry_id | The entry_id of the PCAP file to mine. |
| protocol_output | A comma-separated list of protocols to output as context. If empty, will not output any protocols to context. |
| extract_strings | Whether to extract IP, URL, and Email from PCAP file. Can be “True” or “False”. |
| pcap_filter | Filter to apply on PCAP. Wireshark syntax as can be found here: https://www.wireshark.org/docs/man-pages/wireshark-filter.html |
| custom_regex | Your own regular expression to extract from the PCAP. |
| filtered_file_name | The name of the PCAP file to save to the War Room after applying the `pcap_filter` (i.e. `filtered_file.pcap`). |
| rsa_decrypt_key_entry_id | The entry ID for the RSA decryption key. |
| convs_to_display | Number of conversations to display. The default is 15. |
| wpa_password | The WPA password. By providing the password you will be able to decrypt encrypted traffic data. |
| extract_ips | Output to context the source and destination IPs in the PCAP file. Can be “True” or “False”. The default is “False”. |
Outputs
| Path | Description | Type |
|---|---|---|
| PCAPResults.Bytes | The number of bytes transmitted in the PCAP file. | Number |
| PCAPResults.Packets | The number of packets transmitted in the PCAP file. | Number |
| PCAPResults.EntryID | The entryID of the PCAP file. | String |
| PCAPResults.StreamCount | The number of streams in the PCAP file. | String |
| PCAPResults.StartTime | The date and time of the first packet in the PCAP file. | Date |
| PCAPResults.EndTime | The date and time of the last packet in the PCAP file. | String |
| PCAPResults.UniqueSourceIP | The number of unique IPs from which packets were transmitted. | Number |
| PCAPResults.UniqueDestIP | The number of unique IPs from to packets were transmitted. | Number |
| PCAPResultsFlow.Bytes | The number of bytes transmitted in the flow. | String |
| PCAPResultsFlow.DestIP | The destination IP of the flow. | String |
| PCAPResultsFlow.SourceIP | The source IP of the flow. | String |
| PCAPResultsFlow.Transport | The transport protocol of the flow. | String |
| PCAPResultsFlow.SourcePort | The source port of the flow. | String |
| PCAPResultsFlow.DestPort | The destination port of the flow. | String |
| PCAPResultsFlow.Duration | The duration of the flow (in seconds). | String |
| PCAPResultsFlow.EndTime | The date/time the flow ended. | Date |
| PCAPResultsFlow.StartTime | The date/time the flow started. | Date |
| PCAPResults.URL | The URLs extracted from the file. | String |
| PCAPResults.IP | The IPs extracted from the file. | String |
| PCAPResults.Email | The emails extracted from the file. | String |
| PCAPResults.Regex | The regular expressions specified in `extract_regex` extracted from the file. | String |
| PCAPResultsHTTP.ResponseStatusCode | The response code. | String |
| PCAPResultsHTTP.RequestVersion | The request version. | String |
| PCAPResultsHTTP.RequestCacheControl | The cache control of the request. | String |
| PCAPResultsHTTP.ResponseDate | The date/time of the response. | Date |
| PCAPResultsHTTP.RequestMethod | The request method. | String |
| PCAPResultsHTTP.RequestSourceIP | The source IP of the request. | String |
| PCAPResultsHTTP.ResponseContentType | The response content type. | String |
| PCAPResultsHTTP.RequestAgent | The request agent. | String |
| PCAPResultsHTTP.RequestHost | The request host. | String |
| PCAPResultsHTTP.ResponseVersion | The response version. | String |
| PCAPResultsHTTP.ID | The ID of the HTTP interaction. | String |
| PCAPResultsHTTP.EntryID | The PCAP entry ID. | String |
| PCAPResultsHTTP.RequestURI | The request URI. | String |
| PCAPResultsHTTP.ResponseContentLength | The length of the response content. | String |
| PCAPResultsHTTP.ResponseCodeDesc | The code description of the response. | String |
| PCAPResultsDNS.ID | The ID of the DNS request. | String |
| PCAPResultsDNS.Request | The DNS request. | String |
| PCAPResultsDNS.Response | The DNS response. | String |
| PCAPResultsDNS.Type | The type of the DNS request. | String |
| PCAPResultsDNS.ID | The DNS packet ID. | String |
| PCAPResultsDNS.EntryID | The PCAP entry ID. | String |
| PCAPResults.Protocols | List of protocols found in the PCAP. | String |
| PCAPResultsSMTP.From | The mail sender. | String |
| PCAPResultsSMTP.To | The mail recipients. | String |
| PCAPResultsSMTP.Subject | The mail subject. | String |
| PCAPResultsSMTP.MimeVersion | The mime version. | String |
| PCAPResultsSMTP.ID | The SMTP packet’s ID. | String |
| PCAPResultsSMTP.EntryID | The PCAP entry ID. | String |
| PCAPResultsKERBEROS.EntryID | The PCAP entry ID. | String |
| PCAPResultsKERBEROS.Realm | The KERBEROS realm. | String |
| PCAPResultsKERBEROS.SName | The KERBEROS SName. | String |
| PCAPResultsKERBEROS.CName | The KERBEROS CName. | String |
| PCAPResultsTelnet.Data | The telnet data. | String |
| PCAPResultsTelnet.Commands | The telnet commands. | String |
| PCAPResultsTelnet.EntryID | The PCAP entry ID. | String |
| PCAPResultsLLMNR.EntryID | The PCAP entry ID. | String |
| PCAPResultsLLMNR.QueryClass | The LLMNR query class. | String |
| PCAPResultsLLMNR.QueryName | The LLMNR query name. | String |
| PCAPResultsLLMNR.Questions | The LLMNR questions. | String |
| PCAPResultsLLMNR.ID | The LLMNR packet ID. | String |
| PCAPResultsLLMNR.QueryType | The LLMNR query type. | String |
| PCAPResultsSYSLOG.EntryID | The PCAP entry ID. | String |
| PCAPResultsSYSLOG.ID | The SYSLOGS packet ID. | String |
| PCAPResultsSYSLOG.Message | The SYSLOGS message. | String |
| PCAPResultsSYSLOG.Hostname | The SYSLOGS host name. | String |
| PCAPResultsSYSLOG.Timestamp | The SYSLOGS time stamp. | String |
| PCAPResultsSMB2.EntryID | The PCAP entry ID. | String |
| PCAPResultsSMB2.ID | The SMB2 packet ID. | String |
| PCAPResultsSMB2.UserName | The SMB2 user name. | String |
| PCAPResultsSMB2.Domain | The SMB2 domain. | String |
| PCAPResultsSMB2.HostName | The SMB2 host name. | String |
| PCAPResultsSMB2.Command | The SMB2 command. | String |
| PCAPResultsSMB2.FileName | The SMB2 file name. | String |
| PCAPResultsSMB2.Tree | The SMB2 tree. | String |
| PCAPResultsNETBIOS.EntryID | The PCAP entry ID. | String |
| PCAPResultsNETBIOS.ID | The NETIOS packet ID. | String |
| PCAPResultsNETBIOS.Name | The NETIOS name. | String |
| PCAPResultsNETBIOS.Type | The NETIOS type. | String |
| PCAPResultsNETBIOS.Class | The NETIOS class. | String |
| PCAPResultsIRC.EntryID | The PCAP entry ID. | String |
| PCAPResultsIRC.ID | The IRC packet ID. | String |
| PCAPResultsIRC.RequestCommand | The IRC request command. | String |
| PCAPResultsIRC.RequestTrailer | The IRC request trailer. | String |
| PCAPResultsIRC.RequestPrefix | The IRC request prefix. | String |
| PCAPResultsIRC.RequestParameters | The IRC request parameters. | String |
| PCAPResultsIRC.ResponseCommand | The IRC response command. | String |
| PCAPResultsIRC.ResponseTrailer | The IRC response trailers. | String |
| PCAPResultsIRC.ResponsePrefix | The IRC response prefix. | String |
| PCAPResultsIRC.ResponseParameters | The IRC response parameters. | String |
| PCAPResultsFTP.EntryID | The PCAP entry ID. | String |
| PCAPResultsFTP.ID | The FTP packet ID. | String |
| PCAPResultsFTP.RequestCommand | The FTP request command. | String |
| PCAPResultsFTP.ResponseArgs | The FTP response arguments. | String |
| PCAPResultsFTP.ResponseCode | The FTP response code. | String |
| PCAPResultsICMP | ICMP data. | String |
| PCAPResultsSSH.EntryID | The PCAP’s entry ID. | String |
| PCAPResultsSSH.ClientProtocols | The SSH client protocols in the PCAP. | String |
| PCAPResultsSSH.ServerProtocols | The SSH server protocols in the PCAP. | String |
| PCAPResultsSSH.KeyExchangeMessageCode | The SSH key exchange message codes. | String |