summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-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)
self.assertEqual(self.sample.done, False)
self.assertEqual(self.sample.submit_path, None)
- self.assertFalse(self.sample.office_macros)
self.assertEqual(self.sample.file_size, 4)
def test_4_initialised_sample_attributes(self):
@@ -565,7 +565,6 @@ class TestSample(unittest.TestCase):
self.assertEqual(self.sample.done, False)
self.assertRegexpMatches(
self.sample.submit_path, '/%s.py$' % self.sample.sha256sum)
- self.assertFalse(self.sample.office_macros)
self.assertEqual(self.sample.file_size, 4)
def test_5_mark_done(self):