Skip to content

Commit

Permalink
Merge pull request fusioninventory#557 from TECLIB/feature/win32-anti…
Browse files Browse the repository at this point in the history
…virus-update

Feature/win32 antivirus update
  • Loading branch information
g-bougard authored Sep 12, 2018
2 parents 4e5b410 + c2457de commit 426ce5f
Show file tree
Hide file tree
Showing 3 changed files with 240 additions and 68 deletions.
6 changes: 6 additions & 0 deletions Changes
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,12 @@ Revision history for FusionInventory agent
inventory:
* Fix physical memory error correction detection via WMI under win32
* Fix #299: Added UWP/APPX/Windows Store software inventory
* win32 antivirus detection enhanced support:
- add support for few antivirus base versions (defender, kaspersky,
EST, avira, MSE, McAfee, F-Secure)
- try to set license expiration date for F-Secure, kaspersky & avira
* Fix #442: kaspersky not fully recognized in russia
* Fix #501: wrong status was reported when windows defender was disabled

deploy:
* Bump Deploy task version to 2.7
Expand Down
4 changes: 2 additions & 2 deletions lib/FusionInventory/Agent/Inventory.pm
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,8 @@ my %fields = (
ACCESSLOG => [ qw/USERID LOGDATE/ ],

ANTIVIRUS => [ qw/COMPANY ENABLED GUID NAME UPTODATE VERSION
DATFILECREATION DATFILEVERSION ENGINEVERSION32
ENGINEVERSION64/ ],
EXPIRATION BASE_CREATION BASE_VERSION
32ENGINE_VERSION 64ENGINE_VERSION/ ],
BATTERIES => [ qw/CAPACITY CHEMISTRY DATE NAME SERIAL MANUFACTURER
VOLTAGE/ ],
CONTROLLERS => [ qw/CAPTION DRIVER NAME MANUFACTURER PCICLASS VENDORID
Expand Down
298 changes: 232 additions & 66 deletions lib/FusionInventory/Agent/Task/Inventory/Win32/AntiVirus.pm
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ use warnings;

use parent 'FusionInventory::Agent::Task::Inventory::Module';

use UNIVERSAL::require;

use FusionInventory::Agent::Tools;
use FusionInventory::Agent::Tools::Win32;

Expand Down Expand Up @@ -51,11 +53,12 @@ sub doInventory {
};

if ($object->{productState}) {
my $bin = sprintf( "%b\n", $object->{productState});
# http://blogs.msdn.com/b/alejacma/archive/2008/05/12/how-to-get-antivirus-information-with-wmi-vbscript.aspx?PageIndex=2#comments
if ($bin =~ /(\d)\d{5}(\d)\d{6}(\d)\d{5}$/) {
$antivirus->{UPTODATE} = $1 || $2;
$antivirus->{ENABLED} = $3 ? 0 : 1;
my $hex = dec2hex($object->{productState});
# See http://neophob.com/2010/03/wmi-query-windows-securitycenter2/
my ($enabled, $uptodate) = $hex =~ /(.{2})(.{2})$/;
if (defined($enabled) && defined($uptodate)) {
$antivirus->{ENABLED} = $enabled =~ /^1.$/ ? 1 : 0;
$antivirus->{UPTODATE} = $uptodate =~ /^00$/ ? 1 : 0;
}
}

Expand All @@ -64,11 +67,27 @@ sub doInventory {
my ($defender) = getWMIObjects(
moniker => 'winmgmts://./root/microsoft/windows/defender',
class => "MSFT_MpComputerStatus",
properties => [ qw/AMProductVersion AntivirusEnabled/ ]
properties => [ qw/AMProductVersion AntivirusEnabled
AntivirusSignatureVersion/ ]
);
$antivirus->{VERSION} = $defender->{AMProductVersion}
if ($defender && $defender->{AntivirusEnabled} && $defender->{AMProductVersion});
if ($defender) {
$antivirus->{VERSION} = $defender->{AMProductVersion}
if $defender->{AMProductVersion};
$antivirus->{ENABLED} = $defender->{AntivirusEnabled}
if (defined($defender->{AntivirusEnabled}));
$antivirus->{BASE_VERSION} = $defender->{AntivirusSignatureVersion}
if $defender->{AntivirusSignatureVersion};
}
$antivirus->{COMPANY} = "Microsoft Corporation";
# Finally try registry for base version
if (!$antivirus->{BASE_VERSION}) {
$defender = _getSoftwareRegistryKeys(
'Microsoft/Windows Defender/Signature Updates',
[ 'AVSignatureVersion' ]
);
$antivirus->{BASE_VERSION} = $defender->{'/AVSignatureVersion'}
if $defender && $defender->{'/AVSignatureVersion'};
}
}

# Finally try to get version from software installation in registry
Expand All @@ -85,10 +104,19 @@ sub doInventory {
# avoid duplicates
next if $seen->{$antivirus->{NAME}}->{$antivirus->{VERSION}||'_undef_'}++;

# McAfee data
# Check for other product datas for update
if ($antivirus->{NAME} =~ /McAfee/i) {
my $info = _getMcAfeeInfo();
$antivirus->{$_} = $info->{$_} foreach keys %$info;
_setMcAfeeInfos($antivirus);
} elsif ($antivirus->{NAME} =~ /Kaspersky/i) {
_setKasperskyInfos($antivirus);
} elsif ($antivirus->{NAME} =~ /ESET/i) {
_setESETInfos($antivirus);
} elsif ($antivirus->{NAME} =~ /Avira/i) {
_setAviraInfos($antivirus);
} elsif ($antivirus->{NAME} =~ /Security Essentials/i) {
_setMSEssentialsInfos($antivirus);
} elsif ($antivirus->{NAME} =~ /F-Secure/i) {
_setFSecureInfos($antivirus);
}

$inventory->addEntry(
Expand All @@ -104,81 +132,219 @@ sub _getAntivirusUninstall {

return unless $name;

my ($regUninstall, $AVRegUninstall);
# Cleanup name from localized chars to keep a clean regex pattern
my ($pattern) = $name =~ /^([a-zA-Z0-9 ._-]+)/
or return;
# Escape dot in pattern
$pattern =~ s/\./\\./g;
my $match = qr/^$pattern/i;

return _getSoftwareRegistryKeys(
'Microsoft/Windows/CurrentVersion/Uninstall',
[ 'DisplayName', 'DisplayVersion', 'Publisher' ],
sub {
my ($registry) = @_;
return first {
$_->{"/DisplayName"} && $_->{"/DisplayName"} =~ $match;
} values(%{$registry});
}
);
}

if (is64bit()) {
$regUninstall = getRegistryKey(
path => 'HKEY_LOCAL_MACHINE/SOFTWARE/Wow6432Node/Microsoft/Windows/CurrentVersion/Uninstall',
);
$AVRegUninstall = first {
$_->{"/DisplayName"} && $_->{"/DisplayName"} =~ /$name/i;
} values(%{$regUninstall});
}
sub _setMcAfeeInfos {
my ($antivirus) = @_;

if (!$AVRegUninstall) {
$regUninstall = getRegistryKey(
path => 'HKEY_LOCAL_MACHINE/SOFTWARE/Microsoft/Windows/CurrentVersion/Uninstall',
);
$AVRegUninstall = first {
$_->{"/DisplayName"} && $_->{"/DisplayName"} =~ /$name/i;
} values(%{$regUninstall});
}
my %properties = (
'BASE_VERSION' => [ 'AVDatVersion', 'AVDatVersionMinor' ],
'32ENGINE_VERSION' => [ 'EngineVersion32Major', 'EngineVersion32Minor' ],
'64ENGINE_VERSION' => [ 'EngineVersionMajor', 'EngineVersionMinor' ],
);

my $regvalues = [ map { @{$_} } values(%properties) ];

return $AVRegUninstall;
my $macafeeReg = _getSoftwareRegistryKeys('McAfee/AVEngine', $regvalues)
or return;

# major.minor versions properties
foreach my $property (keys %properties) {
my $keys = $properties{$property};
my $major = $macafeeReg->{'/' . $keys->[0]};
my $minor = $macafeeReg->{'/' . $keys->[1]};
$antivirus->{$property} = sprintf("%04d.%04d", hex2dec($major), hex2dec($minor))
if defined $major && defined $minor;
}
}

sub _getMcAfeeInfo {
sub _setKasperskyInfos {
my ($antivirus) = @_;

my %properties = (
DATFILEVERSION => [ 'AVDatVersion', 'AVDatVersionMinor' ],
ENGINEVERSION32 => [ 'EngineVersion32Major', 'EngineVersion32Minor' ],
ENGINEVERSION64 => [ 'EngineVersionMajor', 'EngineVersionMinor' ],
);
my $regvalues = [ qw(LastSuccessfulUpdate LicKeyType LicDaysTillExpiration) ];

my $regvalues = [ 'AVDatDate', map { @{$_} } values(%properties) ];
my $kasperskyReg = _getSoftwareRegistryKeys('KasperskyLab\protected', $regvalues)
or return;

my ($info, $macafeeReg);
my $found = first {
$_->{"Data/"} && $_->{"Data/"}->{"/LastSuccessfulUpdate"}
} values(%{$kasperskyReg});

if (is64bit()) {
$macafeeReg = getRegistryKey(
path => 'HKEY_LOCAL_MACHINE/SOFTWARE/Wow6432Node/McAfee/AVEngine',
wmiopts => { # Only used for remote WMI optimization
values => $regvalues
if ($found) {
my $lastupdate = hex2dec($found->{"Data/"}->{"/LastSuccessfulUpdate"});
if ($lastupdate && $lastupdate != 0xFFFFFFFF) {
my @date = localtime($lastupdate);
# Format BASE_VERSION as YYYYMMDD
$antivirus->{BASE_VERSION} = sprintf(
"%04d%02d%02d",$date[5]+1900,$date[4]+1,$date[3]);
}
# Set expiration date only if we found a licence key type
my $keytype = hex2dec($found->{"Data/"}->{"/LicKeyType"});
if ($keytype) {
my $expiration = hex2dec($found->{"Data/"}->{"/LicDaysTillExpiration"});
if (defined($expiration)) {
my @date = localtime(time+86400*$expiration);
$antivirus->{EXPIRATION} = sprintf(
"%02d/%02d/%04d",$date[3],$date[4]+1,$date[5]+1900);
}
);
}
}
}

if (!$macafeeReg) {
$macafeeReg = getRegistryKey(
path => 'HKEY_LOCAL_MACHINE/SOFTWARE/McAfee/AVEngine',
wmiopts => { # Only used for remote WMI optimization
values => $regvalues
}
);
sub _setESETInfos {
my ($antivirus) = @_;

my $esetReg = _getSoftwareRegistryKeys(
'ESET\ESET Security\CurrentVersion\Info',
[ qw(ProductVersion ScannerVersion) ]
);
return unless $esetReg;

unless ($antivirus->{VERSION}) {
$antivirus->{VERSION} = $esetReg->{"/ProductVersion"}
if $esetReg->{"/ProductVersion"};
}

return unless $macafeeReg;
$antivirus->{BASE_VERSION} = $esetReg->{"/ScannerVersion"}
if $esetReg->{"/ScannerVersion"};
}

# major.minor versions properties
foreach my $property (keys %properties) {
my $keys = $properties{$property};
my $major = $macafeeReg->{'/' . $keys->[0]};
my $minor = $macafeeReg->{'/' . $keys->[1]};
$info->{$property} = sprintf("%04d.%04d", hex2dec($major), hex2dec($minor))
if defined $major && defined $major;
sub _setAviraInfos {
my ($antivirus) = @_;

my ($aviraInfos) = getWMIObjects(
moniker => 'winmgmts://./root/CIMV2/Applications/Avira_AntiVir',
class => "License_Info",
properties => [ qw/License_Expiration/ ]
);
if($aviraInfos && $aviraInfos->{License_Expiration}) {
my ($expiration) = $aviraInfos->{License_Expiration} =~ /^(\d+\.\d+\.\d+)/;
if ($expiration) {
$expiration =~ s/\./\//g;
$antivirus->{EXPIRATION} = $expiration;
}
}

# file creation date property
if ($macafeeReg->{'/AVDatDate'}) {
my $datFileCreation = encodeFromRegistry($macafeeReg->{'/AVDatDate'});
# from YYYY/MM/DD to DD/MM/YYYY
if ($datFileCreation =~ /(\d\d\d\d)\/(\d\d)\/(\d\d)/) {
$datFileCreation = join( '/', ($3, $2, $1) );
my $aviraReg = _getSoftwareRegistryKeys(
'Avira/Antivirus',
[ qw(VdfVersion) ]
);
return unless $aviraReg;

$antivirus->{BASE_VERSION} = $aviraReg->{"/VdfVersion"}
if $aviraReg->{"/VdfVersion"};
}

sub _setMSEssentialsInfos {
my ($antivirus) = @_;

my $mseReg = _getSoftwareRegistryKeys(
'Microsoft\Microsoft Antimalware\Signature Updates',
[ 'AVSignatureVersion' ]
);
return unless $mseReg;

$antivirus->{BASE_VERSION} = $mseReg->{"/AVSignatureVersion"}
if $mseReg->{"/AVSignatureVersion"};
}

sub _setFSecureInfos {
my ($antivirus) = @_;

my $fsecReg = _getSoftwareRegistryKeys(
'F-Secure\Ultralight\Updates\aquarius',
[ qw(file_set_visible_version) ]
);
return unless $fsecReg;

my $found = first { $_->{"/file_set_visible_version"} } values(%{$fsecReg});

$antivirus->{BASE_VERSION} = $found->{"/file_set_visible_version"}
if $found->{"/file_set_visible_version"};

# Try to find license "expiry_date" from a specific json file
$fsecReg = _getSoftwareRegistryKeys(
'F-Secure\CCF\DLLHoster\100\Plugins\CosmosService',
[ qw(DataPath) ]
);
return unless $fsecReg;

my $path = $fsecReg->{"/DataPath"};
return unless $path && -d $path;

# This is the full path for the expected json file
$path .= "\\safe.S-1-5-18.local.cosmos";
return unless -f $path;

my $infos = getAllLines(file => $path);
return unless $infos;

JSON::PP->require();
my @licenses;
eval {
$infos = JSON::PP::decode_json($infos);
@licenses = @{$infos->{local}->{windows}->{secl}->{subscription}->{license_table}};
};
return unless @licenses;

my $expiry_date;
# In the case more than one license is found, assume we need the one with appid=2
foreach my $license (@licenses) {
$expiry_date = $license->{expiry_date}
if $license->{expiry_date};
last if $expiry_date && $license->{appid} && $license->{appid} == 2;
}
return unless $expiry_date;

my @date = localtime($expiry_date);
$antivirus->{EXPIRATION} = sprintf("%02d/%02d/%04d",$date[3],$date[4]+1,$date[5]+1900);
}

sub _getSoftwareRegistryKeys {
my ($base, $values, $callback) = @_;

my $reg;
if (is64bit()) {
$reg = getRegistryKey(
path => 'HKEY_LOCAL_MACHINE/SOFTWARE/Wow6432Node/'.$base,
wmiopts => { # Only used for remote WMI optimization
values => $values
}
);
if ($reg) {
if ($callback) {
my $filter = &{$callback}($reg);
return $filter if $filter;
} else {
return $reg;
}
}
$info->{DATFILECREATION} = $datFileCreation;
}

return $info;
$reg = getRegistryKey(
path => 'HKEY_LOCAL_MACHINE/SOFTWARE/'.$base,
wmiopts => { # Only used for remote WMI optimization
values => $values
}
);
return ($callback && $reg) ? &{$callback}($reg) : $reg;
}

1;

0 comments on commit 426ce5f

Please sign in to comment.