mirror of
https://github.com/mandiant/capa.git
synced 2025-12-12 23:59:48 -08:00
597 lines
25 KiB
Python
597 lines
25 KiB
Python
# -*- coding: utf-8 -*-
|
|
# Copyright (C) 2020 FireEye, Inc. All Rights Reserved.
|
|
# 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: [package root]/LICENSE.txt
|
|
# 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 os
|
|
import sys
|
|
import os.path
|
|
import binascii
|
|
import contextlib
|
|
import collections
|
|
|
|
import pytest
|
|
|
|
import capa.main
|
|
import capa.features.file
|
|
import capa.features.insn
|
|
import capa.features.basicblock
|
|
from capa.features import ARCH_X32, ARCH_X64
|
|
|
|
try:
|
|
from functools import lru_cache
|
|
except ImportError:
|
|
from backports.functools_lru_cache import lru_cache
|
|
|
|
|
|
CD = os.path.dirname(__file__)
|
|
|
|
|
|
@contextlib.contextmanager
|
|
def xfail(condition, reason=None):
|
|
"""
|
|
context manager that wraps a block that is expected to fail in some cases.
|
|
when it does fail (and is expected), then mark this as pytest.xfail.
|
|
if its unexpected, raise an exception, so the test fails.
|
|
|
|
example::
|
|
|
|
# this test:
|
|
# - passes on py3 if foo() works
|
|
# - fails on py3 if foo() fails
|
|
# - xfails on py2 if foo() fails
|
|
# - fails on py2 if foo() works
|
|
with xfail(sys.version_info < (3, 0), reason="py2 doesn't foo"):
|
|
foo()
|
|
"""
|
|
try:
|
|
# do the block
|
|
yield
|
|
except:
|
|
if condition:
|
|
# we expected the test to fail, so raise and register this via pytest
|
|
pytest.xfail(reason)
|
|
else:
|
|
# we don't expect an exception, so the test should fail
|
|
raise
|
|
else:
|
|
if not condition:
|
|
# here we expect the block to run successfully,
|
|
# and we've received no exception,
|
|
# so this is good
|
|
pass
|
|
else:
|
|
# we expected an exception, but didn't find one. that's an error.
|
|
raise RuntimeError("expected to fail, but didn't")
|
|
|
|
|
|
@lru_cache()
|
|
def get_viv_extractor(path):
|
|
import capa.features.extractors.viv
|
|
|
|
if "raw32" in path:
|
|
vw = capa.main.get_workspace(path, "sc32", should_save=False)
|
|
elif "raw64" in path:
|
|
vw = capa.main.get_workspace(path, "sc64", should_save=False)
|
|
else:
|
|
vw = capa.main.get_workspace(path, "auto", should_save=True)
|
|
extractor = capa.features.extractors.viv.VivisectFeatureExtractor(vw, path)
|
|
fixup_viv(path, extractor)
|
|
return extractor
|
|
|
|
|
|
def fixup_viv(path, extractor):
|
|
"""
|
|
vivisect fixups to overcome differences between backends
|
|
"""
|
|
if "3b13b" in path:
|
|
# vivisect only recognizes calling thunk function at 0x10001573
|
|
extractor.vw.makeFunction(0x10006860)
|
|
|
|
|
|
@lru_cache()
|
|
def get_smda_extractor(path):
|
|
from smda.SmdaConfig import SmdaConfig
|
|
from smda.Disassembler import Disassembler
|
|
|
|
import capa.features.extractors.smda
|
|
|
|
config = SmdaConfig()
|
|
config.STORE_BUFFER = True
|
|
disasm = Disassembler(config)
|
|
report = disasm.disassembleFile(path)
|
|
|
|
return capa.features.extractors.smda.SmdaFeatureExtractor(report, path)
|
|
|
|
|
|
@lru_cache()
|
|
def extract_file_features(extractor):
|
|
features = collections.defaultdict(set)
|
|
for feature, va in extractor.extract_file_features():
|
|
features[feature].add(va)
|
|
return features
|
|
|
|
|
|
# f may not be hashable (e.g. ida func_t) so cannot @lru_cache this
|
|
def extract_function_features(extractor, f):
|
|
features = collections.defaultdict(set)
|
|
for bb in extractor.get_basic_blocks(f):
|
|
for insn in extractor.get_instructions(f, bb):
|
|
for feature, va in extractor.extract_insn_features(f, bb, insn):
|
|
features[feature].add(va)
|
|
for feature, va in extractor.extract_basic_block_features(f, bb):
|
|
features[feature].add(va)
|
|
for feature, va in extractor.extract_function_features(f):
|
|
features[feature].add(va)
|
|
return features
|
|
|
|
|
|
# f may not be hashable (e.g. ida func_t) so cannot @lru_cache this
|
|
def extract_basic_block_features(extractor, f, bb):
|
|
features = collections.defaultdict(set)
|
|
for insn in extractor.get_instructions(f, bb):
|
|
for feature, va in extractor.extract_insn_features(f, bb, insn):
|
|
features[feature].add(va)
|
|
for feature, va in extractor.extract_basic_block_features(f, bb):
|
|
features[feature].add(va)
|
|
return features
|
|
|
|
|
|
def get_data_path_by_name(name):
|
|
if name == "mimikatz":
|
|
return os.path.join(CD, "data", "mimikatz.exe_")
|
|
elif name == "kernel32":
|
|
return os.path.join(CD, "data", "kernel32.dll_")
|
|
elif name == "kernel32-64":
|
|
return os.path.join(CD, "data", "kernel32-64.dll_")
|
|
elif name == "pma12-04":
|
|
return os.path.join(CD, "data", "Practical Malware Analysis Lab 12-04.exe_")
|
|
elif name == "pma16-01":
|
|
return os.path.join(CD, "data", "Practical Malware Analysis Lab 16-01.exe_")
|
|
elif name == "pma21-01":
|
|
return os.path.join(CD, "data", "Practical Malware Analysis Lab 21-01.exe_")
|
|
elif name == "al-khaser x86":
|
|
return os.path.join(CD, "data", "al-khaser_x86.exe_")
|
|
elif name == "al-khaser x64":
|
|
return os.path.join(CD, "data", "al-khaser_x64.exe_")
|
|
elif name.startswith("39c05"):
|
|
return os.path.join(CD, "data", "39c05b15e9834ac93f206bc114d0a00c357c888db567ba8f5345da0529cbed41.dll_")
|
|
elif name.startswith("499c2"):
|
|
return os.path.join(CD, "data", "499c2a85f6e8142c3f48d4251c9c7cd6.raw32")
|
|
elif name.startswith("9324d"):
|
|
return os.path.join(CD, "data", "9324d1a8ae37a36ae560c37448c9705a.exe_")
|
|
elif name.startswith("a1982"):
|
|
return os.path.join(CD, "data", "a198216798ca38f280dc413f8c57f2c2.exe_")
|
|
elif name.startswith("a933a"):
|
|
return os.path.join(CD, "data", "a933a1a402775cfa94b6bee0963f4b46.dll_")
|
|
elif name.startswith("bfb9b"):
|
|
return os.path.join(CD, "data", "bfb9b5391a13d0afd787e87ab90f14f5.dll_")
|
|
elif name.startswith("c9188"):
|
|
return os.path.join(CD, "data", "c91887d861d9bd4a5872249b641bc9f9.exe_")
|
|
elif name.startswith("64d9f"):
|
|
return os.path.join(CD, "data", "64d9f7d96b99467f36e22fada623c3bb.dll_")
|
|
elif name.startswith("82bf6"):
|
|
return os.path.join(CD, "data", "82BF6347ACF15E5D883715DC289D8A2B.exe_")
|
|
elif name.startswith("pingtaest"):
|
|
return os.path.join(CD, "data", "ping_täst.exe_")
|
|
elif name.startswith("77329"):
|
|
return os.path.join(CD, "data", "773290480d5445f11d3dc1b800728966.exe_")
|
|
elif name.startswith("3b13b"):
|
|
return os.path.join(CD, "data", "3b13b6f1d7cd14dc4a097a12e2e505c0a4cff495262261e2bfc991df238b9b04.dll_")
|
|
else:
|
|
raise ValueError("unexpected sample fixture: %s" % name)
|
|
|
|
|
|
def get_sample_md5_by_name(name):
|
|
"""used by IDA tests to ensure the correct IDB is loaded"""
|
|
if name == "mimikatz":
|
|
return "5f66b82558ca92e54e77f216ef4c066c"
|
|
elif name == "kernel32":
|
|
return "e80758cf485db142fca1ee03a34ead05"
|
|
elif name == "kernel32-64":
|
|
return "a8565440629ac87f6fef7d588fe3ff0f"
|
|
elif name == "pma12-04":
|
|
return "56bed8249e7c2982a90e54e1e55391a2"
|
|
elif name == "pma16-01":
|
|
return "7faafc7e4a5c736ebfee6abbbc812d80"
|
|
elif name == "pma21-01":
|
|
return "c8403fb05244e23a7931c766409b5e22"
|
|
elif name == "al-khaser x86":
|
|
return "db648cd247281954344f1d810c6fd590"
|
|
elif name == "al-khaser x64":
|
|
return "3cb21ae76ff3da4b7e02d77ff76e82be"
|
|
elif name.startswith("39c05"):
|
|
return "b7841b9d5dc1f511a93cc7576672ec0c"
|
|
elif name.startswith("499c2"):
|
|
return "499c2a85f6e8142c3f48d4251c9c7cd6"
|
|
elif name.startswith("9324d"):
|
|
return "9324d1a8ae37a36ae560c37448c9705a"
|
|
elif name.startswith("a1982"):
|
|
return "a198216798ca38f280dc413f8c57f2c2"
|
|
elif name.startswith("a933a"):
|
|
return "a933a1a402775cfa94b6bee0963f4b46"
|
|
elif name.startswith("bfb9b"):
|
|
return "bfb9b5391a13d0afd787e87ab90f14f5"
|
|
elif name.startswith("c9188"):
|
|
return "c91887d861d9bd4a5872249b641bc9f9"
|
|
elif name.startswith("64d9f"):
|
|
return "64d9f7d96b99467f36e22fada623c3bb"
|
|
elif name.startswith("82bf6"):
|
|
return "82bf6347acf15e5d883715dc289d8a2b"
|
|
elif name.startswith("77329"):
|
|
return "773290480d5445f11d3dc1b800728966"
|
|
elif name.startswith("3b13b"):
|
|
# file name is SHA256 hash
|
|
return "56a6ffe6a02941028cc8235204eef31d"
|
|
else:
|
|
raise ValueError("unexpected sample fixture: %s" % name)
|
|
|
|
|
|
def resolve_sample(sample):
|
|
return get_data_path_by_name(sample)
|
|
|
|
|
|
@pytest.fixture
|
|
def sample(request):
|
|
return resolve_sample(request.param)
|
|
|
|
|
|
def get_function(extractor, fva):
|
|
for f in extractor.get_functions():
|
|
if f.__int__() == fva:
|
|
return f
|
|
raise ValueError("function not found")
|
|
|
|
|
|
def get_basic_block(extractor, f, va):
|
|
for bb in extractor.get_basic_blocks(f):
|
|
if bb.__int__() == va:
|
|
return bb
|
|
raise ValueError("basic block not found")
|
|
|
|
|
|
def resolve_scope(scope):
|
|
if scope == "file":
|
|
|
|
def inner(extractor):
|
|
return extract_file_features(extractor)
|
|
|
|
inner.__name__ = scope
|
|
return inner
|
|
elif "bb=" in scope:
|
|
# like `function=0x401000,bb=0x40100A`
|
|
fspec, _, bbspec = scope.partition(",")
|
|
fva = int(fspec.partition("=")[2], 0x10)
|
|
bbva = int(bbspec.partition("=")[2], 0x10)
|
|
|
|
def inner(extractor):
|
|
f = get_function(extractor, fva)
|
|
bb = get_basic_block(extractor, f, bbva)
|
|
return extract_basic_block_features(extractor, f, bb)
|
|
|
|
inner.__name__ = scope
|
|
return inner
|
|
elif scope.startswith("function"):
|
|
# like `function=0x401000`
|
|
va = int(scope.partition("=")[2], 0x10)
|
|
|
|
def inner(extractor):
|
|
f = get_function(extractor, va)
|
|
return extract_function_features(extractor, f)
|
|
|
|
inner.__name__ = scope
|
|
return inner
|
|
else:
|
|
raise ValueError("unexpected scope fixture")
|
|
|
|
|
|
@pytest.fixture
|
|
def scope(request):
|
|
return resolve_scope(request.param)
|
|
|
|
|
|
def make_test_id(values):
|
|
return "-".join(map(str, values))
|
|
|
|
|
|
def parametrize(params, values, **kwargs):
|
|
"""
|
|
extend `pytest.mark.parametrize` to pretty-print features.
|
|
by default, it renders objects as an opaque value.
|
|
ref: https://docs.pytest.org/en/2.9.0/example/parametrize.html#different-options-for-test-ids
|
|
rendered ID might look something like:
|
|
mimikatz-function=0x403BAC-api(CryptDestroyKey)-True
|
|
"""
|
|
ids = list(map(make_test_id, values))
|
|
return pytest.mark.parametrize(params, values, ids=ids, **kwargs)
|
|
|
|
|
|
FEATURE_PRESENCE_TESTS = [
|
|
# file/characteristic("embedded pe")
|
|
("pma12-04", "file", capa.features.Characteristic("embedded pe"), True),
|
|
# file/string
|
|
("mimikatz", "file", capa.features.String("SCardControl"), True),
|
|
("mimikatz", "file", capa.features.String("SCardTransmit"), True),
|
|
("mimikatz", "file", capa.features.String("ACR > "), True),
|
|
("mimikatz", "file", capa.features.String("nope"), False),
|
|
# file/sections
|
|
("mimikatz", "file", capa.features.file.Section(".text"), True),
|
|
("mimikatz", "file", capa.features.file.Section(".nope"), False),
|
|
# IDA doesn't extract unmapped sections by default
|
|
# ("mimikatz", "file", capa.features.file.Section(".rsrc"), True),
|
|
# file/exports
|
|
("kernel32", "file", capa.features.file.Export("BaseThreadInitThunk"), True),
|
|
("kernel32", "file", capa.features.file.Export("lstrlenW"), True),
|
|
("kernel32", "file", capa.features.file.Export("nope"), False),
|
|
# file/imports
|
|
("mimikatz", "file", capa.features.file.Import("advapi32.CryptSetHashParam"), True),
|
|
("mimikatz", "file", capa.features.file.Import("CryptSetHashParam"), True),
|
|
("mimikatz", "file", capa.features.file.Import("kernel32.IsWow64Process"), True),
|
|
("mimikatz", "file", capa.features.file.Import("msvcrt.exit"), True),
|
|
("mimikatz", "file", capa.features.file.Import("cabinet.#11"), True),
|
|
("mimikatz", "file", capa.features.file.Import("#11"), False),
|
|
("mimikatz", "file", capa.features.file.Import("#nope"), False),
|
|
("mimikatz", "file", capa.features.file.Import("nope"), False),
|
|
("mimikatz", "file", capa.features.file.Import("advapi32.CryptAcquireContextW"), True),
|
|
("mimikatz", "file", capa.features.file.Import("advapi32.CryptAcquireContext"), True),
|
|
("mimikatz", "file", capa.features.file.Import("CryptAcquireContextW"), True),
|
|
("mimikatz", "file", capa.features.file.Import("CryptAcquireContext"), True),
|
|
# function/characteristic(loop)
|
|
("mimikatz", "function=0x401517", capa.features.Characteristic("loop"), True),
|
|
("mimikatz", "function=0x401000", capa.features.Characteristic("loop"), False),
|
|
# bb/characteristic(tight loop)
|
|
("mimikatz", "function=0x402EC4", capa.features.Characteristic("tight loop"), True),
|
|
("mimikatz", "function=0x401000", capa.features.Characteristic("tight loop"), False),
|
|
# bb/characteristic(stack string)
|
|
("mimikatz", "function=0x4556E5", capa.features.Characteristic("stack string"), True),
|
|
("mimikatz", "function=0x401000", capa.features.Characteristic("stack string"), False),
|
|
# bb/characteristic(tight loop)
|
|
("mimikatz", "function=0x402EC4,bb=0x402F8E", capa.features.Characteristic("tight loop"), True),
|
|
("mimikatz", "function=0x401000,bb=0x401000", capa.features.Characteristic("tight loop"), False),
|
|
# insn/mnemonic
|
|
("mimikatz", "function=0x40105D", capa.features.insn.Mnemonic("push"), True),
|
|
("mimikatz", "function=0x40105D", capa.features.insn.Mnemonic("movzx"), True),
|
|
("mimikatz", "function=0x40105D", capa.features.insn.Mnemonic("xor"), True),
|
|
("mimikatz", "function=0x40105D", capa.features.insn.Mnemonic("in"), False),
|
|
("mimikatz", "function=0x40105D", capa.features.insn.Mnemonic("out"), False),
|
|
# insn/number
|
|
("mimikatz", "function=0x40105D", capa.features.insn.Number(0xFF), True),
|
|
("mimikatz", "function=0x40105D", capa.features.insn.Number(0x3136B0), True),
|
|
# insn/number: stack adjustments
|
|
("mimikatz", "function=0x40105D", capa.features.insn.Number(0xC), False),
|
|
("mimikatz", "function=0x40105D", capa.features.insn.Number(0x10), False),
|
|
# insn/number: arch flavors
|
|
("mimikatz", "function=0x40105D", capa.features.insn.Number(0xFF), True),
|
|
("mimikatz", "function=0x40105D", capa.features.insn.Number(0xFF, arch=ARCH_X32), True),
|
|
("mimikatz", "function=0x40105D", capa.features.insn.Number(0xFF, arch=ARCH_X64), False),
|
|
# insn/offset
|
|
("mimikatz", "function=0x40105D", capa.features.insn.Offset(0x0), True),
|
|
("mimikatz", "function=0x40105D", capa.features.insn.Offset(0x4), True),
|
|
("mimikatz", "function=0x40105D", capa.features.insn.Offset(0xC), True),
|
|
# insn/offset, issue #276
|
|
("64d9f", "function=0x10001510,bb=0x100015B0", capa.features.insn.Offset(0x4000), True),
|
|
# insn/offset: stack references
|
|
("mimikatz", "function=0x40105D", capa.features.insn.Offset(0x8), False),
|
|
("mimikatz", "function=0x40105D", capa.features.insn.Offset(0x10), False),
|
|
# insn/offset: negative
|
|
("mimikatz", "function=0x4011FB", capa.features.insn.Offset(-0x1), True),
|
|
("mimikatz", "function=0x4011FB", capa.features.insn.Offset(-0x2), True),
|
|
# insn/offset: arch flavors
|
|
("mimikatz", "function=0x40105D", capa.features.insn.Offset(0x0), True),
|
|
("mimikatz", "function=0x40105D", capa.features.insn.Offset(0x0, arch=ARCH_X32), True),
|
|
("mimikatz", "function=0x40105D", capa.features.insn.Offset(0x0, arch=ARCH_X64), False),
|
|
# insn/api
|
|
("mimikatz", "function=0x403BAC", capa.features.insn.API("advapi32.CryptAcquireContextW"), True),
|
|
("mimikatz", "function=0x403BAC", capa.features.insn.API("advapi32.CryptAcquireContext"), True),
|
|
("mimikatz", "function=0x403BAC", capa.features.insn.API("advapi32.CryptGenKey"), True),
|
|
("mimikatz", "function=0x403BAC", capa.features.insn.API("advapi32.CryptImportKey"), True),
|
|
("mimikatz", "function=0x403BAC", capa.features.insn.API("advapi32.CryptDestroyKey"), True),
|
|
("mimikatz", "function=0x403BAC", capa.features.insn.API("CryptAcquireContextW"), True),
|
|
("mimikatz", "function=0x403BAC", capa.features.insn.API("CryptAcquireContext"), True),
|
|
("mimikatz", "function=0x403BAC", capa.features.insn.API("CryptGenKey"), True),
|
|
("mimikatz", "function=0x403BAC", capa.features.insn.API("CryptImportKey"), True),
|
|
("mimikatz", "function=0x403BAC", capa.features.insn.API("CryptDestroyKey"), True),
|
|
("mimikatz", "function=0x403BAC", capa.features.insn.API("Nope"), False),
|
|
("mimikatz", "function=0x403BAC", capa.features.insn.API("advapi32.Nope"), False),
|
|
# insn/api: thunk
|
|
("mimikatz", "function=0x4556E5", capa.features.insn.API("advapi32.LsaQueryInformationPolicy"), True),
|
|
("mimikatz", "function=0x4556E5", capa.features.insn.API("LsaQueryInformationPolicy"), True),
|
|
# insn/api: x64
|
|
(
|
|
"kernel32-64",
|
|
"function=0x180001010",
|
|
capa.features.insn.API("RtlVirtualUnwind"),
|
|
True,
|
|
),
|
|
("kernel32-64", "function=0x180001010", capa.features.insn.API("RtlVirtualUnwind"), True),
|
|
# insn/api: x64 thunk
|
|
(
|
|
"kernel32-64",
|
|
"function=0x1800202B0",
|
|
capa.features.insn.API("RtlCaptureContext"),
|
|
True,
|
|
),
|
|
("kernel32-64", "function=0x1800202B0", capa.features.insn.API("RtlCaptureContext"), True),
|
|
# insn/api: x64 nested thunk
|
|
("al-khaser x64", "function=0x14004B4F0", capa.features.insn.API("__vcrt_GetModuleHandle"), True),
|
|
# insn/api: call via jmp
|
|
("mimikatz", "function=0x40B3C6", capa.features.insn.API("LocalFree"), True),
|
|
("c91887...", "function=0x40156F", capa.features.insn.API("CloseClipboard"), True),
|
|
# TODO ignore thunk functions that call via jmp?
|
|
# insn/api: resolve indirect calls
|
|
("c91887...", "function=0x401A77", capa.features.insn.API("kernel32.CreatePipe"), True),
|
|
("c91887...", "function=0x401A77", capa.features.insn.API("kernel32.SetHandleInformation"), True),
|
|
("c91887...", "function=0x401A77", capa.features.insn.API("kernel32.CloseHandle"), True),
|
|
("c91887...", "function=0x401A77", capa.features.insn.API("kernel32.WriteFile"), True),
|
|
# insn/string
|
|
("mimikatz", "function=0x40105D", capa.features.String("SCardControl"), True),
|
|
("mimikatz", "function=0x40105D", capa.features.String("SCardTransmit"), True),
|
|
("mimikatz", "function=0x40105D", capa.features.String("ACR > "), True),
|
|
("mimikatz", "function=0x40105D", capa.features.String("nope"), False),
|
|
("773290...", "function=0x140001140", capa.features.String(r"%s:\\OfficePackagesForWDAG"), True),
|
|
# insn/regex, issue #262
|
|
("pma16-01", "function=0x4021B0", capa.features.Regex("HTTP/1.0"), True),
|
|
("pma16-01", "function=0x4021B0", capa.features.Regex("www.practicalmalwareanalysis.com"), False),
|
|
# insn/string, pointer to string
|
|
("mimikatz", "function=0x44EDEF", capa.features.String("INPUTEVENT"), True),
|
|
# insn/string, direct memory reference
|
|
("mimikatz", "function=0x46D6CE", capa.features.String("(null)"), True),
|
|
# insn/bytes
|
|
("mimikatz", "function=0x40105D", capa.features.Bytes("SCardControl".encode("utf-16le")), True),
|
|
("mimikatz", "function=0x40105D", capa.features.Bytes("SCardTransmit".encode("utf-16le")), True),
|
|
("mimikatz", "function=0x40105D", capa.features.Bytes("ACR > ".encode("utf-16le")), True),
|
|
("mimikatz", "function=0x40105D", capa.features.Bytes("nope".encode("ascii")), False),
|
|
# IDA features included byte sequences read from invalid memory, fixed in #409
|
|
("mimikatz", "function=0x44570F", capa.features.Bytes(binascii.unhexlify("FF" * 256)), False),
|
|
# insn/bytes, pointer to bytes
|
|
("mimikatz", "function=0x44EDEF", capa.features.Bytes("INPUTEVENT".encode("utf-16le")), True),
|
|
# insn/characteristic(nzxor)
|
|
("mimikatz", "function=0x410DFC", capa.features.Characteristic("nzxor"), True),
|
|
("mimikatz", "function=0x40105D", capa.features.Characteristic("nzxor"), False),
|
|
# insn/characteristic(nzxor): no security cookies
|
|
("mimikatz", "function=0x46D534", capa.features.Characteristic("nzxor"), False),
|
|
# insn/characteristic(nzxor): xorps
|
|
# viv needs fixup to recognize function, see above
|
|
("3b13b...", "function=0x10006860", capa.features.Characteristic("nzxor"), True),
|
|
# insn/characteristic(peb access)
|
|
("kernel32-64", "function=0x1800017D0", capa.features.Characteristic("peb access"), True),
|
|
("mimikatz", "function=0x4556E5", capa.features.Characteristic("peb access"), False),
|
|
# insn/characteristic(gs access)
|
|
("kernel32-64", "function=0x180001068", capa.features.Characteristic("gs access"), True),
|
|
("mimikatz", "function=0x4556E5", capa.features.Characteristic("gs access"), False),
|
|
# insn/characteristic(cross section flow)
|
|
("a1982...", "function=0x4014D0", capa.features.Characteristic("cross section flow"), True),
|
|
# insn/characteristic(cross section flow): imports don't count
|
|
("kernel32-64", "function=0x180001068", capa.features.Characteristic("cross section flow"), False),
|
|
("mimikatz", "function=0x4556E5", capa.features.Characteristic("cross section flow"), False),
|
|
# insn/characteristic(recursive call)
|
|
("39c05...", "function=0x10003100", capa.features.Characteristic("recursive call"), True),
|
|
("mimikatz", "function=0x4556E5", capa.features.Characteristic("recursive call"), False),
|
|
# insn/characteristic(indirect call)
|
|
("mimikatz", "function=0x4175FF", capa.features.Characteristic("indirect call"), True),
|
|
("mimikatz", "function=0x4556E5", capa.features.Characteristic("indirect call"), False),
|
|
# insn/characteristic(calls from)
|
|
("mimikatz", "function=0x4556E5", capa.features.Characteristic("calls from"), True),
|
|
("mimikatz", "function=0x4702FD", capa.features.Characteristic("calls from"), False),
|
|
# function/characteristic(calls to)
|
|
("mimikatz", "function=0x40105D", capa.features.Characteristic("calls to"), True),
|
|
("mimikatz", "function=0x4556E5", capa.features.Characteristic("calls to"), False),
|
|
]
|
|
|
|
FEATURE_PRESENCE_TESTS_IDA = [
|
|
# file/imports
|
|
# IDA can recover more names of APIs imported by ordinal
|
|
("mimikatz", "file", capa.features.file.Import("cabinet.FCIAddFile"), True),
|
|
]
|
|
|
|
FEATURE_COUNT_TESTS = [
|
|
("mimikatz", "function=0x40E5C2", capa.features.basicblock.BasicBlock(), 7),
|
|
("mimikatz", "function=0x4702FD", capa.features.Characteristic("calls from"), 0),
|
|
("mimikatz", "function=0x40E5C2", capa.features.Characteristic("calls from"), 3),
|
|
("mimikatz", "function=0x4556E5", capa.features.Characteristic("calls to"), 0),
|
|
("mimikatz", "function=0x40B1F1", capa.features.Characteristic("calls to"), 3),
|
|
]
|
|
|
|
|
|
def do_test_feature_presence(get_extractor, sample, scope, feature, expected):
|
|
extractor = get_extractor(sample)
|
|
features = scope(extractor)
|
|
if expected:
|
|
msg = "%s should be found in %s" % (str(feature), scope.__name__)
|
|
else:
|
|
msg = "%s should not be found in %s" % (str(feature), scope.__name__)
|
|
assert feature.evaluate(features) == expected, msg
|
|
|
|
|
|
def do_test_feature_count(get_extractor, sample, scope, feature, expected):
|
|
extractor = get_extractor(sample)
|
|
features = scope(extractor)
|
|
msg = "%s should be found %d times in %s, found: %d" % (
|
|
str(feature),
|
|
expected,
|
|
scope.__name__,
|
|
len(features[feature]),
|
|
)
|
|
assert len(features[feature]) == expected, msg
|
|
|
|
|
|
def get_extractor(path):
|
|
extractor = get_viv_extractor(path)
|
|
# overload the extractor so that the fixture exposes `extractor.path`
|
|
setattr(extractor, "path", path)
|
|
return extractor
|
|
|
|
|
|
@pytest.fixture
|
|
def mimikatz_extractor():
|
|
return get_extractor(get_data_path_by_name("mimikatz"))
|
|
|
|
|
|
@pytest.fixture
|
|
def a933a_extractor():
|
|
return get_extractor(get_data_path_by_name("a933a..."))
|
|
|
|
|
|
@pytest.fixture
|
|
def kernel32_extractor():
|
|
return get_extractor(get_data_path_by_name("kernel32"))
|
|
|
|
|
|
@pytest.fixture
|
|
def a1982_extractor():
|
|
return get_extractor(get_data_path_by_name("a1982..."))
|
|
|
|
|
|
@pytest.fixture
|
|
def z9324d_extractor():
|
|
return get_extractor(get_data_path_by_name("9324d..."))
|
|
|
|
|
|
@pytest.fixture
|
|
def pma12_04_extractor():
|
|
return get_extractor(get_data_path_by_name("pma12-04"))
|
|
|
|
|
|
@pytest.fixture
|
|
def pma16_01_extractor():
|
|
return get_extractor(get_data_path_by_name("pma16-01"))
|
|
|
|
|
|
@pytest.fixture
|
|
def bfb9b_extractor():
|
|
return get_extractor(get_data_path_by_name("bfb9b..."))
|
|
|
|
|
|
@pytest.fixture
|
|
def pma21_01_extractor():
|
|
return get_extractor(get_data_path_by_name("pma21-01"))
|
|
|
|
|
|
@pytest.fixture
|
|
def c9188_extractor():
|
|
return get_extractor(get_data_path_by_name("c9188..."))
|
|
|
|
|
|
@pytest.fixture
|
|
def z39c05_extractor():
|
|
return get_extractor(get_data_path_by_name("39c05..."))
|
|
|
|
|
|
@pytest.fixture
|
|
def z499c2_extractor():
|
|
return get_extractor(get_data_path_by_name("499c2..."))
|
|
|
|
|
|
@pytest.fixture
|
|
def al_khaser_x86_extractor():
|
|
return get_extractor(get_data_path_by_name("al-khaser x86"))
|
|
|
|
|
|
@pytest.fixture
|
|
def pingtaest_extractor():
|
|
return get_extractor(get_data_path_by_name("pingtaest"))
|