#!/usr/bin/perl -w
# Date: 20150418
# Author: Daniel Marsh
# Email: daniel@stiw.org
#############################################################################
## This program is free software: you can redistribute it and/or modify
## it under the terms of the GNU General Public License as published by
## the Free Software Foundation, either version 3 of the License, or (at
## your option) any later version.
##
## This program is distributed in the hope that it will be useful, but
## WITHOUT ANY WARRANTY; without even the implied warranty of
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
## General Public License for more details.
##
## You should have received a copy of the GNU General Public License
## along with this program. If not, see .
#############################################################################
# look at batch queries via api. Apparently 4 hashes per second are supported
# define library path - this is for libs installed via CPAN shell on my system
use lib '/home/username/perl5/lib/perl5';
# include the libraries we need
use strict;
use warnings;
use DBI qw(:sql_types); # cannot forget this... big problems otherwise ;)
use VT::API;
use Data::Dumper;
use DBD::SQLite;
# config zone
my $dbfile = "/var/local/ossec-vt.db"; # Location of database to use
my $pathtoschk = "/var/ossec/queue/syscheck/"; # default OSSEC path
my $apikey = "insert private/public api key from VirusTotal";
# end config zone
#################################################################################
# Use the following to create the required SQLite DB
# ---
# BEGIN TRANSACTION;
# CREATE TABLE `files` (
# `file_id` INTEGER PRIMARY KEY AUTOINCREMENT,
# `filename` TEXT,
# `checksum` TEXT
# );
# CREATE TABLE `computers` (
# `computer_id` INTEGER PRIMARY KEY AUTOINCREMENT,
# `computer` TEXT,
# `file` TEXT
# );
# CREATE TABLE `checksums` (
# `checksum_id` INTEGER PRIMARY KEY AUTOINCREMENT,
# `datetime` TEXT,
# `checksum` INTEGER UNIQUE,
# `checked` INTEGER,
# `virus` INTEGER
# );
# CREATE TABLE `check_time` (
# `last_check` INTEGER,
# `id` INTEGER
# );
# INSERT INTO `check_time` VALUES (NULL,1);
# COMMIT;
# ---
#################################################################################
# open connection to database
my $dbh = DBI->connect("dbi:SQLite:dbname=$dbfile", "", "");
# read the syscheck files into memory - will need to update this to
# loop through files in /var/ossec/queue/syscheck, record the machine
# names as well...
opendir(DIR, $pathtoschk) or die "Could not open $pathtoschk: $!\n";
my @dbs = grep(/\(.*syscheck$/, readdir(DIR));
# hash to store the checksums, files, and machine names
my %checksums; # we're about to populate this...
foreach my $f (@dbs) {
open(SCHK, $pathtoschk.$f) or die "Could not open file $f: $!\n";
my $comp = (split(/ /, $f, 2))[0];
$comp =~ s/\)//;
$comp =~ s/\(//;
while () {
chomp; # remove trailing newline
# split into three parts
my @line = split(/ /, $_, 3);
my @bline = split(/:/, $line[0]);
# bline[5] is the sha hash, bline[4] is md5
# line[2] is the filename
$checksums{$bline[5]}->{$line[2]}->{$comp}=1;
}
close(SCHK);
}
close(DIR);
#print Data::Dumper->Dumper(\%checksums);
#print "\n";
# setup new connection to VirusTotal
my $api = VT::API->new(key => $apikey);
my $result;
# get the date and time
my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime(time);
$year += 1900; $mon++;
my $now = "$year-$mon-$mday $hour:$min:$sec\n";
#-- # Need to write date/time of last query against VirusTotal into DB,
#-- # then we need to verify that atleast 15 seconds has passed before we
#-- # can make another request
my $sth = "";
my $timesth = $dbh->prepare("UPDATE check_time SET last_check=? WHERE id=1");
#-$timesth->bind_param(1, time(), SQL_INTEGER);
#-$timesth->execute;
my $gettimesth = $dbh->prepare("SELECT last_check FROM check_time WHERE id=1");
$gettimesth->execute;
my $row = $gettimesth->fetch;
foreach my $keys (keys %checksums) {
my $checked = 0;
my $virus = 0;
my $csum = $keys;
$sth = $dbh->prepare("SELECT checked FROM checksums WHERE checksum = ?");
$sth->bind_param(1, $csum, SQL_VARCHAR);
$sth->execute;
$row = $sth->fetch;
# we don't want to check any checksums already checked
if( defined $row->[0] ) {
print "Skipping $csum\n";
next if $row->[0] >= 1;
}
# get time of last query to VT
$gettimesth->execute;
$row = $gettimesth->fetch;
# check if it's been atleast 15 seconds since last check, or we just
# goto sleep for 20 seconds
my $t = time();
my $diff = $t - $row->[0];
if ( $diff <= 15 ) {
print "Sleeping for 20...\n";
sleep(20);
}
# check VT for the reported CSUM
$result = $api->get_file_report($csum);
print "Checked $csum at ".time()."\n";
if(not defined $result->{"report"} ) {
$checked = 2;
# set it to 2 to indicate that no report was obtained
}
# set time of this check
$timesth->bind_param(1, time(), SQL_INTEGER);
$timesth->execute;
# check for viruses/malware
my $hashref = $result->{"report"}->[1];
slee(p16); # Can only do 4 queries a minute...
# set the virus counter for the number of items that are detected as viruses
my $virus_counter = 0;
foreach my $key ( keys %{$hashref} ) {
if ( $hashref->{$key} ne '' ) {
$virus_counter++;
}
# if we got here, we've checked that csum
$checked=1;
}
# report if virus detected
$virus = $virus_counter > 0 ? 1 : 0;
# checksum column has UNIQUE constraint...
# so make sure all found checksums are inserted into the database
$sth = $dbh->prepare("INSERT OR REPLACE INTO checksums ('checksum_id', 'datetime', 'checksum', 'checked','virus') VALUES (NULL, ?, ?, ?, ?)");
$sth->bind_param(1, $now, SQL_VARCHAR);
$sth->bind_param(2, $csum, SQL_VARCHAR);
$sth->bind_param(3, $checked, SQL_INTEGER);
$sth->bind_param(4, $virus, SQL_INTEGER);
$sth->execute;
# populate the files table
foreach my $files ( keys %{$checksums{$keys}}) {
$sth = $dbh->prepare("DELETE FROM files WHERE filename=? AND checksum=?");
$sth->bind_param(1, $files, SQL_VARCHAR);
$sth->bind_param(2, $csum, SQL_VARCHAR);
$sth->execute;
$sth = $dbh->prepare("INSERT OR IGNORE INTO files ('file_id', 'filename', 'checksum') VALUES (NULL, ?, ?)");
$sth->bind_param(1, $files, SQL_VARCHAR);
$sth->bind_param(2, $csum, SQL_VARCHAR);
$sth->execute;
# populate the computers table
foreach my $comp (keys %{$checksums{$keys}->{$files}}) {
$sth = $dbh->prepare("DELETE FROM computers WHERE computer=? and file=?");
$sth->bind_param(1, $comp, SQL_VARCHAR);
$sth->bind_param(2, $files, SQL_VARCHAR);
$sth->execute;
$sth = $dbh->prepare("INSERT INTO computers ('computer_id', 'computer', 'file') VALUES (NULL, ?, ?)");
$sth->bind_param(1, $comp, SQL_VARCHAR);
$sth->bind_param(2, $files, SQL_VARCHAR);
$sth->execute;
}
}
}
#-- # eicar signature for testing
#-- my $eicar = "131f95c51cc819465fa1797f6ccacf9d494aaaff46fa3eac73ae63ffbdfd8267";
#-- # clean signature for testing
#-- my $clean = "30ced927e23b584725cf16351394175a6d2a9577";
exit;