"Fossies" - the Fresh Open Source Software Archive  

Source code changes of the file "lib/Mail/SpamAssassin/Plugin/OLEVBMacro.pm" between
Mail-SpamAssassin-3.4.4.tar.bz2 and Mail-SpamAssassin-3.4.5.tar.bz2

About: SpamAssassin is a mail filter that uses a wide range of heuristic tests on mail headers and body text to identify "spam" (also known as unsolicited commercial email) incl. Bayesian (statistical) spam filter and several internet-based realtime blacklists.

OLEVBMacro.pm  (Mail-SpamAssassin-3.4.4.tar.bz2):OLEVBMacro.pm  (Mail-SpamAssassin-3.4.5.tar.bz2)
skipping to change at line 44 skipping to change at line 44
describe OLEMACRO_ENCRYPTED Has an Office doc that is encrypted describe OLEMACRO_ENCRYPTED Has an Office doc that is encrypted
body OLEMACRO_RENAME eval:check_olemacro_renamed() body OLEMACRO_RENAME eval:check_olemacro_renamed()
describe OLEMACRO_RENAME Has an Office doc that has been renamed describe OLEMACRO_RENAME Has an Office doc that has been renamed
body OLEMACRO_ZIP_PW eval:check_olemacro_zip_password() body OLEMACRO_ZIP_PW eval:check_olemacro_zip_password()
describe OLEMACRO_ZIP_PW Has an Office doc that is password protected in a z ip describe OLEMACRO_ZIP_PW Has an Office doc that is password protected in a z ip
body OLEMACRO_CSV eval:check_olemacro_csv() body OLEMACRO_CSV eval:check_olemacro_csv()
describe OLEMACRO_CSV Malicious csv file that tries to exec cmd.exe detected describe OLEMACRO_CSV Malicious csv file that tries to exec cmd.exe detected
body OLEMACRO_DOWNLOAD_EXE eval:check_olemacro_download_exe()
describe OLEMACRO_DOWNLOAD_EXE Malicious code inside the Office doc that tri
es to download a .exe file detected
endif endif
=head1 DESCRIPTION =head1 DESCRIPTION
This plugin detects OLE Macro inside documents attached to emails. This plugin detects OLE Macro inside documents attached to emails.
It can detect documents inside zip files as well as encrypted documents. It can detect documents inside zip files as well as encrypted documents.
=head1 REQUIREMENT =head1 REQUIREMENT
This plugin requires Archive::Zip and IO::String perl modules. This plugin requires Archive::Zip and IO::String perl modules.
skipping to change at line 94 skipping to change at line 97
use vars qw(@ISA); use vars qw(@ISA);
@ISA = qw(Mail::SpamAssassin::Plugin); @ISA = qw(Mail::SpamAssassin::Plugin);
our $VERSION = '0.52'; our $VERSION = '0.52';
# https://www.openoffice.org/sc/compdocfileformat.pdf # https://www.openoffice.org/sc/compdocfileformat.pdf
# http://blog.rootshell.be/2015/01/08/searching-for-microsoft-office-files-conta ining-macro/ # http://blog.rootshell.be/2015/01/08/searching-for-microsoft-office-files-conta ining-macro/
my $marker1 = "\xd0\xcf\x11\xe0"; my $marker1 = "\xd0\xcf\x11\xe0";
my $marker2 = "\x00\x41\x74\x74\x72\x69\x62\x75\x74\x00"; my $marker2 = "\x00\x41\x74\x74\x72\x69\x62\x75\x74\x00";
# Office 2003 embedded ole
my $marker2a = "\x01\x00\x4f\x00\x6c\x00\x65\x00\x31\x00\x30\x00\x4e\x00\x61\x00
";
# embedded object in rtf files (https://www.biblioscape.com/rtf15_spec.htm) # embedded object in rtf files (https://www.biblioscape.com/rtf15_spec.htm)
my $marker3 = "\x5c\x6f\x62\x6a\x65\x6d\x62"; my $marker3 = "\x5c\x6f\x62\x6a\x65\x6d\x62";
my $marker4 = "\x5c\x6f\x62\x6a\x64\x61\x74"; my $marker4 = "\x5c\x6f\x62\x6a\x64\x61\x74";
my $marker5 = "\x5c\x20\x6f\x62\x6a\x64\x61\x74"; my $marker5 = "\x5c\x20\x6f\x62\x6a\x64\x61\x74";
# Excel .xlsx encrypted package, thanks to Dan Bagwell for the sample # Excel .xlsx encrypted package, thanks to Dan Bagwell for the sample
my $encrypted_marker = "\x45\x00\x6e\x00\x63\x00\x72\x00\x79\x00\x70\x00\x74\x00 \x65\x00\x64\x00\x50\x00\x61\x00\x63\x00\x6b\x00\x61\x00\x67\x00\x65"; my $encrypted_marker = "\x45\x00\x6e\x00\x63\x00\x72\x00\x79\x00\x70\x00\x74\x00 \x65\x00\x64\x00\x50\x00\x61\x00\x63\x00\x6b\x00\x61\x00\x67\x00\x65";
# .exe file downloaded from external website
my $exe_marker1 = "\x00((https?)://)[-A-Za-z0-9+&@#/%?=~_|!:,.;]{5,1000}[-A-Za-z
0-9+&@#/%=~_|]{5,1000}(\.exe|\.cmd|\.bat)([\x06|\x00])";
my $exe_marker2 = "URLDownloadToFileA";
# this code burps an ugly message if it fails, but that's redirected elsewhere # this code burps an ugly message if it fails, but that's redirected elsewhere
# AZ_OK is a constant exported by Archive::Zip # AZ_OK is a constant exported by Archive::Zip
my $az_ok; my $az_ok;
eval '$az_ok = AZ_OK'; eval '$az_ok = AZ_OK';
# constructor: register the eval rule # constructor: register the eval rule
sub new { sub new {
my $class = shift; my $class = shift;
my $mailsaobject = shift; my $mailsaobject = shift;
skipping to change at line 124 skipping to change at line 132
bless ($self, $class); bless ($self, $class);
$self->set_config($mailsaobject->{conf}); $self->set_config($mailsaobject->{conf});
$self->register_eval_rule("check_olemacro"); $self->register_eval_rule("check_olemacro");
$self->register_eval_rule("check_olemacro_csv"); $self->register_eval_rule("check_olemacro_csv");
$self->register_eval_rule("check_olemacro_malice"); $self->register_eval_rule("check_olemacro_malice");
$self->register_eval_rule("check_olemacro_renamed"); $self->register_eval_rule("check_olemacro_renamed");
$self->register_eval_rule("check_olemacro_encrypted"); $self->register_eval_rule("check_olemacro_encrypted");
$self->register_eval_rule("check_olemacro_zip_password"); $self->register_eval_rule("check_olemacro_zip_password");
$self->register_eval_rule("check_olemacro_download_exe");
return $self; return $self;
} }
sub dbg { sub dbg {
Mail::SpamAssassin::Plugin::dbg ("OLEVBMacro: @_"); Mail::SpamAssassin::Plugin::dbg ("OLEVBMacro: @_");
} }
sub set_config { sub set_config {
my ($self, $conf) = @_; my ($self, $conf) = @_;
skipping to change at line 464 skipping to change at line 473
} }
sub check_olemacro_zip_password { sub check_olemacro_zip_password {
my ($self,$pms,$body,$name) = @_; my ($self,$pms,$body,$name) = @_;
_check_attachments(@_) unless exists $pms->{olemacro_zip_password}; _check_attachments(@_) unless exists $pms->{olemacro_zip_password};
return $pms->{olemacro_zip_password}; return $pms->{olemacro_zip_password};
} }
sub check_olemacro_download_exe {
my ($self,$pms,$body,$name) = @_;
_check_attachments(@_) unless exists $pms->{olemacro_download_exe};
return $pms->{olemacro_download_exe};
}
sub _check_attachments { sub _check_attachments {
my ($self,$pms,$body,$name) = @_; my ($self,$pms,$body,$name) = @_;
my $mimec = 0; my $mimec = 0;
my $chunk_size = $pms->{conf}->{olemacro_max_file}; my $chunk_size = $pms->{conf}->{olemacro_max_file};
$pms->{olemacro_exists} = 0; $pms->{olemacro_exists} = 0;
$pms->{olemacro_malice} = 0; $pms->{olemacro_malice} = 0;
$pms->{olemacro_renamed} = 0; $pms->{olemacro_renamed} = 0;
skipping to change at line 498 skipping to change at line 515
# we skipped what we need/want to # we skipped what we need/want to
my $data = undef; my $data = undef;
# if name is macrotype - return true # if name is macrotype - return true
if ($name =~ /$pms->{conf}->{olemacro_macro_exts}/i) { if ($name =~ /$pms->{conf}->{olemacro_macro_exts}/i) {
dbg("Found macrotype attachment with name $name"); dbg("Found macrotype attachment with name $name");
$pms->{olemacro_exists} = 1; $pms->{olemacro_exists} = 1;
$data = $part->decode($chunk_size) unless defined $data; $data = $part->decode($chunk_size) unless defined $data;
_check_encrypted_doc($pms, $name, $data); if (defined $data) {
_check_macrotype_doc($pms, $name, $data); _check_encrypted_doc($pms, $name, $data);
_check_macrotype_doc($pms, $name, $data);
}
return 1 if $pms->{olemacro_exists} == 1; return 1 if $pms->{olemacro_exists} == 1;
} }
# if name is ext type - check and return true if needed # if name is ext type - check and return true if needed
if ($name =~ /$pms->{conf}->{olemacro_exts}/i) { if ($name =~ /$pms->{conf}->{olemacro_exts}/i) {
dbg("Found attachment with name $name"); dbg("Found attachment with name $name");
$data = $part->decode($chunk_size) unless defined $data; $data = $part->decode($chunk_size) unless defined $data;
_check_encrypted_doc($pms, $name, $data); if (defined $data) {
_check_oldtype_doc($pms, $name, $data); _check_encrypted_doc($pms, $name, $data);
# zipped doc that matches olemacro_exts - strange _check_oldtype_doc($pms, $name, $data);
if (_check_macrotype_doc($pms, $name, $data)) { # zipped doc that matches olemacro_exts - strange
$pms->{olemacro_renamed} = $pms->{olemacro_office_xml}; if (_check_macrotype_doc($pms, $name, $data)) {
$pms->{olemacro_renamed} = $pms->{olemacro_office_xml};
}
} }
return 1 if $pms->{olemacro_exists} == 1; return 1 if $pms->{olemacro_exists} == 1;
} }
if ($name =~ /$pms->{conf}->{olemacro_zips}/i) { if ($name =~ /$pms->{conf}->{olemacro_zips}/i) {
dbg("Found zip attachment with name $name"); dbg("Found zip attachment with name $name");
$data = $part->decode($chunk_size) unless defined $data; $data = $part->decode($chunk_size) unless defined $data;
_check_zip($pms, $name, $data); if (defined $data) {
_check_zip($pms, $name, $data);
}
return 1 if $pms->{olemacro_exists} == 1; return 1 if $pms->{olemacro_exists} == 1;
} }
if ((defined $data) and ($data =~ /$exe_marker1/) and (index($data, $exe_mar
ker2))) {
dbg('Url that triggers a download to an .exe file found in Office file');
$pms->{olemacro_download_exe} = 1;
}
if ($pms->{conf}->{olemacro_extended_scan} == 1) { if ($pms->{conf}->{olemacro_extended_scan} == 1) {
dbg("Extended scan attachment with name $name"); dbg("Extended scan attachment with name $name");
$data = $part->decode($chunk_size) unless defined $data; $data = $part->decode($chunk_size) unless defined $data;
if (_is_office_doc($data)) { if (defined $data) {
$pms->{olemacro_renamed} = 1; if (_is_office_doc($data)) {
dbg("Found $name to be an Office Doc!"); $pms->{olemacro_renamed} = 1;
_check_encrypted_doc($pms, $name, $data); dbg("Found $name to be an Office Doc!");
_check_oldtype_doc($pms, $name, $data); _check_encrypted_doc($pms, $name, $data);
} _check_oldtype_doc($pms, $name, $data);
}
if (_check_macrotype_doc($pms, $name, $data)) {
$pms->{olemacro_renamed} = $pms->{olemacro_office_xml};
}
if (_check_macrotype_doc($pms, $name, $data)) { _check_zip($pms, $name, $data);
$pms->{olemacro_renamed} = $pms->{olemacro_office_xml};
} }
_check_zip($pms, $name, $data);
return 1 if $pms->{olemacro_exists} == 1; return 1 if $pms->{olemacro_exists} == 1;
} }
# if we get to here with data a part has been scanned nudge as reqd # if we get to here with data a part has been scanned nudge as reqd
$mimec+=1 if defined $data; $mimec+=1 if defined $data;
if ($mimec >= $pms->{conf}->{olemacro_num_mime}) { if ($mimec >= $pms->{conf}->{olemacro_num_mime}) {
dbg('MIME limit reached'); dbg('MIME limit reached');
last; last;
} }
dbg("No Marker of a Macro found in file $name"); dbg("No Marker of a Macro found in file $name");
skipping to change at line 752 skipping to change at line 782
if (!HAS_IO_STRING) { if (!HAS_IO_STRING) {
warn "check_macrotype_doc not supported, required module IO::String missing\ n"; warn "check_macrotype_doc not supported, required module IO::String missing\ n";
return 0; return 0;
} }
return 0 unless _is_zip_file($name, $data); return 0 unless _is_zip_file($name, $data);
my $zip = _open_zip_handle($data); my $zip = _open_zip_handle($data);
return 0 unless $zip; return 0 unless $zip;
#https://www.decalage.info/vba_tools # https://www.decalage.info/vba_tools
# Consider macrofiles as lowercase, they are checked later with a case-insensi
tive method
my %macrofiles = ( my %macrofiles = (
'word/vbaproject.bin' => 'word2k7', 'word/vbaproject.bin' => 'word2k7',
'macros/vba/_vba_project' => 'word97', 'macros/vba/_vba_project' => 'word97',
'xl/vbaproject.bin' => 'xl2k7', 'xl/vbaproject.bin' => 'xl2k7',
'xl/embeddings/oleObject1.bin' => 'xl2k13',
'_vba_project_cur/vba/_vba_project' => 'xl97', '_vba_project_cur/vba/_vba_project' => 'xl97',
'ppt/vbaproject.bin' => 'ppt2k7', 'ppt/vbaproject.bin' => 'ppt2k7',
); );
my @members = $zip->members(); my @members = $zip->members();
foreach my $member (@members){ foreach my $member (@members){
my $mname = lc $member->fileName(); my $mname = lc $member->fileName();
if (exists($macrofiles{$mname})) { if (exists($macrofiles{lc($mname)})) {
dbg("Found $macrofiles{$mname} vba file"); dbg("Found $macrofiles{$mname} vba file");
$pms->{olemacro_exists} = 1; $pms->{olemacro_exists} = 1;
last; last;
} }
} }
# Look for a member named [Content_Types].xml and do checks # Look for a member named [Content_Types].xml and do checks
if (my $ctypesxml = $zip->memberNamed('[Content_Types].xml')) { if (my $ctypesxml = $zip->memberNamed('[Content_Types].xml')) {
dbg('Found [Content_Types].xml file'); dbg('Found [Content_Types].xml file');
$pms->{olemacro_office_xml} = 1; $pms->{olemacro_office_xml} = 1;
skipping to change at line 872 skipping to change at line 902
} }
sub _check_markers { sub _check_markers {
my ($data) = @_; my ($data) = @_;
if (index($data, $marker1) == 0 && index($data, $marker2) > -1) { if (index($data, $marker1) == 0 && index($data, $marker2) > -1) {
dbg('Marker 1 & 2 found'); dbg('Marker 1 & 2 found');
return 1; return 1;
} }
if (index($data, $marker1) == 0 && index($data, $marker2a) > -1) {
dbg('Marker 1 & 2a found');
return 1;
}
if (index($data, $marker3) > -1) { if (index($data, $marker3) > -1) {
dbg('Marker 3 found'); dbg('Marker 3 found');
return 1; return 1;
} }
if (index($data, $marker4) > -1) { if (index($data, $marker4) > -1) {
dbg('Marker 4 found'); dbg('Marker 4 found');
return 1; return 1;
} }
skipping to change at line 896 skipping to change at line 931
if (index($data, 'w:macrosPresent="yes"') > -1) { if (index($data, 'w:macrosPresent="yes"') > -1) {
dbg('XML macros marker found'); dbg('XML macros marker found');
return 1; return 1;
} }
if (index($data, 'vbaProject.bin.rels') > -1) { if (index($data, 'vbaProject.bin.rels') > -1) {
dbg('XML macros marker found'); dbg('XML macros marker found');
return 1; return 1;
} }
} }
sub _find_malice_bins { sub _find_malice_bins {
my ($zip) = @_; my ($zip) = @_;
my @binfiles = $zip->membersMatching( '.*\.bin' ); my @binfiles = $zip->membersMatching( '.*\.bin' );
foreach my $member (@binfiles){ foreach my $member (@binfiles){
my ( $data, $status ) = $member->contents(); my ( $data, $status ) = $member->contents();
next unless $status == $az_ok; next unless $status == $az_ok;
 End of changes. 17 change blocks. 
21 lines changed or deleted 62 lines changed or added

Home  |  About  |  Features  |  All  |  Newest  |  Dox  |  Diffs  |  RSS Feeds  |  Screenshots  |  Comments  |  Imprint  |  Privacy  |  HTTP(S)