PScanDetect version 0.8 is a utility that will detect TCP-based portscans. Tested under FreeBSD 5.1, OpenBSD 3.3, and Slackware Linux 9.0.
1b74539c9e0cfe25914068560f46eada7a570bd6136dcd7882274d57af719da3
/*
* TCP Portscan Detector v0.8
* os: Linux, FreeBSD, OpenBSD
* author: dodo <dodo@darkwired.org>
* date: 23-dec-2003
*
* description:
* this tool can detect TCP portscans and alerts them
* check -h for more info
* tested on: FreeBSD 5.1, OpenBSD 3.3, Slackware linux 9.0
*
* license:
* this code is licensed under the GNU GPL
* by compiling this, you agree to this license
*
* compilation:
* gcc PScanDetect.c -o pscandetect -lpcap
*
* todo:
* SCAN_INSPECT detection
*
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <time.h>
#include <sys/ioctl.h>
#include <sys/socket.h>
#include <net/if.h>
#ifndef __OpenBSD__
#include <net/ethernet.h>
#endif
#include <netinet/in.h>
#include <netinet/if_ether.h>
#include <netinet/tcp.h>
#include <arpa/inet.h>
#include <pcap.h>
#ifdef __Win32__
#error "you idiot, I did not code this for Win32!"
#endif
#ifdef __Linux__
#include <netinet/ether.h>
#endif
/* define the scan types */
#define SCAN_SINGLESTEP 0x1
#define SCAN_LARGESTEP 0x2
#define SCAN_INSPECT 0x3
#define SCAN_MANYHIGH 0x4
/* define boundaries */
#define MAX_BLASTERS 20
#define MAX_SCANNERS 20
#define MAX_PORTS 20
#define BOUNDARY_MANYHIGH 5000
/* define misc constants */
#define VERSION 0.8
#define NUM_TYPES 4
#define NUM_DROP_SECS 5
#define NAME_SCAN_SINGLESTEP "SCAN_SINGLESTEP"
#define NAME_SCAN_LARGESTEP "SCAN_LARGESTEP"
#define NAME_SCAN_INSPECT "SCAN_INSPECT"
#define NAME_SCAN_MANYHIGH "SCAN_MANYHIGH"
struct my_ip {
u_int8_t ip_vhl; /* header length, version */
#define IP_V(ip) (((ip)->ip_vhl & 0xf0) >> 4)
#define IP_HL(ip) ((ip)->ip_vhl & 0x0f)
u_int8_t ip_tos; /* type of service */
u_int16_t ip_len; /* total length */
u_int16_t ip_id; /* identification */
u_int16_t ip_off; /* fragment offset field */
#define IP_DF 0x4000 /* dont fragment flag */
#define IP_MF 0x2000 /* more fragments flag */
#define IP_OFFMASK 0x1fff /* mask for fragmenting bits */
u_int8_t ip_ttl; /* time to live */
u_int8_t ip_p; /* protocol */
u_int16_t ip_sum; /* checksum */
struct in_addr ip_src,ip_dst; /* source and dest address */
};
struct BlastRadius {
unsigned short id; /* identifier */
unsigned short ports[MAX_PORTS]; /* ports accessed */
unsigned short portsn; /* port pointer */
unsigned long birth; /* since when? */
unsigned long lporttime; /* time when last added port */
unsigned short active; /* recycling? */
struct in_addr host; /* the bastard */
};
struct Scanner {
unsigned short id; /* identifier */
unsigned short portsn; /* num ports accessed */
unsigned char type; /* scan type */
unsigned long lporttime; /* time when last added port */
struct in_addr host; /* the bastard */
};
/* interface attributes */
char *dev;
char *logfile = NULL;
FILE *logfd;
unsigned short int level = 3;
unsigned char promiscuous = 0;
unsigned char verbose = 0;
/* internal attributes */
char selfhost[20];
char errbuf[PCAP_ERRBUF_SIZE];
pcap_t *handle;
struct pcap_pkthdr header;
const u_char *packet;
struct ether_header *data_ether;
const struct my_ip* data_ip;
const struct tcphdr* data_tcp;
struct BlastRadius *blasters[MAX_BLASTERS];
struct Scanner *scanners[MAX_SCANNERS];
unsigned long int blastsize = 0;
unsigned long int scanrsize = 0;
int h;
/* methods */
void initPcap();
char *getDevice();
void getDeviceIP();
void setFilter();
void openLive();
void closeLive();
void doLoop();
void nibblePacket();
void inspectBlast(struct in_addr blasthost, unsigned int port);
char addBR(struct in_addr blasthost, unsigned short int port);
void replaceBR(struct in_addr blasthost, unsigned int port);
void updateBR(struct in_addr blasthost, unsigned short int port);
char checkBR(struct in_addr blasthost);
char checkBRPort(struct BlastRadius *br, unsigned short int port);
char checkBRPortMax(struct BlastRadius *br);
void outputAlert(struct BlastRadius *br, char type);
void dumpBR();
void cleanBR();
char checkSelf(struct in_addr blasthost);
char checkScanner(struct in_addr blasthost);
char addScanner(struct in_addr blasthost, unsigned short portsn, unsigned char type);
char updateScanner(struct in_addr blasthost);
void dumpScanners();
void usage(char *self);
char parseOptions(int argc, char *argv[]);
int main(int argc, char *argv[])
{
int i;
dev = getDevice();
if(parseOptions(argc, argv)==0) usage(argv[0]);
getDeviceIP();
openLive();
setFilter();
doLoop();
closeLive();
dumpBR();
}
void usage(char *self)
{
fprintf(stdout,
"\nTCP Portscan Detector v%2.1f\n"
"https://www.darkwired.org/\n\n"
"usage: %s [options]\n"
"options:\n"
" -l <file> output to logfile\n"
" -a <level> alert when scan alert type <= then <level> (default 3)\n"
" -d <device> use this device to detect scans on\n"
" -p enable promiscuous mode (detect scans in all traffic on line)\n"
" -v enable verbose mode\n"
" -h show this\n"
"scan alert types:\n"
" 0x1 SCAN_SINGLESTEP\n"
" 0x2 SCAN_LARGESTEP\n"
" 0x3 SCAN_INSPECT\n"
" 0x4 SCAN_MANYHIGH\n",
VERSION, self);
exit(-1);
}
char parseOptions(int argc, char *argv[])
/* this will be messy :] */
{
int i;
for(i=0; argc > i; i++) {
if(argv[i][0]=='-') {
switch(argv[i][1]) {
case 'l': {
if((i+1)>=argc) return 0; /* option requires param */
logfile = argv[i+1]; i++;
break;
}
case 'd': {
if((i+1)>=argc) return 0; /* option requires param */
dev = argv[i+1]; i++;
break;
}
case 'a': {
if((i+1)>=argc) return 0; /* option requires param */
level = atoi(argv[i+1]); i++;
if(level<1 || level>NUM_TYPES) {
fprintf(stderr, "bad alert level: %d\n", level);
return 0;
}
break;
}
case 'h': {
return 0;
break;
}
case 'p': {
promiscuous = 1;
break;
}
case 'v': {
verbose = 1;
break;
}
}
}
}
if(verbose==1) {
printf("device: %s\n", dev);
if(logfile == NULL) {
printf("logfile: stdout\n");
} else {
printf("logfile: %s\n", logfile);
}
printf("alertlevel: %d\n", level);
printf("promiscuous: %d\n", promiscuous);
}
return 1;
}
char *getDevice()
{
char *dev;
dev = pcap_lookupdev(errbuf);
return dev;
}
void getDeviceIP()
{
struct sockaddr_in *ssin;
struct ifreq ifr;
unsigned long int ip;
int fd;
fd = socket(PF_INET, SOCK_DGRAM, 0);
/* ripped this peace of code somewhere using google */
memset(&ifr, 0, sizeof(ifr));
ssin = (struct sockaddr_in *)&ifr.ifr_addr;
strncpy(ifr.ifr_name, dev, sizeof(ifr.ifr_name));
ifr.ifr_addr.sa_family = AF_INET;
if (ioctl(fd, SIOCGIFADDR, (char*) &ifr) < 0)
{
fprintf(stderr, "unable to get IP address of device: %s\n", dev);
close(fd);
exit(-1);
}
*ssin = * ( (struct sockaddr_in *)&ifr.ifr_addr);
if(verbose==1)
printf("device ip address: %s\n", inet_ntoa(ssin->sin_addr));
strncpy(selfhost, inet_ntoa(ssin->sin_addr), sizeof(selfhost));
}
void setFilter()
{
struct bpf_program filter;
char filter_app[] = "";
bpf_u_int32 mask;
bpf_u_int32 net;
pcap_lookupnet(dev, &net, &mask, errbuf);
pcap_compile(handle, &filter, filter_app, 0, net);
pcap_setfilter(handle, &filter);
}
void openLive()
{
if(dev == NULL) {
fprintf(stderr, "could not get network device (check permissions)\n");
exit(-1);
}
handle = pcap_open_live(dev, BUFSIZ, promiscuous, 0, errbuf);
}
void closeLive()
{
pcap_close(handle);
}
void nibblePacket
(
u_char *user,
struct pcap_pkthdr *phrd,
u_char *packet
)
{
data_ether = (struct ether_header *)packet;
if(ntohs(data_ether->ether_type)==ETHERTYPE_IP)
{
data_ip = (struct my_ip *)(packet+sizeof(struct ether_header));
data_tcp = (struct tcphdr *)(packet+sizeof(struct ether_header)+sizeof(struct my_ip));
#ifdef __FreeBSD__
inspectBlast(data_ip->ip_src, ntohs(data_tcp->th_dport));
#elif defined __OpenBSD__
inspectBlast(data_ip->ip_src, ntohs(data_tcp->th_dport));
#elif defined __linux__
inspectBlast(data_ip->ip_src, ntohs(data_tcp->dest));
#endif
/*printf("%s:%d -> %s:%d\n", //debugging
inet_ntoa(data_ip->ip_src),
ntohs(data_tcp->source),
inet_ntoa(data_ip->ip_dst),
ntohs(data_tcp->dest)
);
*/
}
}
void doLoop()
{
pcap_loop(handle, -1, (pcap_handler)nibblePacket, 0);
}
void inspectBlast(struct in_addr blasthost, unsigned int port)
{
if(checkSelf(blasthost)) {
return;
}
if(checkScanner(blasthost)==1) {
updateScanner(blasthost);
return;
}
if(checkBR(blasthost)==0) {
if(addBR(blasthost, port)==0) {
replaceBR(blasthost, port);
}
} else {
updateBR(blasthost, port);
}
/* now we check wether someone scanned us */
cleanBR();
}
void dumpBR()
{
int i=0, a=0;
for(i=0; blastsize>i; i++) {
printf("%d BlastRadius->host: %s (%d ports in %d secs) [%d]\n", blasters[i]->id, inet_ntoa(blasters[i]->host), blasters[i]->portsn, (int)(time(NULL) - blasters[i]->lporttime), blasters[i]->active);
for(a=0; blasters[i]->portsn>a; a++) {
printf("%d,", blasters[i]->ports[a]);
}
printf("\n");
}
}
char addBR(struct in_addr blasthost, unsigned short int port)
{
if(blastsize==MAX_BLASTERS) {
return 0;
}
blasters[blastsize] = malloc(sizeof(struct BlastRadius));
blasters[blastsize]->id = blastsize;
blasters[blastsize]->portsn = 0;
blasters[blastsize]->active = 1;
blasters[blastsize]->birth = time(NULL);
blasters[blastsize]->lporttime = time(NULL);
blasters[blastsize]->host = blasthost;
blastsize++;
return 1;
}
void replaceBR(struct in_addr blasthost, unsigned int port)
{
int i;
for(i=0; blastsize>i; i++) {
if(blasters[i]->active==0) {
blasters[i]->portsn = 0;
blasters[i]->active = 1;
blasters[i]->birth = time(NULL);
blasters[i]->lporttime = time(NULL);
blasters[i]->host = blasthost;
break;
}
}
}
void updateBR(struct in_addr blasthost, unsigned short int port)
{
char bh[20];
int i;
strncpy(bh, inet_ntoa(blasthost), (sizeof(bh)-1));
for(i=0; blastsize>i; i++) {
if(
strcmp(inet_ntoa(blasters[i]->host), bh) == 0
&&
blasters[i]->active == 1
) {
if(blasters[i]->portsn < MAX_PORTS) {
if(checkBRPort(blasters[i], port)==0) {
/* update last_portadd field */
blasters[i]->lporttime = time(NULL);
blasters[i]->ports[(blasters[i]->portsn)] = port;
blasters[i]->portsn++;
}
}
}
}
}
char checkBRPort(struct BlastRadius *br, unsigned short int port)
{
int i;
for(i=0; br->portsn > i; i++) {
if(br->ports[i] == port) {
return 1;
}
}
return 0;
}
char checkBRPortMax(struct BlastRadius *br)
{
if(br->portsn==MAX_PORTS) return 1;
return 0;
}
char checkBRPortScan(struct BlastRadius *br, char type)
{
if(type==SCAN_LARGESTEP) {
if(checkBRPortScan(br, SCAN_MANYHIGH)==1)
return 0;
if(br->portsn > 8 && checkBRPortScan(br, SCAN_MANYHIGH)==0)
return 1;
}
if(type==SCAN_SINGLESTEP) {
int i;
int cport = br->ports[0];
if(br->portsn < 8) {
return 0;
}
if(checkBRPortScan(br, SCAN_MANYHIGH)==1) {
return 0;
}
for(i=1; br->portsn > i; i++) {
if( (cport+1) == br->ports[i] ) {
cport++;
} else {
return 0;
}
}
return 1;
}
if(type==SCAN_MANYHIGH) {
int i;
if(br->portsn < 8)
return 0;
for(i=1; br->portsn > i; i++) {
if(br->ports[i] < BOUNDARY_MANYHIGH)
return 0;
}
return 1;
}
return 0;
}
char checkBR(struct in_addr blasthost)
{
char bh[20];
int i;
strncpy(bh, inet_ntoa(blasthost), (sizeof(bh)-1));
for(i=0; blastsize>i; i++) {
if(
strcmp(inet_ntoa(blasters[i]->host), bh) == 0
&&
blasters[i]->active == 1
)
return 1;
}
return 0;
}
char checkBRNoSuspect(struct BlastRadius *br)
{
if((*br).portsn < 4) {
/* thanks to ilja for reminding me :p */
if((int)(time(NULL) - br->lporttime) > NUM_DROP_SECS)
return 1;
}
return 0;
}
void cleanBR()
{
int i;
for(i=0; blastsize>i; i++) {
if(checkBRNoSuspect(blasters[i])) {
blasters[i]->active = 0;
}
if(blasters[i]->portsn > 2 && blasters[i]->active==1) {
if(checkBRPortMax(blasters[i])==1) {
blasters[i]->active = 0;
}
if(checkBRPortScan(blasters[i], SCAN_LARGESTEP)) {
blasters[i]->active = 0;
addScanner(blasters[i]->host, blasters[i]->portsn, SCAN_LARGESTEP);
outputAlert(blasters[i], SCAN_LARGESTEP);
}
if(checkBRPortScan(blasters[i], SCAN_SINGLESTEP)) {
blasters[i]->active = 0;
addScanner(blasters[i]->host, blasters[i]->portsn, SCAN_SINGLESTEP);
outputAlert(blasters[i], SCAN_SINGLESTEP);
}
if(checkBRPortScan(blasters[i], SCAN_MANYHIGH)) {
blasters[i]->active = 0;
addScanner(blasters[i]->host, blasters[i]->portsn, SCAN_MANYHIGH);
outputAlert(blasters[i], SCAN_MANYHIGH);
}
}
}
}
void outputAlert(struct BlastRadius *br, char type)
{
char *scanname;
if(type > level) return;
if(logfile == NULL) {
logfd = fopen("/dev/stdout", "a");
} else {
logfd = fopen(logfile, "a");
}
if(!logfd) {
fprintf(stderr, "unable to open logfile\n");
exit(-1);
}
switch(type) {
case SCAN_SINGLESTEP:
scanname = NAME_SCAN_SINGLESTEP;
break;
case SCAN_LARGESTEP:
scanname = NAME_SCAN_LARGESTEP;
break;
case SCAN_MANYHIGH:
scanname = NAME_SCAN_MANYHIGH;
break;
case SCAN_INSPECT:
scanname = NAME_SCAN_INSPECT;
break;
}
fprintf(logfd, "detected scan from %s, type: %s\n", inet_ntoa(br->host), scanname);
fclose(logfd);
/* good place to use debugging functions */
}
char checkSelf(struct in_addr blasthost)
{
if(
strcmp(selfhost, inet_ntoa(blasthost)) == 0
)
return 1;
return 0;
}
char checkScanner(struct in_addr blasthost)
{
char bh[20];
int i;
strncpy(bh, inet_ntoa(blasthost), (sizeof(bh)-1));
for(i=0; scanrsize>i; i++) {
if(
strcmp(inet_ntoa(scanners[i]->host), bh) == 0
)
return 1;
}
return 0;
}
char addScanner(struct in_addr blasthost, unsigned short portsn, unsigned char type)
{
if(scanrsize==MAX_SCANNERS) {
return 0;
}
scanners[scanrsize] = malloc(sizeof(struct Scanner));
scanners[scanrsize]->id = scanrsize;
scanners[scanrsize]->type = type;
scanners[scanrsize]->portsn = portsn;
scanners[scanrsize]->lporttime = time(NULL);
scanners[scanrsize]->host = blasthost;
scanrsize++;
return 1;
}
char updateScanner(struct in_addr blasthost)
{
char bh[20];
int i;
strncpy(bh, inet_ntoa(blasthost), (sizeof(bh)-1));
for(i=0; scanrsize>i; i++) {
if(
strcmp(inet_ntoa(scanners[i]->host), bh) == 0
) {
scanners[i]->lporttime = time(NULL);
return 1;
}
}
return 0;
}
void dumpScanners()
{
int i=0;
for(i=0; scanrsize>i; i++) {
printf("%d Scanner->host: %s (%d ports, last hit: %d secs)\n", scanners[i]->id, inet_ntoa(scanners[i]->host), scanners[i]->portsn, (int)(time(NULL) - scanners[i]->lporttime));
}
}