# logstartup 1.0
# Module to check for invalid users
#########################################################################################
# logstartup 1.0									#
# by Floydman  floydian_99@yahoo.com							#
# Based on previous work from Harlan Carvey						#
# Copyright 2003 SecurIT Informatique Inc.  http://securit.iquebec.com			#
# This program will take the file startup.txt as input, and will match it against the 	#
# startup configuration of the machine in order to detect rogue configuration items.	#
#########################################################################################

use Socket;
use Sys::Hostname;
use Win32::TieRegistry(Delimiter=>"/");
use Win32::Shortcut;
$lasthive = '';

print "LogStartup 1.0, brought to you by Floydman\nBased on previous work from Harlan Carvey\n";
print "Copyright 2003 SecurIT Informatique Inc.\n";
print "http://securit.iquebec.com\n";

open(STARTUPFILE,"<startup.txt") || die "Can't open startup.txt";
@allowedstartup = <STARTUPFILE>;
@allowedstartup = parse (@allowedstartup);
close (STARTUPFILE) || die "Can't close startup.txt";

# Creation of machine ID table
@id = getid();

# Creation of config table
my @config = getconfig();

reportstartup();

#########################################################################################
# procedure getconfig() 								#
# This procedure gets the configuration file config.txt.				#
#########################################################################################

sub getconfig
{
my @configtable;
my @dirtable;
$j = 0;
$numarg = 0;
open(CONFIGFILE,"<config.txt") || die "Can't open config.txt";
$logip = <CONFIGFILE> || die "Can't read  logip from config.txt";
($logip=~m/LOGIP/i) || die "LOGIP entry missing in config.txt";

$loghost = <CONFIGFILE> || die "Can't read loghost from config.txt";
($loghost=~m/LOGHOST/i) || die "LOGHOST entry missing in config.txt";

$loguser = <CONFIGFILE> || die "Can't read loguser from config.txt";
($loguser=~m/LOGUSER/i) || die "LOGUSER entry missing in config.txt";

$logdate = <CONFIGFILE> || die "Can't read logdate from config.txt";
($logdate=~m/LOGDATE/i) || die "LOGDATE entry missing in config.txt";

$logtime = <CONFIGFILE> || die "Can't read logtime from config.txt";
($logtime=~m/LOGTIME/i) || die "LOGTIME entry missing in config.txt";

$showconsole = <CONFIGFILE> || die "Can't showconsole read from config.txt";
($showconsole=~m/SHOWCONSOLE/i) || die "SHOWCONSOLE entry missing in config.txt";

$lp = <CONFIGFILE> || die "Can't read lineprint from config.txt";
($lp=~m/LINEPRINT/i) || die "LINEPRINT entry missing in config.txt";

$lpbuffer = <CONFIGFILE> || die "Can't read lineprint buffer from config.txt";
($lpbuffer=~m/LINEPRINT BUFFER/i) || die "LINEPRINT BUFFER entry missing in config.txt";

while (defined($dir = <CONFIGFILE>))
	{
	 $dirtable[$j]=$dir;
	 $j++;
	}
($j==0) && die "No destination directory specifed in config.txt.";
close (CONFIGFILE) || die "Can't close config.txt";

@configtable = ($logip, $loghost, $loguser, $logdate, $logtime, $showconsole, $lp, $lpbuffer, @dirtable);
@configtable = parse(@configtable);

(($numarg=@configtable)<9) && die "Not enough parameters in config.txt.  Check file for errors.";

# Tranformation of the first 7 lines of configtable to boolean value
$configtable[0]=$configtable[0]=~m/Y/i;
$configtable[1]=$configtable[1]=~m/Y/i;
$configtable[2]=$configtable[2]=~m/Y/i;
$configtable[3]=$configtable[3]=~m/Y/i;
$configtable[4]=$configtable[4]=~m/Y/i;
$configtable[5]=$configtable[5]=~m/Y/i;
$configtable[6]=$configtable[6]=~m/Y/i;

# Get the lineprint buffer
@element=split(/=/, $configtable[7]);
$configtable[7]=$element[1]; ($configtable[7]>=0) || die "LINEPRINT BUFFER entry must be 0 or a positive number in config.txt";
return (@configtable);
}

#########################################################################################
# procedure getid() 									#
# This procedure gets the IP address, the host name and the username of the machine.	#
#########################################################################################

sub getid
{
# Define username, IP address and hostname of the local machine
my $addr = inet_ntoa(scalar(gethostbyname($name)) || 'localhost');
my $host = hostname() || "hostname not defined";
my $login = getlogin || getpwuid($<) || "not logged";
my @id_table = ($addr, $host, $login);
return (@id_table);

}

#########################################################################################
# procedure parse(table_file) 								#
# This procedure cleans the files from non-valid and blank characters that could be	#
# placed in the config files.  The procedure returns the file as a table.		#
#########################################################################################

sub parse
{ my (@table) = @_;

#check for invalid characters in table_file
chomp @table;

foreach $element (@table)
 {
  $element=~s%^\s+%%;
  @char = split (//, $element);

  foreach $char (@char)
    { $char=~s%\\%/%; }
  $element = join ('',@char);
 }

my @tabletemp;
my $x = 0;

foreach $element (@table)
   {
    if ($element ne '') {
	$tabletemp[$x]=$element;
	$x++;}
   }
@table = @tabletemp;
return (@table);
}


#########################################################################################
# procedure reportstartup()								#
# This procedure queries the various places where someone might try to plant a trojan	#
# horse to start automatically, and report to startup.log.				#
#########################################################################################
sub reportstartup
{
my $server = Win32::NodeName;

analyzestartup ("[Checking Registry keys on $server]");
my ($remote);

my %rkeys = ("UserInit" => "SOFTWARE/Microsoft/Windows NT/CurrentVersion/Winlogon/UserInit",
						"Load" => "SOFTWARE/Microsoft/Windows NT/CurrentVersion/Windows/Load");
						
if ($remote = $Registry->{"//$server/LMachine"})
	{
	 foreach my $k (keys %rkeys)
		{
		 my $reg = $rkeys{$k};
		 analyzestartup ("[$reg]");
		 my $data = $remote->{$reg};
		 if (defined $data) {analyzestartup ("  - =$data");}	
		}
	}
else {print "Could not connect to hive.\n";}

my %hives = ("LMachine" => "HKLM", "CUser" => "HKCU");

foreach my $hive (keys %hives) 
	{
	 ($remote = $Registry->{"//$server/$hive"}) ? 
		(\&getTrojanKeys($hive,$remote)) : 
		(print "Could not connect to $hives{$hive} hive.\n");
	}


analyzestartup ("[Checking Startup directories]");

my $start = $ENV{SystemRoot}."\\profiles\\";
my $startup = "\\start menu\\programs\\startup";
my ($dir,$err);

if (-e $start && -d $start) 
	{
	 opendir(ST,"$start");
	 foreach $dir (sort readdir(ST)) 
		{
		 next if ($dir eq "." || $dir eq "..");
		 my $dir2 = $start.$dir;
		 if (-e $dir2 && -d $dir2) 
			{
			 my $newdir = "$start".$dir."$startup";
			 if (-e $newdir && -d $newdir) 
				{analyzestartup ("[$newdir]");
				 opendir(SUP,$newdir);
				 my @files = readdir(SUP);
				 closedir(SUP);
				 if (@files) 
					{
					 foreach my $file (@files) 
						{
						 next if ($file eq "." || $file eq "..");
					  	 if ($file =~m/lnk$/) 
							{
				  			 my $shortcut = Win32::Shortcut->new($newdir."\\".$file);
							 if ($shortcut) {analyzestartup ("$dir=$file:$shortcut->{Path}");}
							 else {analyzestartup ("Error with Shortcut: ".Win32::FormatMessage Win32::GetLastError);}
							}
						 else {	analyzestartup ("$dir=$file");}
						}
					}
				}
			else {print "Warning : $newdir does not exist or is not a directory.\n";}
			}
		else {print "Warning : $dir2 does not exist or is not a directory.\n";}
		}
	closedir(ST);
	}
else {print "Warning : $start does not exist or is not a directory.\n";}
}

#########################################################################################
# procedure getTrojanKeys()								#
# This procedure queries registry hives that could be used to launch trojan horses.	#
# Called by reportstartup();								#
#########################################################################################
sub getTrojanKeys 
{
 my ($hive, $remote) = @_;
 my($path) = 'SOFTWARE/Microsoft/Windows/CurrentVersion';
 analyzestartup ("[Checking $hive/$path keys]");
 my(@keys) = ('Run','RunOnce','RunOnceEx','RunServices');

 foreach my $k (@keys) 
	{
	 my $key = $remote->{"$path/$k"};
 	 if (defined $key) 
		{my @vals = $key->ValueNames;
		 if($#vals != -1) 
			{ 
			 foreach my $val (@vals) 
				{
				 my $data = $key->{$val};
				 $data = "NotFound" unless($data);
				 analyzestartup ("  - $k=$val:$data");
   				}	
  			}
  		}
	}
}

#########################################################################################
# procedure analyzestartup								#
# This procedure analyzes the output from startup.log.					#
#########################################################################################

sub analyzestartup
{ my @rec = @_;
  my $rec = join ('', @rec);
  my $OK = 0;

if (!($rec=~m/^\[/i))
 {
  foreach my $line (@allowedstartup)
	{my @temp = split (/=/,$rec); @temp = parse (@temp);
	 if (lc($line) eq lc($temp[1])) {$OK = 1;}
	}
  if (!$OK) 
	{sendoutput ('startup.log', "ALERT! Unknown item inserted in the startup configuration,$lasthive,$rec", @config);
	}  
 }
else {$lasthive = $rec;}
}


#########################################################################################
# procedure sendoutput(filename, line, config)						#
# This procedure receives as arguments: the name of the modified file, the last line	#
# of the logfile, and then the config table (LOGIP, LOGHOST, LOGUSER, SHOWCONSOLE, and 	#
# the various destination directories).  The procedure checks the configuration to see	#
# if it has to append any information to the original line or not.  If SHOWCONSOLE in 	#
# enabled, then the line is printed on the screen, if not it simply passes to the next	#
# step which is to forward this line to all mentionned destinations in config.txt.	#
#########################################################################################

sub sendoutput
{ my ($filename, $lines, $logip, $loghost, $loguser, $logdate, $logtime, $showconsole, $lp, $lpbuffer, @dest) = @_;

my $line = '';
my @newlines = split (/\n/,$lines);
foreach $line (@newlines)
 {
 my $newline=""; 

 if ($logip) {$newline=$newline.$id[0].",";}
 if ($loghost) {$newline=$newline.$id[1].",";}
 if ($loguser) {$newline=$newline.$id[2].",";}
 if ($logdate || $logtime) {($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime(time);} 
 if ($logdate) {$newline=$newline.($year+=1900)."/".($mon+1)."/".$mday.",";}
 if ($logtime) {$newline=$newline.$hour.":".$min.":".$sec.",";}

$newline=$newline.$line."\n";

  if ($showconsole) {print $newline;}

  if (lc($dest[0])ne"null") {
    foreach $destdir (@dest)
       {
        $destination=$destdir.$filename;
        open (DEST, ">>".$destination) || die "Can't open master log file $destination";
        flock (DEST, 2) || die "Can't lock file for writing";
        print DEST $newline || die "Can't write to file";
        close (DEST) || die "Can't close master log file";
       }
    }


  if ($lp) { push @buffer, $newline; $buffer=@buffer;
	   if ($buffer>=$lpbuffer+1) {
				     open(LINEPRINT,">LPT1") || die "Can't open pipe to LPT1";
				     foreach $line (@buffer){
					print LINEPRINT $line;}
				     close (LINEPRINT) || die "Can't close pipe to LPT1";
				     @buffer=[];
				   } 
         }
  
 }
}

