Capa rules
Malcat embeds a native capa scanner with a 100% C++ execution path, from rule loading to rule evaluation, including feature extraction. Currently, Malcat ships with capa’s standard rules taken from capa version 9.4.0, but you can also import or write your own rules using the same YAML rule format.
This chapter focuses on the parts that matter when you want to add your own capa rules to Malcat. If you want to access capa results from a script, see Capa rules (analysis.capa).
Compatibility and performance
Malcat supports the full capa rule syntax used by capa 9.4.0. This means that rules from the standard capa rule set, as well as third-party rules written for the same rule format, can be loaded by Malcat without conversion.
There is one important conceptual limitation: Malcat is a static analysis engine, so capa evaluation is limited to static scopes and statically extracted features. In other words, Malcat evaluates rules at the usual capa scopes (file, function, basic block and instruction), but it does not emulate the program or observe runtime behavior.
Our implementation is 100% native: rules are loaded in C++, features are extracted through Malcat’s C++ analysis API, and rule evaluation also runs natively. This allows Malcat to take advantage of multithreading and usually makes capa scans much faster than the official Python implementation. In practice, you can expect a speedup around x60 (1 minute -> 1 second). On large programs, the speed gain can be even higher: we have seen fifteen minutes long capa scans reduces to a meager 2 seconds!.
We do our best to stay close to the official capa output, but Malcat cannot guarantee 100% equivalence. The official capa engine relies on Vivisect, while Malcat uses its own analysis engine, disassembler, file parsers and feature extractor. In our experience, these differences are often beneficial (e.g. reduced false positives for some features), but on the other hand our capa port is still young and has not been tested as thoroughly. So you should still expect occasional differences when comparing both tools.
Importing user rules
Malcat loads capa rules from the capa directory of both the Malcat data directory and your User data directory.
The standard rule set is distributed as:
<malcat install dir>/data/capa/official_rules.zip
These rules are considered read-only by Malcat. The original YAML text is stored in memory and exposed in the rule quick view, but the rules should not be edited in place.
Your own rules should be stored as YAML files in:
<user data dir>/capa/
Malcat scans this directory recursively and imports files ending in .yml or .yaml. For instance, the following layout is valid:
<user data dir>/capa/
my-rules/
resolve-custom-loader.yml
detect-odd-dotnet-helper.yml
User rules are editable rules. Malcat stores their file path and reads the rule text from disk when the rule is displayed. This means that rules imported from files remain easy to inspect and edit from outside Malcat.
Note
Zip archives are used by Malcat for the standard capa rule set. If you want to maintain your own rules, prefer individual .yml or .yaml files in your user capa directory.
If you want to deactivate the evaluation of the default Capa rules, either delete the file <malcat install dir>/data/capa/official_rules.zip (but it will be recreated the next time you update Malcat) or create an emty official_rules.zip in your user data directory: it will take precedence.
Rule writing guidelines
Malcat follows capa’s YAML rule format: each rule must contain a rule object with a meta block and a features block. The features block must contain a single top-level statement, usually and, or or not.
The most important metadata fields are:
name: required. This is the rule name shown in the Capa editor / browser and used to access the rule from scripts.
scope: required by capa rules. Supported scopes arefile,function,basic blockandinstruction.
namespace: optional but recommended. Matching rules are easier to browse when grouped by namespace.
authororauthors: optional. Malcat exposes both a comma-separated author string and a list of authors.
description: optional but recommended. This text is displayed in the rule details.
att&ck: optional. Matching ATT&CK entries are exposed through the capa view and throughanalysis.capa.attack.
mbc: optional. Matching MBC entries are exposed through the capa view and throughanalysis.capa.mbc.
lib: optional. Library rules are helper rules and are not displayed as user-visible capabilities.
There is also a capa internal metadata field, capa/subscope-rule. Rules using this field are generated/helper subscope rules and are not displayed as user-visible matches by Malcat.
In practice, if you want a rule to appear as a normal Malcat capa result:
give it a unique
name;choose the narrowest useful
scope;do not set
lib: true;do not set
capa/subscope-rule: true;add
namespace,description,att&ckand/ormbcmetadata when available.
Supported feature syntax
Malcat’s native capa engine supports all capa 9.4.0 rule syntax and the common feature types used by the standard rule set, including:
api,importandexport
string, regular-expression strings andsubstring
bytes
numberandoffset
operand[N].numberandoperand[N].offset
mnemonic
basic blocks
characteristic
section
match
function-name
os, with the following additions:
macos
format, with the following additions:
macho
python
inno
nsis
arch, with the following additions:
arm
aarch64
mips32
mips64
nsis
inno
vbpcode
clr
python2.7
python3.6
python3.7
python3.8
python3.9
python3.10
python3.11
python3.12
python3.13
python3.14
engine: a global Malcat extension identifying the feature extractor used for the scan. Malcat emitsengine: malcat..NET and COM-oriented features such as
class,namespace,property,property/read,property/writeandcom/interfaceNew
namefeature for python bytecode
Malcat also supports capa’s common statement structure: and, or, not, optional, N or more and subscope statements such as basic block or instruction when they are valid for the current rule scope.
If you import a rule that uses a feature not yet extracted by Malcat, the rule will simply not match. Syntax or evaluation errors are reported as capa warnings during analysis, with the rule name and the scope where the problem happened.
Extractor-specific conditions
Different static analysis engines can occasionally recover slightly different features for the same program. The global engine feature lets a rule account for a small, understood extractor discrepancy without weakening its conditions for every engine. Malcat always emits the exact, case-sensitive value malcat:
- and:
- engine: malcat
- api: CreateService
The feature is available at every rule scope. Other capa implementations can use their own value, such as vivisect, if they implement the same extension. Since engine is not part of the official capa 9.4.0 syntax, rules using it are not portable to implementations that do not recognize this extension.
Example rule
Here is a minimal function-scope rule that will be shown as a normal user capability if it matches:
rule:
meta:
name: resolve custom loader imports
namespace: linking/runtime-linking
author: ACME Research
scope: function
description: Detects a custom import resolver used by ACME samples.
att&ck:
- Execution::Shared Modules [T1129]
mbc:
- Discovery::Code Discovery::Enumerate PE Sections [B0046.001]
features:
- and:
- match: resolve function by parsing PE exports
- or:
- string: "LoadLibraryA"
- string: "GetProcAddress"
- basic block:
- and:
- offset: 0x3C = IMAGE_DOS_HEADER.e_lfanew
- offset: 0x78 = IMAGE_EXPORT_DIRECTORY
After saving this file as <user data dir>/capa/resolve-custom-loader.yml, re-run the capa scanner on a file. If the rule matches, it will appear in the capa view and in the summary, and the matching locations will be available through analysis.capa.
Standard rules vs user rules
Malcat internally distinguishes two kinds of capa rules:
standard rules: rules loaded from
data/capa/official_rules.zip. They are read-only. Their YAML text is stored with the loaded rule definition and their source path points to the zip archive.user rules: rules loaded from
.ymlor.yamlfiles. They are not read-only. Their source path points to the rule file and their text is read from disk when displayed.
This distinction is visible from Python:
capa = analysis.capa_run()
rule = capa["resolve custom loader imports"]
if rule is not None:
print(rule.readonly)
print(rule.path)
print(rule.definition)
For more details about the scripting objects returned by capa, see Capa rules (analysis.capa).
Troubleshooting imported rules
If a custom rule does not appear or does not match, check the following points first:
the file extension is
.ymlor.yaml;the file is below
<user data dir>/capa/;the YAML contains a top-level
ruleobject;the rule has both
metaandfeaturesblocks;the
featuresblock contains exactly one top-level statement;the rule has a unique
name;the rule is not marked as
lib: trueorcapa/subscope-rule: trueif you expect it to be displayed;the selected
scopematches the feature you are trying to use.
When Malcat can parse a rule but cannot evaluate one of its statements cleanly, the analysis emits a warning prefixed with [capa]. These warnings include the rule name and the scope that triggered the problem.