sisniff 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541
  1. #!/usr/bin/env python3
  2. # (c) 2017-2022 by Siegrist(SystemLoesungen) <PSS@ZweierNet.ch>
  3. #
  4. # All Rights reserved.
  5. # This program is free software; you can redistribute it and/or
  6. # modify it under the terms of the GNU General Public License as
  7. # published by the Free Software Foundation.
  8. #
  9. # This program is distributed in the hope that it will be useful,
  10. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  11. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  12. # GNU General Public License for more details.
  13. #
  14. from scapy.all import *
  15. import pwd
  16. import os
  17. import re
  18. import glob
  19. import sys
  20. import string
  21. import fcntl
  22. import struct
  23. import argparse
  24. #if sys.version_info.major == 2:
  25. # import commands as subprocess
  26. #elif sys.version_info.major == 3:
  27. # import subprocess
  28. def _to_str(inp):
  29. if sys.version_info.major == 2:
  30. return inp
  31. else:
  32. return "".join( chr(x) for x in inp)
  33. VERSION = "1.5"
  34. PROC_TCP4 = "/proc/net/tcp"
  35. PROC_UDP4 = "/proc/net/udp"
  36. PROC_ICMP4 = "/proc/net/icmp"
  37. PROC_TCP6 = "/proc/net/tcp6"
  38. PROC_UDP6 = "/proc/net/udp6"
  39. PROC_PACKET = "/proc/net/packet"
  40. # Services
  41. TSERV = dict((TCP_SERVICES[k], k) for k in TCP_SERVICES.keys())
  42. USERV = dict((UDP_SERVICES[k], k) for k in UDP_SERVICES.keys())
  43. # IP Protocol Numbers (dec)
  44. IPPROTO_ICMP = 1
  45. IPPROTO_TCP = 6
  46. IPROTOP_IGP = 9
  47. IPPROTO_UDP = 17
  48. nostate = set(['04','05','06''07','08','09','0C','0D'])
  49. tcp_payload_hdrs = ['GET|POST|HTTP|HEAD|PUT|PATCH|DELETE|TRACE|OPTIONS|CONNECT']
  50. numeric = False
  51. payloadH = False
  52. payloadHl = False
  53. fillter = ""
  54. def get_conn_info(proto,hosts,ports,ipvers):
  55. ''' returns: pid, exe, uid '''
  56. uid = 0
  57. line_array = _proc4load(proto,hosts,ports,ipvers)
  58. if line_array == 0:
  59. return ['?','?','?']
  60. '''
  61. try:
  62. uid = pwd.getpwuid(int(line_array[7]))[0] # Get user from UID.
  63. except:
  64. uid = line_array[7]
  65. '''
  66. inode = str(line_array[9])
  67. if inode == "0":
  68. return ['.','.','.']
  69. pid = _get_pid_of_inode(inode) # try get a pid
  70. if pid == "NoPid":
  71. #print(">>>>>>>>>>>NoPID:" + str(hosts) +" "+ str(ports) + "//" + str(line_array))
  72. return ['-', '-', uid]
  73. try: # try read the process name.
  74. exe = os.readlink('/proc/'+pid+'/exe').split('/')[-1]
  75. except:
  76. exe = None
  77. #print(str(lhost) +" "+ str(lport) +" "+ inode +" "+ pid)
  78. return [pid, exe, uid]
  79. def _proc4load(proto,hosts,ports,ipvers):
  80. ''' Read the table of tcp/udp connections
  81. tcp/udp: "sl, local_address, rem_address, st, tx_queue rx_queue, tr tm->when, retrnsmt, uid , timeout, inode ,..."
  82. ---- TCP states from https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/include/net/tcp_states.h?id=HEAD
  83. enum {
  84. TCP_ESTABLISHED = 1,
  85. TCP_SYN_SENT,
  86. TCP_SYN_RECV,
  87. TCP_FIN_WAIT1,
  88. TCP_FIN_WAIT2,
  89. TCP_TIME_WAIT,
  90. TCP_CLOSE,
  91. TCP_CLOSE_WAIT,
  92. TCP_LAST_ACK,
  93. TCP_LISTEN,
  94. TCP_CLOSING, /* Now a valid state */
  95. TCP_NEW_SYN_RECV,
  96. TCP_MAX_STATES /* Leave at the end! */
  97. };
  98. ----------
  99. '''
  100. #xhosts = _ip_hexrev(hosts)
  101. xports = _dec2hex(ports)
  102. if proto == IPPROTO_UDP:
  103. try:
  104. procv = PROC_UDP4
  105. if ipvers == 6:
  106. procv = PROC_UDP6
  107. with open(procv,'r') as f:
  108. next(f)
  109. for line in f:
  110. line_arrayu = _remove_empty(line.split(' '))
  111. l_xhost,l_xport = line_arrayu[1].split(':')
  112. if l_xhost not in xMYADDRS:
  113. continue
  114. if l_xport == xports:
  115. return line_arrayu
  116. return 0
  117. except:
  118. print("open proc_udp4 error")
  119. return 0
  120. elif proto == IPPROTO_TCP:
  121. try:
  122. procv = PROC_TCP4
  123. if ipvers == 6:
  124. procv = PROC_TCP6
  125. with open(procv,'r') as f:
  126. next(f)
  127. for line in f:
  128. line_arrayt = _remove_empty(line.split(' '))
  129. if line_arrayt[3] in nostate: # not some TCP state
  130. continue
  131. l_xhost,l_xport = line_arrayt[1].split(':')
  132. if l_xhost not in xMYADDRS:
  133. continue
  134. if l_xport == xports:
  135. return line_arrayt
  136. return 0
  137. except:
  138. print("open proc_tcp error")
  139. return 0
  140. elif proto == IPPROTO_ICMP:
  141. try:
  142. procv = PROC_ICMP4
  143. if ipvers == 6:
  144. procv = PROC_ICMP6
  145. with open(procv,'r') as f:
  146. next(f)
  147. for line in f:
  148. line_arrayi = _remove_empty(line.split(' '))
  149. l_xhost,l_xport = line_arrayi[1].split(':')
  150. if l_xhost not in xMYADDRS:
  151. continue
  152. if l_xport == xports:
  153. return line_arrayi
  154. return 0
  155. except:
  156. print("open proc_icmp4 error")
  157. return 0
  158. return 0
  159. def _convert_ipv4_port(array):
  160. host,port = array.split(':')
  161. return _ip(host),_hex2dec(port)
  162. def _hex2dec(s):
  163. return str(int(s,16))
  164. def _dec2hex(p):
  165. return hex(int(p)).split('x')[-1].upper()
  166. def _ip(s):
  167. ip = [(_hex2dec(s[6:8])),(_hex2dec(s[4:6])),(_hex2dec(s[2:4])),(_hex2dec(s[0:2]))]
  168. return '.'.join(ip)
  169. def _ip6(s):
  170. 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]]
  171. return ':'.join(ip)
  172. def _ip_hexrev(ip):
  173. return ''.join([hex(int(x)+256)[3:] for x in ip.split('.')][::-1]).upper()
  174. # IPv6 /proc/net/tcp6 format from expanded ip-address
  175. def _to_v6_proc(s):
  176. s = s.replace(":", "")
  177. 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]]
  178. return ''.join(ip).upper()
  179. def expand_v6(ip):
  180. ipa = ip.split(':') # liste
  181. if '' in ipa:
  182. if ipa.count('') > 1: # korr ::1 or :::
  183. for i in range(ipa.count('')-1):
  184. ipa.remove('')
  185. miss = 8 - len(ipa) +1
  186. for i in range(miss):
  187. ipa.insert(ipa.index('')+i, '0000')
  188. ipa.remove('')
  189. return ':'.join(["%04x" % x for x in [int(x, 16) for x in ipa]])
  190. else:
  191. return ':'.join(["%04x" % x for x in [int(x, 16) for x in ipa]])
  192. def _remove_empty(array):
  193. return [x for x in array if x != '']
  194. def _get_pid_of_inode(inode):
  195. s_term = r'^socket\:\['+ inode +r'\]$'
  196. for item in glob.iglob('/proc/[0-9]*/fd/[0-9]*'):
  197. try:
  198. if re.match(s_term,os.readlink(item)):
  199. return item.split('/')[2]
  200. except:
  201. pass
  202. return "NoPid"
  203. def _resolve_ip(host):
  204. """
  205. resolve ip und update dictionary res_cache {'ip': 'name'}.
  206. If resolution for a ip failed, 'name' is n_try ... 0.
  207. """
  208. try:
  209. hname = socket.gethostbyaddr(host)[0]
  210. res_cache[host] = str(hname)
  211. return str(hname)
  212. except:
  213. res_cache[host] = str(host)
  214. return str(host)
  215. def check_root():
  216. if os.getuid() == 0:
  217. return True
  218. else:
  219. return False
  220. ## Define our Custom Action function
  221. def doPackets(packet):
  222. program = "-"
  223. pid = "-"
  224. uid = "-"
  225. o_proto = ""
  226. o_dport = "none"
  227. o_sport = "none"
  228. flags = ""
  229. # only local addresses
  230. if packet[0][1].src in MYADDRS:
  231. conn_addr = packet[0][1].src
  232. if packet.haslayer(TCP) or packet.haslayer(UDP) or packet.haslayer(ICMP):
  233. try:
  234. conn_port = packet[0][2].sport
  235. except:
  236. conn_port = 99999
  237. o_dir = 1
  238. else:
  239. conn_addr = packet[0][1].dst
  240. if packet.haslayer(TCP) or packet.haslayer(UDP) or packet.haslayer(ICMP):
  241. try:
  242. conn_port = packet[0][2].dport
  243. except:
  244. conn_port = 99999
  245. o_dir = 0
  246. 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):
  247. # logemol casch
  248. c_hash = conn_addr+'=:='+str(conn_port)
  249. if not any(x[0] == c_hash for x in conn_cache):
  250. # get the connection info from packet
  251. if packet[0][1].version == 4:
  252. spid,sexe,suid = get_conn_info(packet[0][1].proto, conn_addr, conn_port, packet[0][1].version)
  253. elif packet[0][1].version == 6:
  254. spid,sexe,suid = get_conn_info(packet[0][1].nh, conn_addr, conn_port, packet[0][1].version)
  255. if re.match("[0-9]+$", spid):
  256. program = sexe
  257. pid = spid
  258. uid = suid
  259. # update cache
  260. if len(conn_cache) >= cc_maxlen:
  261. conn_cache.pop(0)
  262. conn_cache.append([c_hash,program,pid])
  263. else:
  264. program = sexe
  265. pid = spid
  266. uid = suid
  267. else:
  268. # me honds fom casch
  269. indx = [x[0] for x in conn_cache].index(c_hash)
  270. program = conn_cache[indx][1]
  271. pid = conn_cache[indx][2]
  272. uid = 0
  273. # cache aktualisieren
  274. renew = conn_cache.pop(indx)
  275. conn_cache.append(renew)
  276. try:
  277. filter_prog
  278. except:
  279. pass
  280. else:
  281. if filter_prog.startswith('not-'):
  282. filter_progn = filter_prog[4:]
  283. if filter_progn.startswith('*') and filter_progn.endswith('*') and re.search(filter_progn[1:-1], program):
  284. return
  285. elif filter_progn.startswith('*') and not filter_progn.endswith('*') and re.search(filter_progn[1:]+'$', program):
  286. return
  287. elif not filter_progn.startswith('*') and filter_progn.endswith('*') and re.match('^'+filter_progn[:-1], program):
  288. return
  289. elif not filter_progn.startswith('*') and not filter_progn.endswith('*') and re.match('^'+filter_progn+'$', program):
  290. return
  291. else:
  292. if filter_prog.startswith('*') and filter_prog.endswith('*') and not re.search(filter_prog[1:-1], program):
  293. return
  294. elif filter_prog.startswith('*') and not filter_prog.endswith('*') and not re.search(filter_prog[1:]+'$', program):
  295. return
  296. elif not filter_prog.startswith('*') and filter_prog.endswith('*') and not re.match('^'+filter_prog[:-1], program):
  297. return
  298. elif not filter_prog.startswith('*') and not filter_prog.endswith('*') and not re.match('^'+filter_prog+'$', program):
  299. return
  300. o_payload = ""
  301. if packet.haslayer(UDP):
  302. o_proto = "UDP"
  303. try:
  304. o_dport = "\033[1m"+USERV[packet[0][2].dport]+"\033[0m"
  305. except:
  306. o_dport = str(packet[0][2].dport)
  307. try:
  308. o_sport = "\033[1m"+USERV[packet[0][2].sport]+"\033[0m"
  309. except:
  310. o_sport = str(packet[0][2].sport)
  311. flags = ""
  312. #o_payload = _to_str(packet[0].sprintf('%10s,UDP.payload%'))
  313. elif packet.haslayer(TCP):
  314. o_proto = "TCP"
  315. try:
  316. o_dport = "\033[1m"+TSERV[packet[0][2].dport]+"\033[0m"
  317. except:
  318. o_dport = str(packet[0][2].dport)
  319. try:
  320. o_sport = "\033[1m"+TSERV[packet[0][2].sport]+"\033[0m"
  321. except:
  322. o_sport = str(packet[0][2].sport)
  323. flags = packet[0].sprintf('%3s,TCP.flags%')
  324. if payloadH == True:
  325. if packet.haslayer(Raw):
  326. #tpld = packet[0].sprintf('%TCP.payload%')
  327. tpld = _to_str(packet[0][TCP].load)
  328. tpldhead = tpld[0:8]
  329. #print("tpld:" + tpldhead)
  330. if re.match(r'GET|POST|HTTP|HEAD|PUT|PATCH|DELETE|TRACE|OPTIONS|CONNECT.*', tpldhead):
  331. if payloadHl == True:
  332. o_payload = str(tpld)
  333. else:
  334. request_line, gaga = tpld.split('\r\n', 1)
  335. o_payload = str(request_line)
  336. #o_payload = tpld[0:20]
  337. elif packet.haslayer(ICMP):
  338. o_proto = "ICMP"
  339. if conn_port == 99999:
  340. o_dport = "-"
  341. o_sport = "-"
  342. else:
  343. try:
  344. o_dport = "\033[1m"+USERV[packet[0][2].sport]+"\033[0m"
  345. except:
  346. o_dport = str(packet[0][2].sport)
  347. try:
  348. o_sport = "\033[1m"+USERV[packet[0][2].dport]+"\033[0m"
  349. except:
  350. o_sport = str(packet[0][2].dport)
  351. flags = "["+packet[0].sprintf('%ICMP.type%') + "/" + packet[0].sprintf('%ICMP.code%')+"]"
  352. else:
  353. layerukn = packet[0][1].getlayer(1)
  354. if layerukn is None:
  355. o_proto = "UNKNOWN"
  356. else:
  357. #print("Layer:", xxl1.name)
  358. o_proto = layerukn.name
  359. #o_proto = "UNKNOWN"
  360. if packet[0][1].version == 4:
  361. packlen = str(packet[0][1].len)
  362. if packet[0][1].version == 6:
  363. packlen = str(packet[0][1].plen)
  364. if o_dir == 1:
  365. if numeric == False:
  366. #if res_cache.has_key(packet[0][1].dst):
  367. if packet[0][1].dst in res_cache:
  368. rem_name = res_cache[packet[0][1].dst]
  369. else:
  370. rem_name = _resolve_ip(packet[0][1].dst)
  371. else:
  372. rem_name = packet[0][1].dst
  373. #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
  374. 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
  375. else:
  376. if numeric == False:
  377. #if res_cache.has_key(packet[0][1].src):
  378. if packet[0][1].src in res_cache:
  379. rem_name = res_cache[packet[0][1].src]
  380. else:
  381. rem_name = _resolve_ip(packet[0][1].src)
  382. else:
  383. rem_name = packet[0][1].src
  384. 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
  385. ## -- Ond denn s'Hooptprogramm
  386. # root check
  387. if not check_root():
  388. print("This program needs root privileges !\nThats because of reading the /proc filesystem and using libpcap functions.\nSo I give up\n")
  389. conf.sniff_promisc=0
  390. conf.sniff_promisc=0
  391. sys.exit()
  392. # get the interfaces
  393. #ifaces = subprocess.getoutput("ls /sys/class/net")
  394. #iface_list = ifaces.split('\n')
  395. iface_list = get_if_list()
  396. iface = conf.route.route("0.0.0.0")[0]
  397. rfilter = "ip or ip6"
  398. print("")
  399. # commandline params
  400. parser = argparse.ArgumentParser(description='sisniff V'+VERSION+"\n2017-2023 by sigi <https://wiki.zweiernet.ch/wiki/sisniff>",
  401. formatter_class=argparse.RawDescriptionHelpFormatter)
  402. parser.add_argument('-i', help="Interface", choices=iface_list)
  403. parser.add_argument('-n', help="Do not resolve IP-Addresses", action="store_true")
  404. parser.add_argument('-P', help="Don't put interface into promiscuous mode", action="store_true")
  405. parser.add_argument('-p', help='Filter by program name (accepts * for matching) ([not-] negates)', type=str, metavar='program|not-program')
  406. parser.add_argument('-4', dest='v4', help="Only IPv4", action="store_true")
  407. parser.add_argument('-6', dest='v6', help="Only IPv6", action="store_true")
  408. parser.add_argument('-H', help="Show HTTP Payload", action="store_true")
  409. parser.add_argument('-Hl', help="Show HTTP Payload, long output", action="store_true")
  410. parser.add_argument('filter', nargs='?', help="Filter (BPF syntax) on top of IP (in dbl-quotes \"...\")", type=str)
  411. args = parser.parse_args()
  412. if args.i:
  413. iface = args.i
  414. if args.n:
  415. numeric = True
  416. if args.v4:
  417. rfilter = "ip"
  418. if args.v6:
  419. rfilter = "ip6"
  420. if args.H:
  421. payloadH = True
  422. if args.Hl:
  423. payloadH = True
  424. payloadHl = True
  425. if args.filter:
  426. fillter = " and (" + args.filter + ")"
  427. print("\033[1m> Applying Filter: \"" + rfilter + fillter + "\"\033[0m")
  428. if args.p:
  429. filter_prog = args.p
  430. no_promisc = ""
  431. if args.P:
  432. conf.sniff_promisc = conf.promisc = 0
  433. no_promisc = " (disabled promiscuous mode)"
  434. # local addresses
  435. if args.v6:
  436. MYADDRS=[]
  437. xMYADDRS = []
  438. else:
  439. MYADDRS = _remove_empty(os.popen("ip addr show " + iface + " | egrep 'inet ' | awk '{{print $2}}' | awk -F'/' '{{print $1}}'").read().split())
  440. MYADDRS.append('0.0.0.0')
  441. MYADDRS.append('127.0.0.1')
  442. xMYADDRS = [_ip_hexrev(x) for x in MYADDRS]
  443. if args.v4:
  444. MYADDRS6=[]
  445. else:
  446. MYADDRS6 = _remove_empty(os.popen("ip addr show " + iface + " | egrep 'inet6' | grep -vi fe80 | awk '{{print $2}}' | awk -F'/' '{{print $1}}'").read().split())
  447. MYADDRS6.append(':::')
  448. MYADDRS6.append('::1')
  449. MYADDRS = MYADDRS + MYADDRS6
  450. xMYADDRS = xMYADDRS + [_to_v6_proc(expand_v6(x)) for x in MYADDRS6]
  451. print("> My IP-Addresses: " + str(MYADDRS))
  452. print("> Listening on: " + iface + no_promisc)
  453. # confirmed connections cache (ringboffer)
  454. conn_cache = []
  455. cc_maxlen = 20
  456. # resolver cache
  457. res_cache = {}
  458. n_try = 3
  459. print("")
  460. 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;")
  461. print("")
  462. print("Program/PID: Local addr:port <<->> Remote addr:port [Flags] Len:length : [Payload]")
  463. print("----------------------------------------------------------------------------------")
  464. # sniff, filtering for IP traffic
  465. try:
  466. sniff(filter=rfilter+fillter,iface=iface,prn=doPackets, store=0)
  467. except Exception as e:
  468. print("\n \033[1mError: " + str(e) + "\033[0m \n")
  469. ## -- oond denn isch schloss