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 are file, function, basic block and instruction.

  • namespace: optional but recommended. Matching rules are easier to browse when grouped by namespace.

  • author or authors: 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 through analysis.capa.attack.

  • mbc: optional. Matching MBC entries are exposed through the capa view and through analysis.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&ck and/or mbc metadata 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, import and export

  • string, regular-expression strings and substring

  • bytes

  • number and offset

  • operand[N].number and operand[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 emits engine: malcat.

  • .NET and COM-oriented features such as class, namespace, property, property/read, property/write and com/interface

  • New name feature 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 .yml or .yaml files. 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 .yml or .yaml;

  • the file is below <user data dir>/capa/;

  • the YAML contains a top-level rule object;

  • the rule has both meta and features blocks;

  • the features block contains exactly one top-level statement;

  • the rule has a unique name;

  • the rule is not marked as lib: true or capa/subscope-rule: true if you expect it to be displayed;

  • the selected scope matches 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.