5
5
by scanlogd {http://www.openwall.com/scanlogd} but with
6
6
added ability to log slow port-scans.
7
7
8
+ This is able to detect all standard TCP/UDP/SCTP scans
9
+ documented in the nmap book - https://nmap.org/book/man-port-scanning-techniques.html .
10
+
8
11
Features
9
12
10
13
1. Detects all stealth (half-open) and full-connect scans.
11
- 2. Detects Idle scan and logs it correctly using correlation!
12
- 3. Detects SCTP scan.
13
- 4. Detects slow port- scans also .
14
+ 2. Detects SCTP scan.
15
+ 3. Custom thresholding
16
+ 4. Ignore duplicate scans.
14
17
15
18
"""
16
19
20
23
import socket
21
24
import time
22
25
import argparse
26
+ import threading
27
+ import multiprocessing as mp
28
+
23
29
import hasher
24
30
import utils
25
31
import entry
34
40
'low' : (1 , 3 )
35
41
}
36
42
37
- PIDFILE = "/var/run/pyscanlogger .pid"
43
+ PIDFILE = "/var/run/pyscanlogd3 .pid"
38
44
39
- class ScanLogger :
45
+ class ScanLogger ( threading . Thread ) :
40
46
""" Port scan detector and logger class """
41
47
42
48
# TCP flags to scan type mapping
@@ -56,7 +62,7 @@ class ScanLogger:
56
62
TH_RST_ACK : TCP_REPLY }
57
63
58
64
def __init__ (self , timeout , threshold , itf = None , maxsize = 8192 ,
59
- daemon = True , ignore_duplicates = False , logfile = '/var/log/pyscanlogd3.log' ):
65
+ ignore_duplicates = False , logfile = '/var/log/pyscanlogd3.log' ):
60
66
self .scans = entry .EntryLog (maxsize )
61
67
self .maxsize = maxsize
62
68
self .long_scans = entry .EntryLog (maxsize )
@@ -72,8 +78,6 @@ def __init__(self, timeout, threshold, itf=None, maxsize=8192,
72
78
self .timeout_l = 3600
73
79
# Long-period scan threshold
74
80
self .threshold_l = self .threshold / 2
75
- # Daemonize ?
76
- self .daemon = daemon
77
81
# Interface
78
82
self .itf = itf
79
83
# Log file
@@ -94,7 +98,8 @@ def __init__(self, timeout, threshold, itf=None, maxsize=8192,
94
98
# a scan occurs at most every 5 seconds, this would be 12.
95
99
self .recent_scans = timerlist .TimerList (12 , 60.0 )
96
100
self .status_report ()
97
-
101
+ threading .Thread .__init__ (self , None )
102
+
98
103
def status_report (self ):
99
104
""" Report current configuration before starting """
100
105
@@ -116,8 +121,7 @@ def log(self, msg):
116
121
self .scanlog .write (line + '\n ' )
117
122
self .scanlog .flush ()
118
123
119
- if not self .daemon :
120
- print (line , file = sys .stderr )
124
+ print (line , file = sys .stderr )
121
125
122
126
def log_scan (self , scan ):
123
127
""" Log the scan to file and/or console """
@@ -329,9 +333,6 @@ def inspect_scan(self, scan, slow_scan=False):
329
333
330
334
def process (self , ts , pkt , decode = None ):
331
335
""" Process an incoming packet looking for scan signatures """
332
-
333
- pkt = decode (pkt )
334
-
335
336
# Dont process non-IP packets
336
337
if not 'ip' in pkt .__dict__ :
337
338
return
@@ -432,7 +433,10 @@ def process(self, ts, pkt, decode=None):
432
433
# print(src, dst, dport, flags)
433
434
# print(scan)
434
435
self .scans [key ] = scan
435
-
436
+
437
+ def run (self ):
438
+ self .loop ()
439
+
436
440
def loop (self ):
437
441
""" Run the main logic in a loop listening to packets """
438
442
@@ -443,50 +447,15 @@ def loop(self):
443
447
444
448
try :
445
449
print ('listening on %s: %s' % (pc .name , pc .filter ))
446
- pc .loop (- 1 , self .process , decode )
450
+ for ts , pkt in pc :
451
+ self .process (ts , decode (pkt ))
447
452
except KeyboardInterrupt :
448
- if not self .daemon :
449
- nrecv , ndrop , nifdrop = pc .stats ()
450
- print ('\n %d packets received by filter' % nrecv )
451
- print ('%d packets dropped by kernel' % ndrop )
452
-
453
- def run_daemon (self ):
454
- # Disconnect from tty
455
- try :
456
- pid = os .fork ()
457
- if pid > 0 :
458
- sys .exit (0 )
459
- except OSError as e :
460
- print ("fork #1 failed" , e , file = sys .stderr )
461
- sys .exit (1 )
462
-
463
- os .setsid ()
464
- os .umask (0 )
465
-
466
- # Second fork
467
- try :
468
- pid = os .fork ()
469
- if pid > 0 :
470
- open (PIDFILE ,'w' ).write (str (pid ))
471
- sys .exit (0 )
472
- except OSError as e :
473
- print ("fork #2 failed" , e , file = sys .stderr )
474
- sys .exit (1 )
475
-
476
- self .loop ()
477
-
478
- def run (self ):
479
- # If dameon, then create a new thread and wait for it
480
- if self .daemon :
481
- print ('Daemonizing...' )
482
- self .run_daemon ()
483
- else :
484
- # Run in foreground
485
- self .loop ()
453
+ nrecv , ndrop , nifdrop = pc .stats ()
454
+ print ('\n %d packets received by filter' % nrecv )
455
+ print ('%d packets dropped by kernel' % ndrop )
486
456
487
457
def main ():
488
458
parser = argparse .ArgumentParser (prog = 'pyscanlogd3' , description = 'pyscanlogd3: Python3 port-scan detection program' )
489
- parser .add_argument ('-d' , '--daemonize' , help = 'Daemonize' , action = 'store_true' , default = False )
490
459
parser .add_argument ('-f' , '--logfile' ,help = 'File to save logs to' ,default = '/var/log/pyscanlogd3.log' )
491
460
parser .add_argument ('-l' ,'--level' ,default = 'medium' , choices = levelParams .keys (),
492
461
help = 'Default threshold level for detection' )
@@ -497,9 +466,8 @@ def main():
497
466
498
467
timeout , threshold = levelParams [args .level ]
499
468
s = ScanLogger (timeout , threshold , itf = args .interface , maxsize = 8192 ,
500
- daemon = args .daemonize , ignore_duplicates = args .ignore_duplicates ,
501
- logfile = args .logfile )
502
- s .run ()
503
-
469
+ ignore_duplicates = args .ignore_duplicates , logfile = args .logfile )
470
+ s .start ()
471
+
504
472
if __name__ == '__main__' :
505
473
main ()
0 commit comments