# -*- coding: utf-8 -*- # Copyright 2023 Google LLC # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. import textwrap import capa.capabilities.common from capa.features.extractors.base_extractor import FunctionFilter def test_match_across_scopes_file_function(z9324d_extractor): rules = capa.rules.RuleSet( [ # this rule should match on a function (0x4073F0) capa.rules.Rule.from_yaml( textwrap.dedent( """ rule: meta: name: install service scopes: static: function dynamic: process examples: - 9324d1a8ae37a36ae560c37448c9705a:0x4073F0 features: - and: - api: advapi32.OpenSCManagerA - api: advapi32.CreateServiceA - api: advapi32.StartServiceA """ ) ), # this rule should match on a file feature capa.rules.Rule.from_yaml( textwrap.dedent( """ rule: meta: name: .text section scopes: static: file dynamic: process examples: - 9324d1a8ae37a36ae560c37448c9705a features: - section: .text """ ) ), # this rule should match on earlier rule matches: # - install service, with function scope # - .text section, with file scope capa.rules.Rule.from_yaml( textwrap.dedent( """ rule: meta: name: .text section and install service scopes: static: file dynamic: process examples: - 9324d1a8ae37a36ae560c37448c9705a features: - and: - match: install service - match: .text section """ ) ), ] ) capabilities = capa.capabilities.common.find_capabilities(rules, z9324d_extractor) assert "install service" in capabilities.matches assert ".text section" in capabilities.matches assert ".text section and install service" in capabilities.matches def test_match_across_scopes(z9324d_extractor): rules = capa.rules.RuleSet( [ # this rule should match on a basic block (including at least 0x403685) capa.rules.Rule.from_yaml( textwrap.dedent( """ rule: meta: name: tight loop scopes: static: basic block dynamic: process examples: - 9324d1a8ae37a36ae560c37448c9705a:0x403685 features: - characteristic: tight loop """ ) ), # this rule should match on a function (0x403660) # based on API, as well as prior basic block rule match capa.rules.Rule.from_yaml( textwrap.dedent( """ rule: meta: name: kill thread loop scopes: static: function dynamic: process examples: - 9324d1a8ae37a36ae560c37448c9705a:0x403660 features: - and: - api: kernel32.TerminateThread - api: kernel32.CloseHandle - match: tight loop """ ) ), # this rule should match on a file feature and a prior function rule match capa.rules.Rule.from_yaml( textwrap.dedent( """ rule: meta: name: kill thread program scopes: static: file dynamic: process examples: - 9324d1a8ae37a36ae560c37448c9705a features: - and: - section: .text - match: kill thread loop """ ) ), ] ) capabilities = capa.capabilities.common.find_capabilities(rules, z9324d_extractor) assert "tight loop" in capabilities.matches assert "kill thread loop" in capabilities.matches assert "kill thread program" in capabilities.matches def test_subscope_bb_rules(z9324d_extractor): rules = capa.rules.RuleSet( [ capa.rules.Rule.from_yaml( textwrap.dedent( """ rule: meta: name: test rule scopes: static: function dynamic: process features: - and: - basic block: - characteristic: tight loop """ ) ) ] ) # tight loop at 0x403685 capabilities = capa.capabilities.common.find_capabilities(rules, z9324d_extractor) assert "test rule" in capabilities.matches def test_match_specific_functions(z9324d_extractor): rules = capa.rules.RuleSet( [ capa.rules.Rule.from_yaml( textwrap.dedent( """ rule: meta: name: receive data scopes: static: function dynamic: call examples: - 9324d1a8ae37a36ae560c37448c9705a:0x401CD0 features: - or: - api: recv """ ) ) ] ) extractor = FunctionFilter(z9324d_extractor, {0x4019C0}) capabilities = capa.capabilities.common.find_capabilities(rules, extractor) matches = capabilities.matches["receive data"] # test that we received only one match assert len(matches) == 1 # and that this match is from the specified function assert matches[0][0] == 0x4019C0 def test_byte_matching(z9324d_extractor): rules = capa.rules.RuleSet( [ capa.rules.Rule.from_yaml( textwrap.dedent( """ rule: meta: name: byte match test scopes: static: function dynamic: process features: - and: - bytes: ED 24 9E F4 52 A9 07 47 55 8E E1 AB 30 8E 23 61 """ ) ) ] ) capabilities = capa.capabilities.common.find_capabilities(rules, z9324d_extractor) assert "byte match test" in capabilities.matches def test_com_feature_matching(z395eb_extractor): rules = capa.rules.RuleSet( [ capa.rules.Rule.from_yaml( textwrap.dedent( """ rule: meta: name: initialize IWebBrowser2 scopes: static: basic block dynamic: unsupported features: - and: - api: ole32.CoCreateInstance - com/class: InternetExplorer #bytes: 01 DF 02 00 00 00 00 00 C0 00 00 00 00 00 00 46 = CLSID_InternetExplorer - com/interface: IWebBrowser2 #bytes: 61 16 0C D3 AF CD D0 11 8A 3E 00 C0 4F C9 E2 6E = IID_IWebBrowser2 """ ) ) ] ) capabilities = capa.main.find_capabilities(rules, z395eb_extractor) assert "initialize IWebBrowser2" in capabilities.matches def test_count_bb(z9324d_extractor): rules = capa.rules.RuleSet( [ capa.rules.Rule.from_yaml( textwrap.dedent( """ rule: meta: name: count bb namespace: test scopes: static: function dynamic: process features: - and: - count(basic blocks): 1 or more """ ) ) ] ) capabilities = capa.capabilities.common.find_capabilities(rules, z9324d_extractor) assert "count bb" in capabilities.matches def test_instruction_scope(z9324d_extractor): # .text:004071A4 68 E8 03 00 00 push 3E8h rules = capa.rules.RuleSet( [ capa.rules.Rule.from_yaml( textwrap.dedent( """ rule: meta: name: push 1000 namespace: test scopes: static: instruction dynamic: process features: - and: - mnemonic: push - number: 1000 """ ) ) ] ) capabilities = capa.capabilities.common.find_capabilities(rules, z9324d_extractor) assert "push 1000" in capabilities.matches assert 0x4071A4 in {result[0] for result in capabilities.matches["push 1000"]} def test_instruction_subscope(z9324d_extractor): # .text:00406F60 sub_406F60 proc near # [...] # .text:004071A4 68 E8 03 00 00 push 3E8h rules = capa.rules.RuleSet( [ capa.rules.Rule.from_yaml( textwrap.dedent( """ rule: meta: name: push 1000 on i386 namespace: test scopes: static: function dynamic: process features: - and: - arch: i386 - instruction: - mnemonic: push - number: 1000 """ ) ) ] ) capabilities = capa.capabilities.common.find_capabilities(rules, z9324d_extractor) assert "push 1000 on i386" in capabilities.matches assert 0x406F60 in {result[0] for result in capabilities.matches["push 1000 on i386"]}