Sisniff: Unterschied zwischen den Versionen
Aus Si:Wiki von Siegrist SystemLösungen - Informatik und Rezepte
Sigi (Diskussion | Beiträge) K |
Sigi (Diskussion | Beiträge) K |
||
(14 dazwischenliegende Versionen desselben Benutzers werden nicht angezeigt) | |||
Zeile 1: | Zeile 1: | ||
{{DISPLAYTITLE:sisniff}} | {{DISPLAYTITLE:sisniff}} | ||
− | Ein Netzwerk-Sniffer der für jedes Packet nebst Adresse und Port die lokal verbundene Anwendung und deren PID ermittelt und anzeigt.<br /> | + | Ein Netzwerk-Sniffer der für jedes Packet nebst Adresse und Port die '''lokal verbundene Anwendung und deren PID''' ermittelt und anzeigt.<br /> |
− | Es wird TCP, UDP und ICMP unterstützt.<br /> | + | |
+ | ''sisniff'' versucht, zu jedem Packet der Netzwerkschnittstelle die dazugehörigen Anwendung zu ermitteln.<br /> | ||
+ | Sinnvoll vor allem in einer Desktop Umgebung<br /> | ||
+ | |||
+ | Es wird TCP, UDP und ICMP unterstützt, für IPv4 und IPv6.<br /> | ||
Der Sniffer akzeptiert Filter wie sie bei tcpdump üblich sind.<br /> | Der Sniffer akzeptiert Filter wie sie bei tcpdump üblich sind.<br /> | ||
Bei HTTP Verbindungen kann ausserdem ein Teil der Payload angezeigt werden. | Bei HTTP Verbindungen kann ausserdem ein Teil der Payload angezeigt werden. | ||
− | [[Datei:Terminal_059.png|400px|thumb|right|sisniff Image]] | + | [[Datei:Terminal_059.png|400px|thumb|right|sisniff Image]] |
+ | |||
+ | |||
Die Option <code>-h</code> gibt eine Argumenteübersicht und listet die verfügbaren Interfaces auf. | Die Option <code>-h</code> gibt eine Argumenteübersicht und listet die verfügbaren Interfaces auf. | ||
<small><pre> | <small><pre> | ||
− | # ./sisniff | + | # ./sisniff -h |
− | usage: sisniff | + | usage: sisniff [-h] -i {eth0,lo,wlan0} [-n] [-P] [-p program|not-program] [-4] [-6] [-H] [-Hl] [filter] |
+ | |||
+ | sisniff V1.5 | ||
+ | 2017-2023 by sigi <https://wiki.zweiernet.ch/wiki/sisniff> | ||
positional arguments: | positional arguments: | ||
− | filter | + | 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 | + | -i {eth0,lo,wlan0} Interface |
− | + | ||
-n Do not resolve IP-Addresses | -n Do not resolve IP-Addresses | ||
− | - | + | -P Don't put interface into promiscuous mode |
+ | -p program|not-program | ||
+ | Filter by program name (accepts * pattern) ([not-] negates) | ||
+ | -4 Only IPv4 | ||
+ | -6 Only IPv6 | ||
+ | -H Show HTTP Payload | ||
+ | -Hl Show HTTP Payload, long output | ||
</pre></small> | </pre></small> | ||
<br /><p> | <br /><p> | ||
;Downloads der aktuellen Version: | ;Downloads der aktuellen Version: | ||
− | : | + | : Download mit wget: <code>wget https://git.zweiernet.ch/sigi/sisniff/raw/master/sisniff</code><br /> |
− | : gzipped: https:// | + | : gzipped: https://git.zweiernet.ch/sigi/sisniff/archive/master.tar.gz<br> |
− | + | : Git Projektseite: https://git.zweiernet.ch/sigi/sisniff<br /> | |
− | + | <br /> | |
+ | <br /> | ||
{{IBox|i| | {{IBox|i| | ||
− | Da <code>sisniff | + | Da <code>sisniff</code> auf der Scapy <code>sniff()</code>-Funktion aufsetzt, wird Scapy > 2.x benötigt: |
:Unter Debian/Ubuntu: <code>apt-get install scapy</code> | :Unter Debian/Ubuntu: <code>apt-get install scapy</code> | ||
+ | :pip/pip3: <code>pip install scapy</code> | ||
:Andere: http://www.secdev.org/projects/scapy/ | :Andere: http://www.secdev.org/projects/scapy/ | ||
}} | }} | ||
<br /> | <br /> | ||
− | + | Beispiele: | |
+ | <small><pre> | ||
+ | # sisniff "port not ssh" | ||
+ | # sisniff -p *vpn* | ||
+ | # sisniff -i wlan0 -p not-thunderbird-bin -4 "host not www.zweiernet.ch" | ||
+ | # sisniff -i eth0 -p firefox -Hl "port 80" | ||
+ | </pre></small> | ||
+ | <br /> | ||
+ | |||
+ | Python Code: | ||
− | <syntaxhighlight lang="python"> | + | <small><syntaxhighlight lang="python"> |
− | #!/usr/bin/ | + | #!/usr/bin/env python3 |
− | # (c) 2017 by Siegrist(SystemLoesungen) <PSS@ZweierNet.ch> | + | # (c) 2017-2022 by Siegrist(SystemLoesungen) <PSS@ZweierNet.ch> |
+ | # | ||
+ | # 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 * | from scapy.all import * | ||
Zeile 53: | Zeile 89: | ||
import fcntl | import fcntl | ||
import struct | import struct | ||
− | |||
import argparse | 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 = " | + | VERSION = "1.4" |
PROC_TCP4 = "/proc/net/tcp" | PROC_TCP4 = "/proc/net/tcp" | ||
Zeile 79: | Zeile 125: | ||
numeric = False | numeric = False | ||
payloadH = False | payloadH = 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: | ||
Zeile 104: | Zeile 151: | ||
if pid == "NoPid": | if pid == "NoPid": | ||
− | #print ">>>>>>>>>>>NoPID:" + str(hosts) +" "+ str(ports) + "//" + str(line_array) | + | #print(">>>>>>>>>>>NoPID:" + str(hosts) +" "+ str(ports) + "//" + str(line_array)) |
return ['-', '-', uid] | return ['-', '-', uid] | ||
Zeile 112: | Zeile 159: | ||
exe = None | exe = None | ||
− | #print str(lhost) +" "+ str(lport) +" "+ inode +" "+ pid | + | #print(str(lhost) +" "+ str(lport) +" "+ inode +" "+ pid) |
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! */ | |
}; | }; | ||
---------- | ---------- | ||
Zeile 144: | Zeile 191: | ||
if proto == IPPROTO_UDP: | if proto == IPPROTO_UDP: | ||
try: | try: | ||
− | with open( | + | 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: | ||
Zeile 156: | Zeile 206: | ||
return 0 | return 0 | ||
except: | except: | ||
− | print "open proc_udp4 error" | + | print("open proc_udp4 error") |
return 0 | return 0 | ||
elif proto == IPPROTO_TCP: | elif proto == IPPROTO_TCP: | ||
try: | try: | ||
− | with open( | + | 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: | ||
Zeile 174: | Zeile 227: | ||
return 0 | return 0 | ||
except: | except: | ||
− | print "open proc_tcp error" | + | print("open proc_tcp error") |
return 0 | return 0 | ||
elif proto == IPPROTO_ICMP: | elif proto == IPPROTO_ICMP: | ||
try: | try: | ||
− | with open( | + | 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: | ||
Zeile 191: | Zeile 247: | ||
return 0 | return 0 | ||
except: | except: | ||
− | print "open proc_icmp4 error" | + | print("open proc_icmp4 error") |
return 0 | return 0 | ||
Zeile 211: | Zeile 267: | ||
def _ip6(s): | def _ip6(s): | ||
− | ip = [s[6:8],s[4:6],s[2:4],s[0:2],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) | 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): | ||
Zeile 284: | Zeile 363: | ||
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: |
− | if re.match(" | + | 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 | program = sexe | ||
pid = spid | pid = spid | ||
Zeile 294: | Zeile 376: | ||
conn_cache.pop(0) | conn_cache.pop(0) | ||
conn_cache.append([c_hash,program,pid]) | conn_cache.append([c_hash,program,pid]) | ||
− | |||
else: | else: | ||
program = sexe | program = sexe | ||
Zeile 308: | Zeile 389: | ||
renew = conn_cache.pop(indx) | renew = conn_cache.pop(indx) | ||
conn_cache.append(renew) | 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 = "" | o_payload = "" | ||
+ | |||
if packet.haslayer(UDP): | if packet.haslayer(UDP): | ||
o_proto = "UDP" | o_proto = "UDP" | ||
Zeile 322: | Zeile 429: | ||
o_sport = str(packet[0][2].sport) | o_sport = str(packet[0][2].sport) | ||
flags = "" | flags = "" | ||
− | #o_payload = packet[0].sprintf('%10s,UDP.payload%') | + | #o_payload = _to_str(packet[0].sprintf('%10s,UDP.payload%')) |
elif packet.haslayer(TCP): | elif packet.haslayer(TCP): | ||
o_proto = "TCP" | o_proto = "TCP" | ||
Zeile 336: | Zeile 443: | ||
if payloadH == True: | if payloadH == True: | ||
if packet.haslayer(Raw): | if packet.haslayer(Raw): | ||
− | tpld = packet[0].sprintf('%TCP.payload%') | + | #tpld = packet[0].sprintf('%TCP.payload%') |
− | if re.match( | + | tpld = _to_str(packet[0][TCP].load) |
− | request_line, gaga = tpld.split('\r\n', 1) | + | 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] | #o_payload = tpld[0:20] | ||
elif packet.haslayer(ICMP): | elif packet.haslayer(ICMP): | ||
Zeile 357: | Zeile 470: | ||
flags = "["+packet[0].sprintf('%ICMP.type%') + "/" + packet[0].sprintf('%ICMP.code%')+"]" | flags = "["+packet[0].sprintf('%ICMP.type%') + "/" + packet[0].sprintf('%ICMP.code%')+"]" | ||
else: | else: | ||
− | 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): |
+ | if packet[0][1].dst in res_cache: | ||
rem_name = res_cache[packet[0][1].dst] | rem_name = res_cache[packet[0][1].dst] | ||
else: | else: | ||
Zeile 367: | Zeile 493: | ||
else: | else: | ||
rem_name = packet[0][1].dst | rem_name = packet[0][1].dst | ||
− | + | ||
− | 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(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: | 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): |
+ | if packet[0][1].src in res_cache: | ||
rem_name = res_cache[packet[0][1].src] | rem_name = res_cache[packet[0][1].src] | ||
else: | else: | ||
Zeile 378: | Zeile 506: | ||
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:" + | + | 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 |
Zeile 389: | Zeile 517: | ||
conf.sniff_promisc=0 | conf.sniff_promisc=0 | ||
conf.sniff_promisc=0 | conf.sniff_promisc=0 | ||
− | |||
sys.exit() | sys.exit() | ||
# get the interfaces | # get the interfaces | ||
− | ifaces = | + | #ifaces = subprocess.getoutput("ls /sys/class/net") |
− | iface_list = ifaces.split('\n') | + | #iface_list = ifaces.split('\n') |
+ | iface_list = get_if_list() | ||
+ | iface = conf.route.route("0.0.0.0")[0] | ||
− | print | + | rfilter = "ip or ip6" |
+ | print("") | ||
# commandline params | # commandline params | ||
− | parser = argparse.ArgumentParser(description='sisniff V'+VERSION) | + | parser = argparse.ArgumentParser(description='sisniff V'+VERSION+"\n2017-2022 by sigi <https://wiki.zweiernet.ch/wiki/sisniff>", |
− | parser.add_argument('-i', help="Interface | + | 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('-n', help="Do not resolve IP-Addresses", action="store_true") | ||
− | parser.add_argument('- | + | 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) | parser.add_argument('filter', nargs='?', help="Filter (BPF syntax) on top of IP (in dbl-quotes \"...\")", type=str) | ||
args = parser.parse_args() | args = parser.parse_args() | ||
− | iface = args.i | + | if args.i: |
+ | iface = args.i | ||
if args.n: | if args.n: | ||
numeric = True | numeric = True | ||
− | if args. | + | if args.v4: |
+ | rfilter = "ip" | ||
+ | if args.v6: | ||
+ | rfilter = "ip6" | ||
+ | if args.H: | ||
payloadH = True | payloadH = True | ||
+ | if args.Hl: | ||
+ | payloadH = True | ||
+ | payloadHl = True | ||
if args.filter: | if args.filter: | ||
fillter = " and (" + args.filter + ")" | fillter = " and (" + args.filter + ")" | ||
− | print "> Applying Filter: \" | + | print("\033[1m> Applying Filter: \"" + rfilter + fillter + "\"\033[0m") |
+ | if args.p: | ||
+ | filter_prog = args.p | ||
+ | |||
− | # local addresses | + | # local addresses |
− | MYADDRS = _remove_empty( | + | if args.v6: |
− | MYADDRS.append('0.0.0.0') | + | MYADDRS=[] |
− | MYADDRS.append('127.0.0.1') | + | xMYADDRS = [] |
− | xMYADDRS = [_ip_hexrev(x) for x in MYADDRS] | + | else: |
− | print "> My IP-Addresses: " + str(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=[] | ||
+ | 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) | ||
# confirmed connections cache (ringboffer) | # confirmed connections cache (ringboffer) | ||
Zeile 427: | Zeile 586: | ||
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= | + | 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 | ## -- oond denn isch schloss | ||
− | </syntaxhighlight> | + | </syntaxhighlight></small> |
Aktuelle Version vom 30. Oktober 2023, 16:20 Uhr
Ein Netzwerk-Sniffer der für jedes Packet nebst Adresse und Port die lokal verbundene Anwendung und deren PID ermittelt und anzeigt.
sisniff versucht, zu jedem Packet der Netzwerkschnittstelle die dazugehörigen Anwendung zu ermitteln.
Sinnvoll vor allem in einer Desktop Umgebung
Es wird TCP, UDP und ICMP unterstützt, für IPv4 und IPv6.
Der Sniffer akzeptiert Filter wie sie bei tcpdump üblich sind.
Bei HTTP Verbindungen kann ausserdem ein Teil der Payload angezeigt werden.
Die Option -h
gibt eine Argumenteübersicht und listet die verfügbaren Interfaces auf.
# ./sisniff -h usage: sisniff [-h] -i {eth0,lo,wlan0} [-n] [-P] [-p program|not-program] [-4] [-6] [-H] [-Hl] [filter] sisniff V1.5 2017-2023 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 -n Do not resolve IP-Addresses -P Don't put interface into promiscuous mode -p program|not-program Filter by program name (accepts * pattern) ([not-] negates) -4 Only IPv4 -6 Only IPv6 -H Show HTTP Payload -Hl Show HTTP Payload, long output
- Downloads der aktuellen Version
- Download mit wget:
wget https://git.zweiernet.ch/sigi/sisniff/raw/master/sisniff
- gzipped: https://git.zweiernet.ch/sigi/sisniff/archive/master.tar.gz
- Git Projektseite: https://git.zweiernet.ch/sigi/sisniff
i |
Da
|
Beispiele:
# sisniff "port not ssh" # sisniff -p *vpn* # sisniff -i wlan0 -p not-thunderbird-bin -4 "host not www.zweiernet.ch" # sisniff -i eth0 -p firefox -Hl "port 80"
Python Code:
#!/usr/bin/env python3 # (c) 2017-2022 by Siegrist(SystemLoesungen) <PSS@ZweierNet.ch> # # 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.4" 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-2022 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") 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 # 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) # 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