mirror of
https://github.com/mandiant/capa.git
synced 2026-07-06 04:34:23 -07:00
@@ -31,7 +31,7 @@ This project and everyone participating in it is governed by the [Capa Code of C
|
||||
|
||||
### Capa and its repositories
|
||||
|
||||
We host the capa project as three Github repositories:
|
||||
We host the capa project as three GitHub repositories:
|
||||
- [capa](https://github.com/mandiant/capa)
|
||||
- [capa-rules](https://github.com/mandiant/capa-rules)
|
||||
- [capa-testfiles](https://github.com/mandiant/capa-testfiles)
|
||||
|
||||
+1
-1
@@ -1308,7 +1308,7 @@ Download a standalone binary below and checkout the readme [here on GitHub](http
|
||||
- setup: pin vivisect version @williballenthin
|
||||
- setup: bump vivisect dependency version @williballenthin
|
||||
- setup: set Python project name to `flare-capa` @williballenthin
|
||||
- ci: run tests and linter via Github Actions @Ana06
|
||||
- ci: run tests and linter via GitHub Actions @Ana06
|
||||
- hooks: run style checkers and hide stashed output @Ana06
|
||||
- linter: ignore period in rule filename @williballenthin
|
||||
- linter: warn on nursery rule with no changes needed @williballenthin
|
||||
|
||||
@@ -63,7 +63,7 @@ def extract_arch(buf) -> Iterator[Tuple[Feature, Address]]:
|
||||
# 2. handling a new file format (e.g. macho)
|
||||
#
|
||||
# for (1) we can't do much - its shellcode and all bets are off.
|
||||
# we could maybe accept a futher CLI argument to specify the arch,
|
||||
# we could maybe accept a further CLI argument to specify the arch,
|
||||
# but i think this would be rarely used.
|
||||
# rules that rely on arch conditions will fail to match on shellcode.
|
||||
#
|
||||
@@ -91,7 +91,7 @@ def extract_os(buf) -> Iterator[Tuple[Feature, Address]]:
|
||||
# 2. handling a new file format (e.g. macho)
|
||||
#
|
||||
# for (1) we can't do much - its shellcode and all bets are off.
|
||||
# we could maybe accept a futher CLI argument to specify the OS,
|
||||
# we could maybe accept a further CLI argument to specify the OS,
|
||||
# but i think this would be rarely used.
|
||||
# rules that rely on OS conditions will fail to match on shellcode.
|
||||
#
|
||||
|
||||
@@ -186,7 +186,7 @@ def get_dotnet_managed_methods(pe: dnfile.dnPE) -> Iterator[DnType]:
|
||||
Each row represents a class in the current assembly.
|
||||
TypeName (index into String heap)
|
||||
TypeNamespace (index into String heap)
|
||||
MethodList (index into MethodDef table; it marks the first of a continguous run of Methods owned by this Type)
|
||||
MethodList (index into MethodDef table; it marks the first of a contiguous run of Methods owned by this Type)
|
||||
"""
|
||||
accessor_map: Dict[int, str] = {}
|
||||
for (methoddef, methoddef_access) in get_dotnet_methoddef_property_accessors(pe):
|
||||
@@ -223,7 +223,7 @@ def get_dotnet_fields(pe: dnfile.dnPE) -> Iterator[DnType]:
|
||||
Each row represents a class in the current assembly.
|
||||
TypeName (index into String heap)
|
||||
TypeNamespace (index into String heap)
|
||||
FieldList (index into Field table; it marks the first of a continguous run of Fields owned by this Type)
|
||||
FieldList (index into Field table; it marks the first of a contiguous run of Fields owned by this Type)
|
||||
"""
|
||||
for (rid, typedef) in iter_dotnet_table(pe, dnfile.mdtable.TypeDef.number):
|
||||
assert isinstance(typedef, dnfile.mdtable.TypeDefRow)
|
||||
|
||||
@@ -31,7 +31,7 @@ def extract_os() -> Iterator[Tuple[Feature, Address]]:
|
||||
# 2. handling a new file format (e.g. macho)
|
||||
#
|
||||
# for (1) we can't do much - its shellcode and all bets are off.
|
||||
# we could maybe accept a futher CLI argument to specify the OS,
|
||||
# we could maybe accept a further CLI argument to specify the OS,
|
||||
# but i think this would be rarely used.
|
||||
# rules that rely on OS conditions will fail to match on shellcode.
|
||||
#
|
||||
|
||||
@@ -42,7 +42,7 @@ def get_previous_instructions(vw: VivWorkspace, va: int) -> List[int]:
|
||||
ret = []
|
||||
|
||||
# find the immediate prior instruction.
|
||||
# ensure that it fallsthrough to this one.
|
||||
# ensure that it falls through to this one.
|
||||
loc = vw.getPrevLocation(va, adjacent=True)
|
||||
if loc is not None:
|
||||
ploc = vw.getPrevLocation(va, adjacent=True)
|
||||
|
||||
@@ -82,7 +82,7 @@ class Mnemonic(Feature):
|
||||
super().__init__(value, description=description)
|
||||
|
||||
|
||||
# max number of operands to consider for a given instrucion.
|
||||
# max number of operands to consider for a given instruction.
|
||||
# since we only support Intel and .NET, we can assume this is 3
|
||||
# which covers cases up to e.g. "vinserti128 ymm0,ymm0,ymm5,1"
|
||||
MAX_OPERAND_COUNT = 4
|
||||
|
||||
@@ -75,7 +75,7 @@ can update using the `Settings` button.
|
||||
* Hover your cursor over a rule match to view the source content of the rule
|
||||
* Double-click the `Address` column to navigate your Disassembly view to the address of the associated feature
|
||||
* Double-click a result in the `Rule Information` column to expand its children
|
||||
* Select a checkbox in the `Rule Information` column to highlight the address of the associated feature in your Dissasembly view
|
||||
* Select a checkbox in the `Rule Information` column to highlight the address of the associated feature in your Disassembly view
|
||||
|
||||
#### Tips for Rule Generator
|
||||
|
||||
|
||||
@@ -85,7 +85,7 @@ class CapaExplorerPlugin(idaapi.plugin_t):
|
||||
# so we need to register a callback that's invoked from the main thread after the plugin is registered.
|
||||
#
|
||||
# after a lot of guess-and-check, we can use `UI_Hooks.updated_actions` to
|
||||
# receive notications after IDA has created an action for each plugin.
|
||||
# receive notifications after IDA has created an action for each plugin.
|
||||
# so, create this hook, wait for capa plugin to load, set the icon, and unhook.
|
||||
|
||||
|
||||
|
||||
@@ -483,7 +483,7 @@ class CapaExplorerForm(idaapi.PluginForm):
|
||||
label2.setText("Editor")
|
||||
label2.setFont(font)
|
||||
|
||||
self.view_rulegen_limit_features_by_ea = QtWidgets.QCheckBox("Limit features to current dissasembly address")
|
||||
self.view_rulegen_limit_features_by_ea = QtWidgets.QCheckBox("Limit features to current disassembly address")
|
||||
self.view_rulegen_limit_features_by_ea.setChecked(False)
|
||||
self.view_rulegen_limit_features_by_ea.stateChanged.connect(self.slot_checkbox_limit_features_by_ea)
|
||||
|
||||
|
||||
+1
-1
@@ -79,7 +79,7 @@ You'll find that the `capa.exe` (Windows) or `capa` (Linux/MacOS) executables in
|
||||
|
||||
For development, we recommend to use [venv](https://docs.python.org/3/tutorial/venv.html). It allows you to create a virtual environment: a self-contained directory tree that contains a Python installation for a particular version of Python, plus a number of additional packages. This approach avoids conflicts between the requirements of different applications on your computer. It also ensures that you don't overlook to add a new requirement to `setup.up` using a library already installed on your system.
|
||||
|
||||
To create an environment (in the parent directory, to avoid commiting it by accident or messing with the linters), run: `$ python3 -m venv ../capa-env`
|
||||
To create an environment (in the parent directory, to avoid committing it by accident or messing with the linters), run: `$ python3 -m venv ../capa-env`
|
||||
|
||||
To activate `capa-env` in Linux or MacOS, run:
|
||||
`$ source ../capa-env/bin/activate`
|
||||
|
||||
@@ -129,7 +129,7 @@ def convert_capa_number_to_yara_bytes(number):
|
||||
|
||||
def convert_rule_name(rule_name):
|
||||
|
||||
# yara rule names: "Identifiers must follow the same lexical conventions of the C programming language, they can contain any alphanumeric character and the underscore character, but the first character cannot be a digit. Rule identifiers are case sensitive and cannot exceed 128 characters." so we replace any non-alpanum with _
|
||||
# yara rule names: "Identifiers must follow the same lexical conventions of the C programming language, they can contain any alphanumeric character and the underscore character, but the first character cannot be a digit. Rule identifiers are case sensitive and cannot exceed 128 characters." so we replace any non-alphanum with _
|
||||
rule_name = re.sub(r"\W", "_", rule_name)
|
||||
rule_name = "capa_" + rule_name
|
||||
|
||||
@@ -283,12 +283,12 @@ def convert_rule(rule, rulename, cround, depth):
|
||||
# change capas /xxx/i to yaras /xxx/ nocase, count will be used later to decide appending 'nocase'
|
||||
regex, count = re.subn(r"/i$", "/", regex)
|
||||
|
||||
# remove / in the begining and end
|
||||
# remove / in the beginning and end
|
||||
regex = regex[1:-1]
|
||||
|
||||
# all .* in the regexes of capa look like they should be maximum 100 chars so take 1000 to speed up rules and prevent yara warnings on poor performance
|
||||
regex = regex.replace(".*", ".{,1000}")
|
||||
# strange: capa accepts regexes with unsescaped / like - string: /com/exe4j/runtime/exe4jcontroller/i in capa-rules/compiler/exe4j/compiled-with-exe4j.yml, needs a fix for yara:
|
||||
# strange: capa accepts regexes with unescaped / like - string: /com/exe4j/runtime/exe4jcontroller/i in capa-rules/compiler/exe4j/compiled-with-exe4j.yml, needs a fix for yara:
|
||||
# would assume that get_value_str() gives the raw string
|
||||
regex = re.sub(r"(?<!\\)/", r"\/", regex)
|
||||
|
||||
@@ -296,7 +296,7 @@ def convert_rule(rule, rulename, cround, depth):
|
||||
# /reg(|.exe)/ => /reg(.exe)?/
|
||||
regex = re.sub(r"\(\|([^\)]+)\)", r"(\1)?", regex)
|
||||
|
||||
# change begining of line to null byte, e.g. /^open => /\x00open (not word boundary because we're not looking for the begining of a word in a text but usually a function name if there's ^ in a capa rule)
|
||||
# change beginning of line to null byte, e.g. /^open => /\x00open (not word boundary because we're not looking for the beginning of a word in a text but usually a function name if there's ^ in a capa rule)
|
||||
regex = re.sub(r"^\^", r"\\x00", regex)
|
||||
|
||||
# regex = re.sub(r"^\^", r"\\b", regex)
|
||||
@@ -377,7 +377,7 @@ def convert_rule(rule, rulename, cround, depth):
|
||||
|
||||
if s_type == "Some":
|
||||
cmin = kid.count
|
||||
logger.info("Some type with mininum: " + str(cmin))
|
||||
logger.info("Some type with minimum: " + str(cmin))
|
||||
|
||||
if not cmin:
|
||||
logger.info("this is optional: which means, we can just ignore it")
|
||||
@@ -482,7 +482,7 @@ def convert_rule(rule, rulename, cround, depth):
|
||||
|
||||
elif statement == "Some":
|
||||
cmin = rule.count
|
||||
logger.info("Some type with mininum at2: " + str(cmin))
|
||||
logger.info("Some type with minimum at2: " + str(cmin))
|
||||
|
||||
if not cmin:
|
||||
logger.info("this is optional: which means, we can just ignore it")
|
||||
@@ -623,7 +623,7 @@ def convert_rules(rules, namespaces, cround, make_priv):
|
||||
value = re.sub(r"^([0-9a-f]{20,64}):0x[0-9a-f]{1,10}$", r"\1", value, flags=re.IGNORECASE)
|
||||
|
||||
# examples in capa can contain the same hash several times with different offset, so check if it's already there:
|
||||
# (keeping the offset might be interessting for some but breaks yara-ci for checking of the final rules
|
||||
# (keeping the offset might be interesting for some but breaks yara-ci for checking of the final rules
|
||||
if value not in seen_hashes:
|
||||
yara_meta += "\t" + meta_name + ' = "' + value + '"\n'
|
||||
seen_hashes.append(value)
|
||||
|
||||
@@ -16,7 +16,7 @@ import capa.features.freeze.features as frzf
|
||||
from capa.engine import *
|
||||
|
||||
|
||||
# == Render ddictionary helpers
|
||||
# == Render dictionary helpers
|
||||
def render_meta(doc: rd.ResultDocument, result):
|
||||
result["md5"] = doc.meta.sample.md5
|
||||
result["sha1"] = doc.meta.sample.sha1
|
||||
|
||||
+2
-2
@@ -11,12 +11,12 @@
|
||||
# Use a console with emojis support for a better experience
|
||||
# Use venv to ensure that `python` calls the correct python version
|
||||
|
||||
# Stash uncommited changes
|
||||
# Stash uncommitted changes
|
||||
MSG="pre-push-$(date +%s)";
|
||||
git stash push -kum "$MSG" &>/dev/null ;
|
||||
STASH_LIST=$(git stash list);
|
||||
if [[ "$STASH_LIST" == *"$MSG"* ]]; then
|
||||
echo "Uncommited changes stashed with message '$MSG', if you abort before they are restored run \`git stash pop\`";
|
||||
echo "Uncommitted changes stashed with message '$MSG', if you abort before they are restored run \`git stash pop\`";
|
||||
fi
|
||||
|
||||
restore_stashed() {
|
||||
|
||||
+1
-1
@@ -902,7 +902,7 @@ def redirecting_print_to_tqdm():
|
||||
old_print(*args, **kwargs)
|
||||
|
||||
try:
|
||||
# Globaly replace print with new_print.
|
||||
# Globally replace print with new_print.
|
||||
# Verified this works manually on Python 3.11:
|
||||
# >>> import inspect
|
||||
# >>> inspect.builtins
|
||||
|
||||
@@ -70,7 +70,7 @@ class MitreExtractor:
|
||||
self._memory_store = MemoryStore(stix_data=stix_json["objects"])
|
||||
|
||||
@staticmethod
|
||||
def _remove_deprecated_objetcs(stix_objects) -> List[AttackPattern]:
|
||||
def _remove_deprecated_objects(stix_objects) -> List[AttackPattern]:
|
||||
"""Remove any revoked or deprecated objects from queries made to the data source."""
|
||||
return list(
|
||||
filter(
|
||||
@@ -82,7 +82,7 @@ class MitreExtractor:
|
||||
def _get_tactics(self) -> List[Dict]:
|
||||
"""Get tactics IDs from Mitre matrix."""
|
||||
# Only one matrix for enterprise att&ck framework
|
||||
matrix = self._remove_deprecated_objetcs(
|
||||
matrix = self._remove_deprecated_objects(
|
||||
self._memory_store.query(
|
||||
[
|
||||
Filter("type", "=", "x-mitre-matrix"),
|
||||
@@ -93,7 +93,7 @@ class MitreExtractor:
|
||||
|
||||
def _get_techniques_from_tactic(self, tactic: str) -> List[AttackPattern]:
|
||||
"""Get techniques and sub techniques from a Mitre tactic (kill_chain_phases->phase_name)"""
|
||||
techniques = self._remove_deprecated_objetcs(
|
||||
techniques = self._remove_deprecated_objects(
|
||||
self._memory_store.query(
|
||||
[
|
||||
Filter("type", "=", "attack-pattern"),
|
||||
@@ -107,7 +107,7 @@ class MitreExtractor:
|
||||
def _get_parent_technique_from_subtechnique(self, technique: AttackPattern) -> AttackPattern:
|
||||
"""Get parent technique of a sub technique using the technique ID TXXXX.YYY"""
|
||||
sub_id = technique["external_references"][0]["external_id"].split(".")[0]
|
||||
parent_technique = self._remove_deprecated_objetcs(
|
||||
parent_technique = self._remove_deprecated_objects(
|
||||
self._memory_store.query(
|
||||
[
|
||||
Filter("type", "=", "attack-pattern"),
|
||||
|
||||
@@ -37,7 +37,7 @@ def check_input_file(wanted):
|
||||
def get_ida_extractor(_path):
|
||||
check_input_file("5f66b82558ca92e54e77f216ef4c066c")
|
||||
|
||||
# have to import import this inline so pytest doesn't bail outside of IDA
|
||||
# have to import this inline so pytest doesn't bail outside of IDA
|
||||
import capa.features.extractors.ida.extractor
|
||||
|
||||
return capa.features.extractors.ida.extractor.IdaFeatureExtractor()
|
||||
|
||||
Reference in New Issue
Block a user