Beispiel #1
0
def analyze_lambda_handler(event_data, lambda_context):
    """Lambda function entry point.

    Args:
        event_data: [dict] of the form: {
            'S3Objects': [...],  # S3 object keys.
            'SQSReceipts': [...]  # SQS receipt handles (to be deleted after processing).
        }
            There can be any number of S3objects, but no more than 10 SQS receipts.
        lambda_context: LambdaContext object (with .function_version).

    Returns:
        A dict mapping S3 object identifier [string] to a summary [dict] of file info and matched
        YARA rule information.
    """
    result = {}
    binaries = []  # List of the BinaryInfo data.

    # Build the YaraAnalyzer now if we could not do it when this file was imported.
    global ANALYZER, NUM_YARA_RULES  # pylint: disable=global-statement
    if not ANALYZER:
        ANALYZER = yara_analyzer.YaraAnalyzer(COMPILED_RULES_FILEPATH)
        NUM_YARA_RULES = ANALYZER.num_rules

    # The Lambda version must be an integer.
    try:
        lambda_version = int(lambda_context.function_version)
    except ValueError:
        lambda_version = -1

    LOGGER.info('Processing %d record(s)', len(event_data['S3Objects']))
    for s3_key in event_data['S3Objects']:
        LOGGER.info('Analyzing %s', s3_key)

        with binary_info.BinaryInfo(os.environ['S3_BUCKET_NAME'], s3_key,
                                    ANALYZER) as binary:
            result[binary.s3_identifier] = binary.summary()
            binaries.append(binary)

            if binary.yara_matches:
                LOGGER.warning('%s matched YARA rules: %s', binary,
                               binary.matched_rule_ids)
                binary.save_matches_and_alert(
                    lambda_version,
                    os.environ['YARA_MATCHES_DYNAMO_TABLE_NAME'],
                    os.environ['YARA_ALERTS_SNS_TOPIC_ARN'])
            else:
                LOGGER.info('%s did not match any YARA rules', binary)

    # Delete all of the SQS receipts (mark them as completed).
    analyzer_aws_lib.delete_sqs_messages(os.environ['SQS_QUEUE_URL'],
                                         event_data['SQSReceipts'])

    # Publish metrics.
    try:
        analyzer_aws_lib.put_metric_data(NUM_YARA_RULES, binaries)
    except BotoError:
        LOGGER.exception('Error saving metric data')

    return result
    def test_analyze_no_matches(self):
        """Analyze returns empty list if no matches."""
        # Setup a different YaraAnalyzer with an empty ruleset.
        yara_mocks.save_test_yara_rules('./empty.yara.rules',
                                        empty_rules_file=True)
        empty_analyzer = yara_analyzer.YaraAnalyzer('./empty.yara.rules')

        self.assertEqual([], empty_analyzer.analyze('/target.exe'))
    def setUp(self):
        """For each test, build a new YaraAnalyzer."""
        self.setUpPyfakefs()
        yara_mocks.save_test_yara_rules('./all.yara.rules')
        self._analyzer = yara_analyzer.YaraAnalyzer('./all.yara.rules')

        # Write target file.
        self.fs.CreateFile(
            './target.exe',
            contents='This is definitely not an evil file. ^_^\n')
Beispiel #4
0
    def test_analyze_no_matches(self):
        """Analyze returns empty list if no matches."""
        # Setup a different YaraAnalyzer with an empty ruleset.
        yara_mocks.save_test_yara_rules('./empty.yara.rules',
                                        empty_rules_file=True)
        with mock.patch.object(yara_analyzer.yara,
                               'load',
                               side_effect=yara_mocks.mock_yara_load):
            empty_analyzer = yara_analyzer.YaraAnalyzer('./empty.yara.rules')

        self.assertEqual([], empty_analyzer.analyze('/target.exe'))
Beispiel #5
0
    def setUp(self):
        """For each test, build a new YaraAnalyzer."""
        self.setUpPyfakefs()
        yara_mocks.save_test_yara_rules('./all.yara.rules')
        with mock.patch.object(yara_analyzer.yara,
                               'load',
                               side_effect=yara_mocks.mock_yara_load):
            self._analyzer = yara_analyzer.YaraAnalyzer('./all.yara.rules')

        # Write target file.
        self.fs.CreateFile(
            './target.exe',
            contents='This is definitely not an evil file. ^_^\n')
    def setUp(self):
        """For each test, build a new YaraAnalyzer."""
        self.setUpPyfakefs()
        with mock.patch.object(subprocess,
                               'Popen',
                               side_effect=thor_mocks.mock_thor_start):
            self._analyzer = yara_analyzer.YaraAnalyzer()

        # Write target file.
        # pylint: disable=no-member
        self.fs.create_file(
            './target.exe',
            contents='This is definitely not an evil file. ^_^\n')
Beispiel #7
0
from botocore.exceptions import ClientError as BotoError

if __package__:
    # Imported by unit tests or other external code.
    from lambda_functions.analyzer import analyzer_aws_lib, binary_info, yara_analyzer
    from lambda_functions.analyzer.common import COMPILED_RULES_FILEPATH, LOGGER
else:
    import analyzer_aws_lib
    import binary_info
    from common import COMPILED_RULES_FILEPATH, LOGGER
    import yara_analyzer

# Build the YaraAnalyzer from the compiled rules file at import time (i.e. once per container).
# This saves 50-100+ ms per Lambda invocation, depending on the size of the rules file.
ANALYZER = yara_analyzer.YaraAnalyzer(COMPILED_RULES_FILEPATH)
# Due to a bug in yara-python, num_rules only be computed once. Thereafter, it will return 0.
# So we have to compute this here since multiple invocations may share the same analyzer.
NUM_YARA_RULES = ANALYZER.num_rules


def analyze_lambda_handler(event_data: Dict[str, Any],
                           lambda_context) -> Dict[str, Dict[str, Any]]:
    """Lambda function entry point.

    Args:
        event_data: [dict] of the form: {
            'S3Objects': [...],  # S3 object keys.
            'SQSReceipts': [...]  # SQS receipt handles (to be deleted after processing).
        }
            There can be any number of S3objects, but no more than 10 SQS receipts.
Beispiel #8
0
#   YARA_MATCHES_DYNAMO_TABLE_NAME: Name of the Dynamo table which stores YARA match results.
#   YARA_ALERTS_SNS_TOPIC_ARN: ARN of the SNS topic which should be alerted on a YARA match.
# Expects a binary YARA rules file to be at './compiled_yara_rules.bin'
import json
import os
from typing import Any, Dict, Generator, Tuple
import urllib.parse

from botocore.exceptions import ClientError

from lambda_functions.analyzer import analyzer_aws_lib, binary_info, yara_analyzer
from lambda_functions.analyzer.common import LOGGER

# Build the YaraAnalyzer from the compiled rules file at import time (i.e. once per container).
# This saves 50-100+ ms per Lambda invocation, depending on the size of the rules file.
ANALYZER = yara_analyzer.YaraAnalyzer()
# Due to a bug in yara-python, num_rules only be computed once. Thereafter, it will return 0.
# So we have to compute this here since multiple invocations may share the same analyzer.
NUM_YARA_RULES = ANALYZER.num_rules


def _objects_to_analyze(
        event: Dict[str, Any]) -> Generator[Tuple[str, str], None, None]:
    """Parse the invocation event into a list of objects to analyze.

    Args:
        event: Invocation event (SQS message whose message body is an S3 event notification)

    Yields:
        (bucket_name, object_key) string tuples to analyze
    """
Beispiel #9
0
def init():
    global analyzer
    compile_rules.compile_rules(COMPILED_RULES_FILENAME)
    analyzer = yara_analyzer.YaraAnalyzer(COMPILED_RULES_FILENAME)
    return ("OK", 200)