#!/usr/bin/perl # # PECluester v0.2, written by Hexacorn.com, 2012-09 # # 2013-11-12 - minor fix for argument parsing and date printing # # This is a simple script that attempts to find suspicious PE files # by enumerating all files within a given directory recursively # reading PE compilation timestamp from all Portable Executables, # then grouping these into clusters using 1 day as a boundary # # Usage: # perl PECluester.pl # if is not provided, script scans current directory # options: # -r - recursive scan # Examples: # perl PECluester.pl c:\windows\system32 # perl PECluester.pl -r c:\windows\system32 use strict; use warnings; $| = 1; # (60*60*24)*1 = boundary of 1 day - you can chg it to # xyz days using formula (60*60*24)*xyz my $CLUSTER_BOUNDARY = (60*60*24)*1; print STDERR " ===================================================================== PECluester v0.2, written by Hexacorn.com, 2012-09 ===================================================================== "; my $recursive = 0; my $arg = shift; $arg = '' if !defined($arg); if ($arg =~ /[-\/]r/i) { $recursive++; $arg = shift; } my $target = '.'; $target = $arg if defined($arg); print STDERR "Target Directory: $target\n"; print STDERR "Recursive scan: ".($recursive==0?'no':'yes')."\n"; my %PEFiles; scan ($target); for my $k (sort keys %PEFiles) { print "$k\n"; my @sorted_entries = sort @{$PEFiles{$k}{'data'}}; for (my $i=0; $i<$PEFiles{$k}{'cnt'}; $i++) { print "\t$sorted_entries[$i]\n"; } print "\n"; } sub scan { my $DIR=shift; print STDERR "$DIR\r"; opendir(DIR, $DIR); my @dir = readdir DIR; my @dir_sorted = sort @dir; closedir DIR; foreach my $filename (@dir_sorted) { next if ($filename =~ /^\.{1,2}$/); my $pathfilename = $DIR.'\\'.$filename; if (-d $pathfilename) { scan($pathfilename) if $recursive==1; next; } next if (-s $pathfilename < 128 ); if (!open (FILE, '<'.$pathfilename)) { print "Can't open \"$pathfilename\"\n"; next; } binmode (FILE); my $topdata; if (!read (FILE, $topdata, 2)) { close FILE; print "Can't read \"$pathfilename\"\n"; next; } if ($topdata =~ /^MZ/) { seek (FILE, 0x3C, 0); read (FILE, my $o2PE,4); $o2PE = unpack("I32",$o2PE); if ($o2PE<16384) { seek (FILE, $o2PE, 0); read (FILE, my $PEHeader,32); if (substr($PEHeader,0,2) eq 'PE') { seek (FILE, $o2PE+8 ,0); read (FILE, my $TimeStamp,4); print "PE File: $pathfilename\n"; my ($dev,$ino,$mode,$nlink,$uid,$gid,$rdev,$size,$atime,$mtime,$ctime,$blksize,$blocks) = stat($pathfilename); $TimeStamp = unpack("I32",$TimeStamp); my $GMT = epoch2GMT($TimeStamp); my $GMT2 = epoch2GMT($CLUSTER_BOUNDARY*int($TimeStamp/$CLUSTER_BOUNDARY)); $PEFiles {$GMT2}{'cnt'}=0 if !defined ($PEFiles {$GMT2}{'cnt'}); $PEFiles {$GMT2}{'data'}[ $PEFiles {$GMT2}{'cnt'} ]="$GMT\t$pathfilename"; $PEFiles {$GMT2}{'cnt'}++; } } } close FILE; } } sub epoch2GMT { my $t = shift; my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime($t); return '' if !defined ($year); $year += 1900; $mon += 1; return sprintf ("%d-%02d-%02d %02d:%02d:%02d", $year,$mon,$mday,$hour,$min,$sec); }