1#!/usr/bin/env python
2# Copyright (c) 2003 CORE Security Technologies
3#
4# This software is provided under under a slightly modified version
5# of the Apache Software License. See the accompanying LICENSE file
6# for more information.
7#
8# Pcap dump splitter.
9#
10# This tools splits pcap capture files into smaller ones, one for each
11# different TCP/IP connection found in the original.
12#
13# Authors:
14#  Alejandro D. Weil <aweil@coresecurity.com>
15#  Javier Kohen <jkohen@coresecurity.com>
16#
17# Reference for:
18#  pcapy: open_offline, pcapdumper.
19#  ImpactDecoder.
20
21import sys
22from exceptions import Exception
23import pcapy
24from pcapy import open_offline
25
26from impacket.ImpactDecoder import EthDecoder, LinuxSLLDecoder
27
28
29class Connection:
30    """This class can be used as a key in a dictionary to select a connection
31    given a pair of peers. Two connections are considered the same if both
32    peers are equal, despite the order in which they were passed to the
33    class constructor.
34    """
35
36    def __init__(self, p1, p2):
37        """This constructor takes two tuples, one for each peer. The first
38        element in each tuple is the IP address as a string, and the
39        second is the port as an integer.
40        """
41
42        self.p1 = p1
43        self.p2 = p2
44
45    def getFilename(self):
46        """Utility function that returns a filename composed by the IP
47        addresses and ports of both peers.
48        """
49        return '%s.%d-%s.%d.pcap'%(self.p1[0],self.p1[1],self.p2[0],self.p2[1])
50
51    def __cmp__(self, other):
52        if ((self.p1 == other.p1 and self.p2 == other.p2)
53            or (self.p1 == other.p2 and self.p2 == other.p1)):
54            return 0
55        else:
56            return -1
57
58    def __hash__(self):
59        return (hash(self.p1[0]) ^ hash(self.p1[1])
60                ^ hash(self.p2[0]) ^ hash(self.p2[1]))
61
62
63class Decoder:
64    def __init__(self, pcapObj):
65        # Query the type of the link and instantiate a decoder accordingly.
66        datalink = pcapObj.datalink()
67        if pcapy.DLT_EN10MB == datalink:
68            self.decoder = EthDecoder()
69        elif pcapy.DLT_LINUX_SLL == datalink:
70            self.decoder = LinuxSLLDecoder()
71        else:
72            raise Exception("Datalink type not supported: " % datalink)
73
74        self.pcap = pcapObj
75        self.connections = {}
76
77    def start(self):
78        # Sniff ad infinitum.
79        # PacketHandler shall be invoked by pcap for every packet.
80        self.pcap.loop(0, self.packetHandler)
81
82    def packetHandler(self, hdr, data):
83        """Handles an incoming pcap packet. This method only knows how
84        to recognize TCP/IP connections.
85        Be sure that only TCP packets are passed onto this handler (or
86        fix the code to ignore the others).
87
88        Setting r"ip proto \tcp" as part of the pcap filter expression
89        suffices, and there shouldn't be any problem combining that with
90        other expressions.
91        """
92
93        # Use the ImpactDecoder to turn the rawpacket into a hierarchy
94        # of ImpactPacket instances.
95        p = self.decoder.decode(data)
96        ip = p.child()
97        tcp = ip.child()
98
99        # Build a distinctive key for this pair of peers.
100        src = (ip.get_ip_src(), tcp.get_th_sport() )
101        dst = (ip.get_ip_dst(), tcp.get_th_dport() )
102        con = Connection(src,dst)
103
104        # If there isn't an entry associated yetwith this connection,
105        # open a new pcapdumper and create an association.
106        if not self.connections.has_key(con):
107            fn = con.getFilename()
108            print "Found a new connection, storing into:", fn
109            try:
110                dumper = self.pcap.dump_open(fn)
111            except pcapy.PcapError, e:
112                print "Can't write packet to:", fn
113                return
114            self.connections[con] = dumper
115
116        # Write the packet to the corresponding file.
117        self.connections[con].dump(hdr, data)
118
119
120
121def main(filename):
122    # Open file
123    p = open_offline(filename)
124
125    # At the moment the callback only accepts TCP/IP packets.
126    p.setfilter(r'ip proto \tcp')
127
128    print "Reading from %s: linktype=%d" % (filename, p.datalink())
129
130    # Start decoding process.
131    Decoder(p).start()
132
133
134# Process command-line arguments.
135if __name__ == '__main__':
136    if len(sys.argv) <= 1:
137        print "Usage: %s <filename>" % sys.argv[0]
138        sys.exit(1)
139
140    main(sys.argv[1])
141