summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorFelix Bauer <jack@ai4me.de>2019-08-20 10:47:36 +0200
committerGitHub <noreply@github.com>2019-08-20 10:47:36 +0200
commit18d0ac2148d76aaf1dfe659c1a11cf74021ebae9 (patch)
treeb663d8ad8a6d42e80329aff75329aa28540364c0
parentbf5f7a7c906f13cb3fdf22a17d182cac8d03fe17 (diff)
Add an additional static test to the ruleset to check for office macros with suspicious keywords (#87)
The new rule is deactivated by default, it uses oletools to check SUSPICIOUSKEYWORDS in the macro code of office documents. Ole is now an analyser module much like Cuckoo inside the toolbox. All logic has been moved there. Sample is merely there for caching. Evaluation of Rules uses the toolbox and report back. Regex based matching of MS office files for configurable keywords. Also detection of macros has been improved. Tests for correct handling of none office file extension Tests for correct handling of empty file with correct extension Tests for correct detection of office file with suspicious macro Tests for correct pass of blank office document Tests for correct handling of empty word doc. Tests for correct non detection of Excel file with macro.
-rw-r--r--peekaboo/config.py13
-rw-r--r--peekaboo/locale/de/LC_MESSAGES/peekaboo.mobin4074 -> 4392 bytes
-rw-r--r--peekaboo/locale/de/LC_MESSAGES/peekaboo.po73
-rw-r--r--peekaboo/locale/peekaboo.pot66
-rw-r--r--peekaboo/ruleset/engine.py9
-rw-r--r--peekaboo/ruleset/rules.py61
-rw-r--r--peekaboo/sample.py20
-rw-r--r--peekaboo/toolbox/ms_office.py61
-rw-r--r--peekaboo/toolbox/ole.py120
-rw-r--r--ruleset.conf.sample19
-rw-r--r--tests/test-data/office/blank.docbin0 -> 22528 bytes
-rw-r--r--tests/test-data/office/empty.doc0
-rw-r--r--tests/test-data/office/legitmacro.xlsbin0 -> 35840 bytes
-rw-r--r--tests/test-data/office/suspiciousMacro.docbin0 -> 28672 bytes
-rwxr-xr-xtests/test.py (renamed from test.py)56
15 files changed, 355 insertions, 143 deletions
diff --git a/peekaboo/config.py b/peekaboo/config.py
index 5c182bb..af5b81f 100644
--- a/peekaboo/config.py
+++ b/peekaboo/config.py
@@ -41,6 +41,7 @@ class PeekabooConfigParser( # pylint: disable=too-many-ancestors
exist or cannot be opened. """
LOG_LEVEL = object()
RELIST = object()
+ IRELIST = object()
def __init__(self, config_file):
# super() does not work here because ConfigParser uses old-style
@@ -114,7 +115,14 @@ class PeekabooConfigParser( # pylint: disable=too-many-ancestors
self.lists[section][option] = value
return value
- def getrelist(self, section, option, raw=False, vars=None, fallback=None):
+ def getirelist(self, section, option, raw=False, vars=None, fallback=None, flags=None):
+ """ Special getter for lists of regular expressions that are compiled to match
+ case insesitive (IGNORECASE). Returns the compiled expression objects in a
+ list ready for matching and searching.
+ """
+ return self.getrelist(section, option, raw=raw, vars=vars, fallback=fallback, flags=re.IGNORECASE)
+
+ def getrelist(self, section, option, raw=False, vars=None, fallback=None, flags=0):
""" Special getter for lists of regular expressions. Returns the
compiled expression objects in a list ready for matching and searching.
"""
@@ -137,7 +145,7 @@ class PeekabooConfigParser( # pylint: disable=too-many-ancestors
compiled_res = []
for regex in strlist:
try:
- compiled_res.append(re.compile(regex))
+ compiled_res.append(re.compile(regex, flags))
except (ValueError, TypeError) as error:
raise PeekabooConfigException(
'Failed to compile regular expression "%s" (section %s, '
@@ -203,6 +211,7 @@ class PeekabooConfigParser( # pylint: disable=too-many-ancestors
# these only work when given explicitly as option_type
self.LOG_LEVEL: self.get_log_level,
self.RELIST: self.getrelist,
+ self.IRELIST: self.getirelist,
}
return getter[option_type](section, option, fallback=fallback)
diff --git a/peekaboo/locale/de/LC_MESSAGES/peekaboo.mo b/peekaboo/locale/de/LC_MESSAGES/peekaboo.mo
index c89b24b..af5a962 100644
--- a/peekaboo/locale/de/LC_MESSAGES/peekaboo.mo
+++ b/peekaboo/locale/de/LC_MESSAGES/peekaboo.mo
Binary files differ
diff --git a/peekaboo/locale/de/LC_MESSAGES/peekaboo.po b/peekaboo/locale/de/LC_MESSAGES/peekaboo.po
index d82f699..77ab127 100644
--- a/peekaboo/locale/de/LC_MESSAGES/peekaboo.po
+++ b/peekaboo/locale/de/LC_MESSAGES/peekaboo.po
@@ -6,7 +6,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PeekabooAV 1.6.2\n"
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
-"POT-Creation-Date: 2019-04-17 09:26+0000\n"
+"POT-Creation-Date: 2019-08-20 10:35+0200\n"
"PO-Revision-Date: 2019-02-14 22:02+0000\n"
"Last-Translator: Michael Weiser <michael.weiser@gmx.de>\n"
"Language: de\n"
@@ -15,28 +15,28 @@ msgstr ""
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=utf-8\n"
"Content-Transfer-Encoding: 8bit\n"
-"Generated-By: Babel 2.4.0\n"
+"Generated-By: Babel 2.7.0\n"
#: peekaboo/queuing.py:382
msgid "Sample initialization failed"
msgstr "Initialisierung der zu analysierenden Datei fehlgeschlagen"
-#: peekaboo/sample.py:186
+#: peekaboo/sample.py:185
#, python-format
msgid "File \"%s\" %s is being analyzed"
msgstr "Datei \"%s\" %s wird analysiert"
-#: peekaboo/sample.py:239
+#: peekaboo/sample.py:238
#, python-format
msgid "File \"%s\" is considered \"%s\""
msgstr "Die Datei \"%s\" wird als \"%s\" betrachtet"
-#: peekaboo/sample.py:299
+#: peekaboo/sample.py:298
#, python-format
msgid "File \"%s\": %s"
msgstr "Datei \"%s\": %s"
-#: peekaboo/sample.py:495
+#: peekaboo/sample.py:497
#, python-format
msgid "Sample %s successfully submitted to Cuckoo as job %d"
msgstr "Erfolgreich an Cuckoo gegeben %s als Job %d"
@@ -100,55 +100,73 @@ msgstr "Ja"
msgid "No"
msgstr "Nein"
-#: peekaboo/ruleset/engine.py:118
+#: peekaboo/ruleset/engine.py:147
msgid "Rule aborted with error"
msgstr "Regel mit Fehler abgebrochen"
-#: peekaboo/ruleset/rules.py:133
+#: peekaboo/ruleset/rules.py:122
msgid "File is not yet known to the system"
msgstr "Datei ist dem System noch nicht bekannt"
-#: peekaboo/ruleset/rules.py:154
+#: peekaboo/ruleset/rules.py:143
#, python-format
msgid "Failure to determine sample file size: %s"
msgstr "Ermittlung der Dateigröße fehlgeschlagen: %s"
-#: peekaboo/ruleset/rules.py:159
+#: peekaboo/ruleset/rules.py:148
#, python-format
msgid "File has more than %d bytes"
msgstr "Datei hat mehr als %d bytes"
-#: peekaboo/ruleset/rules.py:165
+#: peekaboo/ruleset/rules.py:154
#, python-format
msgid "File is only %d bytes long"
-msgstr ""
+msgstr "Die Datei ist nur %d bytes groß"
-#: peekaboo/ruleset/rules.py:187
+#: peekaboo/ruleset/rules.py:176
msgid "File type is on whitelist"
msgstr "Dateityp ist auf Whitelist"
-#: peekaboo/ruleset/rules.py:191
+#: peekaboo/ruleset/rules.py:180
msgid "File type is not on whitelist"
msgstr "Dateityp ist nicht auf Whitelist"
-#: peekaboo/ruleset/rules.py:213
+#: peekaboo/ruleset/rules.py:202
msgid "File type is on the list of types to analyze"
msgstr "Dateityp ist auf der Liste der zu analysiserenden Typen"
-#: peekaboo/ruleset/rules.py:218
+#: peekaboo/ruleset/rules.py:207
#, python-format
msgid "File type is not on the list of types to analyse (%s)"
msgstr "Dateityp ist nicht auf der Liste der zu analysierenden Typen (%s)"
-#: peekaboo/ruleset/rules.py:231
+#: peekaboo/ruleset/rules.py:223
+msgid "File is not an office document"
+msgstr "Die Datei ist kein Office Dokument"
+
+#: peekaboo/ruleset/rules.py:247
msgid "The file contains an Office macro"
msgstr "Die Datei beinhaltet ein Office-Makro"
-#: peekaboo/ruleset/rules.py:235
+#: peekaboo/ruleset/rules.py:251
msgid "The file does not contain a recognizable Office macro"
msgstr "Die Datei beinhaltet kein erkennbares Office-Makro"
-#: peekaboo/ruleset/rules.py:265 peekaboo/ruleset/rules.py:402
+#: peekaboo/ruleset/rules.py:272
+msgid "The file contains an Office macro which runs at document open"
+msgstr ""
+"Die Datei beinhaltet ein Office Makro welches beim Öffnen der Datei "
+"ausgeführt wird"
+
+#: peekaboo/ruleset/rules.py:277
+msgid ""
+"The file does not contain a recognizable Office macro that is run at "
+"document open"
+msgstr ""
+"Die Datei beinhaltet kein erkennbares Office Makro welches beim Öffnen "
+"ausgeführt wird"
+
+#: peekaboo/ruleset/rules.py:307 peekaboo/ruleset/rules.py:445
msgid ""
"Behavioral analysis by Cuckoo has produced an error and did not finish "
"successfully"
@@ -156,40 +174,41 @@ msgstr ""
"Die Verhaltensanalyse durch Cuckoo hat einen Fehler produziert und konnte"
" nicht erfolgreich abgeschlossen werden"
-#: peekaboo/ruleset/rules.py:322
+#: peekaboo/ruleset/rules.py:365
msgid "No signature suggesting malware detected"
msgstr "Keine Signatur erkannt die auf Schadcode hindeutet"
-#: peekaboo/ruleset/rules.py:327
+#: peekaboo/ruleset/rules.py:370
#, python-format
msgid "The following signatures have been recognized: %s"
msgstr "Folgende Signaturen wurden erkannt: %s"
-#: peekaboo/ruleset/rules.py:346
+#: peekaboo/ruleset/rules.py:389
#, python-format
msgid "Cuckoo score >= %s: %s"
msgstr ""
-#: peekaboo/ruleset/rules.py:351
+#: peekaboo/ruleset/rules.py:394
#, python-format
msgid "Cuckoo score < %s: %s"
msgstr ""
-#: peekaboo/ruleset/rules.py:375
+#: peekaboo/ruleset/rules.py:418
#, python-format
msgid "The file attempts to contact at least one domain on the blacklist (%s)"
msgstr ""
"Die Datei versucht mindestens eine Domain aus der Blacklist zu "
"kontaktieren (%s)"
-#: peekaboo/ruleset/rules.py:381
+#: peekaboo/ruleset/rules.py:424
msgid "File does not seem to attempt contact with domains on the blacklist"
msgstr "Datei scheint keine Domains aus der Blacklist kontaktieren zu wollen"
-#: peekaboo/ruleset/rules.py:418
+#: peekaboo/ruleset/rules.py:461
msgid "Behavioral analysis by Cuckoo completed successfully"
msgstr "Die Verhaltensanalyse durch Cuckoo wurde erfolgreich abgeschlossen"
-#: peekaboo/ruleset/rules.py:435
+#: peekaboo/ruleset/rules.py:478
msgid "File does not seem to exhibit recognizable malicious behaviour"
msgstr "Datei scheint keine erkennbaren Schadroutinen zu starten"
+
diff --git a/peekaboo/locale/peekaboo.pot b/peekaboo/locale/peekaboo.pot
index 56b6113..085c447 100644
--- a/peekaboo/locale/peekaboo.pot
+++ b/peekaboo/locale/peekaboo.pot
@@ -8,35 +8,35 @@ msgid ""
msgstr ""
"Project-Id-Version: PROJECT VERSION\n"
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
-"POT-Creation-Date: 2019-04-17 09:26+0000\n"
+"POT-Creation-Date: 2019-08-20 10:35+0200\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=utf-8\n"
"Content-Transfer-Encoding: 8bit\n"
-"Generated-By: Babel 2.4.0\n"
+"Generated-By: Babel 2.7.0\n"
#: peekaboo/queuing.py:382
msgid "Sample initialization failed"
msgstr ""
-#: peekaboo/sample.py:186
+#: peekaboo/sample.py:185
#, python-format
msgid "File \"%s\" %s is being analyzed"
msgstr ""
-#: peekaboo/sample.py:239
+#: peekaboo/sample.py:238
#, python-format
msgid "File \"%s\" is considered \"%s\""
msgstr ""
-#: peekaboo/sample.py:299
+#: peekaboo/sample.py:298
#, python-format
msgid "File \"%s\": %s"
msgstr ""
-#: peekaboo/sample.py:495
+#: peekaboo/sample.py:497
#, python-format
msgid "Sample %s successfully submitted to Cuckoo as job %d"
msgstr ""
@@ -99,93 +99,107 @@ msgstr ""
msgid "No"
msgstr ""
-#: peekaboo/ruleset/engine.py:118
+#: peekaboo/ruleset/engine.py:147
msgid "Rule aborted with error"
msgstr ""
-#: peekaboo/ruleset/rules.py:133
+#: peekaboo/ruleset/rules.py:122
msgid "File is not yet known to the system"
msgstr ""
-#: peekaboo/ruleset/rules.py:154
+#: peekaboo/ruleset/rules.py:143
#, python-format
msgid "Failure to determine sample file size: %s"
msgstr ""
-#: peekaboo/ruleset/rules.py:159
+#: peekaboo/ruleset/rules.py:148
#, python-format
msgid "File has more than %d bytes"
msgstr ""
-#: peekaboo/ruleset/rules.py:165
+#: peekaboo/ruleset/rules.py:154
#, python-format
msgid "File is only %d bytes long"
msgstr ""
-#: peekaboo/ruleset/rules.py:187
+#: peekaboo/ruleset/rules.py:176
msgid "File type is on whitelist"
msgstr ""
-#: peekaboo/ruleset/rules.py:191
+#: peekaboo/ruleset/rules.py:180
msgid "File type is not on whitelist"
msgstr ""
-#: peekaboo/ruleset/rules.py:213
+#: peekaboo/ruleset/rules.py:202
msgid "File type is on the list of types to analyze"
msgstr ""
-#: peekaboo/ruleset/rules.py:218
+#: peekaboo/ruleset/rules.py:207
#, python-format
msgid "File type is not on the list of types to analyse (%s)"
msgstr ""
-#: peekaboo/ruleset/rules.py:231
+#: peekaboo/ruleset/rules.py:223
+msgid "File is not an office document"
+msgstr ""
+
+#: peekaboo/ruleset/rules.py:247
msgid "The file contains an Office macro"
msgstr ""
-#: peekaboo/ruleset/rules.py:235
+#: peekaboo/ruleset/rules.py:251
msgid "The file does not contain a recognizable Office macro"
msgstr ""
-#: peekaboo/ruleset/rules.py:265 peekaboo/ruleset/rules.py:402
+#: peekaboo/ruleset/rules.py:272
+msgid "The file contains an Office macro which runs at document open"
+msgstr ""
+
+#: peekaboo/ruleset/rules.py:277
+msgid ""
+"The file does not contain a recognizable Office macro that is run at "
+"document open"
+msgstr ""
+
+#: peekaboo/ruleset/rules.py:307 peekaboo/ruleset/rules.py:445
msgid ""
"Behavioral analysis by Cuckoo has produced an error and did not finish "
"successfully"
msgstr ""
-#: peekaboo/ruleset/rules.py:322
+#: peekaboo/ruleset/rules.py:365
msgid "No signature suggesting malware detected"
msgstr ""
-#: peekaboo/ruleset/rules.py:327
+#: peekaboo/ruleset/rules.py:370
#, python-format
msgid "The following signatures have been recognized: %s"
msgstr ""
-#: peekaboo/ruleset/rules.py:346
+#: peekaboo/ruleset/rules.py:389
#, python-format
msgid "Cuckoo score >= %s: %s"
msgstr ""
-#: peekaboo/ruleset/rules.py:351
+#: peekaboo/ruleset/rules.py:394
#, python-format
msgid "Cuckoo score < %s: %s"
msgstr ""
-#: peekaboo/ruleset/rules.py:375
+#: peekaboo/ruleset/rules.py:418
#, python-format
msgid "The file attempts to contact at least one domain on the blacklist (%s)"
msgstr ""
-#: peekaboo/ruleset/rules.py:381
+#: peekaboo/ruleset/rules.py:424
msgid "File does not seem to attempt contact with domains on the blacklist"
msgstr ""
-#: peekaboo/ruleset/rules.py:418
+#: peekaboo/ruleset/rules.py:461
msgid "Behavioral analysis by Cuckoo completed successfully"
msgstr ""
-#: peekaboo/ruleset/rules.py:435
+#: peekaboo/ruleset/rules.py:478
msgid "File does not seem to exhibit recognizable malicious behaviour"
msgstr ""
diff --git a/peekaboo/ruleset/engine.py b/peekaboo/ruleset/engine.py
index 96af1c6..6e736f5 100644
--- a/peekaboo/ruleset/engine.py
+++ b/peekaboo/ruleset/engine.py
@@ -49,6 +49,7 @@ class RulesetEngine(object):
CuckooEvilSigRule,
CuckooScoreRule,
OfficeMacroRule,
+ OfficeMacroWithSuspiciousKeyword,
RequestsEvilDomainRule,
CuckooAnalysisFailedRule,
ContainsPeekabooYarRule,
@@ -127,7 +128,7 @@ class RulesetEngine(object):
rule wrapper for in/out logging and reporting
"""
rule_name = rule_class.rule_name
- logger.debug("Processing rule '%s' for %s" % (rule_name, sample))
+ logger.debug("Processing rule '%s' for %s", rule_name, sample)
try:
rule = rule_class(config=self.config, db_con=self.db_con)
@@ -138,8 +139,8 @@ class RulesetEngine(object):
raise
# catch all other exceptions for this rule
except Exception as e:
- logger.warning("Unexpected error in '%s' for %s" % (rule_name,
- sample))
+ logger.warning("Unexpected error in '%s' for %s", rule_name,
+ sample)
logger.exception(e)
# create "fake" RuleResult
result = RuleResult("RulesetEngine", result=Result.failed,
@@ -147,5 +148,5 @@ class RulesetEngine(object):
further_analysis=False)
sample.add_rule_result(result)
- logger.info("Rule '%s' processed for %s" % (rule_name, sample))
+ logger.info("Rule '%s' processed for %s", rule_name, sample)
return result
diff --git a/peekaboo/ruleset/rules.py b/peekaboo/ruleset/rules.py
index 797858a..6635c0c 100644
--- a/peekaboo/ruleset/rules.py
+++ b/peekaboo/ruleset/rules.py
@@ -31,6 +31,8 @@ import logging
from peekaboo.ruleset import Result, RuleResult
from peekaboo.exceptions import PeekabooAnalysisDeferred, \
CuckooSubmitFailedException, PeekabooRulesetConfigError
+from peekaboo.toolbox.ole import Oletools, OletoolsReport, \
+ OleNotAnOfficeDocumentException
logger = logging.getLogger(__name__)
@@ -207,13 +209,40 @@ class FileTypeOnGreylistRule(Rule):
False)
-class OfficeMacroRule(Rule):
+class OleRule(Rule):
+ """ A common base class for rules that evaluate the Ole report. """
+ def evaluate(self, sample):
+ """ Report the sample as bad if it contains a macro. """
+ if sample.oletools_report is None:
+ try:
+ ole = Oletools()
+ report = ole.get_report(sample)
+ sample.register_oletools_report(OletoolsReport(report))
+ except OleNotAnOfficeDocumentException:
+ return self.result(Result.unknown,
+ _("File is not an office document"),
+ True)
+ except Exception:
+ raise
+
+ return self.evaluate_report(sample.oletools_report)
+
+ def evaluate_report(self, report):
+ """ Evaluate an Ole report.
+
+ @param report: The Ole report.
+ @returns: RuleResult containing verdict.
+ """
+ raise NotImplementedError
+
+
+class OfficeMacroRule(OleRule):
""" A rule checking the sample for Office macros. """
rule_name = 'office_macro'
- def evaluate(self, sample):
+ def evaluate_report(self, report):
""" Report the sample as bad if it contains a macro. """
- if sample.office_macros:
+ if report.has_office_macros():
return self.result(Result.bad,
_("The file contains an Office macro"),
False)
@@ -224,6 +253,32 @@ class OfficeMacroRule(Rule):
True)
+class OfficeMacroWithSuspiciousKeyword(OleRule):
+ """ A rule checking the sample for Office macros. """
+ rule_name = 'office_macro_with_suspicious_keyword'
+
+ def get_config(self):
+ # get list of keywords from config file
+ self.suspicious_keyword_list = self.get_config_value(
+ 'keyword', [], option_type=self.config.IRELIST)
+ if not self.suspicious_keyword_list:
+ raise PeekabooRulesetConfigError(
+ "Empty suspicious keyword list, check %s rule config." %
+ self.rule_name)
+
+ def evaluate_report(self, report):
+ if report.has_office_macros_with_suspicious_keyword(self.suspicious_keyword_list):
+ return self.result(Result.bad,
+ _("The file contains an Office macro which "
+ "runs at document open"),
+ False)
+
+ return self.result(Result.unknown,
+ _("The file does not contain a recognizable "
+ "Office macro that is run at document open"),
+ True)
+
+
class CuckooRule(Rule):
""" A common base class for rules that evaluate the Cuckoo report. """
def evaluate(self, sample):
diff --git a/peekaboo/sample.py b/peekaboo/sample.py
index 35c661d..d4c5a62 100644
--- a/peekaboo/sample.py
+++ b/peekaboo/sample.py
@@ -38,7 +38,6 @@ from builtins import open
from datetime import datetime
from peekaboo.toolbox.files import guess_mime_type_from_file_contents, \
guess_mime_type_from_filename
-from peekaboo.toolbox.ms_office import has_office_macros
from peekaboo.ruleset import Result
@@ -91,6 +90,7 @@ class Sample(object):
self.__submit_path = None
self.__cuckoo_job_id = -1
self.__cuckoo_report = None
+ self.__oletools_report = None
self.__done = False
self.__status_change = status_change
self.__result = Result.unchecked
@@ -101,7 +101,6 @@ class Sample(object):
self.__sha256sum = None
self.__mimetypes = None
self.__file_extension = None
- self.__office_macros = None
self.__base_dir = base_dir
self.__job_hash = None
self.__job_hash_regex = job_hash_regex
@@ -462,14 +461,6 @@ class Sample(object):
return self.__cuckoo_job_id
@property
- def office_macros(self):
- """ Determines if this sample contains any office macros. """
- if not self.__office_macros:
- self.__office_macros = has_office_macros(self.__path)
-
- return self.__office_macros
-
- @property
def file_size(self):
""" Determine and cache sample file size
@@ -485,6 +476,11 @@ class Sample(object):
return self.__cuckoo_report
@property
+ def oletools_report(self):
+ """ Returns the oletools report """
+ return self.__oletools_report
+
+ @property
def submit_path(self):
""" Returns the path to use for submission to Cuckoo """
return self.__submit_path
@@ -506,6 +502,10 @@ class Sample(object):
""" Records a Cuckoo report for later evaluation. """
self.__cuckoo_report = report
+ def register_oletools_report(self, report):
+ """ Records a Oletools report for alter evaluation. """
+ self.__oletools_report = report
+
def cleanup(self):
""" Clean up after the sample has been analysed, removing a potentially
created workdir. """
diff --git a/peekaboo/toolbox/ms_office.py b/peekaboo/toolbox/ms_office.py
deleted file mode 100644
index b5ab902..0000000
--- a/peekaboo/toolbox/ms_office.py
+++ /dev/null
@@ -1,61 +0,0 @@
-###############################################################################
-# #
-# Peekaboo Extended Email Attachment Behavior Observation Owl #
-# #
-# toolbox/ #
-# ms_office.py #
-###############################################################################
-# #
-# Copyright (C) 2016-2019 science + computing ag #
-# #
-# 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 <http://www.gnu.org/licenses/>. #
-# #
-###############################################################################
-
-""" Tool functions for handling office macros. """
-
-import logging
-from oletools.olevba import VBA_Parser
-
-
-logger = logging.getLogger(__name__)
-
-MS_OFFICE_EXTENSIONS = [
- ".doc", ".docm", ".dotm", ".docx",
- ".ppt", ".pptm", ".pptx", ".potm", ".ppam", ".ppsm",
- ".xls", ".xlsm", ".xlsx",
-]
-
-
-def has_office_macros(office_file):
- """
- Detects macros in Microsoft Office documents.
-
- @param office_file: The MS Office document to check for macros.
- @return: True if macros where found, otherwise False.
- If VBA_Parser crashes it returns False too.
- """
- file_extension = office_file.split('.')[-1]
- if file_extension not in MS_OFFICE_EXTENSIONS:
- return False
- try:
- # VBA_Parser reports macros for office documents
- vbaparser = VBA_Parser(office_file)
- return vbaparser.detect_vba_macros()
- except TypeError:
- # The given file is not an office document.
- return False
- except Exception as error:
- logger.exception(error)
- return False
diff --git a/peekaboo/toolbox/ole.py b/peekaboo/toolbox/ole.py
new file mode 100644
index 0000000..e4a3ce0
--- /dev/null
+++ b/peekaboo/toolbox/ole.py
@@ -0,0 +1,120 @@
+###############################################################################
+# #
+# Peekaboo Extended Email Attachment Behavior Observation Owl #
+# #
+# toolbox/ #
+# ole.py #
+###############################################################################
+# #
+# Copyright (C) 2016-2019 science + computing ag #
+# #
+# 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 <http://www.gnu.org/licenses/>. #
+# #
+###############################################################################
+
+
+import logging
+import re
+from oletools.olevba import VBA_Parser
+
+logger = logging.getLogger(__name__)
+
+
+class OleNotAnOfficeDocumentException(Exception):
+ pass
+
+class Oletools(object):
+ """ Parent class, defines interface to Oletools. """
+ def __init__(self):
+ self.MS_OFFICE_EXTENSIONS = [
+ "doc", "docm", "dotm", "docx",
+ "ppt", "pptm", "pptx", "potm", "ppam", "ppsm",
+ "xls", "xlsm", "xlsx",
+ ]
+
+ def get_report(self, sample):
+ """ Return oletools report or create if not already cached. """
+ if sample.oletools_report != None:
+ return sample.oletools_report
+
+ report = {}
+ if sample.file_extension not in self.MS_OFFICE_EXTENSIONS:
+ raise OleNotAnOfficeDocumentException(sample.file_extension)
+
+ try:
+ vbaparser = VBA_Parser(sample.file_path)
+
+ # List from oletools/olevba.py#L553
+ oletype = ('OLE', 'OpenXML', 'FlatOPC_XML', 'Word2003_XML', 'MHTML', 'PPT')
+
+ # check if ole detects it as an office file
+ if vbaparser.type not in oletype:
+ raise OleNotAnOfficeDocumentException(sample.file_extension)
+
+ # VBA_Parser reports macros for office documents
+ report['has_macros'] = vbaparser.detect_vba_macros() or vbaparser.detect_xlm_macros()
+ try:
+ report['vba'] = vbaparser.reveal()
+ except TypeError:
+ # no macros
+ pass
+ vbaparser.close()
+ except IOError:
+ raise
+ except TypeError:
+ # The given file is not an office document.
+ pass
+ except Exception as error:
+ logger.exception(error)
+ sample.register_oletools_report(report)
+ return report
+
+
+class OletoolsReport(object):
+ """ Represents a custom Oletools report. """
+ def __init__(self, report):
+ self.report = report
+
+ def has_office_macros(self):
+ """
+ Detects macros in Microsoft Office documents.
+
+ @return: True if macros where found, otherwise False.
+ If VBA_Parser crashes it returns False too.
+ """
+
+ try:
+ return self.report['has_macros']
+ except KeyError:
+ return False
+
+ def has_office_macros_with_suspicious_keyword(self, suspicious_keywords):
+ """
+ Detects macros with supplied suspicious keywords in Microsoft Office documents.
+
+ @param suspicious_keywords: List of suspicious keyword regexes.
+ @return: True if macros with keywords where found, otherwise False.
+ If VBA_Parser crashes it returns False too.
+ """
+ suspicious = False
+ try:
+ vba = self.report['vba']
+ for w in suspicious_keywords:
+ if re.search(w, vba):
+ suspicious = True
+ break
+ except KeyError:
+ return False
+
+ return suspicious
diff --git a/ruleset.conf.sample b/ruleset.conf.sample
index c8909a9..9a9ea73 100644
--- a/ruleset.conf.sample
+++ b/ruleset.conf.sample
@@ -9,13 +9,14 @@ rule.1 : known
rule.2 : file_larger_than
rule.3 : file_type_on_whitelist
rule.4 : file_type_on_greylist
-rule.5 : cuckoo_evil_sig
-rule.6 : cuckoo_score
-rule.7 : office_macro
-#rule.8 : requests_evil_domain
-rule.9 : cuckoo_analysis_failed
-#rule.10 : contains_peekabooyar
-rule.11 : final_rule
+#rule.5 : office_macro
+#rule.6 : office_macro_with_suspicious_keyword
+rule.7 : cuckoo_evil_sig
+rule.8 : cuckoo_score
+#rule.9 : requests_evil_domain
+rule.10 : cuckoo_analysis_failed
+#rule.11 : contains_peekabooyar
+rule.12 : final_rule
# rule specific configuration options
# the section name equals the name of the rule
@@ -71,6 +72,10 @@ greylist.34 : application/vnd.ms-excel.template.macroEnabled.12
greylist.35 : application/vnd.ms-excel
greylist.36 : application/msword
+[office_macro_with_suspicious_keyword]
+keyword.1 : AutoOpen
+keyword.2 : AutoClose
+
[cuckoo_evil_sig]
signature.1 : A potential heapspray has been detected. .*
signature.2 : A process attempted to delay the analysis task.
diff --git a/tests/test-data/office/blank.doc b/tests/test-data/office/blank.doc
new file mode 100644
index 0000000..a7b04c5
--- /dev/null
+++ b/tests/test-data/office/blank.doc
Binary files differ
diff --git a/tests/test-data/office/empty.doc b/tests/test-data/office/empty.doc
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/tests/test-data/office/empty.doc
diff --git a/tests/test-data/office/legitmacro.xls b/tests/test-data/office/legitmacro.xls
new file mode 100644
index 0000000..cfb3caa
--- /dev/null
+++ b/tests/test-data/office/legitmacro.xls
Binary files differ
diff --git a/tests/test-data/office/suspiciousMacro.doc b/tests/test-data/office/suspiciousMacro.doc
new file mode 100644
index 0000000..f618a97
--- /dev/null
+++ b/tests/test-data/office/suspiciousMacro.doc
Binary files differ
diff --git a/test.py b/tests/test.py
index 8321ac7..39318fb 100755
--- a/test.py
+++ b/tests/test.py
@@ -51,7 +51,8 @@ from peekaboo.ruleset.engine import RulesetEngine
from peekaboo.ruleset.rules import FileTypeOnWhitelistRule, \
FileTypeOnGreylistRule, CuckooAnalysisFailedRule, \
KnownRule, FileLargerThanRule, CuckooEvilSigRule, \
- CuckooScoreRule, RequestsEvilDomainRule, FinalRule
+ CuckooScoreRule, RequestsEvilDomainRule, FinalRule, \
+ OfficeMacroRule, OfficeMacroWithSuspiciousKeyword
from peekaboo.toolbox.cuckoo import CuckooReport
from peekaboo.db import PeekabooDatabase, PeekabooDatabaseError
# pylint: enable=wrong-import-position
@@ -537,7 +538,6 @@ class TestSample(unittest.TestCase):
self.assertEqual(self.sample.cuckoo_report, None)