diff --git a/capa/features/common.py b/capa/features/common.py index 221f15c1..b3a0735a 100644 --- a/capa/features/common.py +++ b/capa/features/common.py @@ -27,6 +27,11 @@ ARCH_X32 = "x32" ARCH_X64 = "x64" VALID_ARCH = (ARCH_X32, ARCH_X64) +OS_WINDOWS = "os/windows" +OS_LINUX = "os/linux" +OS_MACOS = "os/macos" +VALID_OS = (OS_WINDOWS, OS_LINUX, OS_MACOS) + def bytes_to_str(b: bytes) -> str: return str(codecs.encode(b, "hex").decode("utf-8")) @@ -131,6 +136,7 @@ class MatchedRule(Feature): class Characteristic(Feature): def __init__(self, value: str, description=None): + super(Characteristic, self).__init__(value, description=description) diff --git a/capa/rules.py b/capa/rules.py index 479a0773..3d62b7f5 100644 --- a/capa/rules.py +++ b/capa/rules.py @@ -34,7 +34,7 @@ import capa.features.insn import capa.features.common import capa.features.basicblock from capa.engine import Statement, FeatureSet -from capa.features.common import MAX_BYTES_FEATURE_SIZE, Feature +from capa.features.common import OS_LINUX, OS_MACOS, OS_WINDOWS, MAX_BYTES_FEATURE_SIZE, Feature logger = logging.getLogger(__name__) @@ -78,6 +78,9 @@ SUPPORTED_FEATURES = { capa.features.file.FunctionName, capa.features.common.Characteristic("embedded pe"), capa.features.common.String, + capa.features.common.Characteristic(OS_WINDOWS), + capa.features.common.Characteristic(OS_LINUX), + capa.features.common.Characteristic(OS_MACOS), }, FUNCTION_SCOPE: { # plus basic block scope features, see below @@ -86,6 +89,9 @@ SUPPORTED_FEATURES = { capa.features.common.Characteristic("calls to"), capa.features.common.Characteristic("loop"), capa.features.common.Characteristic("recursive call"), + capa.features.common.Characteristic(OS_WINDOWS), + capa.features.common.Characteristic(OS_LINUX), + capa.features.common.Characteristic(OS_MACOS), }, BASIC_BLOCK_SCOPE: { capa.features.common.MatchedRule, @@ -103,6 +109,9 @@ SUPPORTED_FEATURES = { capa.features.common.Characteristic("tight loop"), capa.features.common.Characteristic("stack string"), capa.features.common.Characteristic("indirect call"), + capa.features.common.Characteristic(OS_WINDOWS), + capa.features.common.Characteristic(OS_LINUX), + capa.features.common.Characteristic(OS_MACOS), }, } diff --git a/tests/test_rules.py b/tests/test_rules.py index 12791d2e..6b57b89a 100644 --- a/tests/test_rules.py +++ b/tests/test_rules.py @@ -15,7 +15,7 @@ import capa.engine import capa.features.common from capa.features.file import FunctionName from capa.features.insn import Number, Offset -from capa.features.common import ARCH_X32, ARCH_X64, String +from capa.features.common import ARCH_X32, ARCH_X64, OS_WINDOWS, String, Characteristic def test_rule_ctor(): @@ -944,3 +944,20 @@ def test_function_name_features(): assert (FunctionName("strcpy") in children) == True assert (FunctionName("strcmp", description="copy from here to there") in children) == True assert (FunctionName("strdup", description="duplicate a string") in children) == True + + +def test_os_features(): + rule = textwrap.dedent( + """ + rule: + meta: + name: test rule + scope: file + features: + - and: + - characteristic: os/windows + """ + ) + r = capa.rules.Rule.from_yaml(rule) + children = list(r.statement.get_children()) + assert (Characteristic(OS_WINDOWS) in children) == True