├── LICENSE ├── Makefile ├── README.md ├── in.h ├── in_linux.c ├── in_nids.c ├── in_pcap.c ├── params.h ├── scanlogd.8 ├── scanlogd.c ├── scanlogd.init └── scanlogd.spec /LICENSE: -------------------------------------------------------------------------------- 1 | Redistribution and use in source and binary forms, with or without 2 | modification, are permitted. 3 | 4 | There's ABSOLUTELY NO WARRANTY, express or implied. 5 | 6 | (This is a heavily cut-down "BSD license".) 7 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | CC = gcc 2 | LD = $(CC) 3 | RM = rm -f 4 | CFLAGS = -Wall -O2 -fomit-frame-pointer 5 | LDFLAGS = -s 6 | 7 | PCAP_H = -I/usr/include/pcap 8 | PCAP_L = -lpcap 9 | 10 | NIDS_H = -I/usr/local/include 11 | NIDS_L = -L/usr/local/lib -lnids -lnet -lpcap 12 | 13 | PROJ = scanlogd 14 | 15 | OBJS_COMMON = scanlogd.o 16 | OBJS = $(OBJS_COMMON) in_linux.o in_nids.o in_pcap.o 17 | 18 | default: 19 | @echo "You need to choose a packet capture interface. Use one of:" 20 | @echo " make linux to use the raw socket interface on Linux" 21 | @echo " make libnids to use libnids (with libpcap and libnet)" 22 | @echo " make libpcap to use libpcap alone" 23 | @echo "See the man page for a short explanation of the interfaces." 24 | 25 | linux: $(OBJS_COMMON) in_linux.o 26 | $(LD) $(LDFLAGS) $(OBJS_COMMON) in_linux.o -o scanlogd 27 | 28 | libnids: $(OBJS_COMMON) in_nids.o 29 | $(LD) $(LDFLAGS) $(OBJS_COMMON) in_nids.o $(NIDS_L) -o scanlogd 30 | 31 | libpcap: $(OBJS_COMMON) in_pcap.o 32 | $(LD) $(LDFLAGS) $(OBJS_COMMON) in_pcap.o $(PCAP_L) -o scanlogd 33 | 34 | in_pcap.o: params.h in.h 35 | $(CC) $(CFLAGS) $(PCAP_H) -c in_pcap.c 36 | 37 | in_nids.o: params.h in.h 38 | $(CC) $(CFLAGS) $(NIDS_H) -c in_nids.c 39 | 40 | scanlogd.o: params.h in.h 41 | in_linux.o: params.h in.h 42 | 43 | .c.o: 44 | $(CC) $(CFLAGS) -c $*.c 45 | 46 | clean: 47 | $(RM) $(PROJ) $(OBJS) 48 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | scanlogd - detects and logs TCP port scans 2 | ========================================== 3 | 4 | Description 5 | ----------- 6 | 7 | scanlogd detects port scans and writes one line per scan via the syslog(3) 8 | mechanism. If a source address sends multiple packets to different ports in 9 | a short time, the event will be logged. The format of the messages is: 10 | 11 | saddr[:sport] to daddr [and others,] ports port[, port...], ..., flags[, TOS TOS][, TTL TTL] @HH:MM:SS 12 | 13 | The fields in square brackets are optional; sport, TOS, and TTL will only be 14 | displayed if they were constant during the scan. 15 | 16 | The flags field represents TCP control bits seen in packets coming to the 17 | system from the address of the scan. It is a combination of eight charac- 18 | ters, with each corresponding to one of the six defined and two reserved TCP 19 | control bits (see RFC 793). Control bits that were always set are encoded 20 | with an uppercase letter, and a lowercase letter is used if the bit was 21 | always clear. A question mark is used to indicate bits that changed from 22 | packet to packet. 23 | 24 | Interfaces 25 | ---------- 26 | 27 | In order to do its job, scanlogd needs a way to obtain raw IP packets that 28 | either come to the system scanlogd is running on, or travel across a network 29 | segment that is directly connected to the system. Current versions of scan- 30 | logd can be built with support for one of several packet capture interfaces. 31 | 32 | scanlogd is aware of the raw socket interface on Linux, libnids, and libpcap. 33 | 34 | The use of libpcap alone is discouraged. If you're on a system other than 35 | Linux and/or want to monitor the traffic of an entire network at once, you 36 | should be using libnids in order to handle fragmented IP packets. 37 | 38 | Compile-time defaults 39 | --------------------- 40 | 41 | At least 7 different privileged or 21 non-privileged ports, or a weighted 42 | combination of those, have to be accessed with no longer than 3 seconds 43 | between the accesses to be treated as a scan. If more than 5 scans are 44 | detected within 20 seconds, that event will be logged and logging will be 45 | stopped temporarily. 46 | 47 | Logging is done with a facility of daemon and a priority level alert. 48 | 49 | scanlogd should be started as root since it needs access to a packet capture 50 | interface. By default, it chroots to /var/empty and switches to running as 51 | user scanlogd after the packet capture interface is initialized. 52 | 53 | Exit status 54 | ----------- 55 | 56 | If the daemon couldn't start up successfully, it will exit with a status of 1. 57 | 58 | Usage 59 | ----- 60 | 61 | You're expected to create a dummy user for scanlogd to run as. Make sure you 62 | allocate unique UID and GID to the user. 63 | 64 | In most cases, scanlogd should be started from a rc.d script on system 65 | startup. 66 | 67 | In /etc/syslog.conf you may use something like: 68 | 69 | daemon.alert /var/log/alert 70 | 71 | Security notes 72 | -------------- 73 | 74 | As the name indicates, scanlogd only logs port scans. It does not prevent 75 | them. You will only receive summarized information in the system's log. 76 | 77 | Obviously, the source address of port scans can be spoofed. Don't take any 78 | action against the source of attacks unless other evidence is available. 79 | Sometimes IP addresses are shared between many people; this is the case for 80 | ISP shell servers, dynamic dialup pools, and corporate networks behind NAT 81 | (masquerading). 82 | 83 | Due to the nature of port scans, both false positives (detecting a scan when 84 | there isn't one) and false negatives (not detecting a scan when there's one) 85 | are possible. In particular, false positives occur when many small files are 86 | transferred rapidly with passive mode FTP. 87 | -------------------------------------------------------------------------------- /in.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Generic packet capture interface for scanlogd. 3 | */ 4 | 5 | #ifndef _SCANLOGD_IN_H 6 | #define _SCANLOGD_IN_H 7 | 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | #ifndef IP_MF 14 | #define IP_MF 0x2000 15 | #endif 16 | #ifndef IP_OFFMASK 17 | #define IP_OFFMASK 0x1fff 18 | #endif 19 | 20 | /* 21 | * Packet header as read from a packet capture interface. In reality, the 22 | * TCP header can be at a different offset; this is just to get the total 23 | * size right. 24 | */ 25 | struct header { 26 | struct ip ip; 27 | struct tcphdr tcp; 28 | char space[60 - sizeof(struct ip)]; 29 | }; 30 | 31 | extern int in_init(void); 32 | extern void in_run(void (*process_packet)(struct header *packet, int size)); 33 | 34 | #endif 35 | -------------------------------------------------------------------------------- /in_linux.c: -------------------------------------------------------------------------------- 1 | #define _BSD_SOURCE 2 | #define _DEFAULT_SOURCE 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | #include "params.h" 13 | #include "in.h" 14 | 15 | #ifndef __linux__ 16 | #warning "This code will only work on Linux; use an alternate make target" 17 | #endif 18 | #ifdef SCANLOGD_DEVICE 19 | #warning "SCANLOGD_DEVICE makes no sense for the Linux raw socket interface" 20 | #endif 21 | #if SCANLOGD_PROMISC 22 | #warning "SCANLOGD_PROMISC makes no sense for the Linux raw socket interface" 23 | #endif 24 | 25 | static int raw; 26 | 27 | int in_init(void) 28 | { 29 | if ((raw = socket(AF_INET, SOCK_RAW, IPPROTO_TCP)) < 0) { 30 | perror("socket"); 31 | return 1; 32 | } 33 | 34 | return 0; 35 | } 36 | 37 | void in_run(void (*process_packet)(struct header *packet, int size)) 38 | { 39 | struct header packet; 40 | int size; 41 | 42 | while (1) 43 | if ((size = read(raw, &packet, sizeof(packet))) >= sizeof(packet.ip)) 44 | process_packet(&packet, size); 45 | } 46 | -------------------------------------------------------------------------------- /in_nids.c: -------------------------------------------------------------------------------- 1 | #define _BSD_SOURCE 2 | #define _DEFAULT_SOURCE 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | #include 11 | 12 | #include "params.h" 13 | #include "in.h" 14 | 15 | #if !SCANLOGD_PROMISC 16 | #if !(defined(NIDS_MAJOR) && (NIDS_MAJOR > 1 || NIDS_MINOR >= 14)) 17 | #warning "SCANLOGD_PROMISC is 0, but your libnids will set PROMISC anyway" 18 | #endif 19 | #endif 20 | 21 | static void (*scanlogd_process_packet)(struct header *packet, int size); 22 | 23 | static void nids_process_packet(struct ip *packet) 24 | { 25 | /* Sanity check to make sure we calculate the packet size correctly. We 26 | * don't expect any fragments here since libnids should have defragmented 27 | * stuff for us; this is for testing with nids_register_ip_frag(). */ 28 | if (packet->ip_off & htons(IP_MF | IP_OFFMASK)) 29 | return; 30 | 31 | scanlogd_process_packet((struct header *)packet, 32 | (unsigned int)ntohs(packet->ip_len)); 33 | } 34 | 35 | static void dummy_syslog(int type, int errnum, struct ip *iph, void *data) 36 | { 37 | } 38 | 39 | int in_init(void) 40 | { 41 | #ifdef SCANLOGD_DEVICE 42 | nids_params.device = SCANLOGD_DEVICE; 43 | #endif 44 | 45 | #if defined(NIDS_MAJOR) && (NIDS_MAJOR > 1 || NIDS_MINOR >= 14) 46 | nids_params.n_tcp_streams = 0; 47 | #else 48 | nids_params.n_tcp_streams = 1; 49 | #endif 50 | nids_params.n_hosts = HASH_SIZE; 51 | nids_params.syslog = dummy_syslog; 52 | nids_params.scan_num_hosts = 0; 53 | nids_params.pcap_filter = SCANLOGD_PCAP_FILTER; 54 | #if defined(NIDS_MAJOR) && (NIDS_MAJOR > 1 || NIDS_MINOR >= 14) 55 | nids_params.promisc = SCANLOGD_PROMISC; 56 | #endif 57 | 58 | if (!nids_init()) { 59 | fprintf(stderr, "nids_init: %s\n", nids_errbuf); 60 | return 1; 61 | } 62 | 63 | return 0; 64 | } 65 | 66 | void in_run(void (*process_packet)(struct header *packet, int size)) 67 | { 68 | scanlogd_process_packet = process_packet; 69 | nids_register_ip(nids_process_packet); 70 | 71 | nids_run(); 72 | } 73 | -------------------------------------------------------------------------------- /in_pcap.c: -------------------------------------------------------------------------------- 1 | #define _BSD_SOURCE 2 | #define _DEFAULT_SOURCE 3 | #include 4 | 5 | #include 6 | 7 | #include "params.h" 8 | #include "in.h" 9 | 10 | static pcap_t *p; 11 | 12 | int in_init(void) 13 | { 14 | char *device; 15 | char error[PCAP_ERRBUF_SIZE]; 16 | struct bpf_program filter; 17 | 18 | #ifdef SCANLOGD_DEVICE 19 | device = SCANLOGD_DEVICE; 20 | #else 21 | if (!(device = pcap_lookupdev(error))) { 22 | fprintf(stderr, "pcap_lookupdev: %s\n", error); 23 | return 1; 24 | } 25 | #endif 26 | 27 | if (!(p = pcap_open_live(device, sizeof(struct header), 28 | SCANLOGD_PROMISC, 0, error))) { 29 | fprintf(stderr, "pcap_open_live: %s\n", error); 30 | return 1; 31 | } 32 | 33 | if (pcap_compile(p, &filter, SCANLOGD_PCAP_FILTER, 1, 0)) { 34 | pcap_perror(p, "pcap_compile"); 35 | return 1; 36 | } 37 | 38 | if (pcap_setfilter(p, &filter)) { 39 | pcap_perror(p, "pcap_setfilter"); 40 | return 1; 41 | } 42 | 43 | return 0; 44 | } 45 | 46 | void in_run(void (*process_packet)(struct header *packet, int size)) 47 | { 48 | int hw_size, size; 49 | char *packet; 50 | struct pcap_pkthdr header; 51 | 52 | switch (pcap_datalink(p)) { 53 | case DLT_RAW: 54 | case DLT_SLIP: 55 | hw_size = 0; 56 | break; 57 | 58 | case DLT_PPP: 59 | hw_size = 4; 60 | break; 61 | 62 | case DLT_EN10MB: 63 | default: 64 | hw_size = 14; 65 | } 66 | 67 | while (1) 68 | if ((packet = (char *)pcap_next(p, &header))) { 69 | packet += hw_size; 70 | size = header.caplen - hw_size; 71 | process_packet((struct header *)packet, size); 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /params.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Configurable compile-time parameters for scanlogd. 3 | */ 4 | 5 | #ifndef _SCANLOGD_PARAMS_H 6 | #define _SCANLOGD_PARAMS_H 7 | 8 | #include 9 | #include 10 | 11 | /* 12 | * An unprivileged dummy user to run as. The user and its UID must not be 13 | * used for any other purpose (that is, don't use "nobody" here). You can 14 | * #undef this to let scanlogd run as root, but this is not recommended. 15 | */ 16 | #define SCANLOGD_USER "scanlogd" 17 | 18 | /* 19 | * An empty root-owned directory to chroot to. THE DIRECTORY AND ITS PARENT 20 | * DIRECTORIES MUST NOT BE WRITABLE BY ANYONE BUT ROOT. 21 | */ 22 | #define SCANLOGD_CHROOT "/var/empty" 23 | 24 | /* 25 | * Device to monitor, if you're using libnids or libpcap directly. #undef 26 | * this either if you're using the raw socket interface on Linux instead, 27 | * or if you'd like to let libpcap autodetect this for you. 28 | * 29 | * Recent versions of libpcap support magic device name "any" and recent 30 | * libnids supports magic device name "all". 31 | */ 32 | #undef SCANLOGD_DEVICE 33 | 34 | /* 35 | * Whether we want scanlogd to set the device into promiscuous mode, for 36 | * use with libpcap. 37 | */ 38 | #define SCANLOGD_PROMISC 0 39 | 40 | /* 41 | * The libpcap filter expression to use when scanlogd is built with libnids 42 | * or direct libpcap support. The intent is to reduce CPU load by hopefully 43 | * filtering out most of the uninteresting packets at the kernel level if 44 | * supported by libpcap on a given platform. 45 | */ 46 | #define SCANLOGD_PCAP_FILTER \ 47 | "tcp and " \ 48 | "((tcp[13] != 0x10 and tcp[13] != 0x18) or ip[6:2] & 0x3fff != 0)" 49 | 50 | /* 51 | * High port numbers have a lower weight to reduce the frequency of false 52 | * positives, such as from passive mode FTP transfers. 53 | */ 54 | #define PORT_WEIGHT_PRIV 3 55 | #define PORT_WEIGHT_HIGH 1 56 | 57 | /* 58 | * Port scan detection thresholds: at least COUNT ports need to be scanned 59 | * from the same source, with no longer than DELAY seconds between ports. 60 | */ 61 | #define SCAN_MIN_COUNT 7 62 | #define SCAN_MAX_COUNT (SCAN_MIN_COUNT * PORT_WEIGHT_PRIV) 63 | #define SCAN_WEIGHT_THRESHOLD SCAN_MAX_COUNT 64 | #define SCAN_DELAY_THRESHOLD 3 65 | 66 | /* 67 | * Log flood detection thresholds: temporarily stop logging if more than 68 | * COUNT port scans are detected with no longer than DELAY seconds between 69 | * them. 70 | */ 71 | #define LOG_COUNT_THRESHOLD 5 72 | #define LOG_DELAY_THRESHOLD 20 73 | 74 | /* 75 | * Log line length limit, such as to fit into one SMS message. #undef this 76 | * for no limit. 77 | */ 78 | #define LOG_MAX_LENGTH (160 - 40) 79 | 80 | /* 81 | * You might want to adjust these for using your tiny append-only log file. 82 | */ 83 | #define SYSLOG_IDENT "scanlogd" 84 | #define SYSLOG_FACILITY LOG_DAEMON 85 | #define SYSLOG_LEVEL LOG_ALERT 86 | 87 | /* 88 | * Keep track of up to LIST_SIZE source addresses, using a hash table of 89 | * HASH_SIZE entries for faster lookups, but limiting hash collisions to 90 | * HASH_MAX source addresses per the same hash value. 91 | */ 92 | #define LIST_SIZE 0x100 93 | #define HASH_LOG 9 94 | #define HASH_SIZE (1 << HASH_LOG) 95 | #define HASH_MAX 0x10 96 | 97 | #endif 98 | -------------------------------------------------------------------------------- /scanlogd.8: -------------------------------------------------------------------------------- 1 | .TH SCANLOGD 8 "2 June 2004" "Openwall Project" "System Administration" 2 | .SH NAME 3 | scanlogd \- detects and logs TCP port scans 4 | .SH SYNOPSIS 5 | .B scanlogd 6 | .SH DESCRIPTION 7 | .B scanlogd 8 | detects port scans and writes one line per scan via the 9 | .BR syslog (3) 10 | mechanism. If a source address sends multiple 11 | packets to different ports in a short time, the event will be 12 | logged. The format of the messages is: 13 | .LP 14 | .BR saddr "[:" sport "] to " daddr " [and others,] ports " port "[, " port "...], ..., " flags "[, TOS " TOS "][, TTL " TTL "] @" HH:MM:SS 15 | .PP 16 | The fields in square brackets are optional; 17 | .BR sport ", " TOS ", and " TTL 18 | will only be displayed if they were constant during the scan. 19 | .PP 20 | The 21 | .B flags 22 | field represents TCP control bits seen in packets 23 | coming to the system from the address of the scan. It is a 24 | combination of eight characters, with each corresponding to 25 | one of the six defined and two reserved TCP control bits (see 26 | RFC 793). Control bits that were always set are encoded with an 27 | uppercase letter, and a lowercase letter is used if the bit was 28 | always clear. A question mark is used to indicate bits that 29 | changed from packet to packet. 30 | .SH INTERFACES 31 | In order to do its job, 32 | .B scanlogd 33 | needs a way to obtain raw IP packets that either come to the system 34 | .B scanlogd 35 | is running on, or travel across a network segment that is directly 36 | connected to the system. Current versions of 37 | .B scanlogd 38 | can be built with support for one of several packet capture interfaces. 39 | .PP 40 | .B scanlogd 41 | is aware of the 42 | .B raw socket 43 | interface on Linux, 44 | .BR libnids , 45 | and 46 | .BR libpcap . 47 | .PP 48 | The use of 49 | .B libpcap 50 | alone is discouraged. If you're on a system other than Linux and/or 51 | want to monitor the traffic of an entire network at once, you should 52 | be using 53 | .B libnids 54 | in order to handle fragmented IP packets. 55 | .SH COMPILE-TIME DEFAULTS 56 | At least 7 different privileged or 21 non-privileged ports, or a 57 | weighted combination of those, have to be accessed with no longer 58 | than 3 seconds between the accesses to be treated as a scan. 59 | If more than 5 scans are detected within 20 seconds, that event 60 | will be logged and logging will be stopped temporarily. 61 | .PP 62 | Logging is done with a facility of 63 | .B daemon 64 | and a priority level 65 | .BR alert . 66 | .PP 67 | .B scanlogd 68 | should be started as root since it needs access to a packet capture 69 | interface. 70 | By default, it chroots to 71 | .I /var/empty 72 | and switches to running as user 73 | .B scanlogd 74 | after the packet capture interface is initialized. 75 | .SH EXIT STATUS 76 | If the daemon couldn't start up successfully, it will exit with a 77 | status of 1. 78 | .SH USAGE 79 | You're expected to create a dummy user for 80 | .B scanlogd 81 | to run as. Make sure you allocate unique UID and GID to the user. 82 | .PP 83 | In most cases, 84 | .B scanlogd 85 | should be started from a rc.d script on system startup. 86 | .PP 87 | In /etc/syslog.conf you may use something like: 88 | .PP 89 | daemon.alert /var/log/alert 90 | .SH SECURITY NOTES 91 | As the name indicates, 92 | .B scanlogd 93 | only logs port scans. 94 | .B It does not prevent them. 95 | You will only receive summarized information in the system's log. 96 | .PP 97 | Obviously, the source address of port scans can be spoofed. 98 | .B Don't take any action against the source of attacks 99 | .B unless other evidence is available. 100 | Sometimes IP addresses are shared between many people; this is the 101 | case for ISP shell servers, dynamic dialup pools, and corporate 102 | networks behind NAT (masquerading). 103 | .SH BUGS 104 | Due to the nature of port scans, both false positives (detecting a 105 | scan when there isn't one) and false negatives (not detecting a scan 106 | when there's one) are possible. In particular, false positives occur 107 | when many small files are transferred rapidly with passive mode FTP. 108 | .SH AUTHORS 109 | .nf 110 | Solar Designer 111 | .fi 112 | Steffen Dettmer 113 | wrote the initial version of this manual page. 114 | .SH SEE ALSO 115 | .BR syslog (3), 116 | .BR syslog.conf (5), 117 | .BR libnids (3), 118 | .BR pcap (3) 119 | .nf 120 | .BR scanlogd " home page: http://www.openwall.com/scanlogd/" 121 | .BR "Phrack Magazine" ", issue 53, article 13" 122 | -------------------------------------------------------------------------------- /scanlogd.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 1998-2012 by Solar Designer 3 | * See LICENSE 4 | */ 5 | 6 | #define _BSD_SOURCE 7 | #define _DEFAULT_SOURCE 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | 28 | #include "params.h" 29 | #include "in.h" 30 | 31 | static clock_t scan_delay_threshold, log_delay_threshold; 32 | 33 | #define HF_DADDR_CHANGING 0x01 34 | #define HF_SPORT_CHANGING 0x02 35 | #define HF_TOS_CHANGING 0x04 36 | #define HF_TTL_CHANGING 0x08 37 | 38 | /* 39 | * Information we keep per each source address. 40 | */ 41 | struct host { 42 | struct host *next; /* Next entry with the same hash */ 43 | clock_t timestamp; /* Last update time */ 44 | time_t start; /* Entry creation time */ 45 | struct in_addr saddr, daddr; /* Source and destination addresses */ 46 | unsigned short sport; /* Source port */ 47 | int count; /* Number of ports in the list */ 48 | int weight; /* Total weight of ports in the list */ 49 | unsigned short ports[SCAN_MAX_COUNT - 1]; /* List of ports */ 50 | unsigned char tos; /* TOS */ 51 | unsigned char ttl; /* TTL */ 52 | unsigned char flags_or; /* TCP flags OR mask */ 53 | unsigned char flags_and; /* TCP flags AND mask */ 54 | unsigned char flags; /* HF_ flags bitmask */ 55 | }; 56 | 57 | /* 58 | * State information. 59 | */ 60 | static struct { 61 | struct host list[LIST_SIZE]; /* List of source addresses */ 62 | struct host *hash[HASH_SIZE]; /* Hash: pointers into the list */ 63 | int index; /* Oldest entry to be replaced */ 64 | } state; 65 | 66 | /* 67 | * Convert an IP address into a hash table index. 68 | */ 69 | static int hashfunc(struct in_addr addr) 70 | { 71 | unsigned int value; 72 | int hash; 73 | 74 | value = addr.s_addr; 75 | hash = 0; 76 | do { 77 | hash ^= value; 78 | } while ((value >>= HASH_LOG)); 79 | 80 | return hash & (HASH_SIZE - 1); 81 | } 82 | 83 | /* 84 | * Log this port scan. 85 | */ 86 | static void do_log(struct host *info) 87 | { 88 | int limit; 89 | char s_saddr[32]; 90 | char s_daddr[64 + 8 * SCAN_MAX_COUNT]; 91 | char s_flags[16]; 92 | char s_tos[16]; 93 | char s_ttl[16]; 94 | char s_time[32]; 95 | int index, size; 96 | unsigned char mask; 97 | 98 | /* We try to log everything we can at first, then remove port numbers one 99 | * by one if necessary until we fit into the maximum allowed length */ 100 | limit = info->count; 101 | prepare: 102 | 103 | /* Source address and port number, if fixed */ 104 | snprintf(s_saddr, sizeof(s_saddr), 105 | (info->flags & HF_SPORT_CHANGING) ? "%s" : "%s:%u", 106 | inet_ntoa(info->saddr), 107 | (unsigned int)ntohs(info->sport)); 108 | 109 | /* Destination address */ 110 | snprintf(s_daddr, sizeof(s_daddr), "%s%s ports ", 111 | inet_ntoa(info->daddr), 112 | (info->flags & HF_DADDR_CHANGING) ? " and others," : ""); 113 | 114 | /* Scanned port numbers */ 115 | for (index = 0; index < limit; index++) { 116 | size = strlen(s_daddr); 117 | #ifdef LOG_MAX_LENGTH 118 | if (size >= LOG_MAX_LENGTH) { 119 | limit = index; 120 | break; 121 | } 122 | #endif 123 | snprintf(s_daddr + size, sizeof(s_daddr) - size, 124 | "%u, ", (unsigned int)ntohs(info->ports[index])); 125 | } 126 | 127 | /* TCP flags: lowercase letters for "always clear", uppercase for "always 128 | * set", and question marks for "sometimes set". */ 129 | for (index = 0; index < 8; index++) { 130 | mask = 1 << index; 131 | if ((info->flags_or & mask) == (info->flags_and & mask)) { 132 | s_flags[index] = "fsrpauxy"[index]; 133 | if (info->flags_or & mask) 134 | s_flags[index] = 135 | toupper((int)(unsigned char)s_flags[index]); 136 | } else 137 | s_flags[index] = '?'; 138 | } 139 | s_flags[index] = 0; 140 | 141 | /* TOS, if fixed */ 142 | snprintf(s_tos, sizeof(s_tos), 143 | (info->flags & HF_TOS_CHANGING) ? "" : ", TOS %02x", 144 | (unsigned int)info->tos); 145 | 146 | /* TTL, if fixed */ 147 | snprintf(s_ttl, sizeof(s_ttl), 148 | (info->flags & HF_TTL_CHANGING) ? "" : ", TTL %u", 149 | (unsigned int)info->ttl); 150 | 151 | /* Scan start time */ 152 | strftime(s_time, sizeof(s_time), "%X", localtime(&info->start)); 153 | 154 | /* Check against the length limit, and possibly re-format everything */ 155 | #ifdef LOG_MAX_LENGTH 156 | if (strlen(s_saddr) + strlen(s_daddr) + 157 | strlen(s_tos) + strlen(s_ttl) + strlen(s_time) + 158 | (4 + 5 + 8 + 2) > LOG_MAX_LENGTH) { 159 | if (--limit > 0) goto prepare; 160 | } 161 | #endif 162 | 163 | /* Log it all */ 164 | syslog(SYSLOG_LEVEL, 165 | "%s to %s..., %s%s%s @%s", 166 | s_saddr, s_daddr, s_flags, s_tos, s_ttl, s_time); 167 | } 168 | 169 | /* 170 | * Log this port scan unless we're being flooded. 171 | */ 172 | static void safe_log(struct host *info) 173 | { 174 | static clock_t last = 0; 175 | static int count = 0; 176 | clock_t now; 177 | 178 | now = info->timestamp; 179 | if (now - last > log_delay_threshold || now < last) count = 0; 180 | if (++count <= LOG_COUNT_THRESHOLD + 1) last = now; 181 | 182 | if (count <= LOG_COUNT_THRESHOLD) 183 | do_log(info); 184 | else if (count == LOG_COUNT_THRESHOLD + 1) 185 | syslog(SYSLOG_LEVEL, "More possible port scans follow"); 186 | } 187 | 188 | /* 189 | * Process a TCP packet. 190 | */ 191 | static void process_packet(struct header *packet, int size) 192 | { 193 | struct ip *ip; 194 | struct tcphdr *tcp; 195 | struct in_addr addr; 196 | unsigned short port; 197 | unsigned char flags; 198 | struct tms buf; 199 | clock_t now; 200 | struct host *current, *last, **head; 201 | int hash, index, count; 202 | 203 | /* Get the IP and TCP headers */ 204 | ip = &packet->ip; 205 | tcp = (struct tcphdr *)((char *)packet + ((int)ip->ip_hl << 2)); 206 | 207 | /* Sanity check */ 208 | if (ip->ip_p != IPPROTO_TCP || (ip->ip_off & htons(IP_OFFMASK)) || 209 | (char *)tcp + sizeof(struct tcphdr) > (char *)packet + size) 210 | return; 211 | 212 | /* Get the source address, destination port, and TCP flags */ 213 | addr = ip->ip_src; 214 | port = tcp->th_dport; 215 | flags = tcp->th_flags; 216 | 217 | /* We're using IP address 0.0.0.0 for a special purpose here, so don't let 218 | * them spoof us. */ 219 | if (!addr.s_addr) return; 220 | 221 | /* Use times(2) here not to depend on someone setting the time while we're 222 | * running; we need to be careful with possible return value overflows. */ 223 | now = times(&buf); 224 | 225 | /* Do we know this source address already? */ 226 | count = 0; 227 | last = NULL; 228 | if ((current = *(head = &state.hash[hash = hashfunc(addr)]))) 229 | do { 230 | if (current->saddr.s_addr == addr.s_addr) break; 231 | count++; 232 | if (current->next) last = current; 233 | } while ((current = current->next)); 234 | 235 | /* We know this address, and the entry isn't too old. Update it. */ 236 | if (current) 237 | if (now - current->timestamp <= scan_delay_threshold && 238 | now >= current->timestamp) { 239 | /* Just update the TCP flags if we've seen this port already */ 240 | for (index = 0; index < current->count; index++) 241 | if (current->ports[index] == port) { 242 | current->flags_or |= flags; 243 | current->flags_and &= flags; 244 | return; 245 | } 246 | 247 | /* ACK and/or RST to a new port? This could be an outgoing connection. */ 248 | if (flags & (TH_ACK | TH_RST)) return; 249 | 250 | /* Packet to a new port, and not ACK: update the timestamp */ 251 | current->timestamp = now; 252 | 253 | /* Logged this scan already? Then leave. */ 254 | if (current->weight >= SCAN_WEIGHT_THRESHOLD) return; 255 | 256 | /* Update the TCP flags */ 257 | current->flags_or |= flags; 258 | current->flags_and &= flags; 259 | 260 | /* Specify if destination address, source port, TOS, or TTL are not fixed */ 261 | if (current->daddr.s_addr != ip->ip_dst.s_addr) 262 | current->flags |= HF_DADDR_CHANGING; 263 | if (current->sport != tcp->th_sport) 264 | current->flags |= HF_SPORT_CHANGING; 265 | if (current->tos != ip->ip_tos) 266 | current->flags |= HF_TOS_CHANGING; 267 | if (current->ttl != ip->ip_ttl) 268 | current->flags |= HF_TTL_CHANGING; 269 | 270 | /* Update the total weight */ 271 | current->weight += (ntohs(port) < 1024) ? 272 | PORT_WEIGHT_PRIV : PORT_WEIGHT_HIGH; 273 | 274 | /* Got enough destination ports to decide that this is a scan? Then log it. */ 275 | if (current->weight >= SCAN_WEIGHT_THRESHOLD) { 276 | safe_log(current); 277 | return; 278 | } 279 | 280 | /* Remember the new port */ 281 | if (current->count < SCAN_MAX_COUNT - 1) 282 | current->ports[current->count++] = port; 283 | 284 | return; 285 | } 286 | 287 | /* We know this address, but the entry is outdated. Mark it unused and 288 | * remove from the hash table. We'll allocate a new entry instead since 289 | * this one might get re-used too soon. */ 290 | if (current) { 291 | current->saddr.s_addr = 0; 292 | 293 | if (last) 294 | last->next = last->next->next; 295 | else if (*head) 296 | *head = (*head)->next; 297 | last = NULL; 298 | } 299 | 300 | /* We don't need an ACK from a new source address */ 301 | if (flags & TH_ACK) return; 302 | 303 | /* Got too many source addresses with the same hash value? Then remove the 304 | * oldest one from the hash table, so that they can't take too much of our 305 | * CPU time even with carefully chosen spoofed IP addresses. */ 306 | if (count >= HASH_MAX && last) last->next = NULL; 307 | 308 | /* We're going to re-use the oldest list entry, so remove it from the hash 309 | * table first (if it is really already in use, and isn't removed from the 310 | * hash table already because of the HASH_MAX check above). */ 311 | 312 | /* First, find it */ 313 | if (state.list[state.index].saddr.s_addr) 314 | head = &state.hash[hashfunc(state.list[state.index].saddr)]; 315 | else 316 | head = &last; 317 | last = NULL; 318 | if ((current = *head)) 319 | do { 320 | if (current == &state.list[state.index]) break; 321 | last = current; 322 | } while ((current = current->next)); 323 | 324 | /* Then, remove it */ 325 | if (current) { 326 | if (last) 327 | last->next = last->next->next; 328 | else if (*head) 329 | *head = (*head)->next; 330 | } 331 | 332 | /* Get our list entry */ 333 | current = &state.list[state.index++]; 334 | if (state.index >= LIST_SIZE) state.index = 0; 335 | 336 | /* Link it into the hash table */ 337 | head = &state.hash[hash]; 338 | current->next = *head; 339 | *head = current; 340 | 341 | /* And fill in the fields */ 342 | current->timestamp = now; 343 | current->start = time(NULL); 344 | current->saddr = addr; 345 | current->daddr = ip->ip_dst; 346 | current->sport = tcp->th_sport; 347 | current->count = 1; 348 | current->weight = (ntohs(port) < 1024) ? 349 | PORT_WEIGHT_PRIV : PORT_WEIGHT_HIGH; 350 | current->ports[0] = port; 351 | current->tos = ip->ip_tos; 352 | current->ttl = ip->ip_ttl; 353 | current->flags_or = current->flags_and = flags; 354 | current->flags = 0; 355 | } 356 | 357 | /* 358 | * Simple, but we only expect errors at startup, so this should suffice. 359 | */ 360 | void pexit(char *name) 361 | { 362 | perror(name); 363 | exit(1); 364 | } 365 | 366 | #ifdef SCANLOGD_USER 367 | static void drop_root(void) 368 | { 369 | struct passwd *pw; 370 | gid_t groups[2]; 371 | 372 | errno = 0; 373 | if (!(pw = getpwnam(SCANLOGD_USER))) { 374 | fprintf(stderr, 375 | "getpwnam(\"" SCANLOGD_USER "\"): %s\n", 376 | errno ? strerror(errno) : "No such user"); 377 | exit(1); 378 | } 379 | 380 | #ifdef SCANLOGD_CHROOT 381 | if (chroot(SCANLOGD_CHROOT)) pexit("chroot(\"" SCANLOGD_CHROOT "\")"); 382 | if (chdir("/")) pexit("chdir"); 383 | #endif 384 | 385 | groups[0] = groups[1] = pw->pw_gid; 386 | if (setgroups(1, groups)) pexit("setgroups"); 387 | if (setgid(pw->pw_gid)) pexit("setgid"); 388 | if (setuid(pw->pw_uid)) pexit("setuid"); 389 | } 390 | #elif defined(SCANLOGD_CHROOT) 391 | #warning SCANLOGD_CHROOT makes no sense without SCANLOGD_USER; ignored. 392 | #endif 393 | 394 | /* 395 | * Hmm, what could this be? 396 | */ 397 | int main(void) 398 | { 399 | int dev_null_fd; 400 | clock_t clk_tck; 401 | 402 | /* Initialize the packet capture interface */ 403 | if (in_init()) return 1; 404 | 405 | /* Prepare for daemonizing */ 406 | if (chdir("/")) pexit("chdir"); 407 | setsid(); 408 | 409 | /* Must do these before chroot'ing */ 410 | tzset(); 411 | openlog(SYSLOG_IDENT, LOG_NDELAY, SYSLOG_FACILITY); 412 | dev_null_fd = open("/dev/null", O_RDONLY); 413 | 414 | /* Also do this early - who knows what this system's sysconf() relies upon */ 415 | #if defined(_SC_CLK_TCK) || !defined(CLK_TCK) 416 | clk_tck = sysconf(_SC_CLK_TCK); 417 | #else 418 | clk_tck = CLK_TCK; 419 | #endif 420 | scan_delay_threshold = SCAN_DELAY_THRESHOLD * clk_tck; 421 | log_delay_threshold = LOG_DELAY_THRESHOLD * clk_tck; 422 | 423 | /* We can drop root now */ 424 | #ifdef SCANLOGD_USER 425 | drop_root(); 426 | #endif 427 | 428 | /* Become a daemon */ 429 | switch (fork()) { 430 | case -1: 431 | pexit("fork"); 432 | 433 | case 0: 434 | break; 435 | 436 | default: 437 | /* in_init() could have registered an atexit(3) function to restore the 438 | * interface, but this is not a real exit, yet (in fact, we're starting 439 | * up), so we use _exit(2) rather than exit(3) here */ 440 | _exit(0); 441 | } 442 | 443 | setsid(); 444 | 445 | /* Just assume that stdin, stdout, and stderr fd's were open at startup and 446 | * thus are indeed not allocated to anything else. */ 447 | if (dev_null_fd >= 0) { 448 | dup2(dev_null_fd, STDIN_FILENO); 449 | dup2(dev_null_fd, STDOUT_FILENO); 450 | dup2(dev_null_fd, STDERR_FILENO); 451 | if (dev_null_fd >= 3) close(dev_null_fd); 452 | } 453 | 454 | /* Initialize the state. All source IP addresses are set to 0.0.0.0, which 455 | * means the list entries aren't in use yet. */ 456 | memset(&state, 0, sizeof(state)); 457 | 458 | /* Let's start */ 459 | in_run(process_packet); 460 | 461 | /* We shouldn't reach this */ 462 | return 1; 463 | } 464 | -------------------------------------------------------------------------------- /scanlogd.init: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # $Owl: Owl/packages/scanlogd/scanlogd/scanlogd.init,v 1.3 2005/11/16 13:31:51 solar Exp $ 3 | # 4 | # chkconfig: - 35 85 5 | # description: \ 6 | # scanlogd detects and logs TCP port scans. 7 | # processname: scanlogd 8 | 9 | # Source function library. 10 | . /etc/rc.d/init.d/functions 11 | 12 | OWL_STARTUP_ENABLE=1 13 | 14 | case "$1" in 15 | start) 16 | if [ "0$OWL_STARTUP_VERSION" -ge 3 ]; then 17 | daemon --expect-user scanlogd scanlogd 18 | else 19 | daemon scanlogd 20 | fi 21 | ;; 22 | stop) 23 | if [ "0$OWL_STARTUP_VERSION" -ge 3 ]; then 24 | killproc --expect-user scanlogd scanlogd 25 | else 26 | killproc scanlogd 27 | fi 28 | ;; 29 | restart) 30 | $0 stop 31 | $0 start 32 | ;; 33 | status) 34 | if [ "0$OWL_STARTUP_VERSION" -ge 3 ]; then 35 | status --expect-user scanlogd scanlogd 36 | else 37 | status scanlogd 38 | fi 39 | ;; 40 | *) 41 | echo "Usage: scanlogd {start|stop|restart|status}" 42 | exit 1 43 | esac 44 | 45 | exit $? 46 | -------------------------------------------------------------------------------- /scanlogd.spec: -------------------------------------------------------------------------------- 1 | Summary: A tool to detect and log TCP port scans. 2 | Name: scanlogd 3 | Version: 2.2.8 4 | Release: owl1 5 | License: BSD-compatible 6 | Group: System Environment/Daemons 7 | URL: http://www.openwall.com/scanlogd/ 8 | Source: ftp://ftp.openwall.com/pub/projects/scanlogd/scanlogd-%version.tar.gz 9 | Requires(post,preun): chkconfig, grep, shadow-utils 10 | BuildRoot: /override/%name-%version 11 | 12 | %description 13 | scanlogd detects port scans and writes one line per scan via the syslog(3) 14 | mechanism. If a source address sends multiple packets to different ports 15 | in a short time, the event will be logged. 16 | 17 | %prep 18 | %setup -q 19 | 20 | %build 21 | %__make linux CFLAGS="-Wall %optflags" 22 | 23 | %install 24 | rm -rf %buildroot 25 | mkdir -p %buildroot{%_sbindir,%_mandir/man8,/etc/rc.d/init.d} 26 | 27 | install -m 700 scanlogd %buildroot%_sbindir/ 28 | install -m 644 scanlogd.8 %buildroot%_mandir/man8/ 29 | install -m 700 scanlogd.init %buildroot/etc/rc.d/init.d/scanlogd 30 | 31 | %pre 32 | grep -q ^scanlogd: /etc/group || groupadd -g 199 scanlogd 33 | grep -q ^scanlogd: /etc/passwd || 34 | useradd -g scanlogd -u 199 -d / -s /bin/false -M scanlogd 35 | rm -f /var/run/scanlogd.restart 36 | if [ $1 -ge 2 ]; then 37 | /etc/rc.d/init.d/scanlogd status && touch /var/run/scanlogd.restart || : 38 | /etc/rc.d/init.d/scanlogd stop || : 39 | fi 40 | 41 | %post 42 | /sbin/chkconfig --add scanlogd 43 | test -f /var/run/scanlogd.restart && /etc/rc.d/init.d/scanlogd start || : 44 | rm -f /var/run/scanlogd.restart 45 | 46 | %preun 47 | if [ $1 -eq 0 ]; then 48 | /etc/rc.d/init.d/scanlogd stop || : 49 | /sbin/chkconfig --del scanlogd 50 | fi 51 | 52 | %files 53 | %defattr(-,root,root) 54 | %doc LICENSE README.md 55 | %_sbindir/scanlogd 56 | %_mandir/man8/scanlogd.8* 57 | %config /etc/rc.d/init.d/scanlogd 58 | 59 | %changelog 60 | * Wed Mar 10 2021 Solar Designer 2.2.8-owl1 61 | - Define _DEFAULT_SOURCE for new glibc, keep _BSD_SOURCE for old glibc 62 | - Clarify that SCANLOGD_CHROOT directory must be root-owned 63 | - Add README.md based on man page 64 | 65 | * Mon Jun 30 2014 (galaxyMaster) 2.2.7-owl2 66 | - Replaced the deprecated PreReq tag with Requires(post,preun). 67 | 68 | * Wed Aug 15 2012 Solar Designer 2.2.7-owl1 69 | - Fixed an off-by-one bug in the safety check against SCAN_MAX_COUNT. In 70 | properly configured builds of scanlogd, SCAN_WEIGHT_THRESHOLD is such that it's 71 | always hit before SCAN_MAX_COUNT would be hit, so this bug did not matter for 72 | those. However, other projects reusing this code could make these settings 73 | runtime (mis)configurable, thereby exposing the bug. Luckily, in scanlogd 74 | itself, if it is misconfigured like this, a port number written right beyond 75 | the array limit would overwrite relatively unimportant data only (part of the 76 | same struct), which could be directly provided/spoofed by the remote system 77 | anyway. However, in other projects reusing code from scanlogd this could be 78 | different. Thanks to Florian Westphal for reporting this bug. 79 | - Switched to heavily cut-down BSD license. 80 | 81 | * Sun Mar 05 2006 Solar Designer 2.2.6-owl1 82 | - Use sysconf(_SC_CLK_TCK) instead of CLK_TCK when _SC_CLK_TCK is known to be 83 | available or CLK_TCK is not. 84 | 85 | * Thu Jun 10 2004 Solar Designer 2.2.5-owl1 86 | - Dropped the cleanup() stuff because it was not async-signal-safe and 87 | to implement it properly would depend on pcap_breakloop() and on a 88 | non-existent(?) equivalent for it with libnids; this code was only used 89 | when running as root which is something to not do anyway. 90 | 91 | * Thu Jun 03 2004 Solar Designer 2.2.4-owl1 92 | - Detach from the tty by opening /dev/null on fd 0, 1, 2. 93 | 94 | * Wed Jun 02 2004 Solar Designer 2.2.3-owl1 95 | - When built with libnids or direct libpcap support, use Pavel Kankovsky's 96 | smart pcap expression, with a minor enhancement. 97 | - Explained "any" and "all" magic device names in a comment in params.h. 98 | - Dropped the rlog stuff; librlog was never released. 99 | - chroot to /var/empty. 100 | - Do register scanlogd with chkconfig, but don't enable it for any runlevels 101 | by default. 102 | - Moved this spec file and the init script to under scanlogd/ to include 103 | them in the non-Owl-specific distribution of scanlogd. 104 | 105 | * Sun May 23 2004 Solar Designer 2.2.2-owl1 106 | - #include for exit(3) (apparently this is actually needed on 107 | FreeBSD). 108 | - Obfuscated e-mail addresses in the man page and sources. 109 | 110 | * Wed May 08 2002 Solar Designer 2.2.1-owl1 111 | - Start after syslogd. 112 | - Don't abuse glibc-internal __feature macros. 113 | 114 | * Wed Feb 06 2002 Solar Designer 115 | - Enforce our new spec file conventions. 116 | 117 | * Thu Jul 12 2001 Solar Designer 118 | - Packaged scanlogd for Owl. 119 | --------------------------------------------------------------------------------