|
@@ -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)
|