plaso-rubanetra/plaso/parsers/pcap.py
2020-04-06 18:48:34 +02:00

844 lines
30 KiB
Python

#!/usr/bin/python
# -*- coding: utf-8 -*-
#
# Copyright 2013 The Plaso Project Authors.
# Please see the AUTHORS file for details on individual authors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""Parser for PCAP files."""
import binascii
import operator
import socket
import dpkt
from plaso.events import time_events
from plaso.lib import errors
from plaso.lib import eventdata
from plaso.parsers import interface
from plaso.parsers import manager
__author__ = 'Dominique Kilman (lexistar97@gmail.com)'
def ParseDNS(dns_packet_data):
"""Parse DNS packets and return a string with relevant details.
Args:
dns_packet_data: DNS packet data.
Returns:
Formatted DNS details.
"""
dns_data = []
try:
dns = dpkt.dns.DNS(dns_packet_data)
if dns.rcode is dpkt.dns.DNS_RCODE_NOERR:
if dns.get_qr() == 1:
if not dns.an:
dns_data.append('DNS Response: No answer for ')
dns_data.append(dns.qd[0].name)
else:
# Type of DNS answer.
for answer in dns.an:
if answer.type == 5:
dns_data.append('DNS-CNAME request ')
dns_data.append(answer.name)
dns_data.append(' response: ')
dns_data.append(answer.cname)
elif answer.type == 1:
dns_data.append('DNS-A request ')
dns_data.append(answer.name)
dns_data.append(' response: ')
dns_data.append(socket.inet_ntoa(answer.rdata))
elif answer.type == 12:
dns_data.append('DNS-PTR request ')
dns_data.append(answer.name)
dns_data.append(' response: ')
dns_data.append(answer.ptrname)
elif not dns.get_qr():
dns_data.append('DNS Query for ')
dns_data.append(dns.qd[0].name)
else:
dns_data.append('DNS error code ')
dns_data.append(str(dns.rcode))
except dpkt.UnpackError as exception:
dns_data.append('DNS Unpack Error: {0:s}. First 20 of data {1:s}'.format(
exception, repr(dns_packet_data[:20])))
except IndexError as exception:
dns_data.append('DNS Index Error: {0:s}'.format(exception))
return u' '.join(dns_data)
def ParseNetBios(netbios_packet):
"""Parse the netBIOS stream details.
Args:
netbios_packet: NetBIOS packet.
Returns:
Formatted netBIOS details.
"""
netbios_data = []
for query in netbios_packet.qd:
netbios_data.append('NETBIOS qd:')
netbios_data.append(repr(dpkt.netbios.decode_name(query.name)))
for answer in netbios_packet.an:
netbios_data.append('NETBIOS an:')
netbios_data.append(repr(dpkt.netbios.decode_name(answer.name)))
for name in netbios_packet.ns:
netbios_data.append('NETBIOS ns:')
netbios_data.append(repr(dpkt.netbios.decode_name(name.name)))
return u' '.join(netbios_data)
def TCPFlags(flag):
"""Check the tcp flags for a packet for future use.
Args:
flag: Flag value from TCP packet.
Returns:
String with printable flags for specific packet.
"""
res = []
if flag & dpkt.tcp.TH_FIN:
res.append('FIN')
if flag & dpkt.tcp.TH_SYN:
res.append('SYN')
if flag & dpkt.tcp.TH_RST:
res.append('RST')
if flag & dpkt.tcp.TH_PUSH:
res.append('PUSH')
if flag & dpkt.tcp.TH_ACK:
res.append('ACK')
if flag & dpkt.tcp.TH_URG:
res.append('URG')
if flag & dpkt.tcp.TH_ECE:
res.append('ECN')
if flag & dpkt.tcp.TH_CWR:
res.append('CWR')
return '|'.join(res)
def ICMPTypes(packet):
"""Parse the type information for the icmp packets.
Args:
packet: ICMP packet data.
Returns:
Formatted ICMP details.
"""
icmp_type = packet.type
icmp_code = packet.code
icmp_data = []
icmp_data.append('ICMP')
# TODO: Make the below code more readable.
# Possible to use lookup dict? Or method
# calls?
if icmp_type is dpkt.icmp.ICMP_CODE_NONE:
icmp_data.append('ICMP without codes')
elif icmp_type is dpkt.icmp.ICMP_ECHOREPLY:
icmp_data.append('echo reply')
elif icmp_type is dpkt.icmp.ICMP_UNREACH:
icmp_data.append('ICMP dest unreachable')
if icmp_code is dpkt.icmp.ICMP_UNREACH_NET:
icmp_data.append(': bad net')
elif icmp_code is dpkt.icmp.ICMP_UNREACH_HOST:
icmp_data.append(': host unreachable')
elif icmp_code is dpkt.icmp.ICMP_UNREACH_PROTO:
icmp_data.append(': bad protocol')
elif icmp_code is dpkt.icmp.ICMP_UNREACH_PORT:
icmp_data.append(': port unreachable')
elif icmp_code is dpkt.icmp.ICMP_UNREACH_NEEDFRAG:
icmp_data.append(': IP_DF caused drop')
elif icmp_code is dpkt.icmp.ICMP_UNREACH_SRCFAIL:
icmp_data.append(': src route failed')
elif icmp_code is dpkt.icmp.ICMP_UNREACH_NET_UNKNOWN:
icmp_data.append(': unknown net')
elif icmp_code is dpkt.icmp.ICMP_UNREACH_HOST_UNKNOWN:
icmp_data.append(': unknown host')
elif icmp_code is dpkt.icmp.ICMP_UNREACH_ISOLATED:
icmp_data.append(': src host isolated')
elif icmp_code is dpkt.icmp.ICMP_UNREACH_NET_PROHIB:
icmp_data.append(': for crypto devs')
elif icmp_code is dpkt.icmp.ICMP_UNREACH_HOST_PROHIB:
icmp_data.append(': for cypto devs')
elif icmp_code is dpkt.icmp.ICMP_UNREACH_TOSNET:
icmp_data.append(': bad tos for net')
elif icmp_code is dpkt.icmp.ICMP_UNREACH_TOSHOST:
icmp_data.append(': bad tos for host')
elif icmp_code is dpkt.icmp.ICMP_UNREACH_FILTER_PROHIB:
icmp_data.append(': prohibited access')
elif icmp_code is dpkt.icmp.ICMP_UNREACH_HOST_PRECEDENCE:
icmp_data.append(': precedence error')
elif icmp_code is dpkt.icmp.ICMP_UNREACH_PRECEDENCE_CUTOFF:
icmp_data.append(': precedence cutoff')
elif icmp_type is dpkt.icmp.ICMP_SRCQUENCH:
icmp_data.append('ICMP source quench')
elif icmp_type is dpkt.icmp.ICMP_REDIRECT:
icmp_data.append('ICMP Redirect')
if icmp_code is dpkt.icmp.ICMP_REDIRECT_NET:
icmp_data.append(' for network')
elif icmp_code is dpkt.icmp.ICMP_REDIRECT_HOST:
icmp_data.append(' for host')
elif icmp_code is dpkt.icmp.ICMP_REDIRECT_TOSNET:
icmp_data.append(' for tos and net')
elif icmp_code is dpkt.icmp.ICMP_REDIRECT_TOSHOST:
icmp_data.append(' for tos and host')
elif icmp_type is dpkt.icmp.ICMP_ALTHOSTADDR:
icmp_data.append('ICMP alternate host address')
elif icmp_type is dpkt.icmp.ICMP_ECHO:
icmp_data.append('ICMP echo')
elif icmp_type is dpkt.icmp.ICMP_RTRADVERT:
icmp_data.append('ICMP Route advertisement')
if icmp_code is dpkt.icmp.ICMP_RTRADVERT_NORMAL:
icmp_data.append(': normal')
elif icmp_code is dpkt.icmp.ICMP_RTRADVERT_NOROUTE_COMMON:
icmp_data.append(': selective routing')
elif icmp_type is dpkt.icmp.ICMP_RTRSOLICIT:
icmp_data.append('ICMP Router solicitation')
elif icmp_type is dpkt.icmp.ICMP_TIMEXCEED:
icmp_data.append('ICMP time exceeded, code:')
if icmp_code is dpkt.icmp.ICMP_TIMEXCEED_INTRANS:
icmp_data.append(' ttl==0 in transit')
elif icmp_code is dpkt.icmp.ICMP_TIMEXCEED_REASS:
icmp_data.append('ttl==0 in reass')
elif icmp_type is dpkt.icmp.ICMP_PARAMPROB:
icmp_data.append('ICMP ip header bad')
if icmp_code is dpkt.icmp.ICMP_PARAMPROB_ERRATPTR:
icmp_data.append(':req. opt. absent')
elif icmp_code is dpkt.icmp.ICMP_PARAMPROB_OPTABSENT:
icmp_data.append(': req. opt. absent')
elif icmp_code is dpkt.icmp.ICMP_PARAMPROB_LENGTH:
icmp_data.append(': length')
elif icmp_type is dpkt.icmp.ICMP_TSTAMP:
icmp_data.append('ICMP timestamp request')
elif icmp_type is dpkt.icmp.ICMP_TSTAMPREPLY:
icmp_data.append('ICMP timestamp reply')
elif icmp_type is dpkt.icmp.ICMP_INFO:
icmp_data.append('ICMP information request')
elif icmp_type is dpkt.icmp.ICMP_INFOREPLY:
icmp_data.append('ICMP information reply')
elif icmp_type is dpkt.icmp.ICMP_MASK:
icmp_data.append('ICMP address mask request')
elif icmp_type is dpkt.icmp.ICMP_MASKREPLY:
icmp_data.append('ICMP address mask reply')
elif icmp_type is dpkt.icmp.ICMP_TRACEROUTE:
icmp_data.append('ICMP traceroute')
elif icmp_type is dpkt.icmp.ICMP_DATACONVERR:
icmp_data.append('ICMP data conversion error')
elif icmp_type is dpkt.icmp.ICMP_MOBILE_REDIRECT:
icmp_data.append('ICMP mobile host redirect')
elif icmp_type is dpkt.icmp.ICMP_IP6_WHEREAREYOU:
icmp_data.append('ICMP IPv6 where-are-you')
elif icmp_type is dpkt.icmp.ICMP_IP6_IAMHERE:
icmp_data.append('ICMP IPv6 i-am-here')
elif icmp_type is dpkt.icmp.ICMP_MOBILE_REG:
icmp_data.append('ICMP mobile registration req')
elif icmp_type is dpkt.icmp.ICMP_MOBILE_REGREPLY:
icmp_data.append('ICMP mobile registration reply')
elif icmp_type is dpkt.icmp.ICMP_DNS:
icmp_data.append('ICMP domain name request')
elif icmp_type is dpkt.icmp.ICMP_DNSREPLY:
icmp_data.append('ICMP domain name reply')
elif icmp_type is dpkt.icmp.ICMP_PHOTURIS:
icmp_data.append('ICMP Photuris')
if icmp_code is dpkt.icmp.ICMP_PHOTURIS_UNKNOWN_INDEX:
icmp_data.append(': unknown sec index')
elif icmp_code is dpkt.icmp.ICMP_PHOTURIS_AUTH_FAILED:
icmp_data.append(': auth failed')
elif icmp_code is dpkt.icmp.ICMP_PHOTURIS_DECOMPRESS_FAILED:
icmp_data.append(': decompress failed')
elif icmp_code is dpkt.icmp.ICMP_PHOTURIS_DECRYPT_FAILED:
icmp_data.append(': decrypt failed')
elif icmp_code is dpkt.icmp.ICMP_PHOTURIS_NEED_AUTHN:
icmp_data.append(': no authentication')
elif icmp_code is dpkt.icmp.ICMP_PHOTURIS_NEED_AUTHZ:
icmp_data.append(': no authorization')
elif icmp_type is dpkt.icmp.ICMP_TYPE_MAX:
icmp_data.append('ICMP Type Max')
return u' '.join(icmp_data)
class Stream(object):
"""Used to store packet details on network streams parsed from a pcap file."""
def __init__(self, packet, prot_data, source_ip, dest_ip, prot):
"""Initialize new stream.
Args:
packet: Packet data.
prot_data: Protocol level data for ARP, UDP, RCP, ICMP.
other types of ether packets, this is just the ether.data.
source_ip: Source IP.
dest_ip: Dest IP.
prot: Protocol (TCP, UDP, ICMP, ARP).
"""
self.packet_id = [packet[1]]
self.timestamps = [packet[0]]
self.size = packet[3]
self.start_time = packet[0]
self.all_data = [prot_data]
self.protocol_data = ''
self.stream_data = []
if prot == 'TCP' or prot == 'UDP':
self.source_port = prot_data.sport
self.dest_port = prot_data.dport
else:
self.source_port = ''
self.dest_port = ''
self.source_ip = source_ip
self.dest_ip = dest_ip
self.protocol = prot
def AddPacket(self, packet, prot_data):
"""Add another packet to an existing stream.
Args:
packet: Packet data.
prot_data: Protocol level data for ARP, UDP, RCP, ICMP.
other types of ether packets, this is just the ether.data
"""
self.packet_id.append(packet[1])
self.timestamps.append(packet[0])
self.all_data.append(prot_data)
self.size += packet[3]
def SpecialTypes(self):
"""Checks for some special types of packets.
This method checks for some special packets and assembles usable data
currently works for: DNS (udp 53), http, netbios (udp 137), ICMP.
Returns:
A tuple consisting of a basic desctiption of the stream
(i.e. HTTP Request) and the prettyfied string for the protocols.
"""
packet_details = []
if self.stream_data[:4] == 'HTTP':
try:
http = dpkt.http.Response(self.stream_data)
packet_details.append('HTTP Response: status: ')
packet_details.append(http.status)
packet_details.append(' reason: ')
packet_details.append(http.reason)
packet_details.append(' version: ')
packet_details.append(http.version)
return 'HTTP Response', u' '.join(packet_details)
except dpkt.UnpackError as exception:
packet_details = (
u'HTTP Response Unpack Error: {0:s}. '
u'First 20 of data {1:s}').format(
exception, repr(self.stream_data[:20]))
return 'HTTP Response', packet_details
except IndexError as exception:
packet_details = (
u'HTTP Response Index Error: {0:s}. First 20 of data {1:s}').format(
exception, repr(self.stream_data[:20]))
return 'HTTP Response', packet_details
except ValueError as exception:
packet_details = (
u'HTTP Response parsing error: {0:s}. '
u'First 20 of data {1:s}').format(
exception, repr(self.stream_data[:20]))
return 'HTTP Response', packet_details
elif self.stream_data[:3] == 'GET' or self.stream_data[:4] == 'POST':
try:
http = dpkt.http.Request(self.stream_data)
packet_details.append('HTTP Request: method: ')
packet_details.append(http.method)
packet_details.append(' uri: ')
packet_details.append(http.uri)
packet_details.append(' version: ')
packet_details.append(http.version)
packet_details.append(' headers: ')
packet_details.append(repr(http.headers))
return 'HTTP Request', u' '.join(packet_details)
except dpkt.UnpackError as exception:
packet_details = (
u'HTTP Request unpack error: {0:s}. First 20 of data {1:s}').format(
exception, repr(self.stream_data[:20]))
return 'HTTP Request', packet_details
except ValueError as exception:
packet_details = (
u'HTTP Request parsing error: {0:s}. '
u'First 20 of data {1:s}').format(
exception, repr(self.stream_data[:20]))
return 'HTTP Request', packet_details
elif self.protocol == 'UDP' and (
self.source_port == 53 or self.dest_port == 53):
# DNS request/replies.
# Check to see if the lengths are valid.
for packet in self.all_data:
if not packet.ulen == len(packet):
packet_details.append('Truncated DNS packets - unable to parse: ')
packet_details.append(repr(self.stream_data[15:40]))
return 'DNS', u' '.join(packet_details)
return 'DNS', ParseDNS(self.stream_data)
elif self.protocol == 'UDP' and (
self.source_port == 137 or self.dest_port == 137):
return 'NetBIOS', ParseNetBios(dpkt.netbios.NS(self.stream_data))
elif self.protocol == 'ICMP':
# ICMP packets all end up as 1 stream, so they need to be
# processed 1 by 1.
return 'ICMP', ICMPTypes(self.all_data[0])
elif '\x03\x01' in self.stream_data[1:3]:
# Some form of ssl3 data.
try:
ssl = dpkt.ssl.SSL2(self.stream_data)
packet_details.append('SSL data. Length: ')
packet_details.append(str(ssl.len))
return 'SSL', u' '.join(packet_details)
except dpkt.UnpackError as exception:
packet_details = (
u'SSL unpack error: {0:s}. First 20 of data {1:s}').format(
exception, repr(self.stream_data[:20]))
return 'SSL', packet_details
elif '\x03\x00' in self.stream_data[1:3]:
# Some form of ssl3 data.
try:
ssl = dpkt.ssl.SSL2(self.stream_data)
packet_details.append('SSL data. Length: ')
packet_details.append(str(ssl.len))
return 'SSL', u' '.join(packet_details)
except dpkt.UnpackError as exception:
packet_details = (
u'SSL unpack error: {0:s}. First 20 of data {1:s}').format(
exception, repr(self.stream_data[:20]))
return 'SSL', packet_details
return 'other', self.protocol_data
def Clean(self):
"""Clean up stream data."""
clean_data = []
for packet in self.all_data:
try:
clean_data.append(packet.data)
except AttributeError:
pass
self.stream_data = ''.join(clean_data)
class PcapEvent(time_events.PosixTimeEvent):
"""Convenience class for a PCAP record event."""
DATA_TYPE = 'metadata:pcap'
def __init__(self, timestamp, usage, stream_object):
"""Initializes the event.
Args:
timestamp: The POSIX value of the timestamp.
usage: A usage description value.
stream_object: The stream object (instance of Stream).
"""
super(PcapEvent, self).__init__(timestamp, usage)
self.source_ip = stream_object.source_ip
self.dest_ip = stream_object.dest_ip
self.source_port = stream_object.source_port
self.dest_port = stream_object.dest_port
self.protocol = stream_object.protocol
self.size = stream_object.size
self.stream_type, self.protocol_data = stream_object.SpecialTypes()
self.first_packet_id = min(stream_object.packet_id)
self.last_packet_id = max(stream_object.packet_id)
self.packet_count = len(stream_object.packet_id)
self.stream_data = repr(stream_object.stream_data[:50])
class PcapParser(interface.BaseParser):
"""Parses PCAP files."""
NAME = 'pcap'
DESCRIPTION = u'Parser for PCAP files.'
def _ParseIPPacket(
self, connections, trunc_list, packet_number, timestamp,
packet_data_size, ip_packet):
"""Parses an IP packet.
Args:
connections: A dictionary object to track the IP connections.
trunc_list: A list of packets that truncated strangely and could
not be turned into a stream.
packet_number: The PCAP packet number, where 1 is the first packet.
timestamp: The PCAP packet timestamp.
packet_data_size: The packet data size.
ip_packet: The IP packet (instance of dpkt.ip.IP).
"""
packet_values = [timestamp, packet_number, ip_packet, packet_data_size]
source_ip_address = socket.inet_ntoa(ip_packet.src)
destination_ip_address = socket.inet_ntoa(ip_packet.dst)
if ip_packet.p == dpkt.ip.IP_PROTO_TCP:
# Later versions of dpkt seem to return a string instead of a TCP object.
if isinstance(ip_packet.data, str):
try:
tcp = dpkt.tcp.TCP(ip_packet.data)
except (dpkt.NeedData, dpkt.UnpackError):
trunc_list.append(packet_values)
return
else:
tcp = ip_packet.data
stream_key = 'tcp: {0:s}:{1:d} > {2:s}:{3:d}'.format(
source_ip_address, tcp.sport, destination_ip_address, tcp.dport)
if stream_key in connections:
connections[stream_key].AddPacket(packet_values, tcp)
else:
connections[stream_key] = Stream(
packet_values, tcp, source_ip_address, destination_ip_address,
'TCP')
elif ip_packet.p == dpkt.ip.IP_PROTO_UDP:
# Later versions of dpkt seem to return a string instead of an UDP object.
if isinstance(ip_packet.data, str):
try:
udp = dpkt.udp.UDP(ip_packet.data)
except (dpkt.NeedData, dpkt.UnpackError):
trunc_list.append(packet_values)
return
else:
udp = ip_packet.data
stream_key = 'udp: {0:s}:{1:d} > {2:s}:{3:d}'.format(
source_ip_address, udp.sport, destination_ip_address, udp.dport)
if stream_key in connections:
connections[stream_key].AddPacket(packet_values, udp)
else:
connections[stream_key] = Stream(
packet_values, udp, source_ip_address, destination_ip_address,
'UDP')
elif ip_packet.p == dpkt.ip.IP_PROTO_ICMP:
# Later versions of dpkt seem to return a string instead of
# an ICMP object.
if isinstance(ip_packet.data, str):
icmp = dpkt.icmp.ICMP(ip_packet.data)
else:
icmp = ip_packet.data
stream_key = 'icmp: {0:d} {1:s} > {2:s}'.format(
timestamp, source_ip_address, destination_ip_address)
if stream_key in connections:
connections[stream_key].AddPacket(packet_values, icmp)
else:
connections[stream_key] = Stream(
packet_values, icmp, source_ip_address, destination_ip_address,
'ICMP')
def _ParseOtherPacket(self, packet_values):
"""Parses a non-IP packet.
Args:
packet_values: list of packet values
Returns:
A stream object (instance of Stream) or None if the packet data
is not supported.
"""
ether = packet_values[2]
stream_object = None
if ether.type == dpkt.ethernet.ETH_TYPE_ARP:
arp = ether.data
arp_data = []
stream_object = Stream(
packet_values, arp, binascii.hexlify(ether.src),
binascii.hexlify(ether.dst), 'ARP')
if arp.op == dpkt.arp.ARP_OP_REQUEST:
arp_data.append('arp request: target IP = ')
arp_data.append(socket.inet_ntoa(arp.tpa))
stream_object.protocol_data = u' '.join(arp_data)
elif arp.op == dpkt.arp.ARP_OP_REPLY:
arp_data.append('arp reply: target IP = ')
arp_data.append(socket.inet_ntoa(arp.tpa))
arp_data.append(' target MAC = ')
arp_data.append(binascii.hexlify(arp.tha))
stream_object.protocol_data = u' '.join(arp_data)
elif arp.op == dpkt.arp.ARP_OP_REVREQUEST:
arp_data.append('arp protocol address request: target IP = ')
arp_data.append(socket.inet_ntoa(arp.tpa))
stream_object.protocol_data = u' '.join(arp_data)
elif arp.op == dpkt.arp.ARP_OP_REVREPLY:
arp_data.append('arp protocol address reply: target IP = ')
arp_data.append(socket.inet_ntoa(arp.tpa))
arp_data.append(' target MAC = ')
arp_data.append(binascii.hexlify(arp.tha))
stream_object.protocol_data = u' '.join(arp_data)
elif ether.type == dpkt.ethernet.ETH_TYPE_IP6:
ip6 = ether.data
stream_object = Stream(
packet_values, ether.data, binascii.hexlify(ip6.src),
binascii.hexlify(ip6.dst), 'IPv6')
stream_object.protocol_data = 'IPv6'
elif ether.type == dpkt.ethernet.ETH_TYPE_CDP:
stream_object = Stream(
packet_values, ether.data, binascii.hexlify(ether.src),
binascii.hexlify(ether.dst), 'CDP')
stream_object.protocol_data = 'CDP'
elif ether.type == dpkt.ethernet.ETH_TYPE_DTP:
stream_object = Stream(
packet_values, ether.data, binascii.hexlify(ether.src),
binascii.hexlify(ether.dst), 'DTP')
stream_object.protocol_data = 'DTP'
elif ether.type == dpkt.ethernet.ETH_TYPE_REVARP:
stream_object = Stream(
packet_values, ether.data, binascii.hexlify(ether.src),
binascii.hexlify(ether.dst), 'RARP')
stream_object.protocol_data = 'Reverse ARP'
elif ether.type == dpkt.ethernet.ETH_TYPE_8021Q:
stream_object = Stream(
packet_values, ether.data, binascii.hexlify(ether.src),
binascii.hexlify(ether.dst), '8021Q packet')
stream_object.protocol_data = '8021Q packet'
elif ether.type == dpkt.ethernet.ETH_TYPE_IPX:
stream_object = Stream(
packet_values, ether.data, binascii.hexlify(ether.src),
binascii.hexlify(ether.dst), 'IPX')
stream_object.protocol_data = 'IPX'
elif ether.type == dpkt.ethernet.ETH_TYPE_PPP:
stream_object = Stream(
packet_values, ether.data, binascii.hexlify(ether.src),
binascii.hexlify(ether.dst), 'PPP')
stream_object.protocol_data = 'PPP'
elif ether.type == dpkt.ethernet.ETH_TYPE_MPLS:
stream_object = Stream(
packet_values, ether.data, binascii.hexlify(ether.src),
binascii.hexlify(ether.dst), 'MPLS')
stream_object.protocol_data = 'MPLS'
elif ether.type == dpkt.ethernet.ETH_TYPE_MPLS_MCAST:
stream_object = Stream(
packet_values, ether.data, binascii.hexlify(ether.src),
binascii.hexlify(ether.dst), 'MPLS')
stream_object.protocol_data = 'MPLS MCAST'
elif ether.type == dpkt.ethernet.ETH_TYPE_PPPoE_DISC:
stream_object = Stream(
packet_values, ether.data, binascii.hexlify(ether.src),
binascii.hexlify(ether.dst), 'PPOE')
stream_object.protocol_data = 'PPoE Disc packet'
elif ether.type == dpkt.ethernet.ETH_TYPE_PPPoE:
stream_object = Stream(
packet_values, ether.data, binascii.hexlify(ether.src),
binascii.hexlify(ether.dst), 'PPPoE')
stream_object.protocol_data = 'PPPoE'
elif str(hex(ether.type)) == '0x2452':
stream_object = Stream(
packet_values, ether.data, binascii.hexlify(ether.src),
binascii.hexlify(ether.dst), '802.11')
stream_object.protocol_data = '802.11'
return stream_object
def _ParseOtherStreams(self, other_list, trunc_list):
"""Process PCAP packets that are not IP packets.
For all packets that were not IP packets, create stream containers
depending on the type of packet.
Args:
other_list: List of non-ip packets.
trunc_list: A list of packets that truncated strangely and could
not be turned into a stream.
Returns:
A list of stream objects (instances of Stream).
"""
other_streams = []
for packet_values in other_list:
stream_object = self._ParseOtherPacket(packet_values)
if stream_object:
other_streams.append(stream_object)
for packet_values in trunc_list:
ip_packet = packet_values[2]
source_ip_address = socket.inet_ntoa(ip_packet.src)
destination_ip_address = socket.inet_ntoa(ip_packet.dst)
stream_object = Stream(
packet_values, ip_packet.data, source_ip_address,
destination_ip_address, 'BAD')
stream_object.protocolData = 'Bad truncated IP packet'
other_streams.append(stream_object)
return other_streams
def Parse(self, parser_context, file_entry, parser_chain=None):
"""Parses a PCAP file.
Args:
parser_context: A parser context object (instance of ParserContext).
file_entry: A file entry object (instance of dfvfs.FileEntry).
parser_chain: Optional string containing the parsing chain up to this
point. The default is None.
"""
file_object = file_entry.GetFileObject()
self.ParseFileObject(
parser_context, file_object, file_entry=file_entry,
parser_chain=parser_chain)
file_object.close()
def ParseFileObject(
self, parser_context, file_object, file_entry=None, parser_chain=None):
"""Parses a PCAP file.
Args:
parser_context: A parser context object (instance of ParserContext).
file_object: A file-like object.
file_entry: Optional file entry object (instance of dfvfs.FileEntry).
The default is None.
parser_chain: Optional string containing the parsing chain up to this
point. The default is None.
Raises:
UnableToParseFile: when the file cannot be parsed.
"""
data = file_object.read(dpkt.pcap.FileHdr.__hdr_len__)
try:
file_header = dpkt.pcap.FileHdr(data)
packet_header_class = dpkt.pcap.PktHdr
except (dpkt.NeedData, dpkt.UnpackError) as exception:
raise errors.UnableToParseFile(
u'[{0:s}] unable to parse file: {1:s} with error: {2:s}'.format(
self.NAME, file_entry.name, exception))
if file_header.magic == dpkt.pcap.PMUDPCT_MAGIC:
try:
file_header = dpkt.pcap.LEFileHdr(data)
packet_header_class = dpkt.pcap.LEPktHdr
except (dpkt.NeedData, dpkt.UnpackError) as exception:
raise errors.UnableToParseFile(
u'[{0:s}] unable to parse file: {1:s} with error: {2:s}'.format(
self.NAME, file_entry.name, exception))
elif file_header.magic != dpkt.pcap.TCPDUMP_MAGIC:
raise errors.UnableToParseFile(u'Unsupported file signature')
# Add ourselves to the parser chain, which will be used in all subsequent
# event creation in this parser.
parser_chain = self._BuildParserChain(parser_chain)
packet_number = 1
connections = {}
other_list = []
trunc_list = []
data = file_object.read(dpkt.pcap.PktHdr.__hdr_len__)
while data:
packet_header = packet_header_class(data)
timestamp = packet_header.tv_sec + (packet_header.tv_usec / 1000000.0)
packet_data = file_object.read(packet_header.caplen)
ethernet_frame = dpkt.ethernet.Ethernet(packet_data)
if ethernet_frame.type == dpkt.ethernet.ETH_TYPE_IP:
self._ParseIPPacket(
connections, trunc_list, packet_number, timestamp,
len(ethernet_frame), ethernet_frame.data)
else:
packet_values = [
timestamp, packet_number, ethernet_frame, len(ethernet_frame)]
other_list.append(packet_values)
packet_number += 1
data = file_object.read(dpkt.pcap.PktHdr.__hdr_len__)
other_streams = self._ParseOtherStreams(other_list, trunc_list)
for stream_object in sorted(
connections.values(), key=operator.attrgetter('start_time')):
if not stream_object.protocol == 'ICMP':
stream_object.Clean()
event_objects = [
PcapEvent(
min(stream_object.timestamps),
eventdata.EventTimestamp.START_TIME, stream_object),
PcapEvent(
max(stream_object.timestamps),
eventdata.EventTimestamp.END_TIME, stream_object)]
parser_context.ProduceEvents(
event_objects, parser_chain=parser_chain, file_entry=file_entry)
for stream_object in other_streams:
event_objects = [
PcapEvent(
min(stream_object.timestamps),
eventdata.EventTimestamp.START_TIME, stream_object),
PcapEvent(
max(stream_object.timestamps),
eventdata.EventTimestamp.END_TIME, stream_object)]
parser_context.ProduceEvents(
event_objects, parser_chain=parser_chain, file_entry=file_entry)
manager.ParsersManager.RegisterParser(PcapParser)