features: add Arch feature at global scope

This commit is contained in:
William Ballenthin
2021-08-16 17:06:56 -06:00
parent 738fa9150e
commit 8e689c39f4
9 changed files with 55 additions and 13 deletions

View File

@@ -9,6 +9,7 @@
- explorer: add option to limit features to currently selected disassembly address #692 @mike-hunhoff
- all: add support for ELF files #700 @Adir-Shemesh @TcM1911
- rule format: add feature `format: ` for file format, like `format: pe` @williballenthin
- rule format: add feature `arch: ` for architecture, like `arch: amd64` @williballenthin
- rule format: add feature `os: ` for operating system, like `os: windows` #701 @williballenthin
### Breaking Changes

View File

@@ -14,6 +14,7 @@ import capa.ida.helpers
import capa.features.extractors.elf
import capa.features.extractors.ida.file
import capa.features.extractors.ida.insn
import capa.features.extractors.ida.global_
import capa.features.extractors.ida.function
import capa.features.extractors.ida.basicblock
from capa.features.common import OS, OS_WINDOWS
@@ -80,7 +81,7 @@ class IdaFeatureExtractor(FeatureExtractor):
super(IdaFeatureExtractor, self).__init__()
self.global_features = []
self.global_features.extend(extract_os())
self.global_features.extend(extract_format())
self.global_features.extend(capa.features.extractors.ida.global_.extract_arch())
def get_base_address(self):
return idaapi.get_imagebase()

View File

@@ -17,8 +17,7 @@ import capa.features.extractors.helpers
import capa.features.extractors.strings
import capa.features.extractors.ida.helpers
from capa.features.file import Export, Import, Section, FunctionName
from capa.features.common import String, Characteristic
from capa.features.common import OS, FORMAT_PE, FORMAT_ELF, OS_WINDOWS, Format
from capa.features.common import OS, FORMAT_PE, FORMAT_ELF, OS_WINDOWS, Format, String, Characteristic
def check_segment_for_pe(seg):

View File

@@ -14,7 +14,7 @@ import capa.features.extractors
import capa.features.extractors.helpers
import capa.features.extractors.strings
from capa.features.file import Export, Import, Section
from capa.features.common import OS, Format, String, Characteristic, OS_WINDOWS, FORMAT_PE
from capa.features.common import OS, FORMAT_PE, OS_WINDOWS, Format, String, Characteristic
from capa.features.extractors.base_extractor import FeatureExtractor
logger = logging.getLogger(__name__)

View File

@@ -3,6 +3,7 @@ from smda.common.SmdaReport import SmdaReport
import capa.features.extractors.common
import capa.features.extractors.smda.file
import capa.features.extractors.smda.insn
import capa.features.extractors.smda.global_
import capa.features.extractors.smda.function
import capa.features.extractors.smda.basicblock
from capa.features.extractors.base_extractor import FeatureExtractor
@@ -16,8 +17,10 @@ class SmdaFeatureExtractor(FeatureExtractor):
with open(self.path, "rb") as f:
self.buf = f.read()
# pre-compute these because we'll yield them at *every* scope.
self.global_features = []
self.global_features.extend(capa.features.extractors.common.extract_os(self.buf))
self.global_features.extend(capa.features.extractors.smda.global_.extract_arch(self.smda_report))
def get_base_address(self):
return self.smda_report.base_addr

View File

@@ -13,6 +13,7 @@ import viv_utils.flirt
import capa.features.extractors.common
import capa.features.extractors.viv.file
import capa.features.extractors.viv.insn
import capa.features.extractors.viv.global_
import capa.features.extractors.viv.function
import capa.features.extractors.viv.basicblock
from capa.features.extractors.base_extractor import FeatureExtractor
@@ -41,8 +42,10 @@ class VivisectFeatureExtractor(FeatureExtractor):
with open(self.path, "rb") as f:
self.buf = f.read()
# pre-compute these because we'll yield them at *every* scope.
self.global_features = []
self.global_features.extend(capa.features.extractors.common.extract_os(self.buf))
self.global_features.extend(capa.features.extractors.viv.global_.extract_arch(self.vw))
def get_base_address(self):
# assume there is only one file loaded into the vw

View File

@@ -80,6 +80,7 @@ SUPPORTED_FEATURES = {
capa.features.common.String,
capa.features.common.Format,
capa.features.common.OS,
capa.features.common.Arch,
},
FUNCTION_SCOPE: {
# plus basic block scope features, see below
@@ -89,6 +90,7 @@ SUPPORTED_FEATURES = {
capa.features.common.Characteristic("loop"),
capa.features.common.Characteristic("recursive call"),
capa.features.common.OS,
capa.features.common.Arch,
},
BASIC_BLOCK_SCOPE: {
capa.features.common.MatchedRule,
@@ -107,6 +109,7 @@ SUPPORTED_FEATURES = {
capa.features.common.Characteristic("stack string"),
capa.features.common.Characteristic("indirect call"),
capa.features.common.OS,
capa.features.common.Arch,
},
}
@@ -254,6 +257,8 @@ def parse_feature(key: str):
return capa.features.common.OS
elif key == "format":
return capa.features.common.Format
elif key == "arch":
return capa.features.common.Arch
else:
raise InvalidRule("unexpected statement: %s" % key)

View File

@@ -22,14 +22,17 @@ import capa.features.insn
import capa.features.common
import capa.features.basicblock
from capa.features.common import (
BITNESS_X32,
BITNESS_X64,
FORMAT_ELF,
FORMAT_PE,
Format,
OS,
OS_LINUX,
ARCH_I386,
FORMAT_PE,
ARCH_AMD64,
FORMAT_ELF,
OS_WINDOWS,
BITNESS_X32,
BITNESS_X64,
Arch,
Format,
)
CD = os.path.dirname(__file__)
@@ -512,11 +515,15 @@ FEATURE_PRESENCE_TESTS = sorted(
("mimikatz", "function=0x456BB9", capa.features.common.Characteristic("calls to"), False),
# file/function-name
("pma16-01", "file", capa.features.file.FunctionName("__aulldiv"), True),
# os & format
# os & format & arch
("pma16-01", "file", OS(OS_WINDOWS), True),
("pma16-01", "file", OS(OS_LINUX), False),
("pma16-01", "function=0x404356", OS(OS_WINDOWS), True),
("pma16-01", "function=0x404356,bb=0x4043B9", OS(OS_WINDOWS), True),
("pma16-01", "file", Arch(ARCH_I386), True),
("pma16-01", "file", Arch(ARCH_AMD64), False),
("pma16-01", "function=0x404356", Arch(ARCH_I386), True),
("pma16-01", "function=0x404356,bb=0x4043B9", Arch(ARCH_I386), True),
("pma16-01", "file", Format(FORMAT_PE), True),
("pma16-01", "file", Format(FORMAT_ELF), False),
# elf support
@@ -524,6 +531,8 @@ FEATURE_PRESENCE_TESTS = sorted(
("7351f.elf", "file", OS(OS_WINDOWS), False),
("7351f.elf", "file", Format(FORMAT_ELF), True),
("7351f.elf", "file", Format(FORMAT_PE), False),
("7351f.elf", "file", Arch(ARCH_I386), False),
("7351f.elf", "file", Arch(ARCH_AMD64), True),
("7351f.elf", "function=0x408753", capa.features.common.String("/dev/null"), True),
("7351f.elf", "function=0x408753,bb=0x408781", capa.features.insn.API("open"), True),
],

View File

@@ -16,15 +16,18 @@ import capa.features.common
from capa.features.file import FunctionName
from capa.features.insn import Number, Offset
from capa.features.common import (
OS,
OS_LINUX,
ARCH_I386,
FORMAT_PE,
ARCH_AMD64,
FORMAT_ELF,
OS_WINDOWS,
OS_LINUX,
BITNESS_X32,
BITNESS_X64,
Arch,
Format,
String,
OS,
Format
)
@@ -990,3 +993,21 @@ def test_format_features():
children = list(r.statement.get_children())
assert (Format(FORMAT_PE) in children) == True
assert (Format(FORMAT_ELF) not in children) == True
def test_arch_features():
rule = textwrap.dedent(
"""
rule:
meta:
name: test rule
scope: file
features:
- and:
- arch: amd64
"""
)
r = capa.rules.Rule.from_yaml(rule)
children = list(r.statement.get_children())
assert (Arch(ARCH_AMD64) in children) == True
assert (Arch(ARCH_I386) not in children) == True