#!/usr/bin/env python3 # (c) 2017-2022 by Siegrist(SystemLoesungen) # # All Rights reserved. # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License as # published by the Free Software Foundation. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # from scapy.all import * import pwd import os import re import glob import sys import string import fcntl import struct import argparse #if sys.version_info.major == 2: # import commands as subprocess #elif sys.version_info.major == 3: # import subprocess def _to_str(inp): if sys.version_info.major == 2: return inp else: return "".join( chr(x) for x in inp) VERSION = "1.5" PROC_TCP4 = "/proc/net/tcp" PROC_UDP4 = "/proc/net/udp" PROC_ICMP4 = "/proc/net/icmp" PROC_TCP6 = "/proc/net/tcp6" PROC_UDP6 = "/proc/net/udp6" PROC_PACKET = "/proc/net/packet" # Services TSERV = dict((TCP_SERVICES[k], k) for k in TCP_SERVICES.keys()) USERV = dict((UDP_SERVICES[k], k) for k in UDP_SERVICES.keys()) # IP Protocol Numbers (dec) IPPROTO_ICMP = 1 IPPROTO_TCP = 6 IPROTOP_IGP = 9 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'] numeric = False payloadH = False payloadHl = False fillter = "" def get_conn_info(proto,hosts,ports,ipvers): ''' returns: pid, exe, uid ''' uid = 0 line_array = _proc4load(proto,hosts,ports,ipvers) if line_array == 0: return ['?','?','?'] ''' try: uid = pwd.getpwuid(int(line_array[7]))[0] # Get user from UID. except: uid = line_array[7] ''' inode = str(line_array[9]) if inode == "0": return ['.','.','.'] pid = _get_pid_of_inode(inode) # try get a pid if pid == "NoPid": #print(">>>>>>>>>>>NoPID:" + str(hosts) +" "+ str(ports) + "//" + str(line_array)) return ['-', '-', uid] try: # try read the process name. exe = os.readlink('/proc/'+pid+'/exe').split('/')[-1] except: exe = None #print(str(lhost) +" "+ str(lport) +" "+ inode +" "+ pid) return [pid, exe, uid] 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! */ }; ---------- ''' #xhosts = _ip_hexrev(hosts) xports = _dec2hex(ports) if proto == IPPROTO_UDP: try: 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(' ')) l_xhost,l_xport = line_arrayu[1].split(':') if l_xhost not in xMYADDRS: continue if l_xport == xports: return line_arrayu return 0 except: print("open proc_udp4 error") return 0 elif proto == IPPROTO_TCP: try: 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(' ')) if line_arrayt[3] in nostate: # not some TCP state continue l_xhost,l_xport = line_arrayt[1].split(':') if l_xhost not in xMYADDRS: continue if l_xport == xports: return line_arrayt return 0 except: print("open proc_tcp error") return 0 elif proto == IPPROTO_ICMP: try: 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(' ')) l_xhost,l_xport = line_arrayi[1].split(':') if l_xhost not in xMYADDRS: continue if l_xport == xports: return line_arrayi return 0 except: print("open proc_icmp4 error") return 0 return 0 def _convert_ipv4_port(array): host,port = array.split(':') return _ip(host),_hex2dec(port) def _hex2dec(s): return str(int(s,16)) def _dec2hex(p): return hex(int(p)).split('x')[-1].upper() def _ip(s): ip = [(_hex2dec(s[6:8])),(_hex2dec(s[4:6])),(_hex2dec(s[2:4])),(_hex2dec(s[0:2]))] return '.'.join(ip) def _ip6(s): 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 != ''] def _get_pid_of_inode(inode): s_term = r'^socket\:\['+ inode +r'\]$' for item in glob.iglob('/proc/[0-9]*/fd/[0-9]*'): try: if re.match(s_term,os.readlink(item)): return item.split('/')[2] except: pass return "NoPid" def _resolve_ip(host): """ resolve ip und update dictionary res_cache {'ip': 'name'}. If resolution for a ip failed, 'name' is n_try ... 0. """ try: hname = socket.gethostbyaddr(host)[0] res_cache[host] = str(hname) return str(hname) except: res_cache[host] = str(host) return str(host) def check_root(): if os.getuid() == 0: return True else: return False ## Define our Custom Action function def doPackets(packet): program = "-" pid = "-" uid = "-" o_proto = "" o_dport = "none" o_sport = "none" flags = "" # only local addresses if packet[0][1].src in MYADDRS: conn_addr = packet[0][1].src if packet.haslayer(TCP) or packet.haslayer(UDP) or packet.haslayer(ICMP): try: conn_port = packet[0][2].sport except: conn_port = 99999 o_dir = 1 else: conn_addr = packet[0][1].dst if packet.haslayer(TCP) or packet.haslayer(UDP) or packet.haslayer(ICMP): try: conn_port = packet[0][2].dport except: conn_port = 99999 o_dir = 0 if packet.haslayer(TCP) or packet.haslayer(UDP) or packet.haslayer(ICMP): # grrr, no info in /proc/net/icmp so far. or packet.haslayer(ICMP): # logemol casch 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 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 uid = suid # update cache if len(conn_cache) >= cc_maxlen: conn_cache.pop(0) conn_cache.append([c_hash,program,pid]) else: program = sexe pid = spid uid = suid else: # me honds fom casch indx = [x[0] for x in conn_cache].index(c_hash) program = conn_cache[indx][1] pid = conn_cache[indx][2] uid = 0 # cache aktualisieren renew = conn_cache.pop(indx) conn_cache.append(renew) try: filter_prog except: pass else: if filter_prog.startswith('not-'): filter_progn = filter_prog[4:] 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 elif not filter_progn.startswith('*') and filter_progn.endswith('*') and re.match('^'+filter_progn[:-1], program): 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 elif not filter_prog.startswith('*') and filter_prog.endswith('*') and not re.match('^'+filter_prog[:-1], program): return elif not filter_prog.startswith('*') and not filter_prog.endswith('*') and not re.match('^'+filter_prog+'$', program): return o_payload = "" if packet.haslayer(UDP): o_proto = "UDP" try: o_dport = "\033[1m"+USERV[packet[0][2].dport]+"\033[0m" except: o_dport = str(packet[0][2].dport) try: o_sport = "\033[1m"+USERV[packet[0][2].sport]+"\033[0m" except: o_sport = str(packet[0][2].sport) flags = "" #o_payload = _to_str(packet[0].sprintf('%10s,UDP.payload%')) elif packet.haslayer(TCP): o_proto = "TCP" try: o_dport = "\033[1m"+TSERV[packet[0][2].dport]+"\033[0m" except: o_dport = str(packet[0][2].dport) try: o_sport = "\033[1m"+TSERV[packet[0][2].sport]+"\033[0m" except: o_sport = str(packet[0][2].sport) flags = packet[0].sprintf('%3s,TCP.flags%') if payloadH == True: if packet.haslayer(Raw): #tpld = packet[0].sprintf('%TCP.payload%') tpld = _to_str(packet[0][TCP].load) tpldhead = tpld[0:8] #print("tpld:" + tpldhead) if re.match(r'GET|POST|HTTP|HEAD|PUT|PATCH|DELETE|TRACE|OPTIONS|CONNECT.*', tpldhead): if payloadHl == True: o_payload = str(tpld) else: request_line, gaga = tpld.split('\r\n', 1) o_payload = str(request_line) #o_payload = tpld[0:20] elif packet.haslayer(ICMP): o_proto = "ICMP" if conn_port == 99999: o_dport = "-" o_sport = "-" else: try: o_dport = "\033[1m"+USERV[packet[0][2].sport]+"\033[0m" except: o_dport = str(packet[0][2].sport) try: o_sport = "\033[1m"+USERV[packet[0][2].dport]+"\033[0m" except: o_sport = str(packet[0][2].dport) flags = "["+packet[0].sprintf('%ICMP.type%') + "/" + packet[0].sprintf('%ICMP.code%')+"]" else: 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): if packet[0][1].dst in res_cache: rem_name = res_cache[packet[0][1].dst] else: rem_name = _resolve_ip(packet[0][1].dst) else: 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 else: if numeric == False: #if res_cache.has_key(packet[0][1].src): if packet[0][1].src in res_cache: rem_name = res_cache[packet[0][1].src] else: rem_name = _resolve_ip(packet[0][1].src) 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 ## -- Ond denn s'Hooptprogramm # root check if not check_root(): print("This program needs root privileges !\nThats because of reading the /proc filesystem and using libpcap functions.\nSo I give up\n") conf.sniff_promisc=0 conf.sniff_promisc=0 sys.exit() # get the interfaces #ifaces = subprocess.getoutput("ls /sys/class/net") #iface_list = ifaces.split('\n') iface_list = get_if_list() 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 ", 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") parser.add_argument('-P', help="Don't put interface into promiscuous mode", action="store_true") parser.add_argument('-p', help='Filter by program name (accepts * for matching) ([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('-H', help="Show HTTP Payload", action="store_true") parser.add_argument('-Hl', 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) args = parser.parse_args() if args.i: iface = args.i if args.n: numeric = True if args.v4: rfilter = "ip" if args.v6: rfilter = "ip6" if args.H: payloadH = True if args.Hl: payloadH = True payloadHl = True if args.filter: fillter = " and (" + args.filter + ")" print("\033[1m> Applying Filter: \"" + rfilter + fillter + "\"\033[0m") if args.p: filter_prog = args.p no_promisc = "" if args.P: conf.sniff_promisc = conf.promisc = 0 no_promisc = " (disabled promiscuous mode)" # 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("> Listening on: " + iface + no_promisc) # confirmed connections cache (ringboffer) conn_cache = [] cc_maxlen = 20 # resolver cache res_cache = {} n_try = 3 print("") 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 try: sniff(filter=rfilter+fillter,iface=iface,prn=doPackets, store=0) except Exception as e: print("\n \033[1mError: " + str(e) + "\033[0m \n") ## -- oond denn isch schloss