Exemple #1
0
    def test_comments(self):
        """Test that comments are valid syntax but stripped from AST."""
        match = parse_query("process where pid=4 and ppid=0")

        query = parse_query(
            """process where pid = 4 /* multi\nline\ncomment */ and ppid=0""")
        self.assertEqual(match, query)

        query = parse_query(
            """process where pid = 4 // something \n and ppid=0""")
        self.assertEqual(match, query)

        query = parse_query("""process where pid
            = 4 and ppid=0
        """)
        self.assertEqual(match, query)

        query = parse_query("""process where
            // test
            //
            //line
            //comments
            pid = 4 and ppid = 0
        """)
        self.assertEqual(match, query)

        match = parse_expression("true")
        query = parse_expression(
            "true // something else \r\n /* test\r\n something \r\n*/")
        self.assertEqual(match, query)

        commented = parse_definitions(
            "macro test() pid = 4 and /* comment */ ppid = 0")
        macro = parse_definitions("macro test() pid = 4 and ppid = 0")
        self.assertEqual(commented, macro)
Exemple #2
0
    def test_mixed_definitions(self):
        """Test that macro and constant definitions can be loaded correctly."""
        defn = parse_definitions("""
        const magic = 100
        macro OR(a, b) a or b
        """)
        pp = PreProcessor(defn)

        # Confirm that copy and adding is working
        pp2 = pp.copy()
        pp.add_definition(
            parse_definition("macro ABC(a, b, c) error_error_error"))
        pp2.add_definition(
            parse_definition("macro ABC(a, b, c) f(a, magic, c)"))

        matches = [
            ("abc", "abc"),
            ("OR(x, y)", "x or y"),
            ("magic", "100"),
            ("ABC(0,1,2)", "f(0, 100, 2)"),
        ]
        for before, after in matches:
            before = parse_expression(before)
            after = parse_expression(after)
            self.assertEqual(pp2.expand(before), after)
Exemple #3
0
    def __init__(self, config=None):
        """Create the engine with an optional list of files."""
        super(BaseEngine, self).__init__(config)
        self.analytics = []  # type: list[EqlAnalytic]
        self.preprocessor = PreProcessor()

        with use_schema(self.schema):
            definitions = self.get_config('definitions', [])
            if is_string(definitions):
                definitions = parse_definitions(definitions)

            self.preprocessor.add_definitions(definitions)

            for path in self.get_config('definitions_files', []):
                with open(path, 'r') as f:
                    definitions = parse_definitions(f.read())
                self.preprocessor.add_definitions(definitions)
Exemple #4
0
    def test_engine_schema(self):
        """Test loading the engine with a custom schema."""
        queries = [
            'movie where name == "*Breakfast*" and IN_80s(release)',
            'person where name == "John Hughes"',
        ]

        analytic_dicts = [{'query': q} for q in queries]
        definitions = """
        macro IN_80s(date) date == "*/*/1980"
        """

        config = {
            'schema': {
                'event_types': {
                    'movie': 1,
                    'person': 2
                }
            },
            'definitions': parse_definitions(definitions),
            'analytics': analytic_dicts
        }

        pp = PreProcessor()
        pp.add_definitions(config['definitions'])

        with use_schema(config['schema']):
            expected = [
                parse_analytic(d, preprocessor=pp) for d in analytic_dicts
            ]

        engine = BaseEngine(config)
        with use_schema(engine.schema):
            engine.add_analytics([parse_analytic(d) for d in analytic_dicts])

        self.assertListEqual(
            engine.analytics, expected,
            "Analytics were not loaded and expanded properly.")
Exemple #5
0
    def test_macro_expansion(self):
        """Test EQL custom macros."""
        expanded = {
            "A_OR_B":
            "a or b",
            "XOR":
            "(a and not b) or (b and not a)",
            "IN_GRAYLIST":
            'proc in ("msbuild.exe", "powershell.exe", "cmd.exe", "netsh.exe")',
            "PROCESS_IN_GRAYLIST":
            'process_name in ("msbuild.exe", "powershell.exe", "cmd.exe", "netsh.exe")',
            "PARENT_XOR_CHILD_IN_GRAYLIST":
            ('(  '
             '   process_name in ("msbuild.exe", "powershell.exe", "cmd.exe", "netsh.exe") and not '
             '   parent_process_name in ("msbuild.exe", "powershell.exe", "cmd.exe", "netsh.exe")'
             ') or ('
             '   parent_process_name in ("msbuild.exe", "powershell.exe", "cmd.exe", "netsh.exe") and not '
             '   process_name in ("msbuild.exe", "powershell.exe", "cmd.exe", "netsh.exe")'
             ')'),
            'DESCENDANT_OF_PROC':
            'descendant of [process where opcode == 1 and expr]'
        }

        macros = parse_definitions(self.macro_definitions)
        lookup = OrderedDict()

        for macro in macros:
            lookup[macro.name] = macro
            rendered = macro.render()
            macro_copy = parse_definition(rendered)
            self.assertEqual(macro, macro_copy)
            self.assertEqual(rendered, macro_copy.render(),
                             "Macro doesn't render valid EQL.")

        # Now load up each macro to the engine
        engine = PreProcessor(macros)

        # Confirm that nested macros are expanded appropriately
        for name, macro in engine.macros.items():
            if name == 'safePath':
                continue
            expected_expr = parse_expression(expanded[name])
            self.assertEqual(macro.expression, expected_expr)
            self.assertEqual(macro.expression.render(), expected_expr.render())

        # Expand some EQL queries
        queries = [
            ('process where DESCENDANT_OF_PROC(process_name="explorer.exe")',
             'process where descendant of [process where opcode=1 and process_name == "explorer.exe"]'
             ),
            ('process where XOR(a=="b", c=="d")',
             'process where ((a == "b") and not (c == "d")) or  ((c == "d") and not (a == "b"))'
             ),
            (
                'file where true',
                'file where true',
            ),
            ('process where opcode=1 and PROCESS_IN_GRAYLIST()',
             'process where opcode==1 and process_name in ("msbuild.exe","powershell.exe","cmd.exe","netsh.exe")'
             ),
        ]

        for query, expanded_query in queries:
            before_node = parse_query(query)
            actual = engine.expand(before_node)
            expected = parse_query(expanded_query)

            # Test that eval + repr works
            actual_repr = repr(actual)
            eval_actual = eval(actual_repr)

            self.assertEqual(actual, expected)
            self.assertEqual(eval_actual, actual)
            self.assertTrue(actual == expected)
            self.assertFalse(actual != expected)
            error_msg = "'{}' expanded to '{}' instead of '{}'".format(
                query, actual.render(), expected.render())
            self.assertEqual(actual.render(), expected.render(), error_msg)

        query = parse_expression("DESCENDANT_OF_PROC()")
        self.assertRaisesRegexp(ValueError,
                                "Macro .+ expected \d+ arguments .*",
                                engine.expand, query)

        query = parse_expression("DESCENDANT_OF_PROC(1,2,3)")
        self.assertRaisesRegexp(ValueError,
                                "Macro .+ expected \d+ arguments .*",
                                engine.expand, query)