capagap 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- capagap-0.1.0/.editorconfig +12 -0
- capagap-0.1.0/.gitattributes +1 -0
- capagap-0.1.0/.github/workflows/ci.yml +29 -0
- capagap-0.1.0/.github/workflows/publish.yml +51 -0
- capagap-0.1.0/.gitignore +21 -0
- capagap-0.1.0/LICENSE +21 -0
- capagap-0.1.0/MANIFEST.in +11 -0
- capagap-0.1.0/PKG-INFO +145 -0
- capagap-0.1.0/README.md +120 -0
- capagap-0.1.0/docs/CLI.md +135 -0
- capagap-0.1.0/docs/DESIGN.md +94 -0
- capagap-0.1.0/docs/EXPERIMENTS.md +55 -0
- capagap-0.1.0/examples/README.md +29 -0
- capagap-0.1.0/examples/demo-report.md +43 -0
- capagap-0.1.0/examples/demo-rules/synthetic-capability.yml +11 -0
- capagap-0.1.0/examples/dynamic-interactive.json +64 -0
- capagap-0.1.0/examples/dynamic.json +64 -0
- capagap-0.1.0/examples/matrix-dashboard.html +628 -0
- capagap-0.1.0/examples/matrix-report.md +59 -0
- capagap-0.1.0/examples/re-handoff/CapaGapImport_Ghidra.py +88 -0
- capagap-0.1.0/examples/re-handoff/README.txt +43 -0
- capagap-0.1.0/examples/re-handoff/capagap-handoff.json +250 -0
- capagap-0.1.0/examples/re-handoff/capagap-triage.json +35 -0
- capagap-0.1.0/examples/re-handoff/capagap_import_binja.py +114 -0
- capagap-0.1.0/examples/re-handoff/capagap_import_ida.py +91 -0
- capagap-0.1.0/examples/single-run-dashboard.html +628 -0
- capagap-0.1.0/examples/static.json +107 -0
- capagap-0.1.0/pyproject.toml +54 -0
- capagap-0.1.0/scripts/release_check.py +232 -0
- capagap-0.1.0/setup.cfg +4 -0
- capagap-0.1.0/src/capagap/__init__.py +9 -0
- capagap-0.1.0/src/capagap/__main__.py +3 -0
- capagap-0.1.0/src/capagap/analysis.py +265 -0
- capagap-0.1.0/src/capagap/assets/report.css +317 -0
- capagap-0.1.0/src/capagap/assets/report.js +215 -0
- capagap-0.1.0/src/capagap/cli.py +465 -0
- capagap-0.1.0/src/capagap/handoff.py +376 -0
- capagap-0.1.0/src/capagap/hotspots.py +45 -0
- capagap-0.1.0/src/capagap/html_report.py +717 -0
- capagap-0.1.0/src/capagap/importers/binary_ninja.py.tmpl +114 -0
- capagap-0.1.0/src/capagap/importers/ghidra.py.tmpl +88 -0
- capagap-0.1.0/src/capagap/importers/ida.py.tmpl +91 -0
- capagap-0.1.0/src/capagap/io.py +223 -0
- capagap-0.1.0/src/capagap/manifest.py +376 -0
- capagap-0.1.0/src/capagap/matrix.py +256 -0
- capagap-0.1.0/src/capagap/models.py +361 -0
- capagap-0.1.0/src/capagap/render.py +561 -0
- capagap-0.1.0/src/capagap/scoring.py +94 -0
- capagap-0.1.0/src/capagap/triage.py +254 -0
- capagap-0.1.0/src/capagap.egg-info/PKG-INFO +145 -0
- capagap-0.1.0/src/capagap.egg-info/SOURCES.txt +63 -0
- capagap-0.1.0/src/capagap.egg-info/dependency_links.txt +1 -0
- capagap-0.1.0/src/capagap.egg-info/entry_points.txt +2 -0
- capagap-0.1.0/src/capagap.egg-info/requires.txt +5 -0
- capagap-0.1.0/src/capagap.egg-info/top_level.txt +1 -0
- capagap-0.1.0/tests/__init__.py +1 -0
- capagap-0.1.0/tests/test_analysis.py +94 -0
- capagap-0.1.0/tests/test_handoff.py +308 -0
- capagap-0.1.0/tests/test_html_report.py +250 -0
- capagap-0.1.0/tests/test_io.py +73 -0
- capagap-0.1.0/tests/test_manifest.py +165 -0
- capagap-0.1.0/tests/test_matrix.py +200 -0
- capagap-0.1.0/tests/test_release.py +84 -0
- capagap-0.1.0/tests/test_render_cli.py +87 -0
- capagap-0.1.0/tests/test_triage.py +120 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
* text=auto eol=lf
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
name: tests
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
pull_request:
|
|
6
|
+
|
|
7
|
+
permissions:
|
|
8
|
+
contents: read
|
|
9
|
+
|
|
10
|
+
jobs:
|
|
11
|
+
test:
|
|
12
|
+
runs-on: ${{ matrix.os }}
|
|
13
|
+
strategy:
|
|
14
|
+
fail-fast: false
|
|
15
|
+
matrix:
|
|
16
|
+
os: [ubuntu-latest]
|
|
17
|
+
python-version: ["3.10", "3.11", "3.12", "3.13", "3.14"]
|
|
18
|
+
include:
|
|
19
|
+
- os: windows-latest
|
|
20
|
+
python-version: "3.14"
|
|
21
|
+
steps:
|
|
22
|
+
- uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6
|
|
23
|
+
with:
|
|
24
|
+
persist-credentials: false
|
|
25
|
+
- uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6
|
|
26
|
+
with:
|
|
27
|
+
python-version: ${{ matrix.python-version }}
|
|
28
|
+
- run: python -m pip install -e ".[dev]"
|
|
29
|
+
- run: python scripts/release_check.py
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
name: publish
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
release:
|
|
5
|
+
types: [published]
|
|
6
|
+
|
|
7
|
+
permissions:
|
|
8
|
+
contents: read
|
|
9
|
+
|
|
10
|
+
concurrency:
|
|
11
|
+
group: pypi-${{ github.event.release.tag_name }}
|
|
12
|
+
cancel-in-progress: false
|
|
13
|
+
|
|
14
|
+
jobs:
|
|
15
|
+
build:
|
|
16
|
+
if: github.repository == 'sawyershoemaker/capagap' && !github.event.release.prerelease
|
|
17
|
+
runs-on: ubuntu-latest
|
|
18
|
+
steps:
|
|
19
|
+
- uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6
|
|
20
|
+
with:
|
|
21
|
+
persist-credentials: false
|
|
22
|
+
- uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6
|
|
23
|
+
with:
|
|
24
|
+
python-version: "3.14"
|
|
25
|
+
- run: python -m pip install -e ".[dev]"
|
|
26
|
+
- name: Check and build the release
|
|
27
|
+
env:
|
|
28
|
+
RELEASE_TAG: ${{ github.event.release.tag_name }}
|
|
29
|
+
run: python scripts/release_check.py --tag "$RELEASE_TAG" --outdir dist
|
|
30
|
+
- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
|
|
31
|
+
with:
|
|
32
|
+
name: distributions
|
|
33
|
+
path: dist/
|
|
34
|
+
if-no-files-found: error
|
|
35
|
+
retention-days: 7
|
|
36
|
+
|
|
37
|
+
publish:
|
|
38
|
+
needs: build
|
|
39
|
+
runs-on: ubuntu-latest
|
|
40
|
+
environment:
|
|
41
|
+
name: pypi
|
|
42
|
+
url: https://pypi.org/project/capagap/
|
|
43
|
+
permissions:
|
|
44
|
+
id-token: write
|
|
45
|
+
steps:
|
|
46
|
+
- uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8
|
|
47
|
+
with:
|
|
48
|
+
name: distributions
|
|
49
|
+
path: dist/
|
|
50
|
+
- name: Publish to PyPI
|
|
51
|
+
uses: pypa/gh-action-pypi-publish@dc37677b2e1c63e2034f94d8a5b11f265b73ba33 # release/v1
|
capagap-0.1.0/.gitignore
ADDED
capagap-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Sawyer Shoemaker
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
include LICENSE
|
|
2
|
+
include .editorconfig
|
|
3
|
+
include .gitattributes
|
|
4
|
+
include .gitignore
|
|
5
|
+
recursive-include .github *.yml
|
|
6
|
+
recursive-include docs *.md
|
|
7
|
+
recursive-include examples *.html *.json *.md *.py *.txt *.yml
|
|
8
|
+
recursive-include scripts *.py
|
|
9
|
+
recursive-include tests *.py
|
|
10
|
+
recursive-include src/capagap/importers *.tmpl
|
|
11
|
+
recursive-include src/capagap/assets *.css *.js
|
capagap-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: capagap
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Measure malware capability coverage across static analysis and labeled sandbox runs.
|
|
5
|
+
Author: Sawyer Shoemaker
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Repository, https://github.com/sawyershoemaker/capagap
|
|
8
|
+
Project-URL: Issues, https://github.com/sawyershoemaker/capagap/issues
|
|
9
|
+
Project-URL: Documentation, https://github.com/sawyershoemaker/capagap/blob/main/docs/CLI.md
|
|
10
|
+
Keywords: malware-analysis,reverse-engineering,capa,sandbox,dfir
|
|
11
|
+
Classifier: Development Status :: 3 - Alpha
|
|
12
|
+
Classifier: Environment :: Console
|
|
13
|
+
Classifier: Intended Audience :: Information Technology
|
|
14
|
+
Classifier: Programming Language :: Python :: 3
|
|
15
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
16
|
+
Classifier: Topic :: Security
|
|
17
|
+
Requires-Python: >=3.10
|
|
18
|
+
Description-Content-Type: text/markdown
|
|
19
|
+
License-File: LICENSE
|
|
20
|
+
Provides-Extra: dev
|
|
21
|
+
Requires-Dist: build>=1.2; extra == "dev"
|
|
22
|
+
Requires-Dist: ruff>=0.12; extra == "dev"
|
|
23
|
+
Requires-Dist: twine>=6.1; extra == "dev"
|
|
24
|
+
Dynamic: license-file
|
|
25
|
+
|
|
26
|
+
# CapaGap
|
|
27
|
+
|
|
28
|
+
Compare static [capa](https://github.com/mandiant/capa) matches with dynamic runs of the same sample. CapaGap reports which capabilities were observed, which were missing, and which appeared in only some runs.
|
|
29
|
+
|
|
30
|
+
Rules without a supported dynamic scope are excluded from coverage. Results include ranked findings, static match locations, and optional Ghidra, IDA Pro, and Binary Ninja annotations. CapaGap reads result documents; it does not run samples or contact a sandbox.
|
|
31
|
+
|
|
32
|
+
## Install
|
|
33
|
+
|
|
34
|
+
Python 3.10 or newer. No runtime dependencies.
|
|
35
|
+
|
|
36
|
+
Install from PyPI:
|
|
37
|
+
|
|
38
|
+
```sh
|
|
39
|
+
python -m pip install capagap
|
|
40
|
+
capagap --help
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
Or install from a source checkout:
|
|
44
|
+
|
|
45
|
+
```sh
|
|
46
|
+
python -m pip install .
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
If your shell cannot find `capagap`, use `python -m capagap` instead.
|
|
50
|
+
|
|
51
|
+
## Try it
|
|
52
|
+
|
|
53
|
+
The examples below use files from the [source repository](https://github.com/sawyershoemaker/capagap). Download the source and run these commands from its root directory; `pip install` does not copy the examples into your working directory.
|
|
54
|
+
|
|
55
|
+
The inputs are synthetic. You do not need capa or a malware sample to run them.
|
|
56
|
+
|
|
57
|
+
```sh
|
|
58
|
+
capagap compare examples/static.json examples/dynamic.json
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
Excerpt from the result:
|
|
62
|
+
|
|
63
|
+
```text
|
|
64
|
+
Confidence: high
|
|
65
|
+
Coverage: 50.0% (2/4 comparable static capabilities observed)
|
|
66
|
+
Unobserved: 2
|
|
67
|
+
Static-only: 1 (excluded from coverage)
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
For your own inputs, export the static and dynamic results with `capa -j`. Use the same sample and ruleset for both analyses. Plain JSON and gzip-compressed JSON are accepted.
|
|
71
|
+
|
|
72
|
+
### Compare runs
|
|
73
|
+
|
|
74
|
+
```sh
|
|
75
|
+
capagap matrix examples/static.json \
|
|
76
|
+
--run baseline=examples/dynamic.json \
|
|
77
|
+
--run interactive=examples/dynamic-interactive.json \
|
|
78
|
+
--condition baseline:interaction=off \
|
|
79
|
+
--condition interactive:interaction=on \
|
|
80
|
+
--format html --output reports/matrix.html
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
The first run is the baseline. Conditions are supplied by the analyst, not inferred from the reports. CapaGap warns when a comparison changes zero or multiple declared conditions.
|
|
84
|
+
|
|
85
|
+
The example has 75% combined coverage: three of four comparable static capabilities appeared in at least one run. Open `reports/matrix.html` locally to compare runs, expand evidence, and copy addresses. Search covers rule names, namespaces, ATT&CK IDs, and locations. Runtime-only and excluded matches have separate views.
|
|
86
|
+
|
|
87
|
+
HTML reports are self-contained: no server, external assets, or network requests. They include JSON export and remain readable with JavaScript disabled. Text, Markdown, and JSON output are also available.
|
|
88
|
+
|
|
89
|
+
Multiline examples use POSIX shell continuations. In PowerShell, put the command on one line or replace each trailing `\` with a backtick.
|
|
90
|
+
|
|
91
|
+
### Check the ruleset
|
|
92
|
+
|
|
93
|
+
```sh
|
|
94
|
+
capagap manifest path/to/capa-rules --output reports/ruleset.json
|
|
95
|
+
capagap compare static.json dynamic.json --ruleset-manifest reports/ruleset.json
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
Build the manifest from the rule directory used for analysis. Comparable static rules with missing or different source definitions are reported as `ruleset-unverified` and removed from the coverage denominator. A dynamic match that disagrees with the manifest stops the comparison.
|
|
99
|
+
|
|
100
|
+
### Export annotations
|
|
101
|
+
|
|
102
|
+
```sh
|
|
103
|
+
capagap handoff examples/static.json \
|
|
104
|
+
--run baseline=examples/dynamic.json \
|
|
105
|
+
--output reports/handoff
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
The output directory contains the findings, an editable triage worksheet, import scripts, and tool-specific instructions. Static match locations are exported as RVAs and rebased against the open database.
|
|
109
|
+
|
|
110
|
+
After editing `reports/handoff/capagap-triage.json`, create a reviewed copy:
|
|
111
|
+
|
|
112
|
+
```sh
|
|
113
|
+
capagap triage apply reports/handoff/capagap-handoff.json \
|
|
114
|
+
reports/handoff/capagap-triage.json \
|
|
115
|
+
--output reports/handoff/reviewed.json
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
Re-importing updates the corresponding CapaGap annotations. See the [command reference](https://github.com/sawyershoemaker/capagap/blob/main/docs/CLI.md) for selection options, review fields, and exit codes.
|
|
119
|
+
|
|
120
|
+
## Limits
|
|
121
|
+
|
|
122
|
+
- A missing dynamic match is not proof that code did not execute. Trace loss, absent stimuli, packing, and extractor differences can all affect coverage.
|
|
123
|
+
- A ruleset manifest checks source consistency. It cannot prove that every listed rule was loaded during a historical run.
|
|
124
|
+
- Scores set an investigation order; they are not probabilities or severity ratings. Evidence hotspots group exact RVAs, not functions or call-graph edges.
|
|
125
|
+
- Absolute addresses require the static report's image base for handoff. Other address types remain in the bundle as unmappable evidence.
|
|
126
|
+
|
|
127
|
+
Analysis reports can contain sensitive paths and strings. There is no automatic redaction; review them before sharing.
|
|
128
|
+
|
|
129
|
+
## Development
|
|
130
|
+
|
|
131
|
+
```sh
|
|
132
|
+
python -m pip install -e ".[dev]"
|
|
133
|
+
python -m unittest discover -s tests -v
|
|
134
|
+
python scripts/release_check.py
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
The release check runs lint, formatting checks, tests, package builds, metadata validation, and a clean-environment install test. It does not publish anything. The output directory must be empty; use `--outdir path/to/empty-directory` to keep an existing build.
|
|
138
|
+
|
|
139
|
+
The version is defined in `src/capagap/__init__.py`. Release tags use `v` followed by that version.
|
|
140
|
+
|
|
141
|
+
More detail: [command reference](https://github.com/sawyershoemaker/capagap/blob/main/docs/CLI.md), [comparison model](https://github.com/sawyershoemaker/capagap/blob/main/docs/DESIGN.md), [experiment setup](https://github.com/sawyershoemaker/capagap/blob/main/docs/EXPERIMENTS.md), and [example data](https://github.com/sawyershoemaker/capagap/blob/main/examples/README.md).
|
|
142
|
+
|
|
143
|
+
## License
|
|
144
|
+
|
|
145
|
+
[MIT](https://github.com/sawyershoemaker/capagap/blob/main/LICENSE). Copyright 2026 Sawyer Shoemaker.
|
capagap-0.1.0/README.md
ADDED
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
# CapaGap
|
|
2
|
+
|
|
3
|
+
Compare static [capa](https://github.com/mandiant/capa) matches with dynamic runs of the same sample. CapaGap reports which capabilities were observed, which were missing, and which appeared in only some runs.
|
|
4
|
+
|
|
5
|
+
Rules without a supported dynamic scope are excluded from coverage. Results include ranked findings, static match locations, and optional Ghidra, IDA Pro, and Binary Ninja annotations. CapaGap reads result documents; it does not run samples or contact a sandbox.
|
|
6
|
+
|
|
7
|
+
## Install
|
|
8
|
+
|
|
9
|
+
Python 3.10 or newer. No runtime dependencies.
|
|
10
|
+
|
|
11
|
+
Install from PyPI:
|
|
12
|
+
|
|
13
|
+
```sh
|
|
14
|
+
python -m pip install capagap
|
|
15
|
+
capagap --help
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
Or install from a source checkout:
|
|
19
|
+
|
|
20
|
+
```sh
|
|
21
|
+
python -m pip install .
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
If your shell cannot find `capagap`, use `python -m capagap` instead.
|
|
25
|
+
|
|
26
|
+
## Try it
|
|
27
|
+
|
|
28
|
+
The examples below use files from the [source repository](https://github.com/sawyershoemaker/capagap). Download the source and run these commands from its root directory; `pip install` does not copy the examples into your working directory.
|
|
29
|
+
|
|
30
|
+
The inputs are synthetic. You do not need capa or a malware sample to run them.
|
|
31
|
+
|
|
32
|
+
```sh
|
|
33
|
+
capagap compare examples/static.json examples/dynamic.json
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
Excerpt from the result:
|
|
37
|
+
|
|
38
|
+
```text
|
|
39
|
+
Confidence: high
|
|
40
|
+
Coverage: 50.0% (2/4 comparable static capabilities observed)
|
|
41
|
+
Unobserved: 2
|
|
42
|
+
Static-only: 1 (excluded from coverage)
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
For your own inputs, export the static and dynamic results with `capa -j`. Use the same sample and ruleset for both analyses. Plain JSON and gzip-compressed JSON are accepted.
|
|
46
|
+
|
|
47
|
+
### Compare runs
|
|
48
|
+
|
|
49
|
+
```sh
|
|
50
|
+
capagap matrix examples/static.json \
|
|
51
|
+
--run baseline=examples/dynamic.json \
|
|
52
|
+
--run interactive=examples/dynamic-interactive.json \
|
|
53
|
+
--condition baseline:interaction=off \
|
|
54
|
+
--condition interactive:interaction=on \
|
|
55
|
+
--format html --output reports/matrix.html
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
The first run is the baseline. Conditions are supplied by the analyst, not inferred from the reports. CapaGap warns when a comparison changes zero or multiple declared conditions.
|
|
59
|
+
|
|
60
|
+
The example has 75% combined coverage: three of four comparable static capabilities appeared in at least one run. Open `reports/matrix.html` locally to compare runs, expand evidence, and copy addresses. Search covers rule names, namespaces, ATT&CK IDs, and locations. Runtime-only and excluded matches have separate views.
|
|
61
|
+
|
|
62
|
+
HTML reports are self-contained: no server, external assets, or network requests. They include JSON export and remain readable with JavaScript disabled. Text, Markdown, and JSON output are also available.
|
|
63
|
+
|
|
64
|
+
Multiline examples use POSIX shell continuations. In PowerShell, put the command on one line or replace each trailing `\` with a backtick.
|
|
65
|
+
|
|
66
|
+
### Check the ruleset
|
|
67
|
+
|
|
68
|
+
```sh
|
|
69
|
+
capagap manifest path/to/capa-rules --output reports/ruleset.json
|
|
70
|
+
capagap compare static.json dynamic.json --ruleset-manifest reports/ruleset.json
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
Build the manifest from the rule directory used for analysis. Comparable static rules with missing or different source definitions are reported as `ruleset-unverified` and removed from the coverage denominator. A dynamic match that disagrees with the manifest stops the comparison.
|
|
74
|
+
|
|
75
|
+
### Export annotations
|
|
76
|
+
|
|
77
|
+
```sh
|
|
78
|
+
capagap handoff examples/static.json \
|
|
79
|
+
--run baseline=examples/dynamic.json \
|
|
80
|
+
--output reports/handoff
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
The output directory contains the findings, an editable triage worksheet, import scripts, and tool-specific instructions. Static match locations are exported as RVAs and rebased against the open database.
|
|
84
|
+
|
|
85
|
+
After editing `reports/handoff/capagap-triage.json`, create a reviewed copy:
|
|
86
|
+
|
|
87
|
+
```sh
|
|
88
|
+
capagap triage apply reports/handoff/capagap-handoff.json \
|
|
89
|
+
reports/handoff/capagap-triage.json \
|
|
90
|
+
--output reports/handoff/reviewed.json
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
Re-importing updates the corresponding CapaGap annotations. See the [command reference](https://github.com/sawyershoemaker/capagap/blob/main/docs/CLI.md) for selection options, review fields, and exit codes.
|
|
94
|
+
|
|
95
|
+
## Limits
|
|
96
|
+
|
|
97
|
+
- A missing dynamic match is not proof that code did not execute. Trace loss, absent stimuli, packing, and extractor differences can all affect coverage.
|
|
98
|
+
- A ruleset manifest checks source consistency. It cannot prove that every listed rule was loaded during a historical run.
|
|
99
|
+
- Scores set an investigation order; they are not probabilities or severity ratings. Evidence hotspots group exact RVAs, not functions or call-graph edges.
|
|
100
|
+
- Absolute addresses require the static report's image base for handoff. Other address types remain in the bundle as unmappable evidence.
|
|
101
|
+
|
|
102
|
+
Analysis reports can contain sensitive paths and strings. There is no automatic redaction; review them before sharing.
|
|
103
|
+
|
|
104
|
+
## Development
|
|
105
|
+
|
|
106
|
+
```sh
|
|
107
|
+
python -m pip install -e ".[dev]"
|
|
108
|
+
python -m unittest discover -s tests -v
|
|
109
|
+
python scripts/release_check.py
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
The release check runs lint, formatting checks, tests, package builds, metadata validation, and a clean-environment install test. It does not publish anything. The output directory must be empty; use `--outdir path/to/empty-directory` to keep an existing build.
|
|
113
|
+
|
|
114
|
+
The version is defined in `src/capagap/__init__.py`. Release tags use `v` followed by that version.
|
|
115
|
+
|
|
116
|
+
More detail: [command reference](https://github.com/sawyershoemaker/capagap/blob/main/docs/CLI.md), [comparison model](https://github.com/sawyershoemaker/capagap/blob/main/docs/DESIGN.md), [experiment setup](https://github.com/sawyershoemaker/capagap/blob/main/docs/EXPERIMENTS.md), and [example data](https://github.com/sawyershoemaker/capagap/blob/main/examples/README.md).
|
|
117
|
+
|
|
118
|
+
## License
|
|
119
|
+
|
|
120
|
+
[MIT](https://github.com/sawyershoemaker/capagap/blob/main/LICENSE). Copyright 2026 Sawyer Shoemaker.
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
# Command reference
|
|
2
|
+
|
|
3
|
+
Run `capagap COMMAND --help` for the accepted options. Commands below assume the current directory is the project root. Output goes under `reports/`, which is ignored by git.
|
|
4
|
+
|
|
5
|
+
## compare
|
|
6
|
+
|
|
7
|
+
```sh
|
|
8
|
+
capagap compare STATIC DYNAMIC [options]
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
Both inputs must be capa result documents. The first must have `meta.flavor` set to `static`, the second to `dynamic`. Gzip is detected by its file signature.
|
|
12
|
+
|
|
13
|
+
| Option | Effect |
|
|
14
|
+
|---|---|
|
|
15
|
+
| `--format text\|markdown\|json\|html` | Choose a report format. Default: `text`. |
|
|
16
|
+
| `--output PATH` | Write a report instead of printing it. An existing report is overwritten. |
|
|
17
|
+
| `--minimum-priority low\|medium\|high\|critical` | Filter gap findings in text and Markdown output. Default: `low`. |
|
|
18
|
+
| `--limit N` | Limit rows in human-readable sections. Default: `25`; minimum: `1`. |
|
|
19
|
+
| `--include-library` | Include capa helper/library rules, which are excluded by default. |
|
|
20
|
+
| `--allow-mismatch` | Permit different sample SHA-256 values and mark the comparison low-confidence. |
|
|
21
|
+
| `--ruleset-manifest PATH` | Compare rule source digests against a manifest. |
|
|
22
|
+
| `--fail-on-unobserved` | Return `3` if any comparable static capability is unobserved. |
|
|
23
|
+
|
|
24
|
+
JSON and HTML reports retain all findings. Priority filters do not change summary counts or exit-code checks.
|
|
25
|
+
|
|
26
|
+
## matrix
|
|
27
|
+
|
|
28
|
+
```sh
|
|
29
|
+
capagap matrix STATIC --run LABEL=PATH --run LABEL=PATH [options]
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
A matrix needs at least two dynamic results with unique, nonempty labels. It accepts the report options above, except that `--fail-on-never-observed` replaces `--fail-on-unobserved`.
|
|
33
|
+
|
|
34
|
+
The report separates capabilities seen in all runs, seen in only some runs (`environment-sensitive`), and absent from every run (`never-observed`). Coverage is also calculated for the union of all supplied runs.
|
|
35
|
+
|
|
36
|
+
Repeat `--condition LABEL:KEY=VALUE` to record run settings. The first run is the baseline. Each other run is checked for the number of changed keys; zero or multiple changes produce warnings. Unknown labels and duplicate keys within a run are errors. These are checks of declared metadata, not measurements of the sandbox configuration.
|
|
37
|
+
|
|
38
|
+
See [experiment setup](EXPERIMENTS.md) for a worked example.
|
|
39
|
+
|
|
40
|
+
## HTML reports
|
|
41
|
+
|
|
42
|
+
Both comparison commands accept `--format html --output REPORT.html`. Open the file in a modern browser; no web server or internet connection is needed.
|
|
43
|
+
|
|
44
|
+
The matrix starts with comparable capabilities. Use the Runtime-only and Excluded controls to inspect other findings without changing coverage. Expand a row to see its evidence, scopes, identifiers, and follow-up. Runtime-only evidence is labeled by run and retains its original address type. Static addresses show a portable RVA when one can be calculated; unmapped locations remain visible.
|
|
45
|
+
|
|
46
|
+
Search is case-insensitive and matches every space-separated term within a finding's name, namespace, state, identifiers, or evidence locations. The state filter combines with search. Switching views clears the state filter but preserves search. Run conditions, exact-RVA hotspots, input metadata, and analysis context sit below the matrix. Input warnings are expanded by default.
|
|
47
|
+
|
|
48
|
+
| Control | Keyboard behavior |
|
|
49
|
+
|---|---|
|
|
50
|
+
| Search | `/` focuses search when not typing in another control. |
|
|
51
|
+
| Row disclosure | Enter or Space toggles evidence; Right opens it; Left closes it. |
|
|
52
|
+
| Row navigation | Up and Down move between visible row disclosure buttons. |
|
|
53
|
+
| Evidence | Escape closes the row and returns focus to its disclosure button. |
|
|
54
|
+
| Copy dialog | If automatic copying is blocked, select the text and copy manually. Escape closes the dialog. |
|
|
55
|
+
|
|
56
|
+
Export JSON downloads the complete report, not just the filtered view. Copy controls are available for evidence addresses, mapped RVAs, image bases, and sample hashes. Printing includes all findings and evidence, including filtered-out rows. With JavaScript disabled, all rows and evidence remain visible; filtering, copying, and export controls are unavailable.
|
|
57
|
+
|
|
58
|
+
The HTML file embeds the report data. Paths and strings are not redacted; check them before sharing the file or its JSON export.
|
|
59
|
+
|
|
60
|
+
## manifest
|
|
61
|
+
|
|
62
|
+
```sh
|
|
63
|
+
capagap manifest RULE_DIRECTORY --output reports/ruleset.json
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
Scans `.yml` and `.yaml` files recursively. The manifest stores names, scopes, library flags, relative paths, and SHA-256 digests of rule source text with CRLF normalized to LF. Rule names must be unique. Unrelated YAML files are listed as skipped.
|
|
67
|
+
|
|
68
|
+
The metadata reader supports the indented capa rule format, not arbitrary YAML. It does not execute tags or resolve anchors. This command is not a replacement for capa's rule validation.
|
|
69
|
+
|
|
70
|
+
Supply the resulting file with `--ruleset-manifest` on `compare`, `matrix`, or `handoff`. Matched dynamic rules must agree with the manifest. Comparable static rules without an exact source match are excluded from coverage and listed separately as `ruleset-unverified`.
|
|
71
|
+
|
|
72
|
+
Existing manifests require `--force` to overwrite. A supplied manifest is a consistency check, not evidence of which rules were loaded in an earlier analysis.
|
|
73
|
+
|
|
74
|
+
## handoff
|
|
75
|
+
|
|
76
|
+
```sh
|
|
77
|
+
capagap handoff STATIC --run LABEL=PATH --output DIRECTORY [options]
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
One run exports unobserved findings. Two or more runs export never-observed and environment-sensitive findings. Multi-run handoffs accept the same `--condition` syntax as `matrix`.
|
|
81
|
+
|
|
82
|
+
| Option | Effect |
|
|
83
|
+
|---|---|
|
|
84
|
+
| `--tool all\|json\|ghidra\|ida\|binary-ninja` | Choose import scripts. Default: `all`. |
|
|
85
|
+
| `--minimum-priority low\|medium\|high\|critical` | Select findings by score. Default: `low`. |
|
|
86
|
+
| `--never-only` | Exclude environment-sensitive findings. Requires at least two runs. |
|
|
87
|
+
| `--ruleset-manifest PATH` | Verify rule sources before exporting findings. |
|
|
88
|
+
| `--include-library` | Include helper/library rules in the analysis. |
|
|
89
|
+
| `--allow-mismatch` | Permit mismatched report hashes. Importers still reject a known database hash mismatch. |
|
|
90
|
+
| `--force` | Overwrite existing output files, including the triage worksheet. |
|
|
91
|
+
|
|
92
|
+
The output includes:
|
|
93
|
+
|
|
94
|
+
| File | Purpose |
|
|
95
|
+
|---|---|
|
|
96
|
+
| `capagap-handoff.json` | Selected findings, evidence locations, and comments. |
|
|
97
|
+
| `capagap-triage.json` | Editable analyst reviews. |
|
|
98
|
+
| `README.txt` | Import instructions. |
|
|
99
|
+
| `CapaGapImport_Ghidra.py` | Ghidra Analysis bookmarks in the CapaGap category. |
|
|
100
|
+
| `capagap_import_ida.py` | IDA repeatable comments. |
|
|
101
|
+
| `capagap_import_binja.py` | Binary Ninja address comments. |
|
|
102
|
+
|
|
103
|
+
Script selection does not affect the JSON bundle. `--tool json` writes only the two JSON files and instructions.
|
|
104
|
+
|
|
105
|
+
Absolute match addresses are converted to RVAs using `meta.analysis.base_address`. Export fails if that base is missing and the selection contains absolute evidence. File offsets, .NET tokens, and dynamic coordinates are retained as unmappable evidence rather than converted to virtual addresses. Importers skip unmapped addresses and reject a known sample-hash mismatch.
|
|
106
|
+
|
|
107
|
+
## triage
|
|
108
|
+
|
|
109
|
+
Handoff generation creates a worksheet automatically. For an existing bundle:
|
|
110
|
+
|
|
111
|
+
```sh
|
|
112
|
+
capagap triage init HANDOFF --output WORKSHEET
|
|
113
|
+
capagap triage report HANDOFF WORKSHEET --format markdown --output reports/review.md
|
|
114
|
+
capagap triage apply HANDOFF WORKSHEET --output reports/reviewed-handoff.json
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
Each review contains a stable finding ID, disposition, analyst notes, evidence, reviewer, and review date. The text fields are free-form. Do not change the finding ID or `handoff_identity` field.
|
|
118
|
+
|
|
119
|
+
Accepted dispositions: `unreviewed`, `confirmed`, `likely`, `benign`, `false-positive`, `needs-data`, and `deferred`. CapaGap records these as analyst judgments; it does not verify them.
|
|
120
|
+
|
|
121
|
+
The worksheet identity is a hash of the sample SHA-256 and sorted finding IDs. A mismatch stops `report` or `apply`. This is an accidental-mixup check, not a signature or a complete record of run provenance.
|
|
122
|
+
|
|
123
|
+
`init` and `apply` require `--force` to replace an existing output. `report` accepts `text` or `markdown` and overwrites its output path if one is supplied. Applying a worksheet writes a new handoff; it does not modify the original unless explicitly given the same path with `--force`.
|
|
124
|
+
|
|
125
|
+
IDA and Binary Ninja imports replace comment lines with matching CapaGap markers. Ghidra updates the CapaGap bookmark at each location. Keep a database backup before importing annotations.
|
|
126
|
+
|
|
127
|
+
## Exit codes
|
|
128
|
+
|
|
129
|
+
| Code | Meaning |
|
|
130
|
+
|---|---|
|
|
131
|
+
| `0` | Command completed. |
|
|
132
|
+
| `2` | Invalid arguments, input, comparison, or output path. |
|
|
133
|
+
| `3` | A requested gap check failed after the report was written. |
|
|
134
|
+
|
|
135
|
+
The `3` exit code is opt-in through `--fail-on-unobserved` or `--fail-on-never-observed`. It is based on the complete comparison, not the displayed priority threshold.
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
# Comparison model
|
|
2
|
+
|
|
3
|
+
## Rule sets and coverage
|
|
4
|
+
|
|
5
|
+
For a static result `S` and a dynamic result `D`, let `C` contain the static matches whose metadata declares a supported dynamic scope. Library rules are excluded unless explicitly requested.
|
|
6
|
+
|
|
7
|
+
```text
|
|
8
|
+
observed = C ∩ D
|
|
9
|
+
unobserved = C - D
|
|
10
|
+
static-only = S - C
|
|
11
|
+
dynamic-only = D - S
|
|
12
|
+
coverage = |observed| / |C|
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
The unit is a matched capa rule, not a function, instruction, execution path, or percentage of code executed. An empty denominator is reported as `n/a`.
|
|
16
|
+
|
|
17
|
+
Rules are matched by name. Shared rules with different source SHA-256 digests are flagged as drift. Source digests normalize CRLF to LF before hashing.
|
|
18
|
+
|
|
19
|
+
## Ruleset manifests
|
|
20
|
+
|
|
21
|
+
A capa result contains matched rules, not a complete inventory of loaded rules. An absent result therefore does not establish that the rule was available to that run.
|
|
22
|
+
|
|
23
|
+
A supplied manifest adds a source-consistency check:
|
|
24
|
+
|
|
25
|
+
1. Every included dynamic match must exist in the manifest with the same source digest, or comparison fails.
|
|
26
|
+
2. Comparable static matches without an exact manifest source match are classified as `ruleset-unverified`.
|
|
27
|
+
3. Unverified rules are excluded from both observed and unobserved coverage counts.
|
|
28
|
+
|
|
29
|
+
The manifest stores rule names, source digests, scopes, library flags, and relative paths. Its aggregate fingerprint covers sorted rule names and source digests. The metadata reader handles the indented capa rule format without executing YAML tags.
|
|
30
|
+
|
|
31
|
+
A manifest does not attest to the command, extractor configuration, or full ruleset actually used in a historical run. Preserve those details with the case.
|
|
32
|
+
|
|
33
|
+
## Input confidence
|
|
34
|
+
|
|
35
|
+
Matching sample hashes start a comparison at high confidence. Missing hashes lower it to medium. OS, architecture, and capa major-version differences each lower it by one level. Different hashes are rejected unless `--allow-mismatch` is set, in which case confidence is low.
|
|
36
|
+
|
|
37
|
+
This label describes input consistency, not the reliability of a particular behavioral conclusion.
|
|
38
|
+
|
|
39
|
+
## Finding priority
|
|
40
|
+
|
|
41
|
+
The score is deterministic and capped at 100. It combines a baseline with:
|
|
42
|
+
|
|
43
|
+
- the highest applicable namespace category weight;
|
|
44
|
+
- ATT&CK and MBC mappings;
|
|
45
|
+
- repeated static match locations;
|
|
46
|
+
- a supported process, thread, or call scope; and
|
|
47
|
+
- anti-analysis context from observed matches.
|
|
48
|
+
|
|
49
|
+
Each factor is included in the finding's reasons. The implementation is in [scoring.py](../src/capagap/scoring.py).
|
|
50
|
+
|
|
51
|
+
An observed anti-analysis rule gives other gaps a small priority boost. That records a possible lead; it does not establish that the check caused missing behavior.
|
|
52
|
+
|
|
53
|
+
## Multiple runs
|
|
54
|
+
|
|
55
|
+
For dynamic results `D₁ … Dₙ`, each comparable static rule has an observation vector:
|
|
56
|
+
|
|
57
|
+
```text
|
|
58
|
+
observed-in-all = present in every Dᵢ
|
|
59
|
+
environment-sensitive = present in some, but not all, Dᵢ
|
|
60
|
+
never-observed = absent from every Dᵢ
|
|
61
|
+
union coverage = |C ∩ (D₁ ∪ … ∪ Dₙ)| / |C|
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
The first labeled run is the baseline. Conditions supplied with `--condition` are compared by key and value. Zero or multiple changed keys produce warnings; missing condition declarations also produce warnings. Nothing in the result document proves those declarations are complete.
|
|
65
|
+
|
|
66
|
+
Runtime-only matches and anti-analysis signals retain the labels of the runs that observed them.
|
|
67
|
+
|
|
68
|
+
## Address handling
|
|
69
|
+
|
|
70
|
+
Only unambiguous virtual addresses become portable RVAs:
|
|
71
|
+
|
|
72
|
+
```text
|
|
73
|
+
absolute match -> address - static image base
|
|
74
|
+
relative match -> address
|
|
75
|
+
other kinds -> unmappable evidence
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
Handoff rejects a selection containing absolute matches when the static image base is missing. Importers add RVAs to the current database base, check mapped memory, and reject a known sample-hash mismatch.
|
|
79
|
+
|
|
80
|
+
Evidence hotspots group findings at the same exact RVA. They are sorted by finding count, aggregate priority, and RVA. The maximum priority is included for reference. No function boundary or control-flow edge is inferred.
|
|
81
|
+
|
|
82
|
+
## Analyst review
|
|
83
|
+
|
|
84
|
+
Finding IDs are derived from the rule name and source digest. A separate triage worksheet holds analyst dispositions and notes. Its identity hashes the sample SHA-256 and sorted finding IDs to catch accidental mismatches; it is not a digital signature or a complete case identity.
|
|
85
|
+
|
|
86
|
+
Applying a worksheet creates a reviewed handoff. IDA and Binary Ninja replace comment lines carrying the corresponding CapaGap marker. Ghidra updates only the Analysis bookmark in the CapaGap category at each location.
|
|
87
|
+
|
|
88
|
+
## Input handling
|
|
89
|
+
|
|
90
|
+
CapaGap never executes sample content or embedded rule source. JSON inputs are limited to 256 MiB after decompression. Manifest and worksheet inputs have separate size limits.
|
|
91
|
+
|
|
92
|
+
Native importer scripts are fixed package resources. Report-controlled text is loaded as JSON data, not inserted into executable source. Manifest, handoff, and triage writes require explicit overwrite flags; ordinary report output replaces an existing destination.
|
|
93
|
+
|
|
94
|
+
Generated reports can contain sensitive paths, indicators, and analyst notes. They are not redacted automatically.
|