commit 6c3dc833bdde64b0c2c097228b9b34d0a99c617a
tree acdc640ff4b70fddf7a8731fc71b089804d57217
author Engels Antonio <engels@majcms.org> 1314301223 +0800
committer Engels Antonio <engels@majcms.org> 1314301223 +0800
Initial commit, warts and all
diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000..ca83f7c --- /dev/null +++ b/Makefile @@ -0,0 +1,191 @@ +# Makefile for Kalasag +# +# STEALTH MODE: Only works on Linux systems right now. +# +# The snprintf included with the package is for use with NEXTSTEP only, +# (Thanks Timothy <tjl@luomat.org>) although it may work elsewhere. +# We've not tried it under any other OS to date. It shouldn't be needed +# by any modern OS. +# +# Others have used the snprintf from: +# +# http://www.ijs.si/software/snprintf/ +# +# We've not tried this yet but others have had good success. Our only +# piece of advice for those running an OS without built in snprintf() +# is to upgrade. :) +# +# +# Generic compiler (usually linked to gcc on most platforms) +CC = cc + +# GNU.. +#CC = gcc + +# Normal systems flags +CFLAGS = -O3 -funroll-loops -fomit-frame-pointer -Wall + +# Debug mode for kalasag +#CFLAGS = -Wall -g -DNODAEMON -DDEBUG +#CFLAGS = -Wall -g -DNODAEMON +#CFLAGS = -Wall -g -DDEBUG + +# Profiler mode for kalasag +#CFLAGS = -pg -O -Wall -DNODAEMON +#LIBS = /usr/lib/libefence.a + +INSTALLDIR = /opt +CHILDDIR=/kalasag + +all: + @echo "Usage: make <systype>" + @echo "<systype> is one of: linux, debian-linux, bsd, solaris, hpux, hpux-gcc," + @echo "freebsd, osx, openbsd, netbsd, bsdi, aix, osf, irix, generic" + @echo "" + @echo "This code requires snprintf()/vsnprintf() system calls" + @echo "to work. If you run a modern OS it should work on" + @echo "your system with 'make generic'. If you get it to" + @echo "work on an unlisted OS please write us with the" + @echo "changes." + @echo "" + @echo "Install: make install" + @echo "" + @echo "NOTE: This will install the package in this" + @echo " directory: $(INSTALLDIR)" + @echo "" + @echo "Edit the makefile if you wish to change these paths." + @echo "Any existing files will be overwritten." + @echo "" + +clean: + /bin/rm ./kalasag + +uninstall: + /bin/rm $(INSTALLDIR)$(CHILDDIR)/* + /bin/rmdir $(INSTALLDIR)$(CHILDDIR) + +install: + @echo "Creating directory $(INSTALLDIR)" + @if [ ! -d $(INSTALLDIR)]; then /bin/mkdir $(INSTALLDIR); fi + @echo "Setting directory permissions" + @echo "Creating kalasag directory $(INSTALLDIR)$(CHILDDIR)" + @if [ ! -d $(INSTALLDIR)$(CHILDDIR)]; then /bin/mkdir $(INSTALLDIR)$(CHILDDIR); fi + @echo "Setting directory permissions" + chmod 700 $(INSTALLDIR)$(CHILDDIR) + @echo "Copying files" + cp ./kalasag.conf $(INSTALLDIR)$(CHILDDIR) + cp ./kalasag.ignore $(INSTALLDIR)$(CHILDDIR) + cp ./kalasag $(INSTALLDIR)$(CHILDDIR) + @echo "Setting permissions" + chmod 600 $(INSTALLDIR)$(CHILDDIR)/kalasag.ignore + chmod 600 $(INSTALLDIR)$(CHILDDIR)/kalasag.conf + chmod 700 $(INSTALLDIR)$(CHILDDIR)/kalasag + @echo "" + @echo "" + @echo "Edit $(INSTALLDIR)$(CHILDDIR)/kalasag.conf and change" + @echo "your settings if you haven't already. (route, etc)" + @echo "" + +linux: + SYSTYPE=linux + @echo "Making $(SYSTYPE)" + $(CC) $(CFLAGS) -DLINUX -DSUPPORT_STEALTH -o ./kalasag ./kalasag.c ./kalasag_io.c ./kalasag_util.c $(LIBS) + +linux-x86_64: + SYSTYPE=linux + @echo "Making $(SYSTYPE)" + $(CC) $(CFLAGS) -m64 -DLINUX -DSUPPORT_STEALTH -o ./kalasag ./kalasag.c ./kalasag_io.c ./kalasag_util.c $(LIBS) + +debian-linux: + SYSTYPE=debian-linux + @echo "Making $(SYSTYPE)" + $(CC) $(CFLAGS) -DLINUX -DDEBIAN -DSUPPORT_STEALTH -o ./kalasag ./kalasag.c ./kalasag_io.c ./kalasag_util.c $(LIBS) + + +bsd: + SYSTYPE=bsd + @echo "Making $(SYSTYPE)" + $(CC) $(CFLAGS) -DBSD44 -o ./kalasag ./kalasag.c ./kalasag_io.c ./kalasag_util.c + + +openbsd: + SYSTYPE=openbsd + @echo "Making $(SYSTYPE)" + $(CC) $(CFLAGS) -DBSD44 -o ./kalasag ./kalasag.c ./kalasag_io.c ./kalasag_util.c + + +freebsd: + SYSTYPE=freebsd + @echo "Making $(SYSTYPE)" + $(CC) $(CFLAGS) -DBSD44 -o ./kalasag ./kalasag.c ./kalasag_io.c ./kalasag_util.c + +osx: + SYSTYPE=osx + @echo "Making $(SYSTYPE)" + $(CC) $(CFLAGS) -DBSD44 -o ./kalasag ./kalasag.c ./kalasag_io.c ./kalasag_util.c + + +netbsd: + SYSTYPE=netbsd + @echo "Making $(SYSTYPE)" + $(CC) $(CFLAGS) -DBSD44 -o ./kalasag ./kalasag.c ./kalasag_io.c ./kalasag_util.c + + +bsdi: + SYSTYPE=bsdi + @echo "Making $(SYSTYPE)" + $(CC) $(CFLAGS) -DBSD44 -o ./kalasag ./kalasag.c ./kalasag_io.c ./kalasag_util.c + + +generic: + SYSTYPE=generic + @echo "Making $(SYSTYPE)" + $(CC) $(CFLAGS) -o ./kalasag ./kalasag.c ./kalasag_io.c ./kalasag_util.c + + +hpux: + SYSTYPE=hpux + @echo "Making $(SYSTYPE)" + $(CC) -Ae -DHPUX -o ./kalasag ./kalasag.c ./kalasag_io.c ./kalasag_util.c + + +hpux-gcc: + SYSTYPE=hpux-gcc + @echo "Making $(SYSTYPE)" + $(CC) $(CFLAGS) -DHPUX -o ./kalasag ./kalasag.c ./kalasag_io.c ./kalasag_util.c + + +solaris: + SYSTYPE=solaris + @echo "Making $(SYSTYPE)" + $(CC) -lnsl -lsocket -lresolv -lc -o ./kalasag ./kalasag.c ./kalasag_io.c ./kalasag_util.c + + +aix: + SYSTYPE=aix + @echo "Making $(SYSTYPE)" + $(CC) $(CFLAGS) -o ./kalasag ./kalasag.c ./kalasag_io.c ./kalasag_util.c + + +osf: + SYSTYPE=osf + @echo "Making $(SYSTYPE)" + $(CC) $(CFLAGS) -taso -ldb -o ./kalasag ./kalasag.c ./kalasag_io.c ./kalasag_util.c + + +irix: + SYSTYPE=irix + @echo "Making $(SYSTYPE)" + $(CC) $(CFLAGS) -O -n32 -mips3 -o ./kalasag ./kalasag.c ./kalasag_io.c ./kalasag_util.c + + +# NeXTSTEP Users. NeXT used to work, but we changed the log function and +# it now uses vsnprintf() to format strings. This means that this +# version does not work under NeXTSTEP until we can find a workable +# vsnprintf() call to put in the program. Sorry. If you have some good +# vsnprintf() code to use under NeXT please send it to us and we'll +# include it on the next update. +#next: +# SYSTYPE=next +# @echo "Making $(SYSTYPE)" +# $(CC) $(CFLAGS) -DNEXT -DHAS_NO_SNPRINTF -posix -o ./kalasag ./kalasag.c ./kalasag_io.c ./kalasag_util.c
diff --git a/kalasag.c b/kalasag.c
new file mode 100644
index 0000000..2145b43 --- /dev/null +++ b/kalasag.c @@ -0,0 +1,1536 @@ +#include "kalasag.h" +#include "kalasag_io.h" +#include "kalasag_util.h" + +/* Global variables */ +char gblScanDetectHost[MAXSTATE][IPMAXBUF]; +char gblKillRoute[MAXBUF]; +char gblKillHostsDeny[MAXBUF]; +char gblKillRunCmd[MAXBUF]; +char gblBlockedFile[MAXBUF]; +char gblHistoryFile[MAXBUF]; +char gblIgnoreFile[MAXBUF]; +char gblDetectionType[MAXBUF]; + +int gblScanDetectCount = 0; +int gblBlockTCP = 0; +int gblBlockUDP = 0; +int gblRunCmdFirst = 0; +int gblResolveHost = 0; +int gblConfigTriggerCount = 0; + +int main(int argc, char *argv[]) +{ + if (argc != 2){ + Usage(); + Exit(ERROR); + } + + if ((geteuid()) && (getuid()) != 0){ + printf("You need to be root to run this.\n"); + Exit(ERROR); + } + + + /* Cheesy arg parser. Some systems don't support getopt and I don't want to port it. */ + if ((strcmp(argv[1], "-tcp")) && (strcmp(argv[1], "-udp")) + && (strcmp(argv[1], "-stcp")) && (strcmp(argv[1], "-atcp")) + && (strcmp(argv[1], "-sudp")) && (strcmp(argv[1], "-audp")) != 0){ + Usage(); + Exit(ERROR); + } else { + Start(); + /* This copies the startup type to a global for later use */ + if ((SafeStrncpy + (gblDetectionType, strstr(argv[1], "-") + 1, MAXBUF)) + == NULL){ + Log("adminalert: ERROR: Error setting internal scan detection type.\n"); + printf("ERROR: Error setting internal scan detection type.\n"); + printf("ERROR: Kalasag is shutting down!\n"); + Exit(ERROR); + } else if (CheckConfig() == FALSE){ + Log("adminalert: ERROR: Configuration files are missing/corrupted. Shutting down.\n"); + printf("ERROR: Configuration files are missing/corrupted.\n"); + printf + ("ERROR: Check your syslog for a more detailed error message.\n"); + printf("ERROR: Kalasag is shutting down!\n"); + Exit(ERROR); + } else if (InitConfig() == FALSE){ + Log("adminalert: ERROR: Your config file is corrupted/missing mandatory option! Shutting down.\n"); + printf + ("ERROR: Your config file is corrupted/missing mandatory option!\n"); + printf + ("ERROR: Check your syslog for a more detailed error message.\n"); + printf("ERROR: Kalasag is shutting down!\n"); + Exit(ERROR); + } +#ifndef NODAEMON + else if (DaemonSeed() == ERROR){ + Log("adminalert: ERROR: could not go into daemon mode. Shutting down.\n"); + printf + ("ERROR: could not go into daemon mode. Shutting down.\n"); + Exit(ERROR); + } +#endif + } + + + if (strcmp(argv[1], "-tcp") == 0){ + if (KalasagModeTCP() == ERROR){ + Log("adminalert: ERROR: could not go into Kalasag mode. Shutting down.\n"); + Exit(ERROR); + } + } +#ifdef SUPPORT_STEALTH + else if (strcmp(argv[1], "-stcp") == 0){ + if (KalasagStealthModeTCP() == ERROR){ + Log("adminalert: ERROR: could not go into Kalasag mode. Shutting down.\n"); + Exit(ERROR); + } + } else if (strcmp(argv[1], "-atcp") == 0){ + if (KalasagAdvancedStealthModeTCP() == ERROR){ + Log("adminalert: ERROR: could not go into Kalasag mode. Shutting down.\n"); + Exit(ERROR); + } + } else if (strcmp(argv[1], "-sudp") == 0){ + if (KalasagStealthModeUDP() == ERROR){ + Log("adminalert: ERROR: could not go into Kalasag mode. Shutting down.\n"); + Exit(ERROR); + } + } else if (strcmp(argv[1], "-audp") == 0){ + if (KalasagAdvancedStealthModeUDP() == ERROR){ + Log("adminalert: ERROR: could not go into Kalasag mode. Shutting down.\n"); + Exit(ERROR); + } + } +#endif + else if (strcmp(argv[1], "-udp") == 0){ + if (KalasagModeUDP() == ERROR){ + Log("adminalert: ERROR: could not go into Kalasag mode. Shutting down.\n"); + Exit(ERROR); + } + } + + Exit(TRUE); + /* shuts up compiler warning */ + return (0); +} + +/****************************************************************/ +/* Reads generic config options into global variables */ +/****************************************************************/ +int InitConfig(void) +{ + FILE *input; + char configToken[MAXBUF]; + + gblBlockTCP = CheckFlag("BLOCK_TCP"); + gblBlockUDP = CheckFlag("BLOCK_UDP"); + gblResolveHost = CheckFlag("RESOLVE_HOST"); + + memset(gblKillRoute, '\0', MAXBUF); + memset(gblKillHostsDeny, '\0', MAXBUF); + memset(gblKillRunCmd, '\0', MAXBUF); + + if ((ConfigTokenRetrieve("SCAN_TRIGGER", configToken)) == FALSE){ + Log("adminalert: ERROR: Could not read SCAN_TRIGGER option from config file. Disabling SCAN DETECTION TRIGGER"); + gblConfigTriggerCount = 0; + } else { +#ifdef DEBUG + Log("debug: InitConfig: retrieved SCAN_TRIGGER option: %s \n", + configToken); +#endif + gblConfigTriggerCount = atoi(configToken); + } + + if ((ConfigTokenRetrieve("KILL_ROUTE", gblKillRoute)) == TRUE){ +#ifdef DEBUG + Log("debug: InitConfig: retrieved KILL_ROUTE option: %s \n", + gblKillRoute); +#endif + } else { +#ifdef DEBUG + Log("debug: InitConfig: KILL_ROUTE option NOT FOUND.\n"); +#endif + } + + if ((ConfigTokenRetrieve("KILL_HOSTS_DENY", gblKillHostsDeny)) == TRUE){ +#ifdef DEBUG + Log("debug: InitConfig: retrieved KILL_HOSTS_DENY option: %s \n", + gblKillHostsDeny); +#endif + } else { +#ifdef DEBUG + Log("debug: InitConfig: KILL_HOSTS_DENY option NOT FOUND.\n"); +#endif + } + + if ((ConfigTokenRetrieve("KILL_RUN_CMD", gblKillRunCmd)) == TRUE){ +#ifdef DEBUG + Log("debug: InitConfig: retrieved KILL_RUN_CMD option: %s \n", + gblKillRunCmd); +#endif + /* Check the order we should run the KILL_RUN_CMD */ + /* Default is to run the command after blocking */ + gblRunCmdFirst = CheckFlag("KILL_RUN_CMD_FIRST"); + } else { +#ifdef DEBUG + Log("debug: InitConfig: KILL_RUN_CMD option NOT FOUND.\n"); +#endif + } + + if ((ConfigTokenRetrieve("BLOCKED_FILE", gblBlockedFile)) == TRUE){ + if (strlen(gblBlockedFile) < MAXBUF - 5){ + strncat(gblBlockedFile, ".", 1); + strncat(gblBlockedFile, gblDetectionType, 4); + } else { + Log("adminalert: ERROR: Blocked filename is too long to append detection type file extension: %s.\n", gblBlockedFile); + return (FALSE); + } +#ifdef DEBUG + Log("debug: InitConfig: retrieved BLOCKED_FILE option: %s \n", + gblBlockedFile); + Log("debug: CheckConfig: Removing old block file: %s \n", + gblBlockedFile); +#endif + + if ((input = fopen(gblBlockedFile, "w")) == NULL){ + Log("adminalert: ERROR: Cannot delete blocked file on startup: %s.\n", gblBlockedFile); + return (FALSE); + } else + fclose(input); + } else { + Log("InitConfig: Cannot retrieve BLOCKED_FILE option! Aborting\n"); + return (FALSE); + } + + + if ((ConfigTokenRetrieve("HISTORY_FILE", gblHistoryFile)) == TRUE){ +#ifdef DEBUG + Log("debug: InitConfig: retrieved HISTORY_FILE option: %s \n", + gblHistoryFile); +#endif + } else { + Log("InitConfig: Cannot retrieve HISTORY_FILE option! Aborting\n"); + return (FALSE); + } + + if ((ConfigTokenRetrieve("IGNORE_FILE", gblIgnoreFile)) == TRUE){ +#ifdef DEBUG + Log("debug: InitConfig: retrieved IGNORE_FILE option: %s \n", + gblIgnoreFile); +#endif + } else { + Log("InitConfig: Cannot retrieve IGNORE_FILE option! Aborting\n"); + return (FALSE); + } + + return (TRUE); +} + + +#ifdef SUPPORT_STEALTH + +/* Read in a TCP packet taking into account IP options and other */ +/* errors */ +int PacketReadTCP(int socket, struct iphdr *ipPtr, struct tcphdr *tcpPtr) +{ + char packetBuffer[TCPPACKETLEN]; + struct in_addr addr; + + bzero(ipPtr, sizeof(struct iphdr)); + bzero(tcpPtr, sizeof(struct tcphdr)); + + if (read(socket, packetBuffer, TCPPACKETLEN) == ERROR) + return (ERROR); + + memcpy(ipPtr, (struct iphdr *) packetBuffer, sizeof(struct iphdr)); + + if ((ipPtr->ihl < 5) || (ipPtr->ihl > 15)){ + addr.s_addr = (u_int) ipPtr->saddr; + Log("attackalert: Illegal IP header length detected in TCP packet: %d from (possible) host: %s\n", ipPtr->ihl, inet_ntoa(addr)); + return (FALSE); + } else { + memcpy(tcpPtr, + (struct tcphdr *)(packetBuffer + ((ipPtr->ihl) * 4)), + sizeof(struct tcphdr)); + return (TRUE); + } + +} + +/* Read in a UDP packet taking into account IP options and other */ +/* errors */ +int PacketReadUDP(int socket, struct iphdr *ipPtr, struct udphdr *udpPtr) +{ + char packetBuffer[UDPPACKETLEN]; + struct in_addr addr; + + bzero(ipPtr, sizeof(struct iphdr)); + bzero(udpPtr, sizeof(struct udphdr)); + + if (read(socket, packetBuffer, UDPPACKETLEN) == ERROR) + return (ERROR); + + memcpy(ipPtr, (struct iphdr *) packetBuffer, sizeof(struct iphdr)); + + if ((ipPtr->ihl < 5) || (ipPtr->ihl > 15)){ + addr.s_addr = (u_int) ipPtr->saddr; + Log("attackalert: Illegal IP header length detected in UDP packet: %d from (possible) host: %s\n", ipPtr->ihl, inet_ntoa(addr)); + return (FALSE); + } else { + memcpy(udpPtr, + (struct udphdr *)(packetBuffer + ((ipPtr->ihl) * 4)), + sizeof(struct udphdr)); + return (TRUE); + } + +} + +/****************************************************************/ +/* Stealth scan detection Mode One */ +/* */ +/* This mode will read in a list of ports to monitor and will */ +/* then open a raw socket to look for packets matching the port. */ +/* */ +/****************************************************************/ +int KalasagStealthModeTCP(void) +{ + struct sockaddr_in client, server; + int portCount = 0, portCount2 = 0, ports[MAXSOCKS], ports2[MAXSOCKS]; + int count = 0, scanDetectTrigger = TRUE, gotBound = FALSE, result = + TRUE; + int openSockfd = 0, incomingPort = 0; + char *temp, target[IPMAXBUF], configToken[MAXBUF]; + char resolvedHost[DNSMAXBUF], *packetType; + struct in_addr addr; + struct iphdr ip; + struct tcphdr tcp; + + if ((ConfigTokenRetrieve("TCP_PORTS", configToken)) == FALSE){ + Log("adminalert: ERROR: Could not read TCP_PORTS option from config file"); + return (ERROR); + } + + /* break out the ports */ + if ((temp = (char *) strtok(configToken, ",")) != NULL){ + ports[0] = atoi(temp); + for (count = 1; count < MAXSOCKS; count++){ + if ((temp = (char *) strtok(NULL, ",")) != NULL) + ports[count] = atoi(temp); + else + break; + } + portCount = count; + } else { + Log("adminalert: ERROR: No TCP ports supplied in config file. Aborting"); + return (ERROR); + } + + /* ok, now check if they have a network daemon on the socket already, if they do */ + /* then skip that port because it will cause false alarms */ + for (count = 0; count < portCount; count++){ + Log("adminalert: Going into stealth listen mode on TCP port: %d\n", + ports[count]); + if ((openSockfd = OpenTCPSocket()) == ERROR){ + Log("adminalert: ERROR: could not open TCP socket. Aborting.\n"); + return (ERROR); + } + + if (BindSocket(openSockfd, client, server, ports[count]) == ERROR) + Log("adminalert: ERROR: Socket %d is in use and will not be monitored. Attempting to continue\n", ports[count]); + else { /* well we at least bound to one socket so we'll continue */ + + gotBound = TRUE; + ports2[portCount2++] = ports[count]; + } + close(openSockfd); + } + + /* if we didn't bind to anything then abort */ + if (gotBound == FALSE){ + Log("adminalert: ERROR: All supplied TCP sockets are in use and will not be listened to. Shutting down.\n"); + return (ERROR); + } + + /* Open our raw socket for network IO */ + if ((openSockfd = OpenRAWTCPSocket()) == ERROR){ + Log("adminalert: ERROR: could not open RAW TCP socket. Aborting.\n"); + return (ERROR); + } + + Log("adminalert: Kalasag is now active and listening.\n"); + + /* main detection loop */ + for (;;){ + if (PacketReadTCP(openSockfd, &ip, &tcp) != TRUE) + continue; + + + incomingPort = ntohs(tcp.dest); + + /* check for an ACK/RST to weed out established connections in case the user */ + /* is monitoring high ephemeral port numbers */ + if ((tcp.ack != 1) && (tcp.rst != 1)){ + /* this iterates the list of ports looking for a match */ + for (count = 0; count < portCount; count++){ + if (incomingPort == ports2[count]){ + if (SmartVerifyTCP(client, server, incomingPort) == + TRUE) + break; + + /* copy the clients address into our buffer for nuking */ + addr.s_addr = (u_int) ip.saddr; + SafeStrncpy(target, (char *) inet_ntoa(addr), + IPMAXBUF); + /* check if we should ignore this IP */ + result = NeverBlock(target, gblIgnoreFile); + + if (result == ERROR){ + Log("attackalert: ERROR: cannot open ignore file. Blocking host anyway.\n"); + result = FALSE; + } + + if (result == FALSE){ + /* check if they've visited before */ + scanDetectTrigger = CheckStateEngine(target); + if (scanDetectTrigger == TRUE){ + if (gblResolveHost){ /* Do they want DNS resolution? */ + if (CleanAndResolve(resolvedHost, target) + != TRUE){ + Log("attackalert: ERROR: Error resolving host. \ + resolving disabled for this host.\n"); + snprintf(resolvedHost, DNSMAXBUF, "%s", + target); + } + } else { + snprintf(resolvedHost, DNSMAXBUF, "%s", + target); + } + + packetType = ReportPacketType(tcp); + Log("attackalert: %s from host: %s/%s to TCP port: %d", packetType, resolvedHost, target, ports2[count]); + /* Report on options present */ + if (ip.ihl > 5) + Log("attackalert: Packet from host: %s/%s to TCP port: %d has IP options set (detection avoidance technique).", resolvedHost, target, ports2[count]); + + /* check if this target is already blocked */ + if (IsBlocked(target, gblBlockedFile) == FALSE){ + /* toast the prick */ + if (DisposeTCP(target, ports2[count]) != + TRUE) + Log("attackalert: ERROR: Could not block host %s/%s !!", resolvedHost, target); + else + WriteBlocked(target, resolvedHost, + ports2[count], + gblBlockedFile, + gblHistoryFile, "TCP"); + } /* end IsBlocked check */ + else + Log("attackalert: Host: %s/%s is already blocked Ignoring", resolvedHost, target); + } /* end if(scanDetectTrigger) */ + } /* end if(never block) check */ + break; /* get out of for(count) loop above */ + } /* end if(incoming port) == protected port */ + } /* end for( check for protected port loop ) loop */ + } /* end if(TH_ACK) check */ + } /* end for( ; ; ) loop */ + +} /* end KalasagStealthModeTCP */ + + +/****************************************************************/ +/* Advanced Stealth scan detection Mode One */ +/* */ +/* This mode will see what ports are listening below 1024 */ +/* and will then monitor all the rest. This is very sensitive */ +/* and will react on any packet hitting any monitored port, */ +/* regardless of TCP flags set */ +/* */ +/****************************************************************/ +int KalasagAdvancedStealthModeTCP(void) +{ + struct sockaddr_in client, server; + int result = TRUE, scanDetectTrigger = TRUE, hotPort = TRUE; + int openSockfd = 0, incomingPort = 0, smartVerify = FALSE; + unsigned int advancedPorts = 1024; + unsigned int count = 0, inUsePorts[MAXSOCKS], portCount = 0; + char target[IPMAXBUF], configToken[MAXBUF]; + char resolvedHost[DNSMAXBUF], *temp, *packetType; + struct in_addr addr; + struct iphdr ip; + struct tcphdr tcp; + + if ((ConfigTokenRetrieve("ADVANCED_PORTS_TCP", configToken)) == FALSE){ + Log("adminalert: ERROR: Could not read ADVANCED_PORTS_TCP option from config file. Assuming 1024."); + advancedPorts = 1024; + } else + advancedPorts = atoi(configToken); + + Log("adminalert: Advanced mode will monitor first %d ports", + advancedPorts); + + /* try to bind to all ports below 1024, any that are taken we exclude later */ + for (count = 0; count < advancedPorts; count++){ + if ((openSockfd = OpenTCPSocket()) == ERROR){ + Log("adminalert: ERROR: could not open TCP socket. Aborting.\n"); + return (ERROR); + } + if (BindSocket(openSockfd, client, server, count) == ERROR) + inUsePorts[portCount++] = count; + + close(openSockfd); + } + + if ((ConfigTokenRetrieve("ADVANCED_EXCLUDE_TCP", configToken)) != + FALSE){ + /* break out the ports */ + if ((temp = (char *) strtok(configToken, ",")) != NULL){ + inUsePorts[portCount++] = atoi(temp); + Log("adminalert: Advanced mode will manually exclude port: %d ", inUsePorts[portCount - 1]); + for (count = 0; count < MAXSOCKS; count++){ + if ((temp = (char *) strtok(NULL, ",")) != NULL){ + inUsePorts[portCount++] = atoi(temp); + Log("adminalert: Advanced mode will manually exclude port: %d ", inUsePorts[portCount - 1]); + } else + break; + } + } + } else + Log("adminalert: Advanced mode will manually exclude no ports"); + + + for (count = 0; count < portCount; count++) + Log("adminalert: Advanced Stealth scan detection mode activated. Ignored TCP port: %d\n", inUsePorts[count]); + + /* open raw socket for reading */ + if ((openSockfd = OpenRAWTCPSocket()) == ERROR){ + Log("adminalert: ERROR: could not open RAW TCP socket. Aborting.\n"); + return (ERROR); + } + + Log("adminalert: Kalasag is now active and listening.\n"); + + /* main detection loop */ + for (;;){ + if (PacketReadTCP(openSockfd, &ip, &tcp) != TRUE) + continue; + + incomingPort = ntohs(tcp.dest); + + /* don't monitor packets with ACK set (established) or RST */ + /* This could be a hole in some cases */ + if ((tcp.ack != 1) && (tcp.rst != 1)){ + /* check if we should ignore this connection to this port */ + for (count = 0; count < portCount; count++){ + if ((incomingPort == inUsePorts[count]) + || (incomingPort >= advancedPorts)){ + hotPort = FALSE; + break; + } else + hotPort = TRUE; + } + + if (hotPort){ + smartVerify = SmartVerifyTCP(client, server, incomingPort); + + if (smartVerify != TRUE){ + addr.s_addr = (u_int) ip.saddr; + SafeStrncpy(target, (char *) inet_ntoa(addr), + IPMAXBUF); + /* check if we should ignore this IP */ + result = NeverBlock(target, gblIgnoreFile); + + if (result == ERROR){ + Log("attackalert: ERROR: cannot open ignore file. Blocking host anyway.\n"); + result = FALSE; + } + + if (result == FALSE){ + /* check if they've visited before */ + scanDetectTrigger = CheckStateEngine(target); + + if (scanDetectTrigger == TRUE){ + if (gblResolveHost){ /* Do they want DNS resolution? */ + if (CleanAndResolve(resolvedHost, target) + != TRUE){ + Log("attackalert: ERROR: Error resolving host. \ + resolving disabled for this host.\n"); + snprintf(resolvedHost, DNSMAXBUF, "%s", + target); + } + } else { + snprintf(resolvedHost, DNSMAXBUF, "%s", + target); + } + + packetType = ReportPacketType(tcp); + Log("attackalert: %s from host: %s/%s to TCP port: %d", packetType, resolvedHost, target, incomingPort); + /* Report on options present */ + if (ip.ihl > 5) + Log("attackalert: Packet from host: %s/%s to TCP port: %d has IP options set (detection avoidance technique).", resolvedHost, target, incomingPort); + + /* check if this target is already blocked */ + if (IsBlocked(target, gblBlockedFile) == FALSE){ + /* toast the prick */ + if (DisposeTCP(target, incomingPort) != + TRUE) + Log("attackalert: ERROR: Could not block host %s/%s!!", resolvedHost, target); + else + WriteBlocked(target, resolvedHost, + incomingPort, + gblBlockedFile, + gblHistoryFile, "TCP"); + } /* end IsBlocked check */ + else + Log("attackalert: Host: %s/%s is already blocked Ignoring", resolvedHost, target); + } /* end if(scanDetectTrigger) */ + } /* end if(never block) check */ + } /* end if(smartVerify) */ + } /* end if(hotPort) */ + } /* end if(TH_ACK) */ + } /* end for( ; ; ) loop */ +} + +/* end KalasagAdvancedStealthModeTCP */ + + + +/****************************************************************/ +/* UDP "stealth" scan detection */ +/* */ +/* This mode will read in a list of ports to monitor and will */ +/* then open a raw socket to look for packets matching the port. */ +/* */ +/****************************************************************/ +int KalasagStealthModeUDP(void) +{ + struct sockaddr_in client, server; + int portCount = 0, portCount2 = 0, ports[MAXSOCKS], ports2[MAXSOCKS], + result = TRUE; + int count = 0, scanDetectTrigger = TRUE, gotBound = FALSE; + int openSockfd = 0, incomingPort = 0; + char *temp, target[IPMAXBUF], configToken[MAXBUF]; + char resolvedHost[DNSMAXBUF]; + struct in_addr addr; + struct iphdr ip; + struct udphdr udp; + + + if ((ConfigTokenRetrieve("UDP_PORTS", configToken)) == FALSE){ + Log("adminalert: ERROR: Could not read UDP_PORTS option from config file"); + return (ERROR); + } + + /* break out the ports */ + if ((temp = (char *) strtok(configToken, ",")) != NULL){ + ports[0] = atoi(temp); + for (count = 1; count < MAXSOCKS; count++){ + if ((temp = (char *) strtok(NULL, ",")) != NULL) + ports[count] = atoi(temp); + else + break; + } + portCount = count; + } else { + Log("adminalert: ERROR: No UDP ports supplied in config file. Aborting"); + return (ERROR); + } + + /* ok, now check if they have a network daemon on the socket already, if they do */ + /* then skip that port because it will cause false alarms */ + for (count = 0; count < portCount; count++){ + Log("adminalert: Going into stealth listen mode on UDP port: %d\n", + ports[count]); + if ((openSockfd = OpenUDPSocket()) == ERROR){ + Log("adminalert: ERROR: could not open UDP socket. Aborting.\n"); + return (ERROR); + } + + if (BindSocket(openSockfd, client, server, ports[count]) == ERROR) + Log("adminalert: ERROR: Socket %d is in use and will not be monitored. Attempting to continue\n", ports[count]); + else { + gotBound = TRUE; + ports2[portCount2++] = ports[count]; + } + close(openSockfd); + } + + if (gotBound == FALSE){ + Log("adminalert: ERROR: All supplied UDP sockets are in use and will not be listened to. Shutting down.\n"); + return (ERROR); + } + + if ((openSockfd = OpenRAWUDPSocket()) == ERROR){ + Log("adminalert: ERROR: could not open RAW UDP socket. Aborting.\n"); + return (ERROR); + } + + Log("adminalert: Kalasag is now active and listening.\n"); + + /* main detection loop */ + for (;;){ + if (PacketReadUDP(openSockfd, &ip, &udp) != TRUE) + continue; + + incomingPort = ntohs(udp.dest); + + /* this iterates the list of ports looking for a match */ + for (count = 0; count < portCount; count++){ + if (incomingPort == ports2[count]){ + if (SmartVerifyUDP(client, server, incomingPort) == TRUE) + break; + + addr.s_addr = (u_int) ip.saddr; + SafeStrncpy(target, (char *) inet_ntoa(addr), IPMAXBUF); + /* check if we should ignore this IP */ + result = NeverBlock(target, gblIgnoreFile); + + if (result == ERROR){ + Log("attackalert: ERROR: cannot open ignore file. Blocking host anyway.\n"); + result = FALSE; + } + + if (result == FALSE){ + /* check if they've visited before */ + scanDetectTrigger = CheckStateEngine(target); + if (scanDetectTrigger == TRUE){ + if (gblResolveHost){ /* Do they want DNS resolution? */ + if (CleanAndResolve(resolvedHost, target) != + TRUE){ + Log("attackalert: ERROR: Error resolving host. \ + resolving disabled for this host.\n"); + snprintf(resolvedHost, DNSMAXBUF, "%s", + target); + } + } else { + snprintf(resolvedHost, DNSMAXBUF, "%s", + target); + } + + Log("attackalert: UDP scan from host: %s/%s to UDP port: %d", resolvedHost, target, ports2[count]); + /* Report on options present */ + if (ip.ihl > 5) + Log("attackalert: Packet from host: %s/%s to UDP port: %d has IP options set (detection avoidance technique).", resolvedHost, target, incomingPort); + + /* check if this target is already blocked */ + if (IsBlocked(target, gblBlockedFile) == FALSE){ + if (DisposeUDP(target, ports2[count]) != TRUE) + Log("attackalert: ERROR: Could not block host %s/%s!!", resolvedHost, target); + else + WriteBlocked(target, resolvedHost, + ports2[count], gblBlockedFile, + gblHistoryFile, "UDP"); + } /* end IsBlocked check */ + else { + Log("attackalert: Host: %s/%s is already blocked Ignoring", resolvedHost, target); + } + } /* end if(scanDetectTrigger) */ + } /* end if(never block) check */ + break; /* get out of for(count) loop above */ + } /* end if(incoming port) == protected port */ + } /* end for( check for protected port loop ) loop */ + } /* end for( ; ; ) loop */ + +} /* end KalasagStealthModeUDP */ + + +/****************************************************************/ +/* Advanced Stealth scan detection mode for UDP */ +/* */ +/* This mode will see what ports are listening below 1024 */ +/* and will then monitor all the rest. This is very sensitive */ +/* and will react on any packet hitting any monitored port. */ +/* This is a very dangerous option and is for advanced users */ +/* */ +/****************************************************************/ +int KalasagAdvancedStealthModeUDP(void) +{ + struct sockaddr_in client, server; + int result = TRUE, scanDetectTrigger = TRUE, hotPort = TRUE; + int openSockfd = 0, incomingPort = 0, smartVerify = FALSE; + unsigned int advancedPorts = 1024; + unsigned int count = 0, inUsePorts[MAXSOCKS], portCount = 0; + char target[IPMAXBUF], configToken[MAXBUF]; + char resolvedHost[DNSMAXBUF], *temp; + struct in_addr addr; + struct iphdr ip; + struct udphdr udp; + + + if ((ConfigTokenRetrieve("ADVANCED_PORTS_UDP", configToken)) == FALSE){ + Log("adminalert: ERROR: Could not read ADVANCED_PORTS_UDP option from config file. Assuming 1024."); + advancedPorts = 1024; + } else + advancedPorts = atoi(configToken); + + Log("adminalert: Advanced mode will monitor first %d ports", + advancedPorts); + + /* try to bind to all ports below 1024, any that are taken we exclude later */ + for (count = 0; count < advancedPorts; count++){ + if ((openSockfd = OpenUDPSocket()) == ERROR){ + Log("adminalert: ERROR: could not open UDP socket. Aborting.\n"); + return (ERROR); + } + if (BindSocket(openSockfd, client, server, count) == ERROR) + inUsePorts[portCount++] = count; + + close(openSockfd); + } + + if ((ConfigTokenRetrieve("ADVANCED_EXCLUDE_UDP", configToken)) != + FALSE){ + /* break out the ports */ + if ((temp = (char *) strtok(configToken, ",")) != NULL){ + inUsePorts[portCount++] = atoi(temp); + Log("adminalert: Advanced mode will manually exclude port: %d ", inUsePorts[portCount - 1]); + for (count = 0; count < MAXSOCKS; count++){ + if ((temp = (char *) strtok(NULL, ",")) != NULL){ + inUsePorts[portCount++] = atoi(temp); + Log("adminalert: Advanced mode will manually exclude port: %d ", inUsePorts[portCount - 1]); + } else + break; + } + } + } else + Log("adminalert: Advanced mode will manually exclude no ports"); + + + for (count = 0; count < portCount; count++) + Log("adminalert: Advanced Stealth scan detection mode activated. Ignored UDP port: %d\n", inUsePorts[count]); + + if ((openSockfd = OpenRAWUDPSocket()) == ERROR){ + Log("adminalert: ERROR: could not open RAW UDP socket. Aborting.\n"); + return (ERROR); + } + + Log("adminalert: Kalasag is now active and listening.\n"); + + /* main detection loop */ + for (;;){ + if (PacketReadUDP(openSockfd, &ip, &udp) != TRUE) + continue; + + incomingPort = ntohs(udp.dest); + + /* check if we should ignore this connection to this port */ + for (count = 0; count < portCount; count++){ + if ((incomingPort == inUsePorts[count]) + || (incomingPort >= advancedPorts)){ + hotPort = FALSE; + break; + } else + hotPort = TRUE; + } + + if (hotPort){ + smartVerify = SmartVerifyUDP(client, server, incomingPort); + + if (smartVerify != TRUE){ + /* copy the clients address into our buffer for nuking */ + addr.s_addr = (u_int) ip.saddr; + SafeStrncpy(target, (char *) inet_ntoa(addr), IPMAXBUF); + /* check if we should ignore this IP */ + result = NeverBlock(target, gblIgnoreFile); + + if (result == ERROR){ + Log("attackalert: ERROR: cannot open ignore file. Blocking host anyway.\n"); + result = FALSE; + } + + if (result == FALSE){ + /* check if they've visited before */ + scanDetectTrigger = CheckStateEngine(target); + + if (scanDetectTrigger == TRUE){ + if (gblResolveHost){ /* Do they want DNS resolution? */ + if (CleanAndResolve(resolvedHost, target) != + TRUE){ + Log("attackalert: ERROR: Error resolving host. \ + resolving disabled for this host.\n"); + snprintf(resolvedHost, DNSMAXBUF, "%s", + target); + } + } else { + snprintf(resolvedHost, DNSMAXBUF, "%s", + target); + } + + Log("attackalert: UDP scan from host: %s/%s to UDP port: %d", resolvedHost, target, incomingPort); + /* Report on options present */ + if (ip.ihl > 5) + Log("attackalert: Packet from host: %s/%s to UDP port: %d has IP options set (detection avoidance technique).", resolvedHost, target, incomingPort); + + /* check if this target is already blocked */ + if (IsBlocked(target, gblBlockedFile) == FALSE){ + if (DisposeUDP(target, incomingPort) != TRUE) + Log("attackalert: ERROR: Could not block host %s/%s!!", resolvedHost, target); + else + WriteBlocked(target, resolvedHost, + incomingPort, gblBlockedFile, + gblHistoryFile, "UDP"); + } /* end IsBlocked check */ + else + Log("attackalert: Host: %s/%s is already blocked Ignoring", resolvedHost, target); + } /* end if(scanDetectTrigger) */ + } /* end if(never block) check */ + } /* end if (smartVerify) */ + } /* end if(hotPort) */ + } /* end for( ; ; ) loop */ +} + +/* end KalasagAdvancedStealthModeUDP */ + +#endif + + + + +/****************************************************************/ +/* Classic detection Mode */ +/* */ +/* This mode will bind to a list of TCP sockets and wait for */ +/* connections to happen. Although the least prone to false */ +/* alarms, it also won't detect stealth scans */ +/* */ +/****************************************************************/ +int KalasagModeTCP(void) +{ + + struct sockaddr_in client, server; + int length, portCount = 0, ports[MAXSOCKS]; + int openSockfd[MAXSOCKS], incomingSockfd, result = TRUE; + int count = 0, scanDetectTrigger = TRUE, showBanner = + FALSE, boundPortCount = 0; + int selectResult = 0; + char *temp, target[IPMAXBUF], bannerBuffer[MAXBUF], + configToken[MAXBUF]; + char resolvedHost[DNSMAXBUF]; + fd_set selectFds; + + if ((ConfigTokenRetrieve("TCP_PORTS", configToken)) == FALSE){ + Log("adminalert: ERROR: Could not read TCP_PORTS option from config file"); + return (ERROR); + } + + /* break out the ports */ + if ((temp = (char *) strtok(configToken, ",")) != NULL){ + ports[0] = atoi(temp); + for (count = 1; count < MAXSOCKS; count++){ + if ((temp = (char *) strtok(NULL, ",")) != NULL) + ports[count] = atoi(temp); + else + break; + } + portCount = count; + } else { + Log("adminalert: ERROR: No TCP ports supplied in config file. Aborting"); + return (ERROR); + } + + /* read in the banner if one is given */ + if ((ConfigTokenRetrieve("PORT_BANNER", configToken)) == TRUE){ + showBanner = TRUE; + SafeStrncpy(bannerBuffer, configToken, MAXBUF); + } + + + /* setup select call */ + FD_ZERO(&selectFds); + + for (count = 0; count < portCount; count++){ + Log("adminalert: Going into listen mode on TCP port: %d\n", + ports[count]); + if ((openSockfd[boundPortCount] = OpenTCPSocket()) == ERROR){ + Log("adminalert: ERROR: could not open TCP socket. Aborting.\n"); + return (ERROR); + } + + if (BindSocket + (openSockfd[boundPortCount], client, server, + ports[count]) == ERROR){ + Log("adminalert: ERROR: could not bind TCP socket: %d. Attempting to continue\n", ports[count]); + } else /* well we at least bound to one socket so we'll continue */ + boundPortCount++; + } + + + /* if we didn't bind to anything then abort */ + if (boundPortCount == 0){ + Log("adminalert: ERROR: could not bind ANY TCP sockets. Shutting down.\n"); + return (ERROR); + } + + length = sizeof(client); + + Log("adminalert: Kalasag is now active and listening.\n"); + + /* main loop for multiplexing/resetting */ + for (;;){ + /* set up select call */ + for (count = 0; count < boundPortCount; count++) + FD_SET(openSockfd[count], &selectFds); + selectResult = + select(MAXSOCKS, &selectFds, NULL, NULL, + (struct timeval *) NULL); + + /* something blew up */ + if (selectResult < 0){ + Log("adminalert: ERROR: select call failed. Shutting down.\n"); + return (ERROR); + } else if (selectResult == 0){ +#ifdef DEBUG + Log("Select timeout"); +#endif + } + + /* select is reporting a waiting socket. Poll them all to find out which */ + else if (selectResult > 0){ + for (count = 0; count < boundPortCount; count++){ + if (FD_ISSET(openSockfd[count], &selectFds)){ + incomingSockfd = + accept(openSockfd[count], + (struct sockaddr *) &client, &length); + if (incomingSockfd < 0){ + Log("attackalert: Possible stealth scan from unknown host to TCP port: %d (accept failed)", ports[count]); + break; + } + + /* copy the clients address into our buffer for nuking */ + SafeStrncpy(target, + (char *) inet_ntoa(client.sin_addr), + IPMAXBUF); + /* check if we should ignore this IP */ + result = NeverBlock(target, gblIgnoreFile); + + if (result == ERROR){ + Log("attackalert: ERROR: cannot open ignore file. Blocking host anyway.\n"); + result = FALSE; + } + + if (result == FALSE){ + /* check if they've visited before */ + scanDetectTrigger = CheckStateEngine(target); + + if (scanDetectTrigger == TRUE){ + /* show the banner if one was selected */ + if (showBanner == TRUE) + write(incomingSockfd, bannerBuffer, + strlen(bannerBuffer)); + /* we don't need the bonehead anymore */ + close(incomingSockfd); + if (gblResolveHost){ /* Do they want DNS resolution? */ + if (CleanAndResolve(resolvedHost, target) + != TRUE){ + Log("attackalert: ERROR: Error resolving host. \ + resolving disabled for this host.\n"); + snprintf(resolvedHost, DNSMAXBUF, "%s", + target); + } + } else { + snprintf(resolvedHost, DNSMAXBUF, "%s", + target); + } + + Log("attackalert: Connect from host: %s/%s to TCP port: %d", resolvedHost, target, ports[count]); + + /* check if this target is already blocked */ + if (IsBlocked(target, gblBlockedFile) == FALSE){ + if (DisposeTCP(target, ports[count]) != + TRUE) + Log("attackalert: ERROR: Could not block host %s !!", target); + else + WriteBlocked(target, resolvedHost, + ports[count], + gblBlockedFile, + gblHistoryFile, "TCP"); + } else + Log("attackalert: Host: %s is already blocked. Ignoring", target); + } + } + close(incomingSockfd); + break; + } /* end if(FD_ISSET) */ + } /* end for() */ + } /* end else (selectResult > 0) */ + } /* end main for(; ; ) loop */ + +/* not reached */ + close(incomingSockfd); +} + + + + + +/****************************************************************/ +/* Classic detection Mode */ +/* */ +/* This mode will bind to a list of UDP sockets and wait for */ +/* connections to happen. Stealth scanning really doesn't apply */ +/* here. */ +/* */ +/****************************************************************/ +int KalasagModeUDP(void) +{ + struct sockaddr_in client, server; + int length, ports[MAXSOCKS], openSockfd[MAXSOCKS], result = TRUE; + int count = 0, portCount = 0, selectResult = 0, scanDetectTrigger = 0; + int boundPortCount = 0, showBanner = FALSE; + char *temp, target[IPMAXBUF], bannerBuffer[MAXBUF], + configToken[MAXBUF]; + char buffer[MAXBUF]; + char resolvedHost[DNSMAXBUF]; + fd_set selectFds; + + if ((ConfigTokenRetrieve("UDP_PORTS", configToken)) == FALSE){ + Log("adminalert: ERROR: Could not read UDP_PORTS option from config file"); + return (ERROR); + } + + /* break out the ports */ + if ((temp = (char *) strtok(configToken, ",")) != NULL){ + ports[0] = atoi(temp); + for (count = 1; count < MAXSOCKS; count++){ + if ((temp = (char *) strtok(NULL, ",")) != NULL) + ports[count] = atoi(temp); + else + break; + } + portCount = count; + } else { + Log("adminalert: ERROR: No UDP ports supplied in config file. Aborting"); + return (ERROR); + } + + /* read in the banner if one is given */ + if ((ConfigTokenRetrieve("PORT_BANNER", configToken)) == TRUE){ + showBanner = TRUE; + SafeStrncpy(bannerBuffer, configToken, MAXBUF); + } + + /* setup select call */ + FD_ZERO(&selectFds); + + for (count = 0; count < portCount; count++){ + Log("adminalert: Going into listen mode on UDP port: %d\n", + ports[count]); + if ((openSockfd[boundPortCount] = OpenUDPSocket()) == ERROR){ + Log("adminalert: ERROR: could not open UDP socket. Aborting\n"); + return (ERROR); + } + if (BindSocket + (openSockfd[boundPortCount], client, server, + ports[count]) == ERROR){ + Log("adminalert: ERROR: could not bind UDP socket: %d. Attempting to continue\n", ports[count]); + } else /* well we at least bound to one socket so we'll continue */ + boundPortCount++; + } + +/* if we didn't bind to anything then abort */ + if (boundPortCount == 0){ + Log("adminalert: ERROR: could not bind ANY UDP sockets. Shutting down.\n"); + return (ERROR); + } + + + length = sizeof(client); + Log("adminalert: Kalasag is now active and listening.\n"); + +/* main loop for multiplexing/resetting */ + for (;;){ + /* set up select call */ + for (count = 0; count < boundPortCount; count++) + FD_SET(openSockfd[count], &selectFds); + /* setup the select multiplexing (blocking mode) */ + selectResult = + select(MAXSOCKS, &selectFds, NULL, NULL, + (struct timeval *) NULL); + + if (selectResult < 0){ + Log("adminalert: ERROR: select call failed. Shutting down.\n"); + return (ERROR); + } else if (selectResult == 0){ +#ifdef DEBUG + Log("Select timeout"); +#endif + } + + /* select is reporting a waiting socket. Poll them all to find out which */ + else if (selectResult > 0){ + for (count = 0; count < portCount; count++){ + if (FD_ISSET(openSockfd[count], &selectFds)){ + /* here just read in one byte from the UDP socket, that's all we need to */ + /* know that this person is a jerk */ + if (recvfrom(openSockfd[count], buffer, 1, 0, + (struct sockaddr *) &client, &length) < 0) + { + Log("adminalert: ERROR: could not accept incoming socket for UDP port: %d\n", ports[count]); + break; + } + + /* copy the clients address into our buffer for nuking */ + SafeStrncpy(target, + (char *) inet_ntoa(client.sin_addr), + IPMAXBUF); +#ifdef DEBUG + Log("debug: KalasagModeUDP: accepted UDP connection from: %s\n", target); +#endif + /* check if we should ignore this IP */ + result = NeverBlock(target, gblIgnoreFile); + if (result == ERROR){ + Log("attackalert: ERROR: cannot open ignore file. Blocking host anyway.\n"); + result = FALSE; + } + if (result == FALSE){ + /* check if they've visited before */ + scanDetectTrigger = CheckStateEngine(target); + if (scanDetectTrigger == TRUE){ + /* show the banner if one was selected */ + if (showBanner == TRUE) + sendto(openSockfd[count], bannerBuffer, + strlen(bannerBuffer), 0, + (struct sockaddr *) &client, + length); + + if (gblResolveHost){ /* Do they want DNS resolution? */ + if (CleanAndResolve(resolvedHost, target) + != TRUE){ + Log("attackalert: ERROR: Error resolving host. \ + resolving disabled for this host.\n"); + snprintf(resolvedHost, DNSMAXBUF, "%s", + target); + } + } else { + snprintf(resolvedHost, DNSMAXBUF, "%s", + target); + } + + Log("attackalert: Connect from host: %s/%s to UDP port: %d", resolvedHost, target, ports[count]); + /* check if this target is already blocked */ + if (IsBlocked(target, gblBlockedFile) == FALSE){ + if (DisposeUDP(target, ports[count]) != + TRUE) + Log("attackalert: ERROR: Could not block host %s !!", target); + else + WriteBlocked(target, resolvedHost, + ports[count], + gblBlockedFile, + gblHistoryFile, "UDP"); + } else + Log("attackalert: Host: %s is already blocked. Ignoring", target); + } + } + break; + } /* end if(FD_ISSET) */ + } /* end for() */ + } /* end else (selectResult > 0) */ + } /* end main for(; ; ) loop */ + +} /* end UDP Kalasag */ + + + + +/* kill the TCP connection depending on config option */ +int DisposeTCP(char *target, int port) +{ + int status = TRUE; + +#ifdef DEBUG + Log("debug: DisposeTCP: disposing of host %s on port %d with option: %d", target, port, gblBlockTCP); + Log("debug: DisposeTCP: killRunCmd: %s", gblKillRunCmd); + Log("debug: DisposeTCP: gblRunCmdFirst: %d", gblRunCmdFirst); + Log("debug: DisposeTCP: killHostsDeny: %s", gblKillHostsDeny); + Log("debug: DisposeTCP: killRoute: %s %d", gblKillRoute, + strlen(gblKillRoute)); +#endif +/* Should we ignore TCP from active response? */ + if (gblBlockTCP == 1){ + /* run external command first, hosts.deny second, dead route last */ + if (gblRunCmdFirst){ + if (strlen(gblKillRunCmd) > 0) + if (KillRunCmd + (target, port, gblKillRunCmd, + gblDetectionType) != TRUE) + status = FALSE; + if (strlen(gblKillHostsDeny) > 0) + if (KillHostsDeny + (target, port, gblKillHostsDeny, + gblDetectionType) != TRUE) + status = FALSE; + if (strlen(gblKillRoute) > 0) + if (KillRoute(target, port, gblKillRoute, gblDetectionType) + != TRUE) + status = FALSE; + } + /* run hosts.deny first, dead route second, external command last */ + else { + if (strlen(gblKillHostsDeny) > 0) + if (KillHostsDeny + (target, port, gblKillHostsDeny, + gblDetectionType) != TRUE) + status = FALSE; + if (strlen(gblKillRoute) > 0) + if (KillRoute(target, port, gblKillRoute, gblDetectionType) + != TRUE) + status = FALSE; + if (strlen(gblKillRunCmd) > 0) + if (KillRunCmd + (target, port, gblKillRunCmd, + gblDetectionType) != TRUE) + status = FALSE; + } + } else if (gblBlockTCP == 2){ + /* run external command only */ + if (strlen(gblKillRunCmd) > 0) + if (KillRunCmd(target, port, gblKillRunCmd, gblDetectionType) + != TRUE) + status = FALSE; + } else + Log("attackalert: Ignoring TCP response per configuration file setting."); + + return (status); +} + + +/* kill the UDP connection depending on config option */ +int DisposeUDP(char *target, int port) +{ + int status = TRUE; + +#ifdef DEBUG + Log("debug: DisposeUDP: disposing of host %s on port %d with option: %d", target, port, gblBlockUDP); + Log("debug: DisposeUDP: killRunCmd: %d", gblKillRunCmd); + Log("debug: DisposeUDP: gblRunCmdFirst: %s", gblRunCmdFirst); + Log("debug: DisposeUDP: killHostsDeny: %s", gblKillHostsDeny); + Log("debug: DisposeUDP: killRoute: %s %d", gblKillRoute, + strlen(gblKillRoute)); +#endif +/* Should we ignore TCP from active response? */ + if (gblBlockUDP == 1){ + /* run external command first, hosts.deny second, dead route last */ + if (gblRunCmdFirst){ + if (strlen(gblKillRunCmd) > 0) + if (KillRunCmd + (target, port, gblKillRunCmd, + gblDetectionType) != TRUE) + status = FALSE; + if (strlen(gblKillHostsDeny) > 0) + if (KillHostsDeny + (target, port, gblKillHostsDeny, + gblDetectionType) != TRUE) + status = FALSE; + if (strlen(gblKillRoute) > 0) + if (KillRoute(target, port, gblKillRoute, gblDetectionType) + != TRUE) + status = FALSE; + } + /* run hosts.deny first, dead route second, external command last */ + else { + if (strlen(gblKillHostsDeny) > 0) + if (KillHostsDeny + (target, port, gblKillHostsDeny, + gblDetectionType) != TRUE) + status = FALSE; + if (strlen(gblKillRoute) > 0) + if (KillRoute(target, port, gblKillRoute, gblDetectionType) + != TRUE) + status = FALSE; + if (strlen(gblKillRunCmd) > 0) + if (KillRunCmd + (target, port, gblKillRunCmd, + gblDetectionType) != TRUE) + status = FALSE; + } + } else if (gblBlockUDP == 2){ + /* run external command only */ + if (strlen(gblKillRunCmd) > 0) + if (KillRunCmd(target, port, gblKillRunCmd, gblDetectionType) + != TRUE) + status = FALSE; + } else + Log("attackalert: Ignoring UDP response per configuration file setting."); + + return (status); +} + + +/* duh */ +void Usage(void) +{ + printf("Kalasag Port Scan Detector\n"); + printf("Version: %s\n\n", VERSION); +#ifdef SUPPORT_STEALTH + printf("usage: kalasag [-tcp -udp -stcp -atcp -sudp -audp]\n\n"); +#else + printf("Stealth scan detection not supported on this platform\n"); + printf("usage: kalasag [-tcp -udp]\n\n"); +#endif +/* + printf ("*** PLEASE READ THE DOCS BEFORE USING *** \n\n"); +*/ +} + + + +/* our cheesy state engine to monitor who has connected here before */ +int CheckStateEngine(char *target) +{ + int count = 0, scanDetectTrigger = TRUE; + int gotOne = 0; + +/* This is the rather basic scan state engine. It maintains */ +/* an array of past hosts who triggered a connection on a port */ +/* when a new host arrives it is compared against the array */ +/* if it is found in the array it increments a state counter by */ +/* one and checks the remainder of the array. It does this until */ +/* the end is reached or the trigger value has been exceeded */ +/* This would probably be better as a linked list/hash table, */ +/* but for the number of hosts we are tracking this is just as good. */ +/* This will probably change in the future */ + + gotOne = 1; /* our flag counter if we get a match */ + scanDetectTrigger = TRUE; /* set to TRUE until set otherwise */ + + if (gblConfigTriggerCount > 0){ + for (count = 0; count < MAXSTATE; count++){ + /* if the array has the IP address then increment the gotOne counter and */ + /* check the trigger value. If it is exceeded break out of the loop and */ + /* set the detecttrigger to TRUE */ + if (strcmp(gblScanDetectHost[count], target) == 0){ + /* compare the number of matches to the configured trigger value */ + /* if we've exceeded we can stop this noise */ + if (++gotOne >= gblConfigTriggerCount){ + scanDetectTrigger = TRUE; +#ifdef DEBUG + Log("debug: CheckStateEngine: host: %s has exceeded trigger value: %d\n", gblScanDetectHost[count], gblConfigTriggerCount); +#endif + break; + } + } else + scanDetectTrigger = FALSE; + } + + /* now add the fresh meat into the state engine */ + /* if our array is still less than MAXSTATE large add it to the end */ + if (gblScanDetectCount < MAXSTATE){ + SafeStrncpy(gblScanDetectHost[gblScanDetectCount], target, + IPMAXBUF); + gblScanDetectCount++; + } else { + /* otherwise tack it to the beginning and start overwriting older ones */ + gblScanDetectCount = 0; + SafeStrncpy(gblScanDetectHost[gblScanDetectCount], target, + IPMAXBUF); + gblScanDetectCount++; + } + +#ifdef DEBUG + for (count = 0; count < MAXSTATE; count++) + Log("debug: CheckStateEngine: state engine host: %s -> position: %d Detected: %d\n", gblScanDetectHost[count], count, scanDetectTrigger); +#endif + /* end catch to set state if gblConfigTriggerCount == 0 */ + if (gotOne >= gblConfigTriggerCount) + scanDetectTrigger = TRUE; + } + + + if (gblConfigTriggerCount > MAXSTATE){ + Log("securityalert: WARNING: Trigger value %d is larger than state engine capacity of %d.\n", gblConfigTriggerCount); + Log("Adjust the value lower or recompile with a larger state engine value.\n", MAXSTATE); + Log("securityalert: Blocking host anyway because of invalid trigger value"); + scanDetectTrigger = TRUE; + } + return (scanDetectTrigger); +} + + +#ifdef SUPPORT_STEALTH +/* This takes a tcp packet and reports what type of scan it is */ +char *ReportPacketType(struct tcphdr tcpPkt) +{ + static char packetDesc[MAXBUF]; + static char *packetDescPtr = packetDesc; + + if ((tcpPkt.syn == 0) && (tcpPkt.fin == 0) && (tcpPkt.ack == 0) + && (tcpPkt.psh == 0) && (tcpPkt.rst == 0) && (tcpPkt.urg == 0)) + snprintf(packetDesc, MAXBUF, " TCP NULL scan"); + else if ((tcpPkt.fin == 1) && (tcpPkt.urg == 1) && (tcpPkt.psh == 1)) + snprintf(packetDesc, MAXBUF, "TCP XMAS scan"); + else if ((tcpPkt.fin == 1) && (tcpPkt.syn != 1) && (tcpPkt.ack != 1) + && (tcpPkt.psh != 1) && (tcpPkt.rst != 1) + && (tcpPkt.urg != 1)) + snprintf(packetDesc, MAXBUF, "TCP FIN scan"); + else if ((tcpPkt.syn == 1) && (tcpPkt.fin != 1) && (tcpPkt.ack != 1) + && (tcpPkt.psh != 1) && (tcpPkt.rst != 1) + && (tcpPkt.urg != 1)) + snprintf(packetDesc, MAXBUF, "TCP SYN/Normal scan"); + else + snprintf(packetDesc, MAXBUF, + "Unknown Type: TCP Packet Flags: SYN: %d FIN: %d ACK: %d PSH: %d URG: %d RST: %d", + tcpPkt.syn, tcpPkt.fin, tcpPkt.ack, tcpPkt.psh, + tcpPkt.urg, tcpPkt.rst); + + return (packetDescPtr); +} + +int +SmartVerifyTCP(struct sockaddr_in client, struct sockaddr_in server, + int port) +{ + + int testSockfd; + +/* Ok here is where we "Smart-Verify" the socket. If the port was previously */ +/* unbound, but now appears to have someone there, then we will skip responding */ +/* to this inbound packet. This a basic "stateful" inspection of the */ +/* the connection */ + + if ((testSockfd = OpenTCPSocket()) == ERROR){ + Log("adminalert: ERROR: could not open TCP socket to smart-verify.\n"); + return (FALSE); + } else { + if (BindSocket(testSockfd, client, server, port) == ERROR){ +#ifdef DEBUG + Log("debug: SmartVerify: Smart-Verify Port In Use: %d", port); +#endif + close(testSockfd); + return (TRUE); + } + } + + close(testSockfd); + return (FALSE); +} + +int +SmartVerifyUDP(struct sockaddr_in client, struct sockaddr_in server, + int port) +{ + int testSockfd; + +/* Ok here is where we "Smart-Verify" the socket. If the port was previously */ +/* unbound, but now appears to have someone there, then we will skip responding */ +/* to this inbound packet. This essentially is a "stateful" inspection of the */ +/* the connection */ + + if ((testSockfd = OpenUDPSocket()) == ERROR){ + Log("adminalert: ERROR: could not open UDP socket to smart-verify.\n"); + return (FALSE); + } else { + if (BindSocket(testSockfd, client, server, port) == ERROR){ +#ifdef DEBUG + Log("debug: SmartVerify: Smart-Verify Port In Use: %d", port); +#endif + close(testSockfd); + return (TRUE); + } + } + + close(testSockfd); + return (FALSE); +} + +#endif /* SUPPORT_STEALTH */
diff --git a/kalasag.conf b/kalasag.conf
new file mode 100644
index 0000000..aaa111b --- /dev/null +++ b/kalasag.conf @@ -0,0 +1,293 @@ +# Kalasag Configuration +# +# IMPORTANT NOTE: You CAN NOT put spaces between your port arguments. +# +# The default ports will catch a large number of common probes +# +# All entries must be in quotes. + + +####################### +# Port Configurations # +####################### +# +# +# Some example port configs for classic and basic Stealth modes +# +# I like to always keep some ports at the "low" end of the spectrum. +# This will detect a sequential port sweep really quickly and usually +# these ports are not in use (i.e. tcpmux port 1) +# +# ** X-Windows Users **: If you are running X on your box, you need to be sure +# you are not binding Kalasag to port 6000 (or port 2000 for OpenWindows users). +# Doing so will prevent the X-client from starting properly. +# +# These port bindings are *ignored* for Advanced Stealth Scan Detection Mode. +# + +# Un-comment these if you are really anal: +#TCP_PORTS="1,7,9,11,15,70,79,80,109,110,111,119,138,139,143,512,513,514,515,540,635,1080,1524,2000,2001,4000,4001,5742,6000,6001,6667,12345,12346,20034,27665,30303,32771,32772,32773,32774,31337,40421,40425,49724,54320" +#UDP_PORTS="1,7,9,66,67,68,69,111,137,138,161,162,474,513,517,518,635,640,641,666,700,2049,31335,27444,34555,32770,32771,32772,32773,32774,31337,54321" +# +# Use these if you just want to be aware: +TCP_PORTS="1,11,15,79,111,119,143,540,635,1080,1524,2000,5742,6667,12345,12346,20034,27665,31337,32771,32772,32773,32774,40421,49724,54320" +UDP_PORTS="1,7,9,69,161,162,513,635,640,641,700,37444,34555,31335,32770,32771,32772,32773,32774,31337,54321" +# +# Use these for just bare-bones +#TCP_PORTS="1,11,15,110,111,143,540,635,1080,1524,2000,12345,12346,20034,32771,32772,32773,32774,49724,54320" +#UDP_PORTS="1,7,9,69,161,162,513,640,700,32770,32771,32772,32773,32774,31337,54321" + +########################################### +# Advanced Stealth Scan Detection Options # +########################################### +# +# This is the number of ports you want Kalasag to monitor in Advanced mode. +# Any port *below* this number will be monitored. Right now it watches +# everything below 1024. +# +# On many Linux systems you cannot bind above port 61000. This is because +# these ports are used as part of IP masquerading. I don't recommend you +# bind over this number of ports. Realistically: I DON'T RECOMMEND YOU MONITOR +# OVER 1024 PORTS AS YOUR FALSE ALARM RATE WILL ALMOST CERTAINLY RISE. You've been +# warned! Don't write me if you have have a problem because I'll only tell +# you to RTFM and don't run above the first 1024 ports. +# +# +ADVANCED_PORTS_TCP="1024" +ADVANCED_PORTS_UDP="1024" +# +# This field tells Kalasag what ports (besides listening daemons) to +# ignore. This is helpful for services like ident that services such +# as FTP, SMTP, and wrappers look for but you may not run (and probably +# *shouldn't* IMHO). +# +# By specifying ports here Kalasag will simply not respond to +# incoming requests, in effect Kalasag treats them as if they are +# actual bound daemons. The default ports are ones reported as +# problematic false alarms and should probably be left alone for +# all but the most isolated systems/networks. +# +# Default TCP ident and NetBIOS service +ADVANCED_EXCLUDE_TCP="113,139" +# Default UDP route (RIP), NetBIOS, bootp broadcasts. +ADVANCED_EXCLUDE_UDP="520,138,137,67" + + +###################### +# Configuration Files# +###################### +# +# Hosts to ignore +IGNORE_FILE="/opt/kalasag/kalasag.ignore" +# Hosts that have been denied (running history) +HISTORY_FILE="/opt/kalasag/kalasag.history" +# Hosts that have been denied this session only (temporary until next restart) +BLOCKED_FILE="/opt/kalasag/kalasag.blocked" + +############################## +# Misc. Configuration Options# +############################## +# +# DNS Name resolution - Setting this to "1" will turn on DNS lookups +# for attacking hosts. Setting it to "0" (or any other value) will shut +# it off. +RESOLVE_HOST = "1" + +################### +# Response Options# +################### +# Options to dispose of attacker. Each is an action that will +# be run if an attack is detected. If you don't want a particular +# option then comment it out and it will be skipped. +# +# The variable $TARGET$ will be substituted with the target attacking +# host when an attack is detected. The variable $PORT$ will be substituted +# with the port that was scanned. +# +################## +# Ignore Options # +################## +# These options allow you to enable automatic response +# options for UDP/TCP. This is useful if you just want +# warnings for connections, but don't want to react for +# a particular protocol (i.e. you want to block TCP, but +# not UDP). To prevent a possible Denial of service attack +# against UDP and stealth scan detection for TCP, you may +# want to disable blocking, but leave the warning enabled. +# I personally would wait for this to become a problem before +# doing though as most attackers really aren't doing this. +# The third option allows you to run just the external command +# in case of a scan to have a pager script or such execute +# but not drop the route. This may be useful for some admins +# who want to block TCP, but only want pager/e-mail warnings +# on UDP, etc. +# +# +# 0 = Do not block UDP/TCP scans. +# 1 = Block UDP/TCP scans. +# 2 = Run external command only (KILL_RUN_CMD) + +BLOCK_UDP="1" +BLOCK_TCP="1" + +################### +# Dropping Routes:# +################### +# This command is used to drop the route or add the host into +# a local filter table. +# +# The gateway (333.444.555.666) should ideally be a dead host on +# the *local* subnet. On some hosts you can also point this at +# localhost (127.0.0.1) and get the same effect. NOTE THAT +# 333.444.555.66 WILL *NOT* WORK. YOU NEED TO CHANGE IT!! +# +# ALL KILL ROUTE OPTIONS ARE COMMENTED OUT INITIALLY. Make sure you +# uncomment the correct line for your OS. If you OS is not listed +# here and you have a route drop command that works then please +# mail it to me so I can include it. ONLY ONE KILL_ROUTE OPTION +# CAN BE USED AT A TIME SO DON'T UNCOMMENT MULTIPLE LINES. +# +# NOTE: The route commands are the least optimal way of blocking +# and do not provide complete protection against UDP attacks and +# will still generate alarms for both UDP and stealth scans. I +# always recommend you use a packet filter because they are made +# for this purpose. +# + +# Generic +#KILL_ROUTE="/sbin/route add $TARGET$ 333.444.555.666" + +# Generic Linux +#KILL_ROUTE="/sbin/route add -host $TARGET$ gw 333.444.555.666" + +# Newer versions of Linux support the reject flag now. This +# is cleaner than the above option. +#KILL_ROUTE="/sbin/route add -host $TARGET$ reject" + +# Generic BSD (BSDI, OpenBSD, NetBSD, FreeBSD) +#KILL_ROUTE="/sbin/route add $TARGET$ 333.444.555.666" + +# Generic Sun +#KILL_ROUTE="/usr/sbin/route add $TARGET$ 333.444.555.666 1" + +# NEXTSTEP +#KILL_ROUTE="/usr/etc/route add $TARGET$ 127.0.0.1 1" + +# FreeBSD +#KILL_ROUTE="route add -net $TARGET$ -netmask 255.255.255.255 127.0.0.1 -blackhole" + +# Digital UNIX 4.0D (OSF/1 / Compaq Tru64 UNIX) +#KILL_ROUTE="/sbin/route add -host -blackhole $TARGET$ 127.0.0.1" + +# Generic HP-UX +#KILL_ROUTE="/usr/sbin/route add net $TARGET$ netmask 255.255.255.0 127.0.0.1" + +## +# Using a packet filter is the PREFERRED. The below lines +# work well on many OS's. Remember, you can only uncomment *one* +# KILL_ROUTE option. +## + +# ipfwadm support for Linux +#KILL_ROUTE="/sbin/ipfwadm -I -i deny -S $TARGET$ -o" +# +# ipfwadm support for Linux (no logging of denied packets) +#KILL_ROUTE="/sbin/ipfwadm -I -i deny -S $TARGET$" +# +# ipchain support for Linux +#KILL_ROUTE="/sbin/ipchains -I input -s $TARGET$ -j DENY -l" +# +# ipchain support for Linux (no logging of denied packets) +#KILL_ROUTE="/sbin/ipchains -I input -s $TARGET$ -j DENY" +# +# iptables support for Linux +KILL_ROUTE="/sbin/iptables -A INPUT -s $TARGET$ -j DROP" +# +# For those of you running FreeBSD (and compatible) you can +# use their built in firewalling as well. +# +#KILL_ROUTE="/sbin/ipfw add 1 deny all from $TARGET$:255.255.255.255 to any" +# +# +# For those running ipfilt (OpenBSD, etc.) +# NOTE THAT YOU NEED TO CHANGE external_interface TO A VALID INTERFACE!! +# +#KILL_ROUTE="/bin/echo 'block in log on external_interface from $TARGET$/32 to any' | /sbin/ipf -f -" + + +############### +# TCP Wrappers# +############### +# This text will be dropped into the hosts.deny file for wrappers +# to use. There are two formats for TCP wrappers: +# +# Format One: Old Style - The default when extended host processing +# options are not enabled. +# +KILL_HOSTS_DENY="ALL: $TARGET$" + +# Format Two: New Style - The format used when extended option +# processing is enabled. You can drop in extended processing +# options, but be sure you escape all '%' symbols with a backslash +# to prevent problems writing out (i.e. \%c \%h ) +# +#KILL_HOSTS_DENY="ALL: $TARGET$ : DENY" + +################### +# External Command# +################### +# This is a command that is run when a host connects, it can be whatever +# you want it to be (pager, etc.). This command is executed before the +# route is dropped or after depending on the KILL_RUN_CMD_FIRST option below +# +# +# I NEVER RECOMMEND YOU PUT IN RETALIATORY ACTIONS AGAINST THE HOST SCANNING +# YOU! +# +# TCP/IP is an *unauthenticated protocol* and people can make scans appear out +# of thin air. The only time it is reasonably safe (and I *never* think it is +# reasonable) to run reverse probe scripts is when using the "classic" -tcp mode. +# This mode requires a full connect and is very hard to spoof. +# +# The KILL_RUN_CMD_FIRST value should be set to "1" to force the command +# to run *before* the blocking occurs and should be set to "0" to make the +# command run *after* the blocking has occurred. +# +#KILL_RUN_CMD_FIRST = "0" +# +# +#KILL_RUN_CMD="/some/path/here/script $TARGET$ $PORT$" + + +##################### +# Scan trigger value# +##################### +# Enter in the number of port connects you will allow before an +# alarm is given. The default is 0 which will react immediately. +# A value of 1 or 2 will reduce false alarms. Anything higher is +# probably not necessary. This value must always be specified, but +# generally can be left at 0. +# +# NOTE: If you are using the advanced detection option you need to +# be careful that you don't make a hair trigger situation. Because +# Advanced mode will react for *any* host connecting to a non-used +# below your specified range, you have the opportunity to really +# break things. (i.e someone innocently tries to connect to you via +# SSL [TCP port 443] and you immediately block them). Some of you +# may even want this though. Just be careful. +# +SCAN_TRIGGER="0" + +###################### +# Port Banner Section# +###################### +# +# Enter text in here you want displayed to a person tripping the Kalasag. +# I *don't* recommend taunting the person as this will aggravate them. +# Leave this commented out to disable the feature +# +# Stealth scan detection modes don't use this feature +# +#PORT_BANNER="** UNAUTHORIZED ACCESS PROHIBITED *** YOUR CONNECTION ATTEMPT HAS BEEN LOGGED. GO AWAY." + +# EOF
diff --git a/kalasag.h b/kalasag.h
new file mode 100644
index 0000000..99abca2 --- /dev/null +++ b/kalasag.h @@ -0,0 +1,74 @@ +#define VERSION "1.0" + +#include <stdio.h> +#include <syslog.h> +#include <stdlib.h> +#include <unistd.h> +#include <signal.h> +#include <time.h> +#include <netdb.h> +#include <string.h> +#include <ctype.h> +#include <errno.h> +#include <stdarg.h> +#include <assert.h> +#include <sys/param.h> +#include <sys/types.h> +#ifndef _LINUX_C_LIB_VERSION +#include <sys/socket.h> +#include <sys/stat.h> +#include <netinet/in.h> +#endif +#include <arpa/inet.h> + +#include "kalasag_config.h" +#include "kalasag_io.h" +#include "kalasag_util.h" + +#ifdef SUPPORT_STEALTH +#ifdef LINUX +#include "kalasag_tcpip.h" +#include <netinet/in_systm.h> +#endif + +#define TCPPACKETLEN 80 +#define UDPPACKETLEN 68 +#endif /* SUPPORT_STEALTH */ + +#ifdef NEXT +#include <ansi.h> +#endif + +#define ERROR -1 +#define TRUE 1 +#define FALSE 0 +#define MAXBUF 1024 +/* max size of an IP address plus NULL */ +#define IPMAXBUF 16 +/* max sockets we can open */ +#define MAXSOCKS 64 + +/* Really is about 1025, but we don't need the length for our purposes */ +#define DNSMAXBUF 255 + + +/* prototypes */ +int KalasagModeTCP(void); +int KalasagModeUDP(void); +int DisposeUDP(char *, int); +int DisposeTCP(char *, int); +int CheckStateEngine(char *); +int InitConfig(void); +void Usage(void); +int SmartVerifyTCP(struct sockaddr_in, struct sockaddr_in, int); +int SmartVerifyUDP(struct sockaddr_in, struct sockaddr_in, int); + +#ifdef SUPPORT_STEALTH +int KalasagStealthModeTCP(void); +int KalasagAdvancedStealthModeTCP(void); +int KalasagStealthModeUDP(void); +int KalasagAdvancedStealthModeUDP(void); +char *ReportPacketType(struct tcphdr); +int PacketReadTCP(int, struct iphdr *, struct tcphdr *); +int PacketReadUDP(int, struct iphdr *, struct udphdr *); +#endif
diff --git a/kalasag.ignore b/kalasag.ignore
new file mode 100644
index 0000000..fa804ed --- /dev/null +++ b/kalasag.ignore @@ -0,0 +1,2 @@ +127.0.0.1/32 +0.0.0.0
diff --git a/kalasag_config.h b/kalasag_config.h
new file mode 100644
index 0000000..6d5c418 --- /dev/null +++ b/kalasag_config.h @@ -0,0 +1,15 @@ +/* These are probably ok. Be sure you change the Makefile if you */ +/* change the path */ +#define CONFIG_FILE "/opt/kalasag/kalasag.conf" + +/* The location of Wietse Venema's TCP Wrapper hosts.deny file */ +#define WRAPPER_HOSTS_DENY "/etc/hosts.deny" + +/* The default syslog is as daemon.notice. You can also use */ +/* any of the facilities from syslog.h to send messages to (LOCAL0, etc) */ +#define SYSLOG_FACILITY LOG_DAEMON +#define SYSLOG_LEVEL LOG_NOTICE + + +/* the maximum number of hosts to keep in a "previous connect" state engine*/ +#define MAXSTATE 50
diff --git a/kalasag_io.c b/kalasag_io.c
new file mode 100644
index 0000000..f181bc4 --- /dev/null +++ b/kalasag_io.c @@ -0,0 +1,752 @@ +#include "kalasag.h" +#include "kalasag_io.h" +#include "kalasag_util.h" + +/* Main logging function to surrogate syslog */ +void Log(char *logentry, ...) +{ + char logbuffer[MAXBUF]; + + va_list argsPtr; + va_start(argsPtr, logentry); + + vsnprintf(logbuffer, MAXBUF, logentry, argsPtr); + + va_end(argsPtr); + + openlog("kalasag", LOG_PID, SYSLOG_FACILITY); + syslog(SYSLOG_LEVEL, "%s", logbuffer); + closelog(); +} + + +void Exit(int status) +{ + Log("securityalert: Kalasag is shutting down\n"); + Log("adminalert: Kalasag is shutting down\n"); + exit(status); +} + + +void Start(void) +{ + Log("adminalert: Kalasag %s is starting.\n", VERSION); +#ifdef DEBUG + printf("Compiled: " __DATE__ " at " __TIME__ "\n"); +#endif +} + + + +/* The daemonizing code copied from Advanced Programming */ +/* in the UNIX Environment by W. Richard Stevens with minor changes */ +int DaemonSeed(void) +{ + int childpid; + + signal(SIGALRM, SIG_IGN); + signal(SIGHUP, SIG_IGN); + signal(SIGPIPE, SIG_IGN); + signal(SIGTERM, Exit); + signal(SIGABRT, Exit); + signal(SIGURG, Exit); + signal(SIGKILL, Exit); + + if ((childpid = fork()) < 0) + return (ERROR); + else if (childpid > 0) + exit(0); + + setsid(); + chdir("/"); + umask(077); + + /* close stdout, stdin, stderr */ + close(0); + close(1); + close(2); + + return (TRUE); +} + + +/* Compares an IP address against a listed address and its netmask*/ +int CompareIPs(char *target, char *ignoreAddr, int ignoreNetmaskBits) +{ + unsigned long int netmaskAddr, ipAddr, targetAddr; + + ipAddr = inet_addr(ignoreAddr); + targetAddr = inet_addr(target); + netmaskAddr = htonl(0xFFFFFFFF << (32 - ignoreNetmaskBits)); + + +#ifdef DEBUG + Log("debug: target %s\n", target); + Log("debug: ignoreAddr %s\n", ignoreAddr); + Log("debug: ignoreNetmaskBits %d\n", ignoreNetmaskBits); + Log("debug: ipAddr %lu\n", ipAddr); + Log("debug: targetAddr %lu\n", targetAddr); + Log("debug: netmask %x\n", netmaskAddr); + Log("debug: mix ipAddr %lu\n", (ipAddr & netmaskAddr)); + Log("debug: mix target %lu\n", (targetAddr & netmaskAddr)); +#endif + + /* Network portion mask & op and return */ + if ((ipAddr & netmaskAddr) == (targetAddr & netmaskAddr)) + return (TRUE); + else + return (FALSE); +} + + + +/* check hosts that should never be blocked */ +int NeverBlock(char *target, char *filename) +{ + FILE *input; + char buffer[MAXBUF], tempBuffer[MAXBUF], netmaskBuffer[MAXBUF]; + char *slashPos; + int count = 0, dest = 0, netmaskBits = 0; + +#ifdef DEBUG + Log("debug: NeverBlock: Opening ignore file: %s \n", filename); +#endif + if ((input = fopen(filename, "r")) == NULL) + return (ERROR); + +#ifdef DEBUG + Log("debug: NeverBlock: Doing lookup for host: %s \n", target); +#endif + + while (fgets(buffer, MAXBUF, input) != NULL){ + /* Reset destination counter */ + dest = 0; + + if ((buffer[0] == '#') || (buffer[0] == '\n')) + continue; + + for (count = 0; count < strlen(buffer); count++){ + /* Parse out digits, colons, and slashes. Everything else rejected */ + if ((isdigit(buffer[count])) || + (buffer[count] == '.') || (buffer[count] == ':') + || (buffer[count] == '/')){ + tempBuffer[dest++] = buffer[count]; + } else { + tempBuffer[dest] = '\0'; + break; + } + } + + /* Return pointer to slash if it exists and copy data to buffer */ + slashPos = strchr(tempBuffer, '/'); + if (slashPos){ + SafeStrncpy(netmaskBuffer, slashPos + 1, MAXBUF); + /* Terminate tempBuffer string at delimeter for later use */ + *slashPos = '\0'; + } else + /* Copy in a 32 bit netmask if none given */ + SafeStrncpy(netmaskBuffer, "32", MAXBUF); + + + /* Convert netmaskBuffer to bits in netmask */ + netmaskBits = atoi(netmaskBuffer); + if ((netmaskBits < 0) || (netmaskBits > 32)){ + Log("adminalert: Invalid netmask in config file: %s Ignoring entry.\n", buffer); + continue; + } + + if (CompareIPs(target, tempBuffer, netmaskBits)){ +#ifdef DEBUG + Log("debug: NeverBlock: Host: %s found in ignore file with netmask %s\n", target, netmaskBuffer); +#endif + + fclose(input); + return (TRUE); + } + + } /* end while() */ + +#ifdef DEBUG + Log("debug: NeverBlock: Host: %s NOT found in ignore file\n", target); +#endif + + fclose(input); + return (FALSE); +} + + +/* Make sure the config file is available */ +int CheckConfig(void) +{ + FILE *input; + + if ((input = fopen(CONFIG_FILE, "r")) == NULL){ + Log("adminalert: Cannot open config file: %s. Exiting\n", + CONFIG_FILE); + return (FALSE); + } else + fclose(input); + + return (TRUE); +} + + +/* This writes out blocked hosts to the blocked file. It adds the hostname */ +/* time stamp, and port connection that was acted on */ +int +WriteBlocked(char *target, char *resolvedHost, int port, + char *blockedFilename, char *historyFilename, char *portType) +{ + FILE *output; + int blockedStatus = TRUE, historyStatus = TRUE; + + struct tm *tmptr; + + time_t current_time; + current_time = time(0); + tmptr = localtime(¤t_time); + + +#ifdef DEBUG + Log("debug: WriteBlocked: Opening block file: %s \n", blockedFilename); +#endif + + + if ((output = fopen(blockedFilename, "a")) == NULL){ + Log("adminalert: ERROR: Cannot open blocked file: %s.\n", + blockedFilename); + blockedStatus = FALSE; + } else { + fprintf(output, + "%ld - %02d/%02d/%04d %02d:%02d:%02d Host: %s/%s Port: %d %s Blocked\n", + current_time, tmptr->tm_mon + 1, tmptr->tm_mday, + tmptr->tm_year + 1900, tmptr->tm_hour, tmptr->tm_min, + tmptr->tm_sec, resolvedHost, target, port, portType); + fclose(output); + blockedStatus = TRUE; + } + +#ifdef DEBUG + Log("debug: WriteBlocked: Opening history file: %s \n", + historyFilename); +#endif + if ((output = fopen(historyFilename, "a")) == NULL){ + Log("adminalert: ERROR: Cannot open history file: %s.\n", + historyFilename); + historyStatus = FALSE; + } else { + fprintf(output, + "%ld - %02d/%02d/%04d %02d:%02d:%02d Host: %s/%s Port: %d %s Blocked\n", + current_time, tmptr->tm_mon + 1, tmptr->tm_mday, + tmptr->tm_year + 1900, tmptr->tm_hour, tmptr->tm_min, + tmptr->tm_sec, resolvedHost, target, port, portType); + fclose(output); + historyStatus = TRUE; + } + + if (historyStatus || blockedStatus == FALSE) + return (FALSE); + else + return (TRUE); +} + + + + +/* This reads a token from the config file up to the "=" and returns the string */ +/* up to the first space or NULL */ +int ConfigTokenRetrieve(char *token, char *configToken) +{ + FILE *config; + char buffer[MAXBUF], tokenBuffer[MAXBUF]; + int count = 0; + + if ((config = fopen(CONFIG_FILE, "r")) == NULL){ + Log("adminalert: ERROR: Cannot open config file: %s.\n", + CONFIG_FILE); + return (ERROR); + } else { +#ifdef DEBUG + Log("debug: ConfigTokenRetrieve: checking for token %s", token); +#endif + while ((fgets(buffer, MAXBUF, config)) != NULL){ + /* this skips comments */ + if (buffer[0] != '#'){ +#ifdef DEBUG + Log("debug: ConfigTokenRetrieve: data: %s", buffer); +#endif + /* search for the token and make sure the trailing character */ + /* is a " " or "=" to make sure the entire token was found */ + if ((strstr(buffer, token) != (char) NULL) && + ((buffer[strlen(token)] == '=') + || (buffer[strlen(token)] == ' '))){ /* cut off the '=' and send it back */ + if (strstr(buffer, "\"") == (char) NULL){ + Log("adminalert: Quotes missing from %s token. Option skipped\n", token); + fclose(config); + return (FALSE); + } + + SafeStrncpy(tokenBuffer, strstr(buffer, "\"") + 1, + MAXBUF); + + /* strip off unprintables/linefeeds (if any) */ + count = 0; + while (count < MAXBUF - 1){ + if ((isprint(tokenBuffer[count])) + && tokenBuffer[count] != '"') + configToken[count] = tokenBuffer[count]; + else { + configToken[count] = '\0'; + break; + } + count++; + } + +#ifdef DEBUG + Log("debug: ConfigTokenRetrieved token: %s\n", + configToken); +#endif + configToken[MAXBUF - 1] = '\0'; + fclose(config); + return (TRUE); + } + } + } + fclose(config); + return (FALSE); + } + +} + + + + +/* This will bind a socket to a port. It works for UDP/TCP */ +int +BindSocket(int sockfd, struct sockaddr_in client, + struct sockaddr_in server, int port) +{ +#ifdef DEBUG + Log("debug: BindSocket: Binding to port: %d\n", port); +#endif + + bzero((char *) &server, sizeof(server)); + server.sin_family = AF_INET; + server.sin_addr.s_addr = htonl(INADDR_ANY); + server.sin_port = htons(port); + + if (bind(sockfd, (struct sockaddr *) &server, sizeof(server)) < 0){ +#ifdef DEBUG + Log("debug: BindSocket: Binding failed\n"); +#endif + return (ERROR); + } else { +#ifdef DEBUG + Log("debug: BindSocket: Binding successful. Doing listen\n"); +#endif + listen(sockfd, 5); + return (TRUE); + } +} + + +/* Open a TCP Socket */ +int OpenTCPSocket(void) +{ + int sockfd; + +#ifdef DEBUG + Log("debug: OpenTCPSocket: opening TCP socket\n"); +#endif + + if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0) + return (ERROR); + else + return (sockfd); +} + + +/* Open a UDP Socket */ +int OpenUDPSocket(void) +{ + int sockfd; + +#ifdef DEBUG + Log("debug: openUDPSocket opening UDP socket\n"); +#endif + + if ((sockfd = socket(AF_INET, SOCK_DGRAM, 0)) < 0) + return (ERROR); + else + return (sockfd); +} + +#ifdef SUPPORT_STEALTH +/* Open a RAW TCPSocket */ +int OpenRAWTCPSocket(void) +{ + int sockfd; + +#ifdef DEBUG + Log("debug: OpenRAWTCPSocket: opening RAW TCP socket\n"); +#endif + + if ((sockfd = socket(AF_INET, SOCK_RAW, IPPROTO_TCP)) < 0) + return (ERROR); + else + return (sockfd); +} + +/* Open a RAW UDP Socket */ +int OpenRAWUDPSocket(void) +{ + int sockfd; + +#ifdef DEBUG + Log("debug: OpenRAWUDPSocket: opening RAW UDP socket\n"); +#endif + + if ((sockfd = socket(AF_INET, SOCK_RAW, IPPROTO_UDP)) < 0) + return (ERROR); + else + return (sockfd); +} +#endif + +/* This will use a system() call to change the route of the target host to */ +/* a dead IP address on your LOCAL SUBNET. */ +int +KillRoute(char *target, int port, char *killString, char *detectionType) +{ + char cleanAddr[MAXBUF], commandStringTemp[MAXBUF]; + char commandStringTemp2[MAXBUF], commandStringFinal[MAXBUF]; + char portString[MAXBUF]; + int killStatus = ERROR, substStatus = ERROR; + + CleanIpAddr(cleanAddr, target); + snprintf(portString, MAXBUF, "%d", port); + + substStatus = + SubstString(cleanAddr, "$TARGET$", killString, commandStringTemp); + if (substStatus == 0){ + Log("adminalert: No target variable specified in KILL_ROUTE option. Skipping.\n"); + return (ERROR); + } else if (substStatus == ERROR){ + Log("adminalert: Error trying to parse $TARGET$ Token for KILL_ROUTE. Skipping.\n"); + return (ERROR); + } + + if (SubstString + (portString, "$PORT$", commandStringTemp, + commandStringTemp2) == ERROR){ + Log("adminalert: Error trying to parse $PORT$ Token for KILL_ROUTE. Skipping.\n"); + return (ERROR); + } + + if (SubstString + (detectionType, "$MODE$", commandStringTemp2, + commandStringFinal) == ERROR){ + Log("adminalert: Error trying to parse $MODE$ Token for KILL_ROUTE. Skipping.\n"); + return (ERROR); + } + +#ifdef DEBUG + Log("debug: KillRoute: running route command: %s\n", + commandStringFinal); +#endif + + /* Kill the bastard and report a status */ + killStatus = system(commandStringFinal); + + if (killStatus == 127){ + Log("adminalert: ERROR: There was an error trying to block host (exec fail) %s", target); + return (ERROR); + } else if (killStatus < 0){ + Log("adminalert: ERROR: There was an error trying to block host (system fail) %s", target); + return (ERROR); + } else { + Log("attackalert: Host %s has been blocked via dropped route using command: \"%s\"", target, commandStringFinal); + return (TRUE); + } +} + + + +/* This will run a specified command with TARGET as the option if one is given. */ +int +KillRunCmd(char *target, int port, char *killString, char *detectionType) +{ + char cleanAddr[MAXBUF], commandStringTemp[MAXBUF]; + char commandStringTemp2[MAXBUF], commandStringFinal[MAXBUF]; + char portString[MAXBUF]; + int killStatus = ERROR; + + CleanIpAddr(cleanAddr, target); + snprintf(portString, MAXBUF, "%d", port); + + /* Tokens are not required, but we check for an error anyway */ + if (SubstString(cleanAddr, "$TARGET$", killString, commandStringTemp) + == ERROR){ + Log("adminalert: Error trying to parse $TARGET$ Token for KILL_RUN_CMD. Skipping.\n"); + return (ERROR); + } + + if (SubstString + (portString, "$PORT$", commandStringTemp, + commandStringTemp2) == ERROR){ + Log("adminalert: Error trying to parse $PORT$ Token for KILL_RUN_CMD. Skipping.\n"); + return (ERROR); + } + + if (SubstString + (detectionType, "$MODE$", commandStringTemp2, + commandStringFinal) == ERROR){ + Log("adminalert: Error trying to parse $MODE$ Token for KILL_RUN_CMD. Skipping.\n"); + return (ERROR); + } + + + /* Kill the bastard and report a status */ + killStatus = system(commandStringFinal); + + if (killStatus == 127){ + Log("adminalert: ERROR: There was an error trying to run command (exec fail) %s", target); + return (ERROR); + } else if (killStatus < 0){ + Log("adminalert: ERROR: There was an error trying to run command (system fail) %s", target); + return (ERROR); + } else { + /* report success */ + Log("attackalert: External command run for host: %s using command: \"%s\"", target, commandStringFinal); + return (TRUE); + } +} + + +/* this function will drop the host into the TCP wrappers hosts.deny file to deny */ +/* all access. The drop route method is preferred as this stops UDP attacks as well */ +/* as TCP. You may find though that host.deny will be a more permanent home.. */ +int +KillHostsDeny(char *target, int port, char *killString, + char *detectionType) +{ + + FILE *output; + char cleanAddr[MAXBUF], commandStringTemp[MAXBUF]; + char commandStringTemp2[MAXBUF], commandStringFinal[MAXBUF]; + char portString[MAXBUF]; + int substStatus = ERROR; + + CleanIpAddr(cleanAddr, target); + + snprintf(portString, MAXBUF, "%d", port); + +#ifdef DEBUG + Log("debug: KillHostsDeny: parsing string for block: %s\n", + killString); +#endif + + substStatus = + SubstString(cleanAddr, "$TARGET$", killString, commandStringTemp); + if (substStatus == 0){ + Log("adminalert: No target variable specified in KILL_HOSTS_DENY option. Skipping.\n"); + return (ERROR); + } else if (substStatus == ERROR){ + Log("adminalert: Error trying to parse $TARGET$ Token for KILL_HOSTS_DENY. Skipping.\n"); + return (ERROR); + } + + if (SubstString + (portString, "$PORT$", commandStringTemp, + commandStringTemp2) == ERROR){ + Log("adminalert: Error trying to parse $PORT$ Token for KILL_HOSTS_DENY. Skipping.\n"); + return (ERROR); + } + + if (SubstString + (detectionType, "$MODE$", commandStringTemp2, + commandStringFinal) == ERROR){ + Log("adminalert: Error trying to parse $MODE$ Token for KILL_HOSTS_DENY. Skipping.\n"); + return (ERROR); + } +#ifdef DEBUG + Log("debug: KillHostsDeny: result string for block: %s\n", + commandStringFinal); +#endif + + if ((output = fopen(WRAPPER_HOSTS_DENY, "a")) == NULL){ + Log("adminalert: cannot open hosts.deny file: %s for blocking.", + WRAPPER_HOSTS_DENY); + Log("securityalert: ERROR: There was an error trying to block host %s", target); + return (FALSE); + } else { + fprintf(output, "%s\n", commandStringFinal); + fclose(output); + Log("attackalert: Host %s has been blocked via wrappers with string: \"%s\"", target, commandStringFinal); + return (TRUE); + } +} + + +/* check if the host is already blocked */ +int IsBlocked(char *target, char *filename) +{ + FILE *input; + char buffer[MAXBUF], tempBuffer[MAXBUF]; + char *ipOffset; + int count; + + +#ifdef DEBUG + Log("debug: IsBlocked: Opening block file: %s \n", filename); +#endif + if ((input = fopen(filename, "r")) == NULL){ + Log("adminalert: ERROR: Cannot open blocked file: %s for reading. Will create.\n", filename); + return (FALSE); + } + + while (fgets(buffer, MAXBUF, input) != NULL){ + if ((ipOffset = strstr(buffer, target)) != (char) NULL){ + for (count = 0; count < strlen(ipOffset); count++){ + if ((isdigit(ipOffset[count])) || (ipOffset[count] == '.')){ + tempBuffer[count] = ipOffset[count]; + } else { + tempBuffer[count] = '\0'; + break; + } + } + if (strcmp(target, tempBuffer) == 0){ +#ifdef DEBUG + Log("debug: isBlocked: Host: %s found in blocked file\n", + target); +#endif + fclose(input); + return (TRUE); + } + } + + } +#ifdef DEBUG + Log("debug: IsBlocked: Host: %s NOT found in blocked file\n", target); +#endif + fclose(input); + return (FALSE); +} + +/********************************************************************************* +* String substitute function +* +* This function takes: +* +* 1) A token to use for replacement. +* 2) A token to find. +* 3) A string with the tokens in it. +* 4) A string to write the replaced result. +* +* It returns the number of substitutions made during the operation. +**********************************************************************************/ +int +SubstString(const char *replace, const char *find, const char *target, + char *result) +{ + int replaceCount = 0, count = 0, findCount = 0, findLen = + 0, numberOfSubst = 0; + char tempString[MAXBUF], *tempStringPtr; + +#ifdef DEBUG + Log("debug: SubstString: Processing string: %s %d", target, + strlen(target)); + Log("debug: SubstString: Processing search text: %s %d", replace, + strlen(replace)); + Log("debug: SubstString: Processing replace text: %s %d", find, + strlen(find)); +#endif + + /* string not found in target */ + if (strstr(target, find) == (char) NULL){ + strncpy(result, target, MAXBUF); +#ifdef DEBUG + Log("debug: SubstString: Result string: %s", result); +#endif + return (numberOfSubst); + } + /* String/victim/target too long */ + else if ((strlen(target)) + (strlen(replace)) + (strlen(find)) > + MAXBUF) + return (ERROR); + + memset(tempString, '\0', MAXBUF); + memset(result, '\0', MAXBUF); + findLen = strlen(find); + tempStringPtr = tempString; + + for (count = 0; count < MAXBUF; count++){ + if (*target == '\0') + break; + else if ((strncmp(target, find, findLen)) != 0) + *tempStringPtr++ = *target++; + else { + numberOfSubst++; + for (replaceCount = 0; replaceCount < strlen(replace); + replaceCount++) + *tempStringPtr++ = replace[replaceCount]; + for (findCount = 0; findCount < findLen; findCount++) + target++; + } + } + + strncpy(result, tempString, MAXBUF); +#ifdef DEBUG + Log("debug: SubstString: Result string: %s", result); +#endif + return (numberOfSubst); +} + + + +/* This function checks a config variable for a numerical flag and returns it */ +int CheckFlag(char *flagName) +{ + char configToken[MAXBUF]; + + if ((ConfigTokenRetrieve(flagName, configToken)) == TRUE){ +#ifdef DEBUG + Log("debug: CheckFlag: found %s string.\n", flagName); +#endif + return (atoi(configToken)); + } else { +#ifdef DEBUG + Log("debug: CheckFlag: %s option not found. Assuming FALSE.\n", + flagName); +#endif + return (FALSE); + } +} + + +/* snprintf for NEXTSTEP (others??) */ +/* I don't know where this code came from and I don't */ +/* warrant its effectiveness. CHR */ + +#ifdef HAS_NO_SNPRINTF +int snprintf(char *str, size_t n, char const *fmt, ...) +{ + va_list ap; + FILE f; + + if (n > MAXBUF){ + n = MAXBUF; + } + va_start(ap, fmt); + f._file = EOF; + f._flag = _IOWRT | _IOSTRG; + f._base = f._ptr = str; + f._bufsiz = f._cnt = n ? n - 1 : 0; + (void) vfprintf(&f, fmt, ap); + va_end(ap); + if (n){ + *f._ptr = '\0'; + } + return (f._ptr - str); +} +#endif
diff --git a/kalasag_io.h b/kalasag_io.h
new file mode 100644
index 0000000..bb83ec8 --- /dev/null +++ b/kalasag_io.h @@ -0,0 +1,23 @@ +/* prototypes */ +int WriteBlocked(char *, char *, int, char *, char *, char *); +void Log(char *, ...); +void Exit(int); +void Start(void); +int DaemonSeed(void); +int NeverBlock(char *, char *); +int CheckConfig(void); +int OpenTCPSocket(void); +int OpenUDPSocket(void); +#ifdef SUPPORT_STEALTH +int OpenRAWTCPSocket(void); +int OpenRAWUDPSocket(void); +#endif +int BindSocket(int, struct sockaddr_in, struct sockaddr_in, int); +int KillRoute(char *, int, char *, char *); +int KillHostsDeny(char *, int, char *, char *); +int KillRunCmd(char *, int, char *, char *); +int ConfigTokenRetrieve(char *, char *); +int IsBlocked(char *, char *); +int SubstString(const char *, const char *, const char *, char *); +int CheckFlag(char *); +int CompareIPs(char *, char *, int);
diff --git a/kalasag_tcpip.h b/kalasag_tcpip.h
new file mode 100644
index 0000000..b697b1f --- /dev/null +++ b/kalasag_tcpip.h @@ -0,0 +1,159 @@ +/* Versions of Linux are not consistent in how the TCP/UDP/IP headers +* defined. This file contains the Linux/BSD headers from RedHat 5.0 and +* should clear up compile problems. CHR +*/ + + +/* + * Copyright (c) 1982, 1986, 1993 + * The Regents of the University of California. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by the University of + * California, Berkeley and its contributors. + * 4. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * @(#)tcp.h 8.1 (Berkeley) 6/10/93 + */ + +#ifndef _NETINET_TCP_H +#define _NETINET_TCP_H 1 + +#include <features.h> + +__BEGIN_DECLS struct tcphdr { + u_int16_t source; + u_int16_t dest; + u_int32_t seq; + u_int32_t ack_seq; +#if __BYTE_ORDER == __LITTLE_ENDIAN + u_int16_t res1:4; + u_int16_t doff:4; + u_int16_t fin:1; + u_int16_t syn:1; + u_int16_t rst:1; + u_int16_t psh:1; + u_int16_t ack:1; + u_int16_t urg:1; + u_int16_t res2:2; +#elif __BYTE_ORDER == __BIG_ENDIAN + u_int16_t doff:4; + u_int16_t res1:4; + u_int16_t res2:2; + u_int16_t urg:1; + u_int16_t ack:1; + u_int16_t psh:1; + u_int16_t rst:1; + u_int16_t syn:1; + u_int16_t fin:1; +#else +#error "Adjust your <bits/endian.h> defines" +#endif + u_int16_t window; + u_int16_t check; + u_int16_t urg_ptr; +}; + +#endif /* tcp.h */ + + + +#ifndef __NETINET_IP_H +#define __NETINET_IP_H 1 + +__BEGIN_DECLS struct timestamp { + u_int8_t len; + u_int8_t ptr; +#if __BYTE_ORDER == __LITTLE_ENDIAN + u_int8_t flags:4; + u_int8_t overflow:4; +#elif __BYTE_ORDER == __BIG_ENDIAN + u_int8_t overflow:4; + u_int8_t flags:4; +#else +#error "Please fix <bytesex.h>" +#endif + u_int32_t data[9]; +}; + +struct ip_options { + u_int32_t faddr; /* Saved first hop address */ + u_int8_t optlen; + u_int8_t srr; + u_int8_t rr; + u_int8_t ts; + u_int8_t is_setbyuser:1; /* Set by setsockopt? */ + u_int8_t is_data:1; /* Options in __data, rather than skb */ + u_int8_t is_strictroute:1; /* Strict source route */ + u_int8_t srr_is_hit:1; /* Packet destination addr was our one */ + u_int8_t is_changed:1; /* IP checksum more not valid */ + u_int8_t rr_needaddr:1; /* Need to record addr of outgoing dev */ + u_int8_t ts_needtime:1; /* Need to record timestamp */ + u_int8_t ts_needaddr:1; /* Need to record addr of outgoing dev */ + u_int8_t router_alert; + u_int8_t __pad1; + u_int8_t __pad2; + u_int8_t __data[0]; +}; + +struct iphdr { +#if __BYTE_ORDER == __LITTLE_ENDIAN + u_int8_t ihl:4; + u_int8_t version:4; +#elif __BYTE_ORDER == __BIG_ENDIAN + u_int8_t version:4; + u_int8_t ihl:4; +#else +#error "Please fix <bytesex.h>" +#endif + u_int8_t tos; + u_int16_t tot_len; + u_int16_t id; + u_int16_t frag_off; + u_int8_t ttl; + u_int8_t protocol; + u_int16_t check; + u_int32_t saddr; + u_int32_t daddr; + /*The options start here. */ +}; + +#endif + +#ifndef __NETINET_UDP_H +#define __NETINET_UDP_H 1 + +__BEGIN_DECLS +/* UDP header as specified by RFC 768, August 1980. */ + struct udphdr { + u_int16_t source; + u_int16_t dest; + u_int16_t len; + u_int16_t check; +}; + +__END_DECLS +#endif /* netinet/udp.h */
diff --git a/kalasag_util.c b/kalasag_util.c
new file mode 100644
index 0000000..42cb133 --- /dev/null +++ b/kalasag_util.c @@ -0,0 +1,114 @@ +#include "kalasag.h" +#include "kalasag_io.h" +#include "kalasag_util.h" + +/* A replacement for strncpy that covers mistakes a little better */ +char *SafeStrncpy(char *dest, const char *src, size_t size) +{ + if (!dest){ + dest = NULL; + return (NULL); + } else if (size < 1){ + dest = NULL; + return (NULL); + } + + /* Null terminate string. Why the hell strncpy doesn't do this */ + /* for you is mystery to me. God I hate C. */ + memset(dest, '\0', size); + strncpy(dest, src, size - 1); + + return (dest); +} + + +/************************************************************************/ +/* Generic safety function to process an IP address and remove anything */ +/* that is: */ +/* 1) Not a number. */ +/* 2) Not a period. */ +/* 3) Greater than IPMAXBUF (15) */ +/************************************************************************/ +char *CleanIpAddr(char *cleanAddr, const char *dirtyAddr) +{ + int count = 0, maxdot = 0, maxoctet = 0; + +#ifdef DEBUG + Log("debug: cleanAddr: Cleaning Ip address: %s", dirtyAddr); +#endif + + memset(cleanAddr, '\0', IPMAXBUF); + /* dirtyAddr must be valid */ + if (dirtyAddr == NULL) + return (cleanAddr); + + for (count = 0; count < IPMAXBUF - 1; count++){ + if (isdigit(dirtyAddr[count])){ + if (++maxoctet > 3){ + cleanAddr[count] = '\0'; + break; + } + cleanAddr[count] = dirtyAddr[count]; + } else if (dirtyAddr[count] == '.'){ + if (++maxdot > 3){ + cleanAddr[count] = '\0'; + break; + } + maxoctet = 0; + cleanAddr[count] = dirtyAddr[count]; + } else { + cleanAddr[count] = '\0'; + break; + } + } + +#ifdef DEBUG + Log("debug: cleanAddr: Cleaned IpAddress: %s Dirty IpAddress: %s", + cleanAddr, dirtyAddr); +#endif + + return (cleanAddr); +} + + +/************************************************************************/ +/* Generic safety function to process an unresolved address and remove */ +/* anything that is: */ +/* 1) Not a number. */ +/* 2) Not a period. */ +/* 3) Greater than DNSMAXBUF (255) */ +/* 4) Not a legal DNS character (a-z, A-Z, 0-9, - ) */ +/* */ +/* XXX THIS FUNCTION IS NOT COMPLETE */ +/************************************************************************/ +int CleanAndResolve(char *resolvedHost, const char *unresolvedHost) +{ + struct hostent *hostPtr = NULL; + struct in_addr addr; + +#ifdef DEBUG + Log("debug: CleanAndResolv: Resolving address: %s", unresolvedHost); +#endif + + memset(resolvedHost, '\0', DNSMAXBUF); + /* unresolvedHost must be valid */ + if (unresolvedHost == NULL) + return (ERROR); + + /* Not a valid address */ + if ((inet_aton(unresolvedHost, &addr)) == 0) + return (ERROR); + + hostPtr = + gethostbyaddr((char *) &addr.s_addr, sizeof(addr.s_addr), AF_INET); + if (hostPtr != NULL) + snprintf(resolvedHost, DNSMAXBUF, "%s", hostPtr->h_name); + else + snprintf(resolvedHost, DNSMAXBUF, "%s", unresolvedHost); + +#ifdef DEBUG + Log("debug: CleanAndResolve: Cleaned Resolved: %s Dirty Unresolved: %s", resolvedHost, unresolvedHost); +#endif + + return (TRUE); +}
diff --git a/kalasag_util.h b/kalasag_util.h
new file mode 100644
index 0000000..5f95e2f --- /dev/null +++ b/kalasag_util.h @@ -0,0 +1,6 @@ +/* IP address length plus null */ +#define IPMAXBUF 16 + +char *SafeStrncpy(char *, const char *, size_t); +char *CleanIpAddr(char *, const char *); +int CleanAndResolve(char *, const char *);