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. 
 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>
 <p>
 
 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:
 <pre>
  "?/?" = No entry in /proc/net/[TCP/UDP/ICMP]
  "-/-" = Found Inode but no PID
  "./." = The Inode found is '0'
 </pre>
-----
+ 
 
 <p><i>
 !! sisniff uses scapy's sniff() function, so scapy package is needed:<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>
 
-<pre><br> 
 
 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
-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:
   filter                Filter (BPF syntax) on top of IP (in dbl-quotes "...")
 
 optional arguments:
   -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
   -p program|not-program
                         Filter by program name ([not-] negates)
+  -4                    Only IPv4
+  -6                    Only IPv6
   -pH                   Show HTTP Payload
   -pHl                  Show HTTP Payload, long output
 --------------------
 </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 argparse
 if sys.version_info.major == 2:
-	import commands as subprocess
+    import commands as subprocess
 elif sys.version_info.major == 3:
-	import subprocess
+    import subprocess
 
 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_UDP4 = "/proc/net/udp"
@@ -61,11 +61,11 @@ payloadH = False
 payloadHl = False
 fillter = ""
 
-def get_conn_info(proto,hosts,ports):
+def get_conn_info(proto,hosts,ports,ipvers):
     ''' returns: pid, exe, uid '''
     uid = 0
     
-    line_array = _proc4load(proto,hosts,ports)
+    line_array = _proc4load(proto,hosts,ports,ipvers)
     
     if line_array == 0:
         return ['?','?','?']
@@ -96,25 +96,25 @@ def get_conn_info(proto,hosts,ports):
     return [pid, exe, uid]
 
     
-def _proc4load(proto,hosts,ports):
+def _proc4load(proto,hosts,ports,ipvers):
     ''' 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 states from https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/include/net/tcp_states.h?id=HEAD
     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:
         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)
                 for line in f:
                     line_arrayu = _remove_empty(line.split(' '))
@@ -140,7 +143,10 @@ def _proc4load(proto,hosts,ports):
             return 0
     elif proto == IPPROTO_TCP:
         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)
                 for line in f:
                     line_arrayt = _remove_empty(line.split(' '))
@@ -159,7 +165,10 @@ def _proc4load(proto,hosts,ports):
     
     elif proto == IPPROTO_ICMP:
         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)
                 for line in f:
                     line_arrayi = _remove_empty(line.split(' '))
@@ -191,12 +200,36 @@ def _ip(s):
     return '.'.join(ip)
 
 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)
 
 def _ip_hexrev(ip):
     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):
     return [x for x in array if x != '']
 
@@ -263,7 +296,10 @@ def doPackets(packet):
         c_hash = conn_addr+'=:='+str(conn_port)
         if not any(x[0] == c_hash for x in conn_cache):
             # 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):
                 program = sexe
                 pid = spid
@@ -354,15 +390,20 @@ def doPackets(packet):
                 o_sport = str(packet[0][2].dport)
         flags = "["+packet[0].sprintf('%ICMP.type%') + "/" + packet[0].sprintf('%ICMP.code%')+"]"
     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 numeric == False:
             #if res_cache.has_key(packet[0][1].dst):
@@ -374,7 +415,7 @@ def doPackets(packet):
             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(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:
         if numeric == False:
             #if res_cache.has_key(packet[0][1].src):
@@ -385,7 +426,7 @@ def doPackets(packet):
         else:
             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")
 iface_list = ifaces.split('\n')
 
+rfilter = "ip or ip6"
 print("")
 # 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('-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('-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('-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)
@@ -415,6 +460,10 @@ args = parser.parse_args()
 iface = args.i
 if args.n:
     numeric = True
+if args.v4:
+	rfilter = "ip"
+if args.v6:
+	rfilter = "ip6"
 if args.pH:
     payloadH = True
 if args.pHl:
@@ -422,16 +471,28 @@ if args.pHl:
     payloadHl = True
 if args.filter:
     fillter = " and (" + args.filter + ")"
-    print("> Applying Filter: \"ip" + fillter + "\"") 
+    print("> Applying Filter: \"" + rfilter + fillter + "\"") 
 if 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))
 
 # confirmed connections cache (ringboffer)
@@ -442,12 +503,12 @@ cc_maxlen = 20
 res_cache = {}
 n_try = 3
 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("Program/PID: Local addr:port <<->> Remote addr:port [Flags] Len:length : [Payload]")
 print("-------------------------------------------------------------------------------")
 
 # 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