Browse Source

v1.6.1 New param -D: show DNS payload; Corr _get_pid_of_inode()

sigi 3 days ago
parent
commit
d2d2778b4e
2 changed files with 118 additions and 43 deletions
  1. 11 8
      README.md
  2. 107 35
      sisniff

+ 11 - 8
README.md

@@ -10,8 +10,8 @@ It supports TCP, UDP and ICMP packets, both on IPv4 and IPv6<br>
 All BPF-Filter on top of IP which can be used by tcpdump are also supported.<br>
 <p>
 
-For HTTP connections, there is an argument (<code>-H resp. -Hl</code>) to show short or long payload.<br>
-
+For HTTP connections, there is an argument (<code>-H resp. -Hl</code>) to show short or long payload.<br>   
+For DOMAIN connections, there is an argument (<code>-D</code>) to show DNS payload.<br>
  
 Under some cirumstances the program/PID cannot be evaluated. This mavericks would be reported as follow:
 <pre>
@@ -35,15 +35,17 @@ This program needs Python 3.x or Python 2.x.
 Homepage (german): https://wiki.zweiernet.ch/wiki/sisniff
 
 Direct Download: `wget https://git.zweiernet.ch/sigi/sisniff/raw/master/sisniff` 
- 
+
+Recent Version is 1.6.1
+
  
 <pre>
 --------------------
 # sisniff -h
-usage: sisniff [-h] -i {eth0,lo,wlan0} [-n] [-P] [-p program|not-program] [-4] [-6] [-H] [-Hl] [filter]
+usage: sisniff [-h] -i {eth0,lo,wlan0} [-n] [-P] [-p program|not-program] [-4] [-6] [-H] [-Hl] [-D] [filter]
 
-sisniff V1.5
-2017-2023 by sigi <https://wiki.zweiernet.ch/wiki/sisniff>
+sisniff V1.6
+2017-2025 by sigi <https://wiki.zweiernet.ch/wiki/sisniff>
 
 positional arguments:
   filter                Filter (BPF syntax) on top of IP (in dbl-quotes "...")
@@ -57,8 +59,9 @@ optional arguments:
                         Filter by program name (accepts * for matching) ([not-] negates)
   -4                    Only IPv4
   -6                    Only IPv6
-  -H                   Show HTTP Payload
-  -Hl                  Show HTTP Payload, long output
+  -H                    Show HTTP Payload
+  -Hl                   Show HTTP Payload, long output
+  -D                    Show DNS Payload
 --------------------
 </pre>
 

+ 107 - 35
sisniff

@@ -1,6 +1,6 @@
 #!/usr/bin/env python3
 
-# (c) 2017-2022 by Siegrist(SystemLoesungen) <PSS@ZweierNet.ch>
+# (c) 2017-2025 by Siegrist(SystemLoesungen) <PSS@ZweierNet.ch>
 #
 # All Rights reserved.
 # This program is free software; you can redistribute it and/or
@@ -23,6 +23,14 @@ import string
 import fcntl
 import struct
 import argparse
+if sys.version_info.major == 3:
+    try:
+        #from dnslib.dns import DNSRecord,DNSHeader,DNSQuestion,DNSError,QTYPE,EDNS0
+        from dnslib import DNSRecord
+        have_DNSLIB = True
+    except:
+        have_DNSLIB = False
+        #print("Missing 'python3-dnslib': DNS-Payload not available.")
 #if sys.version_info.major == 2:
 #    import commands as subprocess
 #elif sys.version_info.major == 3:
@@ -35,7 +43,7 @@ def _to_str(inp):
         return "".join( chr(x) for x in inp)
     
 
-VERSION = "1.5"
+VERSION = "1.6.1"
 
 PROC_TCP4 = "/proc/net/tcp"
 PROC_UDP4 = "/proc/net/udp"
@@ -56,9 +64,22 @@ IPPROTO_UDP = 17
 
 nostate = set(['04','05','06''07','08','09','0C','0D'])
 tcp_payload_hdrs = ['GET|POST|HTTP|HEAD|PUT|PATCH|DELETE|TRACE|OPTIONS|CONNECT']
+RCODE={0:'', 1:'FORMERR ', 2:'SERVFAIL ', 3:'NXDomain* ',4:'NOTIMP ', 5:'REFUSED ', 6:'YXDOMAIN ', 7:'YXRRSET ',8:'NXRRSET ', 9:'NOTAUTH ', 10:'NOTZONE '}
+DNS_QTYPE={1:'A', 2:'NS', 5:'CNAME', 6:'SOA', 10:'NULL', 12:'PTR', 13:'HINFO',
+                    15:'MX', 16:'TXT', 17:'RP', 18:'AFSDB', 24:'SIG', 25:'KEY',
+                    28:'AAAA', 29:'LOC', 33:'SRV', 35:'NAPTR', 36:'KX',
+                    37:'CERT', 38:'A6', 39:'DNAME', 41:'OPT', 42:'APL',
+                    43:'DS', 44:'SSHFP', 45:'IPSECKEY', 46:'RRSIG', 47:'NSEC',
+                    48:'DNSKEY', 49:'DHCID', 50:'NSEC3', 51:'NSEC3PARAM',
+                    52:'TLSA', 53:'HIP', 55:'HIP', 59:'CDS', 60:'CDNSKEY',
+                    61:'OPENPGPKEY', 62:'CSYNC', 63:'ZONEMD', 64:'SVCB',
+                    65:'HTTPS', 99:'SPF', 108:'EUI48', 109:'EUI64', 249:'TKEY',
+                    250:'TSIG', 251:'IXFR', 252:'AXFR', 255:'ANY', 256:'URI',
+                    257:'CAA', 32768:'TA', 32769:'DLV'}
 numeric = False
 payloadH = False
 payloadHl = False
+payloadDNS = False
 fillter = ""
 
 def get_conn_info(proto,hosts,ports,ipvers):
@@ -208,9 +229,9 @@ def _ip_hexrev(ip):
 
 # 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()
+    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
@@ -234,10 +255,14 @@ def _remove_empty(array):
     return [x for x in array if x != '']
 
 def _get_pid_of_inode(inode):
-    s_term = r'^socket\:\['+ inode +r'\]$'
+    s_term = '['+ inode +']'
     for item in glob.iglob('/proc/[0-9]*/fd/[0-9]*'):
         try:
-            if re.match(s_term,os.readlink(item)):
+            searchlnk = os.readlink(item)
+        except:
+            continue
+        try:
+            if s_term in searchlnk:
                 return item.split('/')[2]
         except:
             pass
@@ -297,9 +322,9 @@ def doPackets(packet):
         if not any(x[0] == c_hash for x in conn_cache):
             # get the connection info from packet
             if packet[0][1].version == 4:
-            	spid,sexe,suid = get_conn_info(packet[0][1].proto, conn_addr, conn_port, packet[0][1].version)
+                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)
+                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
@@ -333,18 +358,18 @@ def doPackets(packet):
             if filter_progn.startswith('*') and filter_progn.endswith('*') and re.search(filter_progn[1:-1], program):
                 return
             elif filter_progn.startswith('*') and not filter_progn.endswith('*') and re.search(filter_progn[1:]+'$', program):
-        	    return
+                return
             elif not filter_progn.startswith('*') and filter_progn.endswith('*') and re.match('^'+filter_progn[:-1], program):
-        	    return
+                return
             elif not filter_progn.startswith('*') and not filter_progn.endswith('*') and re.match('^'+filter_progn+'$', program):
                 return
         else:
             if filter_prog.startswith('*') and filter_prog.endswith('*') and not re.search(filter_prog[1:-1], program):
                 return
             elif filter_prog.startswith('*') and not filter_prog.endswith('*') and not re.search(filter_prog[1:]+'$', program):
-        	    return
+                return
             elif not filter_prog.startswith('*') and filter_prog.endswith('*') and not re.match('^'+filter_prog[:-1], program):
-        	    return
+                return
             elif not filter_prog.startswith('*') and not filter_prog.endswith('*') and not re.match('^'+filter_prog+'$', program):
                 return
         
@@ -363,6 +388,26 @@ def doPackets(packet):
             o_sport = str(packet[0][2].sport)
         flags = ""
         #o_payload = _to_str(packet[0].sprintf('%10s,UDP.payload%'))
+        
+        if payloadDNS == True:
+            if DNS in packet:
+                ppd = packet[0][DNS]
+                p_upld = DNSRecord.parse(bytes(packet[0][DNS]))
+                if ppd.qdcount > 0 and ppd.qr == 0:
+                    o_payload = "DNS -> (" + str(ppd.id) + "): " + RCODE[ppd.rcode] 
+                    o_payload += DNS_QTYPE[p_upld.q.qtype] + "? " + str(p_upld.q.qname)
+                elif ppd.qdcount > 0 and ppd.qr == 1:
+                    o_payload = "DNS <- (" + str(ppd.id) + "): " + RCODE[ppd.rcode]
+                    rr_cnt = 0
+                    for rr in p_upld.rr:
+                        o_payload += f'{rr.rname} {DNS_QTYPE[rr.rtype]} {rr.rdata} | '
+                        rr_cnt += 1
+                    if rr_cnt > 0:
+                        o_payload = o_payload[:-3]
+                else:
+                    o_payload = "DNS: " + RCODE[ppd.rcode] + str(p_upld)
+                
+                
     elif packet.haslayer(TCP):
         o_proto = "TCP"
         try:
@@ -387,6 +432,26 @@ def doPackets(packet):
                         request_line, gaga = tpld.split('\r\n', 1)
                         o_payload = str(request_line)
                     #o_payload = tpld[0:20]
+        if payloadDNS == True:
+            if DNS in packet:
+                ppd = packet[0][DNS]
+                ppd2 = bytes(packet[0][DNS])[2:]    # !!$??@! remove length field (2Byte) from DNS-Record for use with DNSRecord.parse() ??!!!!
+                p_tpld = DNSRecord.parse(bytes(ppd2))
+                if ppd.qdcount > 0 and ppd.qr == 0:
+                    o_payload = "DNS -> (" + str(ppd.id) + "): " + RCODE[ppd.rcode] 
+                    o_payload += DNS_QTYPE[p_tpld.q.qtype] + "? " + str(p_tpld.q.qname)
+                elif ppd.qdcount > 0 and ppd.qr == 1:
+                    o_payload = "DNS <- (" + str(ppd.id) + "): " + RCODE[ppd.rcode]
+                    rr_cnt = 0
+                    for rr in p_tpld.rr:
+                        o_payload += f'{rr.rname} {DNS_QTYPE[rr.rtype]} {rr.rdata} | '
+                        rr_cnt += 1
+                    if rr_cnt > 0:
+                        o_payload = o_payload[:-3]
+                else:
+                    o_payload = "DNS??: " + RCODE[ppd.rcode] + str(p_tpld)
+                
+        
     elif packet.haslayer(ICMP):
         o_proto = "ICMP"
         if conn_port == 99999:
@@ -413,10 +478,11 @@ def doPackets(packet):
         #o_proto = "UNKNOWN"
     
     if packet[0][1].version == 4:
-    	packlen = str(packet[0][1].len)
+        packlen = str(packet[0][1].len)
     if packet[0][1].version == 6:
-    	packlen = str(packet[0][1].plen)
-    	
+        packlen = str(packet[0][1].plen)
+    
+    trenner = " : " if len(o_payload) > 0 else " "    
     if o_dir == 1:
         if numeric == False:
             #if res_cache.has_key(packet[0][1].dst):
@@ -428,7 +494,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:" + packlen + " : " + 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 + trenner + o_payload
     else:
         if numeric == False:
             #if res_cache.has_key(packet[0][1].src):
@@ -439,7 +505,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:" + packlen + " : " + 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 + trenner + o_payload
 
 
 
@@ -461,7 +527,7 @@ iface = conf.route.route("0.0.0.0")[0]
 rfilter = "ip or ip6"
 print("")
 # commandline params
-parser = argparse.ArgumentParser(description='sisniff V'+VERSION+"\n2017-2023 by sigi <https://wiki.zweiernet.ch/wiki/sisniff>",
+parser = argparse.ArgumentParser(description='sisniff V'+VERSION+"\n2017-2025 by sigi <https://wiki.zweiernet.ch/wiki/sisniff>",
                                    formatter_class=argparse.RawDescriptionHelpFormatter)
 parser.add_argument('-i', help="Interface", choices=iface_list)
 parser.add_argument('-n', help="Do not resolve IP-Addresses", action="store_true")
@@ -471,21 +537,27 @@ 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('-H', help="Show HTTP Payload", action="store_true")
 parser.add_argument('-Hl', help="Show HTTP Payload, long output", action="store_true")
+parser.add_argument('-D', help="Show DNS Payload", action="store_true")
 parser.add_argument('filter', nargs='?', help="Filter (BPF syntax) on top of IP (in dbl-quotes \"...\")", type=str)
 args = parser.parse_args()
 if args.i:
-	iface = args.i
+    iface = args.i
 if args.n:
     numeric = True
 if args.v4:
-	rfilter = "ip"
+    rfilter = "ip"
 if args.v6:
-	rfilter = "ip6"
+    rfilter = "ip6"
 if args.H:
     payloadH = True
 if args.Hl:
     payloadH = True
     payloadHl = True
+if args.D:
+    if have_DNSLIB:
+        payloadDNS = True
+    else:
+        print("Missing 'python3-dnslib': DNS-Payload not available.")
 if args.filter:
     fillter = " and (" + args.filter + ")"
     print("\033[1m> Applying Filter: \"" + rfilter + fillter + "\"\033[0m") 
@@ -493,26 +565,26 @@ if args.p:
     filter_prog = args.p
 no_promisc = ""
 if args.P:
-	conf.sniff_promisc = conf.promisc = 0
-	no_promisc = " (disabled promiscuous mode)"
+    conf.sniff_promisc = conf.promisc = 0
+    no_promisc = " (disabled promiscuous mode)"
 
 
 # local addresses
 if args.v6:
-	MYADDRS=[]
-	xMYADDRS = []
+    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]  
+    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=[]
+    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
+    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("> Listening on: " + iface + no_promisc)