Browse Source

v1.00, now IPv6 capable
-p param expanded

Peter Siegrist 4 years ago
parent
commit
28ca2d7bd7
2 changed files with 128 additions and 63 deletions
  1. 18 14
      README.md
  2. 110 49
      sisniff

+ 18 - 14
README.md

@@ -4,57 +4,61 @@ sisniff
 Like tcpdump, sisniff captures and displays all connections from and to the local machine. 
 Like tcpdump, sisniff captures and displays all connections from and to the local machine. 
 Additionally it will show you the <b>applications belonging to each packet</b>.<br> 
 Additionally it will show you the <b>applications belonging to each packet</b>.<br> 
 
 
-It supports TCP, UDP and ICMP packets.<br>
+It supports TCP, UDP and ICMP packets, both IPv4 and IPv6<br>
 The Sniffer accepts some filter like tcpdump.<br>
 The Sniffer accepts some filter like tcpdump.<br>
 <p>
 <p>
 
 
 For HTTP connections, there is an argument to show part of its payload.<br>
 For HTTP connections, there is an argument to show part of its payload.<br>
 
 
-----
+ 
 Under some cirumstances the program/PID cannot be evaluated. This mavericks would be reported as follow:
 Under some cirumstances the program/PID cannot be evaluated. This mavericks would be reported as follow:
 <pre>
 <pre>
  "?/?" = No entry in /proc/net/[TCP/UDP/ICMP]
  "?/?" = No entry in /proc/net/[TCP/UDP/ICMP]
  "-/-" = Found Inode but no PID
  "-/-" = Found Inode but no PID
  "./." = The Inode found is '0'
  "./." = The Inode found is '0'
 </pre>
 </pre>
-----
+ 
 
 
 <p><i>
 <p><i>
 !! sisniff uses scapy's sniff() function, so scapy package is needed:<br>
 !! sisniff uses scapy's sniff() function, so scapy package is needed:<br>
 !! debian: apt-get install scapy<br>
 !! debian: apt-get install scapy<br>
-!! pip: pip/pip3 install scapy
-!! other systems: http://www.secdev.org/projects/scapy<br><p>
+!! pip: pip/pip3 install scapy<br>
+!! other systems: http://www.secdev.org/projects/scapy<br>
 </i>
 </i>
 
 
-<pre><br> 
 
 
 This program needs Python 3.x or Python 2.x. 
 This program needs Python 3.x or Python 2.x. 
 
 
+Current Version can be downloaded from Git at: https://git.zweiernet.ch/sigi/sisniff
 
 
+<pre>
 --------------------
 --------------------
 
 
 # sisniff -h
 # sisniff -h
-usage: sisniff [-h] -i {eth0,lo,wlan0} [-n] [-p program|not-program] [-pH] [-pHl] [filter]
- 
-sisniff V0.90
+usage: sisniff [-h] -i {eth0,lo,wlan0} [-n] [-p program|not-program] [-4] [-6] [-pH] [-pHl] [filter]
+
+sisniff V1.00
+2017-2019 by sigi <https://wiki.zweiernet.ch/wiki/sisniff>
 
 
 positional arguments:
 positional arguments:
   filter                Filter (BPF syntax) on top of IP (in dbl-quotes "...")
   filter                Filter (BPF syntax) on top of IP (in dbl-quotes "...")
 
 
 optional arguments:
 optional arguments:
   -h, --help            show this help message and exit
   -h, --help            show this help message and exit
-  -i {eth0,lo,wlan0}    Interface (required)
+  -i {eth0,lo,wlan0}	Interface (required)
   -n                    Do not resolve IP-Addresses
   -n                    Do not resolve IP-Addresses
   -p program|not-program
   -p program|not-program
                         Filter by program name ([not-] negates)
                         Filter by program name ([not-] negates)
+  -4                    Only IPv4
+  -6                    Only IPv6
   -pH                   Show HTTP Payload
   -pH                   Show HTTP Payload
   -pHl                  Show HTTP Payload, long output
   -pHl                  Show HTTP Payload, long output
 --------------------
 --------------------
 </pre>
 </pre>
 
 
-- Interfaces showed in the help are gathered from the running system.
-- <pre>program</pre> means the name in the 'Program' column, e.g. <pre>thunderbird-bin</pre>
-- <pre>not-program</pre> excludes the program from beeing showed, e.g. <pre>not-thunderbird-bin</pre>
-- <pre>filter</pre> is in same syntax as tcpdump uses. Must be written in double-quotes "..."
+* Interfaces showed in the help are gathered from the running system.
+* <code>program</code> means the name in the 'Program' column, e.g. <code>thunderbird-bin</code>
+* <code>not-program</code> excludes the program from beeing showed, e.g. <code>not-thunderbird-bin</code>
+* <code>filter</code> is in same syntax as tcpdump uses. Must be written in double-quotes "..."
 
 
 
 

+ 110 - 49
sisniff

@@ -24,18 +24,18 @@ import fcntl
 import struct
 import struct
 import argparse
 import argparse
 if sys.version_info.major == 2:
 if sys.version_info.major == 2:
-	import commands as subprocess
+    import commands as subprocess
 elif sys.version_info.major == 3:
 elif sys.version_info.major == 3:
-	import subprocess
+    import subprocess
 
 
 def _to_str(inp):
 def _to_str(inp):
-	if sys.version_info.major == 2:
-		return inp
-	else:
-		return "".join( chr(x) for x in inp)
-	
+    if sys.version_info.major == 2:
+        return inp
+    else:
+        return "".join( chr(x) for x in inp)
+    
 
 
-VERSION = "0.90"
+VERSION = "1.00"
 
 
 PROC_TCP4 = "/proc/net/tcp"
 PROC_TCP4 = "/proc/net/tcp"
 PROC_UDP4 = "/proc/net/udp"
 PROC_UDP4 = "/proc/net/udp"
@@ -61,11 +61,11 @@ payloadH = False
 payloadHl = False
 payloadHl = False
 fillter = ""
 fillter = ""
 
 
-def get_conn_info(proto,hosts,ports):
+def get_conn_info(proto,hosts,ports,ipvers):
     ''' returns: pid, exe, uid '''
     ''' returns: pid, exe, uid '''
     uid = 0
     uid = 0
     
     
-    line_array = _proc4load(proto,hosts,ports)
+    line_array = _proc4load(proto,hosts,ports,ipvers)
     
     
     if line_array == 0:
     if line_array == 0:
         return ['?','?','?']
         return ['?','?','?']
@@ -96,25 +96,25 @@ def get_conn_info(proto,hosts,ports):
     return [pid, exe, uid]
     return [pid, exe, uid]
 
 
     
     
-def _proc4load(proto,hosts,ports):
+def _proc4load(proto,hosts,ports,ipvers):
     ''' Read the table of tcp/udp connections
     ''' Read the table of tcp/udp connections
     tcp/udp: "sl,  local_address, rem_address, st, tx_queue rx_queue, tr tm->when, retrnsmt,   uid , timeout, inode ,..."
     tcp/udp: "sl,  local_address, rem_address, st, tx_queue rx_queue, tr tm->when, retrnsmt,   uid , timeout, inode ,..."
     ---- TCP states from https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/include/net/tcp_states.h?id=HEAD
     ---- TCP states from https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/include/net/tcp_states.h?id=HEAD
     enum {
     enum {
-	TCP_ESTABLISHED = 1,
-	TCP_SYN_SENT,
-	TCP_SYN_RECV,
-	TCP_FIN_WAIT1,
-	TCP_FIN_WAIT2,
-	TCP_TIME_WAIT,
-	TCP_CLOSE,
-	TCP_CLOSE_WAIT,
-	TCP_LAST_ACK,
-	TCP_LISTEN,
-	TCP_CLOSING,	/* Now a valid state */
-	TCP_NEW_SYN_RECV,
-
-	TCP_MAX_STATES	/* Leave at the end! */
+    TCP_ESTABLISHED = 1,
+    TCP_SYN_SENT,
+    TCP_SYN_RECV,
+    TCP_FIN_WAIT1,
+    TCP_FIN_WAIT2,
+    TCP_TIME_WAIT,
+    TCP_CLOSE,
+    TCP_CLOSE_WAIT,
+    TCP_LAST_ACK,
+    TCP_LISTEN,
+    TCP_CLOSING,    /* Now a valid state */
+    TCP_NEW_SYN_RECV,
+
+    TCP_MAX_STATES  /* Leave at the end! */
     };
     };
     ----------
     ----------
     '''
     '''
@@ -124,7 +124,10 @@ def _proc4load(proto,hosts,ports):
     
     
     if proto == IPPROTO_UDP:
     if proto == IPPROTO_UDP:
         try:
         try:
-            with open(PROC_UDP4,'r') as f:
+            procv = PROC_UDP4
+            if ipvers == 6:
+                procv = PROC_UDP6
+            with open(procv,'r') as f:               
                 next(f)
                 next(f)
                 for line in f:
                 for line in f:
                     line_arrayu = _remove_empty(line.split(' '))
                     line_arrayu = _remove_empty(line.split(' '))
@@ -140,7 +143,10 @@ def _proc4load(proto,hosts,ports):
             return 0
             return 0
     elif proto == IPPROTO_TCP:
     elif proto == IPPROTO_TCP:
         try:
         try:
-            with open(PROC_TCP4,'r') as f:
+            procv = PROC_TCP4
+            if ipvers == 6:
+                procv = PROC_TCP6
+            with open(procv,'r') as f:
                 next(f)
                 next(f)
                 for line in f:
                 for line in f:
                     line_arrayt = _remove_empty(line.split(' '))
                     line_arrayt = _remove_empty(line.split(' '))
@@ -159,7 +165,10 @@ def _proc4load(proto,hosts,ports):
     
     
     elif proto == IPPROTO_ICMP:
     elif proto == IPPROTO_ICMP:
         try:
         try:
-            with open(PROC_ICMP4,'r') as f:
+            procv = PROC_ICMP4
+            if ipvers == 6:
+                procv = PROC_ICMP6
+            with open(procv,'r') as f:
                 next(f)
                 next(f)
                 for line in f:
                 for line in f:
                     line_arrayi = _remove_empty(line.split(' '))
                     line_arrayi = _remove_empty(line.split(' '))
@@ -191,12 +200,36 @@ def _ip(s):
     return '.'.join(ip)
     return '.'.join(ip)
 
 
 def _ip6(s):
 def _ip6(s):
-    ip = [s[6:8],s[4:6],s[2:4],s[0:2],s[12:14],s[14:16],s[10:12],s[8:10],s[22:24],s[20:22],s[18:20],s[16:18],s[30:32],s[28:30],s[26:28],s[24:26]]
+    ip = [s[6:8],s[4:6],s[2:4],s[0:2],s[14:16],s[12:14],s[10:12],s[8:10],s[22:24],s[20:22],s[18:20],s[16:18],s[30:32],s[28:30],s[26:28],s[24:26]]
     return ':'.join(ip)
     return ':'.join(ip)
 
 
 def _ip_hexrev(ip):
 def _ip_hexrev(ip):
     return ''.join([hex(int(x)+256)[3:] for x in ip.split('.')][::-1]).upper()
     return ''.join([hex(int(x)+256)[3:] for x in ip.split('.')][::-1]).upper()
 
 
+# IPv6 /proc/net/tcp6 format from expanded ip-address
+def _to_v6_proc(s):
+	s = s.replace(":", "")
+	ip = [s[6:8],s[4:6],s[2:4],s[0:2],s[14:16],s[12:14],s[10:12],s[8:10],s[22:24],s[20:22],s[18:20],s[16:18],s[30:32],s[28:30],s[26:28],s[24:26]]
+	return ''.join(ip).upper()
+
+def expand_v6(ip):
+    ipa = ip.split(':')     # liste
+    
+    if '' in ipa:
+        if ipa.count('') > 1:   # korr ::1 or :::
+            for i in range(ipa.count('')-1):
+                ipa.remove('')
+                
+        miss = 8 - len(ipa) +1
+        for i in range(miss):
+            ipa.insert(ipa.index('')+i, '0000')        
+        ipa.remove('')
+        
+        return ':'.join(["%04x" % x for x in [int(x, 16) for x in ipa]])
+    else:
+        return ':'.join(["%04x" % x for x in [int(x, 16) for x in ipa]])
+    
+
 def _remove_empty(array):
 def _remove_empty(array):
     return [x for x in array if x != '']
     return [x for x in array if x != '']
 
 
@@ -263,7 +296,10 @@ def doPackets(packet):
         c_hash = conn_addr+'=:='+str(conn_port)
         c_hash = conn_addr+'=:='+str(conn_port)
         if not any(x[0] == c_hash for x in conn_cache):
         if not any(x[0] == c_hash for x in conn_cache):
             # get the connection info from packet
             # get the connection info from packet
-            spid,sexe,suid = get_conn_info(packet[0][1].proto, conn_addr, conn_port)
+            if packet[0][1].version == 4:
+            	spid,sexe,suid = get_conn_info(packet[0][1].proto, conn_addr, conn_port, packet[0][1].version)
+            elif packet[0][1].version == 6:
+            	spid,sexe,suid = get_conn_info(packet[0][1].nh, conn_addr, conn_port, packet[0][1].version)
             if re.match("^[0-9]+$", spid):
             if re.match("^[0-9]+$", spid):
                 program = sexe
                 program = sexe
                 pid = spid
                 pid = spid
@@ -354,15 +390,20 @@ def doPackets(packet):
                 o_sport = str(packet[0][2].dport)
                 o_sport = str(packet[0][2].dport)
         flags = "["+packet[0].sprintf('%ICMP.type%') + "/" + packet[0].sprintf('%ICMP.code%')+"]"
         flags = "["+packet[0].sprintf('%ICMP.type%') + "/" + packet[0].sprintf('%ICMP.code%')+"]"
     else:
     else:
-    	layerukn = packet[0][IP].getlayer(1)
-    	if layerukn is None:
-    		o_proto = "UNKNOWN"
-    	else:
-    		#print("Layer:", xxl1.name)
-    		o_proto = layerukn.name
-    	
-        #o_proto = "UNKNOWN"
+        layerukn = packet[0][1].getlayer(1)
+        if layerukn is None:
+            o_proto = "UNKNOWN"
+        else:
+            #print("Layer:", xxl1.name)
+            o_proto = layerukn.name
         
         
+        #o_proto = "UNKNOWN"
+    
+    if packet[0][1].version == 4:
+    	packlen = str(packet[0][1].len)
+    if packet[0][1].version == 6:
+    	packlen = str(packet[0][1].plen)
+    	
     if o_dir == 1:
     if o_dir == 1:
         if numeric == False:
         if numeric == False:
             #if res_cache.has_key(packet[0][1].dst):
             #if res_cache.has_key(packet[0][1].dst):
@@ -374,7 +415,7 @@ def doPackets(packet):
             rem_name = packet[0][1].dst
             rem_name = packet[0][1].dst
 
 
         #return "\033[1m "+str(packet[0].time)+" "+str(program)+"\033[0m" +"/"+ str(pid) + " - " + o_proto + ": " + packet[0][1].src + ":" + o_sport + "\033[1m\033[31m  ->>>  \033[0m" + rem_name + ":" + o_dport + " " + flags + " Len:" + str(packet[0][1].len) + " : " + o_payload          
         #return "\033[1m "+str(packet[0].time)+" "+str(program)+"\033[0m" +"/"+ str(pid) + " - " + o_proto + ": " + packet[0][1].src + ":" + o_sport + "\033[1m\033[31m  ->>>  \033[0m" + rem_name + ":" + o_dport + " " + flags + " Len:" + str(packet[0][1].len) + " : " + o_payload          
-        return "\033[1m"+str(program)+"\033[0m" +"/"+ str(pid) + " - " + o_proto + ": " + packet[0][1].src + ":" + o_sport + "\033[1m\033[31m  ->>>  \033[0m" + rem_name + ":" + o_dport + " " + flags + " Len:" + str(packet[0][1].len) + " : " + o_payload
+        return "\033[1m"+str(program)+"\033[0m" +"/"+ str(pid) + " - " + o_proto + ": " + packet[0][1].src + ":" + o_sport + "\033[1m\033[31m  ->>>  \033[0m" + rem_name + ":" + o_dport + " " + flags + " Len:" + packlen + " : " + o_payload
     else:
     else:
         if numeric == False:
         if numeric == False:
             #if res_cache.has_key(packet[0][1].src):
             #if res_cache.has_key(packet[0][1].src):
@@ -385,7 +426,7 @@ def doPackets(packet):
         else:
         else:
             rem_name = packet[0][1].src
             rem_name = packet[0][1].src
             
             
-        return "\033[1m"+str(program)+"\033[0m" +"/"+ str(pid) + " - " + o_proto + ": " + packet[0][1].dst + ":" + o_dport + "\033[1m\033[36m  <<<-  \033[0m" + rem_name + ":" + o_sport + " " + flags + " Len:" + str(packet[0][1].len) + " : " + o_payload
+        return "\033[1m"+str(program)+"\033[0m" +"/"+ str(pid) + " - " + o_proto + ": " + packet[0][1].dst + ":" + o_dport + "\033[1m\033[36m  <<<-  \033[0m" + rem_name + ":" + o_sport + " " + flags + " Len:" + packlen + " : " + o_payload
 
 
 
 
 
 
@@ -402,12 +443,16 @@ if not check_root():
 ifaces = subprocess.getoutput("ls /sys/class/net")
 ifaces = subprocess.getoutput("ls /sys/class/net")
 iface_list = ifaces.split('\n')
 iface_list = ifaces.split('\n')
 
 
+rfilter = "ip or ip6"
 print("")
 print("")
 # commandline params
 # commandline params
-parser = argparse.ArgumentParser(description='sisniff V'+VERSION)
+parser = argparse.ArgumentParser(description='sisniff V'+VERSION+"\n2017-2019 by sigi <https://wiki.zweiernet.ch/wiki/sisniff>",
+                                   formatter_class=argparse.RawDescriptionHelpFormatter)
 parser.add_argument('-i', help="Interface (required)", choices=iface_list, required=True)
 parser.add_argument('-i', help="Interface (required)", choices=iface_list, required=True)
 parser.add_argument('-n', help="Do not resolve IP-Addresses", action="store_true")
 parser.add_argument('-n', help="Do not resolve IP-Addresses", action="store_true")
 parser.add_argument('-p', help='Filter by program name ([not-] negates)', type=str, metavar='program|not-program')
 parser.add_argument('-p', help='Filter by program name ([not-] negates)', type=str, metavar='program|not-program')
+parser.add_argument('-4', dest='v4', help="Only IPv4", action="store_true")
+parser.add_argument('-6', dest='v6', help="Only IPv6", action="store_true")
 parser.add_argument('-pH', help="Show HTTP Payload", action="store_true")
 parser.add_argument('-pH', help="Show HTTP Payload", action="store_true")
 parser.add_argument('-pHl', help="Show HTTP Payload, long output", action="store_true")
 parser.add_argument('-pHl', help="Show HTTP Payload, long output", action="store_true")
 parser.add_argument('filter', nargs='?', help="Filter (BPF syntax) on top of IP (in dbl-quotes \"...\")", type=str)
 parser.add_argument('filter', nargs='?', help="Filter (BPF syntax) on top of IP (in dbl-quotes \"...\")", type=str)
@@ -415,6 +460,10 @@ args = parser.parse_args()
 iface = args.i
 iface = args.i
 if args.n:
 if args.n:
     numeric = True
     numeric = True
+if args.v4:
+	rfilter = "ip"
+if args.v6:
+	rfilter = "ip6"
 if args.pH:
 if args.pH:
     payloadH = True
     payloadH = True
 if args.pHl:
 if args.pHl:
@@ -422,16 +471,28 @@ if args.pHl:
     payloadHl = True
     payloadHl = True
 if args.filter:
 if args.filter:
     fillter = " and (" + args.filter + ")"
     fillter = " and (" + args.filter + ")"
-    print("> Applying Filter: \"ip" + fillter + "\"") 
+    print("> Applying Filter: \"" + rfilter + fillter + "\"") 
 if args.p:
 if args.p:
     filter_prog = args.p
     filter_prog = args.p
     
     
 
 
-# local addresses 
-MYADDRS = _remove_empty([os.popen('ip addr show '+iface).read().split("inet ")[1].split("/")[0]])
-MYADDRS.append('0.0.0.0')
-MYADDRS.append('127.0.0.1')
-xMYADDRS = [_ip_hexrev(x) for x in MYADDRS]
+# local addresses
+if args.v6:
+	MYADDRS=[]
+	xMYADDRS = []
+else:
+	MYADDRS = _remove_empty(os.popen("ip addr show " + iface + " | egrep 'inet ' | awk '{{print $2}}' | awk -F'/' '{{print $1}}'").read().split())
+	MYADDRS.append('0.0.0.0')
+	MYADDRS.append('127.0.0.1')
+	xMYADDRS = [_ip_hexrev(x) for x in MYADDRS]  
+if args.v4:
+	MYADDRS6=[]
+else:
+	MYADDRS6 = _remove_empty(os.popen("ip addr show " + iface + " | egrep 'inet6' | grep -vi fe80 | awk '{{print $2}}' | awk -F'/' '{{print $1}}'").read().split())
+	MYADDRS6.append(':::')
+	MYADDRS6.append('::1')
+	MYADDRS = MYADDRS + MYADDRS6
+xMYADDRS = xMYADDRS + [_to_v6_proc(expand_v6(x)) for x in MYADDRS6]
 print("> My IP-Addresses: " + str(MYADDRS))
 print("> My IP-Addresses: " + str(MYADDRS))
 
 
 # confirmed connections cache (ringboffer)
 # confirmed connections cache (ringboffer)
@@ -442,12 +503,12 @@ cc_maxlen = 20
 res_cache = {}
 res_cache = {}
 n_try = 3
 n_try = 3
 print("")
 print("")
-print("Prog/PID mavericks: ?/? = No entry in /proc/net/xxx; -/- = No PID for Inode found; ./. = Inode=0;")
+print("Prog/PID mavericks: \033[1m?/?\033[0m = No entry in /proc/net/xxx; \033[1m-/-\033[0m = No PID for Inode found; \033[1m./.\033[0m = Inode=0;")
 print("")
 print("")
 print("Program/PID: Local addr:port <<->> Remote addr:port [Flags] Len:length : [Payload]")
 print("Program/PID: Local addr:port <<->> Remote addr:port [Flags] Len:length : [Payload]")
 print("-------------------------------------------------------------------------------")
 print("-------------------------------------------------------------------------------")
 
 
 # sniff, filtering for IP traffic
 # sniff, filtering for IP traffic
-sniff(filter="ip"+fillter,iface=iface,prn=doPackets, store=0)
+sniff(filter=rfilter+fillter,iface=iface,prn=doPackets, store=0)
 
 
 ## -- oond denn isch schloss
 ## -- oond denn isch schloss