dump.py - Basic Packet Capture

'''
version 6.1
Basic packet capture
October 2014

Version 5 : Classes based version

Version 6 : Capture to file (pcap format)
            6.1 doc review

author: Guillermo Gomez (a.k.a. Momo)

'''

import argparse, os, socket, struct, time
import dpkt, IP_addr

'''
   just keep in mind...rfc 791 and rfc 792
    0                   1                   2                   3   
    0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 
   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
   |Version|  IHL  |Type of Service|          Total Length         |
   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
   |         Identification        |Flags|      Fragment Offset    |
   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
   |  Time to Live |    Protocol   |         Header Checksum       |
   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
   |                       Source Address                          |
   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
   |                    Destination Address                        |
   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
   |                    Options                    |    Padding    |
   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
'''

def tcp_flags_to_text (tcp_flags): # returns a string with TCP FLAGS 
   tcp_flags_names = {1:'fin', 2:'syn', 4:'rst', 8:'push', 16:'ack', 32:'urg'}
   flags_text=''
   for i in range (5,-1,-1):
      if tcp_flags >= 2**i:
         tcp_flags -= 2**i
         flags_text += tcp_flags_names[2**i]
         flags_text += ' '
   return flags_text

def icmp_type_to_text (icmp_type): # returns a string with ICMP Type
   icmp_type_names = { 0:'Echo Reply',
                       3:'Destination Unreachable',
                       4:'Source Quench',
                       5:'Redirect',
                       6:'Alternate Host Address',
                       8:'Echo Request',
                       9:'Router Advertisement',
                       10:'Router Selection',
                       11:'Time Exceeded',
                       12:'Parameter Problem',
                       13:'Timestamp',
                       14:'Timestamp Reply',
                       15:'Information Request',
                       16:'Information Reply',
                       17:'Address Mask Request',
                       18:'Address Mask Reply',
                       30:'Traceroute',
                       31:'Datagram Conversion Error',
                       32:'Mobile Host Redirect',
                       37:'Domain Name Request',
                       38:'Domain Name Reply'}
   try:
      icmp_type_text = icmp_type_names[icmp_type]
   except:
      icmp_type_text = ''
   return icmp_type_text

def pcap_header():
   magic_number = '\xd4\xc3\xb2\xa1'
   Major_version = '\x02\x00'
   Minor_version = '\x04\x00'
   GMT_ZONE_offset_dif = '\x00\x00\x00\x00'
   Timestamp_Accuracy = '\x00\x00\x00\x00'
   Snapshoot_Length = '\x00\x00\x04\x00'
   LL_Header_Type = '\x01\x00\x00\x00'

   Header = magic_number + \
            Major_version + \
            Minor_version + \
            GMT_ZONE_offset_dif + \
            Timestamp_Accuracy + \
            Snapshoot_Length + \
            LL_Header_Type

   return Header

def pcap_pkt(buff, timestamp):
   pkt_tmp = struct.pack ('I', timestamp)
   pkt_mcr = struct.pack ('I', (1e6 * (timestamp % 1)))
   Saved_size = struct.pack ('I', (len (buff) + 0xe))
   Pkt_size = struct.pack ('I', (len (buff) + 0xe))
   dst_mac = '\xff\xff\xff\xff\xff\xff'
   src_mac = '\x00\x00\x00\x00\x00\x00'
   Ether_type = '\x08\x00'

   Packet_pcap = pkt_tmp + \
                 pkt_mcr + \
                 Saved_size + \
                 Pkt_size + \
                 dst_mac + \
                 src_mac + \
                 Ether_type + \
                 buff
   return Packet_pcap
   

def main():
   '''
   Starts analyzer
   '''

   parser = argparse.ArgumentParser(
      prog = 'dump',
      description = 'Simple IP Packet Capture',
      epilog = ' Hope you enjoy ;)')
   
   parser.add_argument ('-f', dest='filter',\
                      help='Host or Network like "x.x.x.x/nn" or "x.x.x.x/m.m.m.m"')
   parser.add_argument ('-p', dest='protocol', default ='ALL',\
                      help='Protocol to capture: ICMP, TCP, UDP, OTHERS or ALL')
   parser.add_argument ('-n', dest='pktnum', type=int, default=100,\
                      help='Number of packets matching filter to capture')
   parser.add_argument ('-c', dest='filename', \
                      help='Capture to file')
   parser.add_argument ('-d', '--debug', action='store_true', \
                      help='Debug mode. Take care.')
   
   options = parser.parse_args()
   
   debug = options.debug

   protocol_number = {'ICMP':1, 'TCP':6, 'UDP':17, 'OTHERS':0, 'ALL':0}
   protocol_name = {1:'ICMP', 6:'TCP', 17:'UDP'}   
   
   # check protocol
   options.protocol = options.protocol.upper()
   if (options.protocol not in protocol_number):
      print (parser.usage)
      exit(0)

   # check filter
   if options.filter != None:
      Filter_IP = IP_addr.IP_addr(options.filter)
      if debug:
         print (Filter_IP.address,
                Filter_IP.mask,
                Filter_IP.prefix,
                Filter_IP.network,
                Filter_IP.error_description)
      if Filter_IP.error_description <> '':
         print (Filter_IP.error_description)
         exit (0)
   else:
      Filter_IP = IP_addr.IP_addr('0.0.0.0/0')

   # check number of packets to capture
   if options.pktnum == None:
      repeat = 100
   else:
      repeat = options.pktnum

   # check file name
   if options.filename == None:
      Capture_to_file = False
   else:
      Capture_to_file = True
      Dump_file = open (options.filename , "wb" )
      Dump_file.write(pcap_header())         

   # the public network interface
   HOST = socket.gethostbyname(socket.gethostname())

   # create a raw socket and bind it to the public interface
   s = socket.socket(socket.AF_INET, socket.SOCK_RAW, socket.IPPROTO_IP)
   s.bind((HOST, 0))

   # Include IP headers
   # s.setsockopt(socket.IPPROTO_IP, socket.IP_HDRINCL, 1)

   # receive all packages
   s.ioctl(socket.SIO_RCVALL, socket.RCVALL_ON)

   # receive requested number of packages
   print ('\n Will Capture {} pakets of {} type.'.format(repeat, options.protocol))
   print ('\n Will filter {}/{}\n'.format(Filter_IP.network, Filter_IP.prefix))
   print ('\n Press Ctrl+C to stop\n')

   total_tcp_pkt = total_udp_pkt = total_icmp_pkt = total_unk_pkt = total_pkt = analyzed_pkt = 0
   capture_init = time.time()

   PKT_IP_src = IP_addr.IP_addr()
   PKT_IP_dst = IP_addr.IP_addr()
   PKT_IP_src.prefix = PKT_IP_dst.prefix = Filter_IP.prefix
   
   try:
      while repeat > 0:
         (buff, address)  = s.recvfrom(65565)
         pkt_time = time.time() - capture_init
         IP_pkt = dpkt.ip.IP(buff)
         PKT_IP_src.address = socket.inet_ntoa(IP_pkt.src)
         PKT_IP_dst.address = socket.inet_ntoa(IP_pkt.dst)
         PKT_IP_src.calculate_network()
         PKT_IP_dst.calculate_network()
         analyzed_pkt += 1
         if Filter_IP.network == PKT_IP_src.network or Filter_IP.network == PKT_IP_dst.network:
            if IP_pkt.p == 17 and  options.protocol in ('UDP', 'ALL'): # UDP
               repeat -= 1
               total_udp_pkt += 1
               UDP_pkt = dpkt.udp.UDP()
               UDP_pkt = IP_pkt.data
               print ('{:>8.4f} - {:>15}:{:>5} --> {:>15}:{:>5} : UDP'.format(
                  pkt_time,
                  PKT_IP_src.address, UDP_pkt.sport,
                  PKT_IP_dst.address, UDP_pkt.dport))
               if Capture_to_file:
                  Dump_file.write(pcap_pkt(buff, time.time()))
               if debug:
                  print (len(buff), '->', IP_pkt)
            elif IP_pkt.p ==6 and options.protocol in ('TCP', 'ALL'): # TCP
               repeat -= 1
               total_tcp_pkt += 1
               TCP_pkt = dpkt.tcp.TCP()
               TCP_pkt = IP_pkt.data
               print ('{:>8.4f} - {:>15}:{:>5} --> {:>15}:{:>5} : TCP {} '.format(
                  pkt_time,
                  PKT_IP_src.address, TCP_pkt.sport,
                  PKT_IP_dst.address, TCP_pkt.dport,
                  tcp_flags_to_text(TCP_pkt.flags)))
               if Capture_to_file:
                  Dump_file.write(pcap_pkt(buff, time.time()))
               if debug:
                  print (len(buff), '->', IP_pkt)
            elif IP_pkt.p ==1 and options.protocol in ('ICMP', 'ALL'): # ICMP
               repeat -= 1
               total_icmp_pkt += 1
               ICMP_pkt = dpkt.icmp.ICMP()
               ICMP_pkt = IP_pkt.data
               print ('{:>8.4f} - {:>15}       --> {:>15}       : ICMP {}'.format(
                  pkt_time,
                  PKT_IP_src.address,
                  PKT_IP_dst.address,
                  icmp_type_to_text(ICMP_pkt.type)))
               if Capture_to_file:
                  Dump_file.write(pcap_pkt(buff, time.time()))
               if debug:
                  print (len(buff), '->', IP_pkt)
            elif IP_pkt.p not in (1,6,17) and options.protocol in ('OTHERS', 'ALL'): # OTHER protocol
               repeat -= 1
               total_unk_pkt += 1
               print ('{:>8.4f} - {:>15}       --> {:>15}       : IP Protocol {:>5}'.format(
                  pkt_time,
                  PKT_IP_src.address,
                  PKT_IP_dst.address,
                  IP_pkt.p))
               if Capture_to_file:
                  Dump_file.write(pcap_pkt(buff, time.time()))
               if debug:
                  print (len(buff), '->', IP_pkt)
   except KeyboardInterrupt:
      pass
   
   # disabled promiscuous mode
   s.ioctl(socket.SIO_RCVALL, socket.RCVALL_OFF)

   # close capture file
   if Capture_to_file:
      Dump_file.close()
   
   total_pkt = total_tcp_pkt + total_udp_pkt + total_icmp_pkt + total_unk_pkt
   
   print ('\n Analyzed {} packets'.format(analyzed_pkt))
   print ('\n Captured {} packets:'.format(total_pkt))
   if total_pkt == 0:
      exit(0)
   print ('     TCP  : {:.>5} ___  {:.>3}%'.format(total_tcp_pkt, 100*total_tcp_pkt/total_pkt))
   print ('     UDP  : {:.>5} ___  {:.>3}%'.format(total_udp_pkt, 100*total_udp_pkt/total_pkt))
   print ('     ICMP : {:.>5} ___  {:.>3}%'.format(total_icmp_pkt, 100*total_icmp_pkt/total_pkt))
   print ('     UNK  : {:.>5} ___  {:.>3}%'.format(total_unk_pkt, 100*total_unk_pkt/total_pkt))

if __name__ == "__main__":
   main()

No comments:

Post a Comment